Skip to content

Commit d859a5e

Browse files
authored
Merge pull request #13138 from Isira-Seneviratne/Merge-dev-to-refactor
2 parents 50b9a7b + f63ea4a commit d859a5e

186 files changed

Lines changed: 1443 additions & 654 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ android {
4747
minSdk = 23
4848
targetSdk = 35
4949

50-
versionCode = System.getProperty("versionCodeOverride")?.toInt() ?: 1006
50+
versionCode = System.getProperty("versionCodeOverride")?.toInt() ?: 1007
5151

52-
versionName = "0.28.1"
52+
versionName = "0.28.2"
5353
System.getProperty("versionNameSuffix")?.let { versionNameSuffix = it }
5454

5555
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import androidx.test.filters.LargeTest
99
import java.io.IOException
1010
import java.net.SocketTimeoutException
1111
import org.junit.Assert.assertEquals
12-
import org.junit.Assert.assertFalse
1312
import org.junit.Assert.assertNull
1413
import org.junit.Assert.assertTrue
1514
import org.junit.Test
@@ -25,7 +24,7 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
2524
@RunWith(AndroidJUnit4::class)
2625
@LargeTest
2726
class ErrorInfoTest {
28-
private val context: Context by lazy { ApplicationProvider.getApplicationContext<Context>() }
27+
private val context: Context by lazy { ApplicationProvider.getApplicationContext() }
2928

3029
/**
3130
* @param errorInfo the error info to access
@@ -122,7 +121,7 @@ class ErrorInfoTest {
122121
)
123122
assertEquals(context.getString(R.string.recaptcha_request_toast), errorInfo.getMessage(context))
124123
assertEquals(url, errorInfo.recaptchaUrl)
125-
assertFalse(errorInfo.isReportable)
124+
assertTrue(errorInfo.isReportable)
126125
assertTrue(errorInfo.isRetryable)
127126
}
128127
}

app/src/androidTest/java/org/schabi/newpipe/ui/components/common/ErrorPanelTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import org.junit.runner.RunWith
1414
import org.schabi.newpipe.R
1515
import org.schabi.newpipe.error.ErrorInfo
1616
import org.schabi.newpipe.error.UserAction
17-
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
1817
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
18+
import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException
1919
import org.schabi.newpipe.ui.theme.AppTheme
2020

2121
@RunWith(AndroidJUnit4::class)
@@ -71,10 +71,10 @@ class ErrorPanelTest {
7171
}
7272

7373
/**
74-
* Test Recaptcha Error shows solve, retry and open in browser buttons
74+
* Test Recaptcha Error shows all buttons: solve, retry, open in browser, report
7575
*/
7676
@Test
77-
fun recaptchaErrorShowsSolveAndRetryOpenInBrowserButtons() {
77+
fun recaptchaErrorShowsAllButtons() {
7878
var retryClicked = false
7979
val recaptchaErrorInfo = ErrorInfo(
8080
throwable = ReCaptchaException(
@@ -99,7 +99,7 @@ class ErrorPanelTest {
9999
composeRule.onNodeWithText(text(R.string.open_in_browser), ignoreCase = true)
100100
.assertIsDisplayed()
101101
composeRule.onNodeWithText(text(R.string.error_snackbar_action), ignoreCase = true)
102-
.assertDoesNotExist()
102+
.assertIsDisplayed()
103103
assert(retryClicked) { "onRetry callback should have been invoked" }
104104
}
105105

@@ -109,14 +109,14 @@ class ErrorPanelTest {
109109
@Test
110110
fun testNonRetryableErrorHidesRetryAndReportButtons() {
111111
val contentNotAvailable = ErrorInfo(
112-
throwable = ContentNotAvailableException("Video has been removed"),
112+
throwable = UnsupportedContentInCountryException("Not available here"),
113113
userAction = UserAction.REQUESTED_STREAM,
114114
request = "https://example.com/watch?v=qux"
115115
)
116116

117117
setErrorPanel(contentNotAvailable)
118118

119-
composeRule.onNodeWithText(text(R.string.content_not_available))
119+
composeRule.onNodeWithText(text(R.string.unsupported_content_in_country))
120120
.assertIsDisplayed()
121121
composeRule.onNodeWithText(text(R.string.retry), ignoreCase = true)
122122
.assertDoesNotExist()

app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,13 +297,9 @@ class ErrorInfo private constructor(
297297
// indicates that it's important and is thus reportable
298298
null -> true
299299

300-
// a recaptcha was detected, and the user needs to solve it, there is no use in
301-
// letting users report it
302-
is ReCaptchaException -> false
303-
304-
// the service explicitly said that content is not available (e.g. age restrictions,
305-
// video deleted, etc.), there is no use in letting users report it
306-
is ContentNotAvailableException -> false
300+
// if the service explicitly said that content is not available (e.g. age
301+
// restrictions, video deleted, etc.), there is no use in letting users report it
302+
is ContentNotAvailableException -> !isContentSurelyNotAvailable(throwable)
307303

308304
// we know the content is not supported, no need to let the user report it
309305
is ContentNotSupportedException -> false
@@ -320,8 +316,8 @@ class ErrorInfo private constructor(
320316

321317
fun isRetryable(throwable: Throwable?): Boolean {
322318
return when (throwable) {
323-
// we know the content is not available, retrying won't help
324-
is ContentNotAvailableException -> false
319+
// if we know the content is surely not available, retrying won't help
320+
is ContentNotAvailableException -> !isContentSurelyNotAvailable(throwable)
325321

326322
// we know the content is not supported, retrying won't help
327323
is ContentNotSupportedException -> false
@@ -331,5 +327,28 @@ class ErrorInfo private constructor(
331327
else -> true
332328
}
333329
}
330+
331+
/**
332+
* Unfortunately sometimes [ContentNotAvailableException] may not indicate that the content
333+
* is blocked/deleted/paid, but may just indicate that we could not extract it. This is an
334+
* inconsistency in the exceptions thrown by the extractor, but until it is fixed, this
335+
* function will distinguish between the two types.
336+
* @return `true` if the content is not available because of a limitation imposed by the
337+
* service or the owner, `false` if the extractor could not extract info about it
338+
*/
339+
fun isContentSurelyNotAvailable(e: ContentNotAvailableException): Boolean {
340+
return when (e) {
341+
is AccountTerminatedException,
342+
is AgeRestrictedContentException,
343+
is GeographicRestrictionException,
344+
is PaidContentException,
345+
is PrivateContentException,
346+
is SoundCloudGoPlusContentException,
347+
is UnsupportedContentInCountryException,
348+
is YoutubeMusicPremiumContentException -> true
349+
350+
else -> false
351+
}
352+
}
334353
}
335354
}

app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
2929
import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode;
3030

31+
import java.util.function.Supplier;
32+
3133
/**
3234
* This fragment is design to be used with persistent data such as
3335
* {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained
@@ -100,7 +102,7 @@ private void refreshItemViewMode() {
100102
//////////////////////////////////////////////////////////////////////////*/
101103

102104
@Nullable
103-
protected ViewBinding getListHeader() {
105+
protected Supplier<View> getListHeaderSupplier() {
104106
return null;
105107
}
106108

@@ -131,9 +133,9 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
131133
itemsList = rootView.findViewById(R.id.items_list);
132134
refreshItemViewMode();
133135

134-
headerRootBinding = getListHeader();
135-
if (headerRootBinding != null) {
136-
itemListAdapter.setHeader(headerRootBinding.getRoot());
136+
final Supplier<View> listHeaderSupplier = getListHeaderSupplier();
137+
if (listHeaderSupplier != null) {
138+
itemListAdapter.setHeaderSupplier(listHeaderSupplier);
137139
}
138140
footerRootBinding = getListFooter();
139141
itemListAdapter.setFooter(footerRootBinding.getRoot());
@@ -210,6 +212,8 @@ public void showEmptyState() {
210212
showListFooter(false);
211213
}
212214

215+
@Deprecated(since = "Calling this method with `true` may cause crashes, see "
216+
+ "https://github.com/TeamNewPipe/NewPipe/pull/12996#pullrequestreview-3713317115")
213217
@Override
214218
public void showListFooter(final boolean show) {
215219
if (itemsList == null) {

app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.time.format.FormatStyle;
3838
import java.util.ArrayList;
3939
import java.util.List;
40+
import java.util.function.Supplier;
4041

4142
/*
4243
* Created by Christian Schabesberger on 01.08.16.
@@ -88,7 +89,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
8889
private final DateTimeFormatter dateTimeFormatter;
8990

9091
private boolean showFooter = false;
91-
private View header = null;
92+
private Supplier<View> headerSupplier = null;
9293
private View footer = null;
9394
private ItemViewMode itemViewMode = ItemViewMode.LIST;
9495
private boolean useItemHandle = false;
@@ -97,6 +98,7 @@ public LocalItemListAdapter(final Context context) {
9798
recordManager = new HistoryRecordManager(context);
9899
localItemBuilder = new LocalItemBuilder(context);
99100
localItems = new ArrayList<>();
101+
100102
dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
101103
.withLocale(Localization.getPreferredLocale(context));
102104
}
@@ -124,7 +126,7 @@ public void addItems(@Nullable final List<? extends LocalItem> data) {
124126
if (DEBUG) {
125127
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", "
126128
+ "localItems.size() = " + localItems.size() + ", "
127-
+ "header = " + header + ", footer = " + footer + ", "
129+
+ "header = " + hasHeader() + ", footer = " + footer + ", "
128130
+ "showFooter = " + showFooter);
129131
}
130132
notifyItemRangeInserted(offsetStart, data.size());
@@ -144,7 +146,7 @@ public void removeItem(final LocalItem data) {
144146
final int index = localItems.indexOf(data);
145147
if (index != -1) {
146148
localItems.remove(index);
147-
notifyItemRemoved(index + (header != null ? 1 : 0));
149+
notifyItemRemoved(index + (hasHeader() ? 1 : 0));
148150
} else {
149151
// this happens when
150152
// 1) removeItem is called on infoItemDuplicate as in showStreamItemDialog of
@@ -189,9 +191,9 @@ public void setUseItemHandle(final boolean useItemHandle) {
189191
this.useItemHandle = useItemHandle;
190192
}
191193

192-
public void setHeader(final View header) {
193-
final boolean changed = header != this.header;
194-
this.header = header;
194+
public void setHeaderSupplier(@Nullable final Supplier<View> headerSupplier) {
195+
final boolean changed = headerSupplier != this.headerSupplier;
196+
this.headerSupplier = headerSupplier;
195197
if (changed) {
196198
notifyDataSetChanged();
197199
}
@@ -201,6 +203,12 @@ public void setFooter(final View view) {
201203
this.footer = view;
202204
}
203205

206+
protected boolean hasHeader() {
207+
return this.headerSupplier != null;
208+
}
209+
210+
@Deprecated(since = "Calling this method with `true` may cause crashes, see "
211+
+ "https://github.com/TeamNewPipe/NewPipe/pull/12996#pullrequestreview-3713317115")
204212
public void showFooter(final boolean show) {
205213
if (DEBUG) {
206214
Log.d(TAG, "showFooter() called with: show = [" + show + "]");
@@ -211,18 +219,20 @@ public void showFooter(final boolean show) {
211219

212220
showFooter = show;
213221
if (show) {
222+
Log.w(TAG, "Calling LocalItemListAdapter.showFooter(true) may cause crashes, see https"
223+
+ "://github.com/TeamNewPipe/NewPipe/pull/12996#pullrequestreview-3713317115");
214224
notifyItemInserted(sizeConsideringHeader());
215225
} else {
216226
notifyItemRemoved(sizeConsideringHeader());
217227
}
218228
}
219229

220230
private int adapterOffsetWithoutHeader(final int offset) {
221-
return offset - (header != null ? 1 : 0);
231+
return offset - (hasHeader() ? 1 : 0);
222232
}
223233

224234
private int sizeConsideringHeader() {
225-
return localItems.size() + (header != null ? 1 : 0);
235+
return localItems.size() + (hasHeader() ? 1 : 0);
226236
}
227237

228238
public ArrayList<LocalItem> getItemsList() {
@@ -232,7 +242,7 @@ public ArrayList<LocalItem> getItemsList() {
232242
@Override
233243
public int getItemCount() {
234244
int count = localItems.size();
235-
if (header != null) {
245+
if (hasHeader()) {
236246
count++;
237247
}
238248
if (footer != null && showFooter) {
@@ -242,7 +252,7 @@ public int getItemCount() {
242252
if (DEBUG) {
243253
Log.d(TAG, "getItemCount() called, count = " + count + ", "
244254
+ "localItems.size() = " + localItems.size() + ", "
245-
+ "header = " + header + ", footer = " + footer + ", "
255+
+ "header = " + hasHeader() + ", footer = " + footer + ", "
246256
+ "showFooter = " + showFooter);
247257
}
248258
return count;
@@ -255,9 +265,9 @@ public int getItemViewType(int position) {
255265
Log.d(TAG, "getItemViewType() called with: position = [" + position + "]");
256266
}
257267

258-
if (header != null && position == 0) {
268+
if (hasHeader() && position == 0) {
259269
return HEADER_TYPE;
260-
} else if (header != null) {
270+
} else if (hasHeader()) {
261271
position--;
262272
}
263273
if (footer != null && position == localItems.size() && showFooter) {
@@ -318,7 +328,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup paren
318328
}
319329
switch (type) {
320330
case HEADER_TYPE:
321-
return new HeaderFooterHolder(header);
331+
return new HeaderFooterHolder(headerSupplier.get());
322332
case FOOTER_TYPE:
323333
return new HeaderFooterHolder(footer);
324334
case LOCAL_PLAYLIST_HOLDER_TYPE:
@@ -366,14 +376,14 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int
366376

367377
if (holder instanceof LocalItemHolder) {
368378
// If header isn't null, offset the items by -1
369-
if (header != null) {
379+
if (hasHeader()) {
370380
position--;
371381
}
372382

373383
((LocalItemHolder) holder)
374384
.updateFromItem(localItems.get(position), recordManager, dateTimeFormatter);
375-
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
376-
((HeaderFooterHolder) holder).view = header;
385+
} else if (holder instanceof HeaderFooterHolder && position == 0 && hasHeader()) {
386+
((HeaderFooterHolder) holder).view = headerSupplier.get();
377387
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
378388
&& footer != null && showFooter) {
379389
((HeaderFooterHolder) holder).view = footer;
@@ -387,10 +397,10 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, fina
387397
for (final Object payload : payloads) {
388398
if (payload instanceof StreamStateEntity) {
389399
((LocalItemHolder) holder).updateState(localItems
390-
.get(header == null ? position : position - 1), recordManager);
400+
.get(hasHeader() ? position - 1 : position), recordManager);
391401
} else if (payload instanceof Boolean) {
392402
((LocalItemHolder) holder).updateState(localItems
393-
.get(header == null ? position : position - 1), recordManager);
403+
.get(hasHeader() ? position - 1 : position), recordManager);
394404
}
395405
}
396406
} else {

app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
import androidx.annotation.NonNull;
1515
import androidx.annotation.Nullable;
16-
import androidx.viewbinding.ViewBinding;
1716

1817
import com.evernote.android.state.State;
1918
import com.google.android.material.snackbar.Snackbar;
@@ -45,6 +44,7 @@
4544
import java.util.Comparator;
4645
import java.util.List;
4746
import java.util.Objects;
47+
import java.util.function.Supplier;
4848

4949
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
5050
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@@ -126,12 +126,12 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
126126
}
127127

128128
@Override
129-
protected ViewBinding getListHeader() {
129+
protected Supplier<View> getListHeaderSupplier() {
130130
headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(),
131131
itemsList, false);
132132
playlistControlBinding = headerBinding.playlistControl;
133133

134-
return headerBinding;
134+
return headerBinding::getRoot;
135135
}
136136

137137
@Override

0 commit comments

Comments
 (0)