diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b87219f4f..4a707ff0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: java-version: 11 distribution: "temurin" cache: 'gradle' - + - name: Build debug APK and run jvm tests run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint @@ -54,7 +54,7 @@ jobs: runs-on: macos-latest strategy: matrix: - # api-level 19 is min sdk, but throws errors related to desugaring + # api-level 19 is min sdk, but throws errors related to desugaring api-level: [ 21, 29 ] steps: - uses: actions/checkout@v2 @@ -72,31 +72,31 @@ jobs: api-level: ${{ matrix.api-level }} # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160 emulator-build: 7425822 - script: ./gradlew connectedCheck + script: ./gradlew connectedCheck --stacktrace -# sonar: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v2 -# with: -# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + sonar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis -# - name: Set up JDK 11 -# uses: actions/setup-java@v2 -# with: -# java-version: 11 # Sonar requires JDK 11 -# distribution: "temurin" -# cache: 'gradle' + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: 11 # Sonar requires JDK 11 + distribution: "temurin" + cache: 'gradle' -# - name: Cache SonarCloud packages -# uses: actions/cache@v2 -# with: -# path: ~/.sonar/cache -# key: ${{ runner.os }}-sonar -# restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarCloud packages + uses: actions/cache@v2 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar -# - name: Build and analyze -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any -# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -# run: ./gradlew build sonarqube --info + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew build sonarqube --info diff --git a/README.ja.md b/README.ja.md index 5d9d46f75..2deab555c 100644 --- a/README.ja.md +++ b/README.ja.md @@ -17,7 +17,7 @@

ウェブサイトブログFAQニュース


-*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。* +*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。* 注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。 diff --git a/app/build.gradle b/app/build.gradle index a813ead57..3b02cc757 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,12 @@ plugins { - id "org.sonarqube" version "3.1.1" + id "com.android.application" + id "kotlin-android" + id "kotlin-kapt" + id "kotlin-parcelize" + id "checkstyle" + id "org.sonarqube" version "3.3" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' -apply plugin: 'checkstyle' - android { compileSdkVersion 30 buildToolsVersion '30.0.3' @@ -17,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdkVersion 19 targetSdkVersion 29 - versionCode 979 - versionName "0.21.13" + versionCode 981 + versionName "0.21.15" multiDexEnabled true @@ -80,13 +79,13 @@ android { // Flag to enable support for the new language APIs coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 encoding 'utf-8' } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_11 } sourceSets { @@ -99,7 +98,7 @@ android { } ext { - checkstyleVersion = '8.38' + checkstyleVersion = '9.2' androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.3.0' @@ -107,13 +106,13 @@ ext { icepickVersion = '3.2.0' exoPlayerVersion = '2.14.2' - googleAutoServiceVersion = '1.0' + googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.0' markwonVersion = '4.6.2' leakCanaryVersion = '2.5' stethoVersion = '1.6.0' - mockitoVersion = '3.6.0' + mockitoVersion = '4.0.0' } configurations { @@ -190,11 +189,11 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:f6f2724634' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:10f6cc71' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" - ktlint 'com.pinterest:ktlint:0.40.0' + ktlint 'com.pinterest:ktlint:0.43.2' /** Kotlin **/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" @@ -202,7 +201,7 @@ dependencies { /** AndroidX **/ implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' @@ -221,7 +220,7 @@ dependencies { // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.webkit:webkit:1.4.0' - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.4.0' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" @@ -231,7 +230,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.13.1" + implementation "org.jsoup:jsoup:1.14.3" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users @@ -269,13 +268,13 @@ dependencies { implementation 'com.jakewharton:process-phoenix:2.1.2' // Reactive extensions for Java VM - implementation "io.reactivex.rxjava3:rxjava:3.0.7" + implementation "io.reactivex.rxjava3:rxjava:3.0.13" implementation "io.reactivex.rxjava3:rxandroid:3.0.0" // RxJava binding APIs for Android UI widgets implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" // Date and time formatting - implementation "org.ocpsoft.prettytime:prettytime:5.0.1.Final" + implementation "org.ocpsoft.prettytime:prettytime:5.0.2.Final" /** Debugging **/ // Memory leak detection @@ -291,11 +290,9 @@ dependencies { testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.mockito:mockito-inline:${mockitoVersion}" - androidTestImplementation "androidx.test.ext:junit:1.1.2" + androidTestImplementation "androidx.test.ext:junit:1.1.3" + androidTestImplementation "androidx.test:runner:1.4.0" androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}" - androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", { - exclude module: 'support-annotations' - } } static String getGitWorkingBranch() { diff --git a/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt new file mode 100644 index 000000000..3f3a038d8 --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt @@ -0,0 +1,153 @@ +package org.schabi.newpipe.local.history + +import androidx.test.core.app.ApplicationProvider +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.Timeout +import org.schabi.newpipe.database.AppDatabase +import org.schabi.newpipe.database.history.model.SearchHistoryEntry +import org.schabi.newpipe.testUtil.TestDatabase +import org.schabi.newpipe.testUtil.TrampolineSchedulerRule +import java.time.OffsetDateTime +import java.util.concurrent.TimeUnit + +class HistoryRecordManagerTest { + + private lateinit var manager: HistoryRecordManager + private lateinit var database: AppDatabase + + @get:Rule + val trampolineScheduler = TrampolineSchedulerRule() + + @get:Rule + val timeout = Timeout(1, TimeUnit.SECONDS) + + @Before + fun setup() { + database = TestDatabase.createReplacingNewPipeDatabase() + manager = HistoryRecordManager(ApplicationProvider.getApplicationContext()) + } + + @After + fun cleanUp() { + database.close() + } + + @Test + fun onSearched() { + manager.onSearched(0, "Hello").test().await().assertValue(1) + + // For some reason the Flowable returned by getAll() never completes, so we can't assert + // that the number of Lists it returns is exactly 1, we can only check if the first List is + // correct. Why on earth has a Flowable been used instead of a Single for getAll()?!? + val entities = database.searchHistoryDAO().all.blockingFirst() + assertEquals(1, entities.size) + assertEquals(1, entities[0].id) + assertEquals(0, entities[0].serviceId) + assertEquals("Hello", entities[0].search) + } + + @Test + fun deleteSearchHistory() { + val entries = listOf( + SearchHistoryEntry(OffsetDateTime.now(), 0, "A"), + SearchHistoryEntry(OffsetDateTime.now(), 2, "A"), + SearchHistoryEntry(OffsetDateTime.now(), 1, "B"), + SearchHistoryEntry(OffsetDateTime.now(), 0, "B"), + ) + + // make sure all 4 were inserted + database.searchHistoryDAO().insertAll(entries) + assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size) + + // try to delete only "A" entries, "B" entries should be untouched + manager.deleteSearchHistory("A").test().await().assertValue(2) + val entities = database.searchHistoryDAO().all.blockingFirst() + assertEquals(2, entities.size) + assertTrue(entries[2].hasEqualValues(entities[0])) + assertTrue(entries[3].hasEqualValues(entities[1])) + + // assert that nothing happens if we delete a search query that does exist in the db + manager.deleteSearchHistory("A").test().await().assertValue(0) + val entities2 = database.searchHistoryDAO().all.blockingFirst() + assertEquals(2, entities2.size) + assertTrue(entries[2].hasEqualValues(entities2[0])) + assertTrue(entries[3].hasEqualValues(entities2[1])) + + // delete all remaining entries + manager.deleteSearchHistory("B").test().await().assertValue(2) + assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size) + } + + @Test + fun deleteCompleteSearchHistory() { + val entries = listOf( + SearchHistoryEntry(OffsetDateTime.now(), 1, "A"), + SearchHistoryEntry(OffsetDateTime.now(), 2, "B"), + SearchHistoryEntry(OffsetDateTime.now(), 0, "C"), + ) + + // make sure all 3 were inserted + database.searchHistoryDAO().insertAll(entries) + assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size) + + // should remove everything + manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size) + assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size) + } + + @Test + fun getRelatedSearches_emptyQuery() { + // make sure all entries were inserted + database.searchHistoryDAO().insertAll(RELATED_SEARCHES_ENTRIES) + assertEquals( + RELATED_SEARCHES_ENTRIES.size, + database.searchHistoryDAO().all.blockingFirst().size + ) + + // make sure correct number of searches is returned and in correct order + val searches = manager.getRelatedSearches("", 6, 4).blockingFirst() + assertEquals(4, searches.size) + assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places) + assertEquals(RELATED_SEARCHES_ENTRIES[4].search, searches[1]) // B + assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[2]) // AA + assertEquals(RELATED_SEARCHES_ENTRIES[2].search, searches[3]) // BA + } + + @Test + fun getRelatedSearched_nonEmptyQuery() { + // make sure all entries were inserted + database.searchHistoryDAO().insertAll(RELATED_SEARCHES_ENTRIES) + assertEquals( + RELATED_SEARCHES_ENTRIES.size, + database.searchHistoryDAO().all.blockingFirst().size + ) + + // make sure correct number of searches is returned and in correct order + val searches = manager.getRelatedSearches("A", 3, 5).blockingFirst() + assertEquals(3, searches.size) + assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places) + assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[1]) // AA + assertEquals(RELATED_SEARCHES_ENTRIES[1].search, searches[2]) // BA + + // also make sure that the string comparison is case insensitive + val searches2 = manager.getRelatedSearches("a", 3, 5).blockingFirst() + assertEquals(searches, searches2) + } + + companion object { + val RELATED_SEARCHES_ENTRIES = listOf( + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(7), 2, "AC"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(6), 0, "ABC"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(5), 1, "BA"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(4), 3, "A"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(2), 0, "B"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(3), 2, "AA"), + SearchHistoryEntry(OffsetDateTime.now().minusSeconds(1), 1, "A"), + ) + } +} diff --git a/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt b/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt index 0a00b0443..249492d8f 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt @@ -1,7 +1,5 @@ package org.schabi.newpipe.local.playlist -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider import org.junit.After import org.junit.Before import org.junit.Rule @@ -10,6 +8,7 @@ import org.junit.rules.Timeout import org.schabi.newpipe.database.AppDatabase import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.extractor.stream.StreamType +import org.schabi.newpipe.testUtil.TestDatabase import org.schabi.newpipe.testUtil.TrampolineSchedulerRule import java.util.concurrent.TimeUnit @@ -22,17 +21,11 @@ class LocalPlaylistManagerTest { val trampolineScheduler = TrampolineSchedulerRule() @get:Rule - val timeout = Timeout(10, TimeUnit.SECONDS) + val timeout = Timeout(1, TimeUnit.SECONDS) @Before fun setup() { - database = Room.inMemoryDatabaseBuilder( - ApplicationProvider.getApplicationContext(), - AppDatabase::class.java - ) - .allowMainThreadQueries() - .build() - + database = TestDatabase.createReplacingNewPipeDatabase() manager = LocalPlaylistManager(database) } diff --git a/app/src/androidTest/java/org/schabi/newpipe/testUtil/TestDatabase.kt b/app/src/androidTest/java/org/schabi/newpipe/testUtil/TestDatabase.kt new file mode 100644 index 000000000..8b1a261be --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/testUtil/TestDatabase.kt @@ -0,0 +1,32 @@ +package org.schabi.newpipe.testUtil + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import org.junit.Assert.assertSame +import org.schabi.newpipe.NewPipeDatabase +import org.schabi.newpipe.database.AppDatabase + +class TestDatabase { + companion object { + fun createReplacingNewPipeDatabase(): AppDatabase { + val database = Room.inMemoryDatabaseBuilder( + ApplicationProvider.getApplicationContext(), + AppDatabase::class.java + ) + .allowMainThreadQueries() + .build() + + val databaseField = NewPipeDatabase::class.java.getDeclaredField("databaseInstance") + databaseField.isAccessible = true + databaseField.set(NewPipeDatabase::class, database) + + assertSame( + "Mocking database failed!", + database, + NewPipeDatabase.getInstance(ApplicationProvider.getApplicationContext()) + ) + + return database + } + } +} diff --git a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index 31c8dd40c..d8f48784d 100644 --- a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -5,6 +5,9 @@ import android.os.Bundle; import androidx.preference.Preference; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; @@ -19,15 +22,21 @@ public class DebugSettingsFragment extends BasePreferenceFragment { = findPreference(getString(R.string.show_memory_leaks_key)); final Preference showImageIndicatorsPreference = findPreference(getString(R.string.show_image_indicators_key)); - final Preference crashTheAppPreference - = findPreference(getString(R.string.crash_the_app_key)); final Preference checkNewStreamsPreference = findPreference(getString(R.string.check_new_streams_key)); + final Preference crashTheAppPreference + = findPreference(getString(R.string.crash_the_app_key)); + final Preference showErrorSnackbarPreference + = findPreference(getString(R.string.show_error_snackbar_key)); + final Preference createErrorNotificationPreference + = findPreference(getString(R.string.create_error_notification_key)); assert showMemoryLeaksPreference != null; assert showImageIndicatorsPreference != null; - assert crashTheAppPreference != null; assert checkNewStreamsPreference != null; + assert crashTheAppPreference != null; + assert showErrorSnackbarPreference != null; + assert createErrorNotificationPreference != null; showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> { startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent()); @@ -39,13 +48,25 @@ public class DebugSettingsFragment extends BasePreferenceFragment { return true; }); - crashTheAppPreference.setOnPreferenceClickListener(preference -> { - throw new RuntimeException(); - }); - checkNewStreamsPreference.setOnPreferenceClickListener(preference -> { NotificationWorker.runNow(preference.getContext()); return true; }); + + crashTheAppPreference.setOnPreferenceClickListener(preference -> { + throw new RuntimeException(); + }); + + showErrorSnackbarPreference.setOnPreferenceClickListener(preference -> { + ErrorUtil.showUiErrorSnackbar(DebugSettingsFragment.this, + "Dummy", new RuntimeException("Dummy")); + return true; + }); + + createErrorNotificationPreference.setOnPreferenceClickListener(preference -> { + ErrorUtil.createNotification(requireContext(), + new ErrorInfo(new RuntimeException("Dummy"), UserAction.UI_ERROR, "Dummy")); + return true; + }); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc631af7a..28cdbf020 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -338,6 +338,7 @@ + diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index ca1bd79d2..3e5f408f7 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -63,6 +63,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { return consumed == dy; } + @Override public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final AppBarLayout child, @NonNull final MotionEvent ev) { diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 0cfa4ee62..b0931d3c1 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -16,8 +16,8 @@ import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; import org.acra.config.CoreConfigurationBuilder; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; @@ -217,7 +217,7 @@ public class App extends MultiDexApplication { ACRA.init(this, acraConfig); } catch (final ACRAConfigurationException exception) { exception.printStackTrace(); - ErrorActivity.reportError(this, new ErrorInfo(exception, + ErrorUtil.openActivity(this, new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report")); } } @@ -226,41 +226,44 @@ public class App extends MultiDexApplication { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels final NotificationChannelCompat mainChannel = new NotificationChannelCompat - .Builder( - getString(R.string.notification_channel_id), + .Builder(getString(R.string.notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.notification_channel_name)) .setDescription(getString(R.string.notification_channel_description)) .build(); final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat - .Builder( - getString(R.string.app_update_notification_channel_id), + .Builder(getString(R.string.app_update_notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.app_update_notification_channel_name)) .setDescription(getString(R.string.app_update_notification_channel_description)) .build(); final NotificationChannelCompat hashChannel = new NotificationChannelCompat - .Builder( - getString(R.string.hash_channel_id), + .Builder(getString(R.string.hash_channel_id), NotificationManagerCompat.IMPORTANCE_HIGH) .setName(getString(R.string.hash_channel_name)) .setDescription(getString(R.string.hash_channel_description)) .build(); + final NotificationChannelCompat errorReportChannel = new NotificationChannelCompat + .Builder(getString(R.string.error_report_channel_id), + NotificationManagerCompat.IMPORTANCE_LOW) + .setName(getString(R.string.error_report_channel_name)) + .setDescription(getString(R.string.error_report_channel_description)) + .build(); + + final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat - .Builder( - getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT) + .Builder(getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) .setName(getString(R.string.streams_notification_channel_name)) .setDescription(getString(R.string.streams_notification_channel_description)) .build(); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.createNotificationChannelsCompat( - Arrays.asList(mainChannel, appUpdateChannel, hashChannel, newStreamsChannel) - ); + notificationManager.createNotificationChannelsCompat(Arrays.asList(mainChannel, + appUpdateChannel, hashChannel, errorReportChannel, newStreamsChannel)); } protected boolean isDisposedRxExceptionsReported() { diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java index 9e43394ac..122660d64 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java @@ -21,8 +21,8 @@ import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; @@ -64,7 +64,7 @@ public final class CheckForNewAppVersion extends IntentService { signatures = PackageInfoCompat.getSignatures(application.getPackageManager(), application.getPackageName()); } catch (final PackageManager.NameNotFoundException e) { - ErrorActivity.reportError(application, new ErrorInfo(e, + ErrorUtil.createNotification(application, new ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info")); return ""; } @@ -79,7 +79,7 @@ public final class CheckForNewAppVersion extends IntentService { final CertificateFactory cf = CertificateFactory.getInstance("X509"); c = (X509Certificate) cf.generateCertificate(input); } catch (final CertificateException e) { - ErrorActivity.reportError(application, new ErrorInfo(e, + ErrorUtil.createNotification(application, new ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error")); return ""; } @@ -89,7 +89,7 @@ public final class CheckForNewAppVersion extends IntentService { final byte[] publicKey = md.digest(c.getEncoded()); return byte2HexFormatted(publicKey); } catch (NoSuchAlgorithmException | CertificateEncodingException e) { - ErrorActivity.reportError(application, new ErrorInfo(e, + ErrorUtil.createNotification(application, new ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key")); return ""; } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index bbf45e035..9895bace7 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -63,7 +63,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding; import org.schabi.newpipe.databinding.DrawerLayoutBinding; import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding; import org.schabi.newpipe.databinding.ToolbarLayoutBinding; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -158,7 +158,7 @@ public class MainActivity extends AppCompatActivity { try { setupDrawer(); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e); + ErrorUtil.showUiErrorSnackbar(this, "Setting up drawer", e); } if (DeviceUtils.isTv(this)) { FocusOverlayView.setupFocusObserver(this); @@ -218,7 +218,7 @@ public class MainActivity extends AppCompatActivity { /** * Builds the drawer menu for the current service. * - * @throws ExtractionException + * @throws ExtractionException if the service didn't provide available kiosks */ private void addDrawerMenuForCurrentService() throws ExtractionException { //Tabs @@ -270,7 +270,7 @@ public class MainActivity extends AppCompatActivity { try { tabSelected(item); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e); + ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e); } break; case R.id.menu_options_about_group: @@ -376,7 +376,7 @@ public class MainActivity extends AppCompatActivity { try { addDrawerMenuForCurrentService(); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e); + ErrorUtil.showUiErrorSnackbar(this, "Showing main page tabs", e); } } } @@ -479,7 +479,7 @@ public class MainActivity extends AppCompatActivity { drawerHeaderBinding.drawerHeaderActionButton.setContentDescription( getString(R.string.drawer_header_description) + selectedServiceName); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e); + ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e); } final SharedPreferences sharedPreferences @@ -789,7 +789,7 @@ public class MainActivity extends AppCompatActivity { NavigationHelper.gotoMainFragment(getSupportFragmentManager()); } } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e); + ErrorUtil.showUiErrorSnackbar(this, "Handling intent", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 4e96f3bb6..9d6e44f04 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -37,8 +37,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; import org.schabi.newpipe.download.DownloadDialog; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.Info; @@ -231,7 +231,7 @@ public class RouterActivity extends AppCompatActivity { } else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) { Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show(); } else { - ErrorActivity.reportError(context, errorInfo); + ErrorUtil.createNotification(context, errorInfo); } if (context instanceof RouterActivity) { diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index a0010a419..8a281bdb4 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -19,6 +19,7 @@ import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE @Dao public interface SearchHistoryDAO extends HistoryDAO { String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC"; + String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC"; @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") @@ -36,16 +37,16 @@ public interface SearchHistoryDAO extends HistoryDAO { @Override Flowable> getAll(); - @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE - + " LIMIT :limit") - Flowable> getUniqueEntries(int limit); + @Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH + + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit") + Flowable> getUniqueEntries(int limit); @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) @Override Flowable> listByService(int serviceId); - @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'" - + " GROUP BY " + SEARCH + " LIMIT :limit") - Flowable> getSimilarEntries(String query, int limit); + @Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'" + + " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit") + Flowable> getSimilarEntries(String query, int limit); } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index a7f5b938f..5c954ad64 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -41,8 +41,8 @@ import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DownloadDialogBinding; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.NewPipe; @@ -53,6 +53,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.settings.NewPipeSettings; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -402,7 +403,7 @@ public class DownloadDialog extends DialogFragment == R.id.video_button) { setupVideoSpinner(); } - }, throwable -> ErrorActivity.reportErrorInSnackbar(context, + }, throwable -> ErrorUtil.showSnackbar(context, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, "Downloading video stream size", currentInfo.getServiceId())))); @@ -412,7 +413,7 @@ public class DownloadDialog extends DialogFragment == R.id.audio_button) { setupAudioSpinner(); } - }, throwable -> ErrorActivity.reportErrorInSnackbar(context, + }, throwable -> ErrorUtil.showSnackbar(context, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, "Downloading audio stream size", currentInfo.getServiceId())))); @@ -422,7 +423,7 @@ public class DownloadDialog extends DialogFragment == R.id.subtitle_button) { setupSubtitleSpinner(); } - }, throwable -> ErrorActivity.reportErrorInSnackbar(context, + }, throwable -> ErrorUtil.showSnackbar(context, new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG, "Downloading subtitle stream size", currentInfo.getServiceId())))); @@ -687,7 +688,12 @@ public class DownloadDialog extends DialogFragment } private void launchDirectoryPicker(final ActivityResultLauncher launcher) { - launcher.launch(StoredDirectoryHelper.getPicker(context)); + NoFileManagerSafeGuard.launchSafe( + launcher, + StoredDirectoryHelper.getPicker(context), + TAG, + context + ); } private void prepareSelectedDownload() { @@ -766,8 +772,12 @@ public class DownloadDialog extends DialogFragment initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context, - filenameTmp, mimeTmp, initialPath)); + NoFileManagerSafeGuard.launchSafe( + requestDownloadSaveAsLauncher, + StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath), + TAG, + context + ); return; } @@ -799,7 +809,7 @@ public class DownloadDialog extends DialogFragment mainStorage.getTag()); } } catch (final Exception e) { - ErrorActivity.reportErrorInSnackbar(this, + ErrorUtil.createNotification(requireContext(), new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java index 60d4908eb..4d9966364 100644 --- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java @@ -33,12 +33,11 @@ public class AcraReportSender implements ReportSender { @Override public void send(@NonNull final Context context, @NonNull final CrashReportData report) { - ErrorActivity.reportError(context, new ErrorInfo( + ErrorUtil.openActivity(context, new ErrorInfo( new String[]{report.getString(ReportField.STACK_TRACE)}, UserAction.UI_ERROR, ErrorInfo.SERVICE_NONE, "ACRA report", - R.string.app_ui_crash, - null)); + R.string.app_ui_crash)); } } diff --git a/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java b/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java deleted file mode 100644 index db94de5e5..000000000 --- a/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.schabi.newpipe.error; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Ensures that a Exception is serializable. - * This is - */ -public final class EnsureExceptionSerializable { - private static final String TAG = "EnsureExSerializable"; - - private EnsureExceptionSerializable() { - // No instance - } - - /** - * Ensures that an exception is serializable. - *
- * If that is not the case a {@link WorkaroundNotSerializableException} is created. - * - * @param exception - * @return if an exception is not serializable a new {@link WorkaroundNotSerializableException} - * otherwise the exception from the parameter - */ - public static Exception ensureSerializable(@NonNull final Exception exception) { - return checkIfSerializable(exception) - ? exception - : WorkaroundNotSerializableException.create(exception); - } - - public static boolean checkIfSerializable(@NonNull final Exception exception) { - try { - // Check by creating a new ObjectOutputStream which does the serialization - try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos) - ) { - oos.writeObject(exception); - oos.flush(); - - bos.toByteArray(); - } - - return true; - } catch (final IOException ex) { - Log.d(TAG, "Exception is not serializable", ex); - return false; - } - } - - public static class WorkaroundNotSerializableException extends Exception { - protected WorkaroundNotSerializableException( - final Throwable notSerializableException, - final Throwable cause) { - super(notSerializableException.toString(), cause); - setStackTrace(notSerializableException.getStackTrace()); - } - - protected WorkaroundNotSerializableException(final Throwable notSerializableException) { - super(notSerializableException.toString()); - setStackTrace(notSerializableException.getStackTrace()); - } - - public static WorkaroundNotSerializableException create( - @NonNull final Exception notSerializableException - ) { - // Build a list of the exception + all causes - final List throwableList = new ArrayList<>(); - - int pos = 0; - Throwable throwableToProcess = notSerializableException; - - while (throwableToProcess != null) { - throwableList.add(throwableToProcess); - - pos++; - throwableToProcess = throwableToProcess.getCause(); - } - - // Reverse list so that it starts with the last one - Collections.reverse(throwableList); - - // Build exception stack - WorkaroundNotSerializableException cause = null; - for (final Throwable t : throwableList) { - cause = cause == null - ? new WorkaroundNotSerializableException(t) - : new WorkaroundNotSerializableException(t, cause); - } - - return cause; - } - - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index db3a92d4f..bd8430296 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -1,9 +1,10 @@ package org.schabi.newpipe.error; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -11,15 +12,12 @@ import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import com.google.android.material.snackbar.Snackbar; import com.grack.nanojson.JsonWriter; import org.schabi.newpipe.BuildConfig; @@ -27,15 +25,13 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityErrorBinding; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - /* * Created by Christian Schabesberger on 24.10.15. * @@ -56,6 +52,10 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; * along with NewPipe. If not, see . */ +/** + * This activity is used to show error details and allow reporting them in various ways. Use {@link + * ErrorUtil#openActivity(Context, ErrorInfo)} to correctly open this activity. + */ public class ErrorActivity extends AppCompatActivity { // LOG TAGS public static final String TAG = ErrorActivity.class.toString(); @@ -77,67 +77,6 @@ public class ErrorActivity extends AppCompatActivity { private ActivityErrorBinding activityErrorBinding; - /** - * Reports a new error by starting a new activity. - *
- * Ensure that the data within errorInfo is serializable otherwise - * an exception will be thrown!
- * {@link EnsureExceptionSerializable} might help. - * - * @param context - * @param errorInfo - */ - public static void reportError(final Context context, final ErrorInfo errorInfo) { - final Intent intent = new Intent(context, ErrorActivity.class); - intent.putExtra(ERROR_INFO, errorInfo); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } - - public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) { - final View rootView = context instanceof Activity - ? ((Activity) context).findViewById(android.R.id.content) : null; - reportErrorInSnackbar(context, rootView, errorInfo); - } - - public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) { - View rootView = fragment.getView(); - if (rootView == null && fragment.getActivity() != null) { - rootView = fragment.getActivity().findViewById(android.R.id.content); - } - reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo); - } - - public static void reportUiErrorInSnackbar(final Context context, - final String request, - final Throwable throwable) { - reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request)); - } - - public static void reportUiErrorInSnackbar(final Fragment fragment, - final String request, - final Throwable throwable) { - reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request)); - } - - - //////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////// - - private static void reportErrorInSnackbar(final Context context, - @Nullable final View rootView, - final ErrorInfo errorInfo) { - if (rootView != null) { - Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) - .setActionTextColor(Color.YELLOW) - .setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v -> - reportError(context, errorInfo)).show(); - } else { - reportError(context, errorInfo); - } - } - //////////////////////////////////////////////////////////////////////// // Activity lifecycle diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 6581b5752..b2ba912ec 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -2,6 +2,8 @@ package org.schabi.newpipe.error import android.os.Parcelable import androidx.annotation.StringRes +import com.google.android.exoplayer2.ExoPlaybackException +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info @@ -21,11 +23,14 @@ class ErrorInfo( val userAction: UserAction, val serviceName: String, val request: String, - val messageStringId: Int, - @Transient // no need to store throwable, all data for report is in other variables - var throwable: Throwable? = null + val messageStringId: Int ) : Parcelable { + // no need to store throwable, all data for report is in other variables + // also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302 + @IgnoredOnParcel + var throwable: Throwable? = null + private constructor( throwable: Throwable, userAction: UserAction, @@ -36,9 +41,10 @@ class ErrorInfo( userAction, serviceName, request, - getMessageStringId(throwable, userAction), - throwable - ) + getMessageStringId(throwable, userAction) + ) { + this.throwable = throwable + } private constructor( throwable: List, @@ -50,9 +56,10 @@ class ErrorInfo( userAction, serviceName, request, - getMessageStringId(throwable.firstOrNull(), userAction), - throwable.firstOrNull() - ) + getMessageStringId(throwable.firstOrNull(), userAction) + ) { + this.throwable = throwable.firstOrNull() + } // constructors with single throwable constructor(throwable: Throwable, userAction: UserAction, request: String) : @@ -102,6 +109,13 @@ class ErrorInfo( throwable is ContentNotSupportedException -> R.string.content_not_supported throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error throwable is ExtractionException -> R.string.parsing_error + throwable is ExoPlaybackException -> { + when (throwable.type) { + ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure + ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure + else -> R.string.player_unrecoverable_failure + } + } action == UserAction.UI_ERROR -> R.string.app_ui_crash action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 228c17f8c..692cb427a 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -118,7 +118,7 @@ class ErrorPanelHelper( showAndSetErrorButtonAction( R.string.error_snackbar_action ) { - ErrorActivity.reportError(context, errorInfo) + ErrorUtil.openActivity(context, errorInfo) } errorTextView.setText(getExceptionDescription(errorInfo.throwable)) @@ -178,7 +178,7 @@ class ErrorPanelHelper( val DEBUG: Boolean = MainActivity.DEBUG @StringRes - public fun getExceptionDescription(throwable: Throwable?): Int { + fun getExceptionDescription(throwable: Throwable?): Int { return when (throwable) { is AgeRestrictedContentException -> R.string.restricted_video_no_stream is GeographicRestrictionException -> R.string.georestricted_content diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt new file mode 100644 index 000000000..3fd743c69 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -0,0 +1,165 @@ +package org.schabi.newpipe.error + +import android.app.Activity +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.view.View +import android.widget.Toast +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import org.schabi.newpipe.R + +/** + * This class contains all of the methods that should be used to let the user know that an error has + * occurred in the least intrusive way possible for each case. This class is for unexpected errors, + * for handled errors (e.g. network errors) use e.g. [ErrorPanelHelper] instead. + * - Use a snackbar if the exception is not critical and it happens in a place where a root view + * is available. + * - Use a notification if the exception happens inside a background service (player, subscription + * import, ...) or there is no activity/fragment from which to extract a root view. + * - Finally use the error activity only as a last resort in case the exception is critical and + * happens in an open activity (since the workflow would be interrupted anyway in that case). + */ +class ErrorUtil { + companion object { + private const val ERROR_REPORT_NOTIFICATION_ID = 5340681 + + /** + * Starts a new error activity allowing the user to report the provided error. Only use this + * method directly as a last resort in case the exception is critical and happens in an open + * activity (since the workflow would be interrupted anyway in that case). So never use this + * for background services. + * + * @param context the context to use to start the new activity + * @param errorInfo the error info to be reported + */ + @JvmStatic + fun openActivity(context: Context, errorInfo: ErrorInfo) { + context.startActivity(getErrorActivityIntent(context, errorInfo)) + } + + /** + * Show a bottom snackbar to the user, with a report button that opens the error activity. + * Use this method if the exception is not critical and it happens in a place where a root + * view is available. + * + * @param context will be used to obtain the root view if it is an [Activity]; if no root + * view can be found an error notification is shown instead + * @param errorInfo the error info to be reported + */ + @JvmStatic + fun showSnackbar(context: Context, errorInfo: ErrorInfo) { + val rootView = if (context is Activity) context.findViewById(R.id.content) else null + showSnackbar(context, rootView, errorInfo) + } + + /** + * Show a bottom snackbar to the user, with a report button that opens the error activity. + * Use this method if the exception is not critical and it happens in a place where a root + * view is available. + * + * @param fragment will be used to obtain the root view if it has a connected [Activity]; if + * no root view can be found an error notification is shown instead + * @param errorInfo the error info to be reported + */ + @JvmStatic + fun showSnackbar(fragment: Fragment, errorInfo: ErrorInfo) { + var rootView = fragment.view + if (rootView == null && fragment.activity != null) { + rootView = fragment.requireActivity().findViewById(R.id.content) + } + showSnackbar(fragment.requireContext(), rootView, errorInfo) + } + + /** + * Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR] + */ + @JvmStatic + fun showUiErrorSnackbar(context: Context, request: String, throwable: Throwable) { + showSnackbar(context, ErrorInfo(throwable, UserAction.UI_ERROR, request)) + } + + /** + * Shortcut to calling [showSnackbar] with an [ErrorInfo] of type [UserAction.UI_ERROR] + */ + @JvmStatic + fun showUiErrorSnackbar(fragment: Fragment, request: String, throwable: Throwable) { + showSnackbar(fragment, ErrorInfo(throwable, UserAction.UI_ERROR, request)) + } + + /** + * Create an error notification. Tapping on the notification opens the error activity. Use + * this method if the exception happens inside a background service (player, subscription + * import, ...) or there is no activity/fragment from which to extract a root view. + * + * @param context the context to use to show the notification + * @param errorInfo the error info to be reported; the error message + * [ErrorInfo.messageStringId] will be shown in the notification + * description + */ + @JvmStatic + fun createNotification(context: Context, errorInfo: ErrorInfo) { + val notificationManager = + ContextCompat.getSystemService(context, NotificationManager::class.java) + if (notificationManager == null) { + // this should never happen, but just in case open error activity + openActivity(context, errorInfo) + } + + var pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE + } + + val notificationBuilder: NotificationCompat.Builder = + NotificationCompat.Builder( + context, + context.getString(R.string.error_report_channel_id) + ) + .setSmallIcon(R.drawable.ic_bug_report) + .setContentTitle(context.getString(R.string.error_report_notification_title)) + .setContentText(context.getString(errorInfo.messageStringId)) + .setAutoCancel(true) + .setContentIntent( + PendingIntent.getActivity( + context, + 0, + getErrorActivityIntent(context, errorInfo), + pendingIntentFlags + ) + ) + + notificationManager!!.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build()) + + // since the notification is silent, also show a toast, otherwise the user is confused + Toast.makeText(context, R.string.error_report_notification_toast, Toast.LENGTH_SHORT) + .show() + } + + private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent { + val intent = Intent(context, ErrorActivity::class.java) + intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + return intent + } + + private fun showSnackbar(context: Context, rootView: View?, errorInfo: ErrorInfo) { + if (rootView == null) { + // fallback to showing a notification if no root view is available + createNotification(context, errorInfo) + } else { + Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) + .setActionTextColor(Color.YELLOW) + .setAction(context.getString(R.string.error_snackbar_action).uppercase()) { + openActivity(context, errorInfo) + }.show() + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index db91755df..9b4bf8377 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -7,12 +7,13 @@ import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorPanelHelper; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.util.InfoCache; import java.util.concurrent.atomic.AtomicBoolean; @@ -198,9 +199,8 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC } /** - * Show a SnackBar and only call - * {@link ErrorActivity#reportErrorInSnackbar(androidx.fragment.app.Fragment, ErrorInfo)} - * IF we a find a valid view (otherwise the error screen appears). + * Directly calls {@link ErrorUtil#showSnackbar(Fragment, ErrorInfo)}, that shows a snackbar if + * a valid view can be found, otherwise creates an error report notification. * * @param errorInfo The error information */ @@ -208,6 +208,6 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (DEBUG) { Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]"); } - ErrorActivity.reportErrorInSnackbar(this, errorInfo); + ErrorUtil.showSnackbar(this, errorInfo); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 7e0186e1c..de68269e9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -23,7 +23,7 @@ import com.google.android.material.tabs.TabLayout; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.FragmentMainBinding; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; @@ -145,7 +145,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte NavigationHelper.openSearchFragment(getFM(), ServiceHelper.getSelectedServiceId(activity), ""); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Opening search fragment", e); + ErrorUtil.showUiErrorSnackbar(this, "Opening search fragment", e); } return true; } @@ -227,16 +227,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte public Fragment getItem(final int position) { final Tab tab = internalTabsList.get(position); - Throwable throwable = null; - Fragment fragment = null; + final Fragment fragment; try { fragment = tab.getFragment(context); } catch (final ExtractionException e) { - throwable = e; - } - - if (throwable != null) { - ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable); + ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e); return new BlankFragment(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 8c6e01537..5a74dc948 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -55,8 +55,8 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.FragmentVideoDetailBinding; import org.schabi.newpipe.download.DownloadDialog; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; @@ -533,7 +533,7 @@ public final class VideoDetailFragment NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(), subChannelUrl, subChannelName); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } } @@ -685,7 +685,7 @@ public final class VideoDetailFragment }); setupBottomPlayer(); - if (!playerHolder.bound) { + if (!playerHolder.isBound()) { setHeightThumbnail(); } else { playerHolder.startService(false, this); @@ -1434,7 +1434,7 @@ public final class VideoDetailFragment bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); } // Rebound to the service if it was closed via notification or mini player - if (!playerHolder.bound) { + if (!playerHolder.isBound()) { playerHolder.startService( false, VideoDetailFragment.this); } @@ -1521,6 +1521,8 @@ public final class VideoDetailFragment animate(binding.detailThumbnailPlayButton, true, 200); binding.detailVideoTitleView.setText(title); + binding.detailSubChannelThumbnailView.setVisibility(View.GONE); + if (!isEmpty(info.getSubChannelName())) { displayBothUploaderAndSubChannel(info); } else if (!isEmpty(info.getUploaderName())) { @@ -1681,9 +1683,8 @@ public final class VideoDetailFragment downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); } catch (final Exception e) { - ErrorActivity.reportErrorInSnackbar(activity, - new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog", - currentInfo)); + ErrorUtil.showSnackbar(activity, new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, + "Showing download dialog", currentInfo)); } } @@ -1981,7 +1982,9 @@ public final class VideoDetailFragment // Prevent jumping of the player on devices with cutout if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { activity.getWindow().getAttributes().layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + isMultiWindowOrFullscreen() + ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; } activity.getWindow().getDecorView().setSystemUiVisibility(0); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); @@ -2003,7 +2006,9 @@ public final class VideoDetailFragment // Prevent jumping of the player on devices with cutout if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { activity.getWindow().getAttributes().layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + isMultiWindowOrFullscreen() + ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN @@ -2020,7 +2025,7 @@ public final class VideoDetailFragment activity.getWindow().getDecorView().setSystemUiVisibility(visibility); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) { + && isMultiWindowOrFullscreen()) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); } @@ -2036,6 +2041,11 @@ public final class VideoDetailFragment } } + private boolean isMultiWindowOrFullscreen() { + return DeviceUtils.isInMultiWindow(activity) + || (isPlayerAvailable() && player.isFullscreen()); + } + private boolean playerIsNotStopped() { return isPlayerAvailable() && !player.isStopped(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index b9065c969..4319d42ee 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -21,7 +21,7 @@ import androidx.viewbinding.ViewBinding; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PignateFooterBinding; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; @@ -293,7 +293,7 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar( + ErrorUtil.showUiErrorSnackbar( BaseListFragment.this, "Opening channel fragment", e); } } @@ -309,7 +309,7 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this, + ErrorUtil.showUiErrorSnackbar(BaseListFragment.this, "Opening playlist fragment", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 5ab54f98a..ccc2be7b4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -33,8 +33,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.databinding.ChannelHeaderBinding; import org.schabi.newpipe.databinding.FragmentChannelBinding; import org.schabi.newpipe.databinding.PlaylistControlBinding; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; @@ -466,7 +466,7 @@ public class ChannelFragment extends BaseListInfoFragment currentInfo.getParentChannelUrl(), currentInfo.getParentChannelName()); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } } else if (DEBUG) { Log.i(TAG, "Can't open parent channel because we got no channel URL"); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index a8763af73..a61cec11d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -24,8 +24,8 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.databinding.PlaylistControlBinding; import org.schabi.newpipe.databinding.PlaylistHeaderBinding; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; @@ -310,7 +310,7 @@ public class PlaylistFragment extends BaseListInfoFragment { NavigationHelper.openChannelFragment(getFM(), result.getServiceId(), result.getUploaderUrl(), result.getUploaderName()); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index d4d73f74f..15424334d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -1,5 +1,10 @@ package org.schabi.newpipe.fragments.list.search; +import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; +import static java.util.Arrays.asList; + import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -36,10 +41,9 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.databinding.FragmentSearchBinding; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; @@ -68,12 +72,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -84,11 +87,6 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.subjects.PublishSubject; -import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; -import static java.util.Arrays.asList; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; - public class SearchFragment extends BaseListFragment> implements BackPressable { /*////////////////////////////////////////////////////////////////////////// @@ -225,8 +223,7 @@ public class SearchFragment extends BaseListFragment 0 + && !infoListAdapter.getItemsList().isEmpty() && !isLoading.get()) { hideSuggestionsPanel(); hideKeyboardSearch(); @@ -743,13 +740,10 @@ public class SearchFragment extends BaseListFragment { - final Set result = new HashSet<>(); // remove duplicates - for (final SearchHistoryEntry entry : searchHistoryEntries) { - result.add(new SuggestionItem(true, entry.getSearch())); - } - return new ArrayList<>(result); - }); + .map(searchHistoryEntries -> + searchHistoryEntries.stream() + .map(entry -> new SuggestionItem(true, entry)) + .collect(Collectors.toList())); } private Observable> getRemoteSuggestionsObservable(final String query) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java index fb144574a..4fc2d9f84 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java @@ -34,12 +34,14 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { public final TextView itemTitleView; private final ImageView itemHeartView; + private final ImageView itemPinnedView; public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) { super(infoItemBuilder, R.layout.list_comments_item, parent); itemTitleView = itemView.findViewById(R.id.itemTitleView); itemHeartView = itemView.findViewById(R.id.detail_heart_image_view); + itemPinnedView = itemView.findViewById(R.id.detail_pinned_view); } @Override @@ -55,5 +57,7 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { itemTitleView.setText(item.getUploaderName()); itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE); + + itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 079efa4a8..cb47efa92 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -13,7 +13,7 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; @@ -171,7 +171,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { item.getUploaderUrl(), item.getUploaderName()); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e); + ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index dea498675..8c608eb0e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -84,27 +84,22 @@ class FeedLoadManager(private val context: Context) { return outdatedSubscriptions .take(1) - .doOnNext { currentProgress.set(0) maxProgress.set(it.size) } .filter { it.isNotEmpty() } - .observeOn(AndroidSchedulers.mainThread()) .doOnNext { notificationUpdater.onNext("") broadcastProgress() } - .observeOn(Schedulers.io()) .flatMap { Flowable.fromIterable(it) } .takeWhile { !cancelSignal.get() } - .parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2) .runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2) .filter { !cancelSignal.get() } - .map { subscriptionEntity -> var error: Throwable? = null try { @@ -154,14 +149,11 @@ class FeedLoadManager(private val context: Context) { } } .sequential() - .observeOn(AndroidSchedulers.mainThread()) .doOnNext(NotificationConsumer()) - .observeOn(Schedulers.io()) .buffer(BUFFER_COUNT_BEFORE_INSERT) .doOnNext(DatabaseConsumer()) - .subscribeOn(Schedulers.io()) .toList() .flatMap { x -> postProcessFeed().toSingleDefault(x.flatten()) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index 0d5d904e8..f2ea40416 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -154,7 +154,9 @@ class FeedLoadService : Service() { private fun createNotification(): NotificationCompat.Builder { val cancelActionIntent = PendingIntent.getBroadcast( this, - NOTIFICATION_ID, Intent(ACTION_CANCEL), 0 + NOTIFICATION_ID, + Intent(ACTION_CANCEL), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 ) return NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index d94088cd0..45445cf58 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -244,9 +244,9 @@ public class HistoryRecordManager { .subscribeOn(Schedulers.io()); } - public Flowable> getRelatedSearches(final String query, - final int similarQueryLimit, - final int uniqueQueryLimit) { + public Flowable> getRelatedSearches(final String query, + final int similarQueryLimit, + final int uniqueQueryLimit) { return query.length() > 0 ? searchHistoryTable.getSimilarEntries(query, similarQueryLimit) : searchHistoryTable.getUniqueEntries(uniqueQueryLimit); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 57e1effbe..008228083 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -55,6 +55,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.OnClickGesture @@ -179,15 +180,23 @@ class SubscriptionFragment : BaseStateFragment() { } private fun onImportPreviousSelected() { - requestImportLauncher.launch(StoredFileHelper.getPicker(activity, JSON_MIME_TYPE)) + NoFileManagerSafeGuard.launchSafe( + requestImportLauncher, + StoredFileHelper.getPicker(activity, JSON_MIME_TYPE), + TAG, + requireContext() + ) } private fun onExportSelected() { val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date()) val exportName = "newpipe_subscriptions_$date.json" - requestExportLauncher.launch( - StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null) + NoFileManagerSafeGuard.launchSafe( + requestExportLauncher, + StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null), + TAG, + requireContext() ) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java index 675799586..4737fa14f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -23,13 +23,14 @@ import androidx.core.text.util.LinkifyCompat; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ServiceHelper; @@ -86,12 +87,11 @@ public class SubscriptionsImportFragment extends BaseFragment { setupServiceVariables(); if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { - ErrorActivity.reportErrorInSnackbar(activity, + ErrorUtil.showSnackbar(activity, new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, NewPipe.getNameOfService(currentServiceId), "Service does not support importing subscriptions", - R.string.general_error, - null)); + R.string.general_error)); activity.finish(); } } @@ -175,8 +175,14 @@ public class SubscriptionsImportFragment extends BaseFragment { } public void onImportFile() { - // leave */* mime type to support all services with different mime types and file extensions - requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity, "*/*")); + NoFileManagerSafeGuard.launchSafe( + requestImportFileLauncher, + // leave */* mime type to support all services + // with different mime types and file extensions + StoredFileHelper.getPicker(activity, "*/*"), + TAG, + getContext() + ); } private void requestImportFileResult(final ActivityResult result) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index 2b964779c..a8c05838f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -54,11 +54,9 @@ class ChannelItem( context.getString(R.string.subscribers_count_not_available) } - if (itemVersion == ItemVersion.NORMAL) { - if (infoItem.streamCount >= 0) { - val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount) - details = Localization.concatenateStrings(details, formattedVideoAmount) - } + if (itemVersion == ItemVersion.NORMAL && infoItem.streamCount >= 0) { + val formattedVideoAmount = Localization.localizeStreamCount(context, infoItem.streamCount) + details = Localization.concatenateStrings(details, formattedVideoAmount) } return details } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 901eabf44..b7c11b160 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -35,8 +35,8 @@ import androidx.core.app.ServiceCompat; import org.reactivestreams.Publisher; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.ktx.ExceptionUtils; @@ -153,7 +153,7 @@ public abstract class BaseImportExportService extends Service { protected void stopAndReportError(final Throwable throwable, final String request) { stopService(); - ErrorActivity.reportError(this, new ErrorInfo( + ErrorUtil.createNotification(this, new ErrorInfo( throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request)); } diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 2d8c1a830..c038f5573 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -141,6 +141,9 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamSegment; @@ -165,7 +168,6 @@ import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.playback.SurfaceHolderCallback; -import org.schabi.newpipe.player.playererror.PlayerErrorHandler; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -268,8 +270,6 @@ public final class Player implements @Nullable private MediaSourceTag currentMetadata; @Nullable private Bitmap currentThumbnail; - @NonNull private PlayerErrorHandler playerErrorHandler; - /*////////////////////////////////////////////////////////////////////////// // Player //////////////////////////////////////////////////////////////////////////*/ @@ -413,8 +413,6 @@ public final class Player implements videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); - playerErrorHandler = new PlayerErrorHandler(context); - windowManager = ContextCompat.getSystemService(context, WindowManager.class); } @@ -2350,7 +2348,8 @@ public final class Player implements NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - private void setRepeatModeButton(final AppCompatImageButton imageButton, final int repeatMode) { + private void setRepeatModeButton(final AppCompatImageButton imageButton, + @RepeatMode final int repeatMode) { switch (repeatMode) { case REPEAT_MODE_OFF: imageButton.setImageResource(R.drawable.exo_controls_repeat_off); @@ -2364,7 +2363,7 @@ public final class Player implements } } - private void setShuffleButton(final ImageButton button, final boolean shuffled) { + private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) { button.setImageAlpha(shuffled ? 255 : 77); } //endregion @@ -2389,7 +2388,7 @@ public final class Player implements return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0; } - private void setMuteButton(final ImageButton button, final boolean isMuted) { + private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) { button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); } @@ -2518,29 +2517,30 @@ public final class Player implements saveStreamProgressState(); + // create error notification + final ErrorInfo errorInfo; + if (currentMetadata == null) { + errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, + "Player error[type=" + error.type + "] occurred, currentMetadata is null"); + } else { + errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM, + "Player error[type=" + error.type + "] occurred while playing " + + currentMetadata.getMetadata().getUrl(), + currentMetadata.getMetadata()); + } + ErrorUtil.createNotification(context, errorInfo); + switch (error.type) { case ExoPlaybackException.TYPE_SOURCE: processSourceError(error.getSourceException()); - playerErrorHandler.showPlayerError( - error, - currentMetadata.getMetadata(), - R.string.player_stream_failure); break; case ExoPlaybackException.TYPE_UNEXPECTED: - playerErrorHandler.showPlayerError( - error, - currentMetadata.getMetadata(), - R.string.player_recoverable_failure); setRecovery(); reloadPlayQueueManager(); break; case ExoPlaybackException.TYPE_REMOTE: case ExoPlaybackException.TYPE_RENDERER: default: - playerErrorHandler.showPlayerError( - error, - currentMetadata.getMetadata(), - R.string.player_unrecoverable_failure); onPlaybackShutdown(); break; } @@ -2877,7 +2877,7 @@ public final class Player implements databaseUpdateDisposable .add(recordManager.saveStreamState(currentMetadata.getMetadata(), progressMillis) .observeOn(AndroidSchedulers.mainThread()) - .doOnError((e) -> { + .doOnError(e -> { if (DEBUG) { e.printStackTrace(); } @@ -3387,7 +3387,7 @@ public final class Player implements playbackSpeedPopupMenu.setOnDismissListener(this); } - private void buildCaptionMenu(final List availableLanguages) { + private void buildCaptionMenu(@NonNull final List availableLanguages) { if (captionPopupMenu == null) { return; } @@ -3455,7 +3455,7 @@ public final class Player implements * Called when an item of the quality selector or the playback speed selector is selected. */ @Override - public boolean onMenuItemClick(final MenuItem menuItem) { + public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { if (DEBUG) { Log.d(TAG, "onMenuItemClick() called with: " + "menuItem = [" + menuItem + "], " @@ -3492,7 +3492,7 @@ public final class Player implements * Called when some popup menu is dismissed. */ @Override - public void onDismiss(final PopupMenu menu) { + public void onDismiss(@Nullable final PopupMenu menu) { if (DEBUG) { Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); } @@ -3545,7 +3545,7 @@ public final class Player implements isSomePopupMenuVisible = true; } - private void setPlaybackQuality(final String quality) { + private void setPlaybackQuality(@Nullable final String quality) { videoResolver.setPlaybackQuality(quality); } //endregion @@ -3569,7 +3569,7 @@ public final class Player implements final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); binding.subtitleView.setFixedTextSize( - TypedValue.COMPLEX_UNIT_PX, (float) minimumLength / captionRatioInverse); + TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); } binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); binding.subtitleView.setStyle(captionStyle); @@ -3846,7 +3846,7 @@ public final class Player implements } @Override // exoplayer listener - public void onVideoSizeChanged(final VideoSize videoSize) { + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { Log.d(TAG, "onVideoSizeChanged() called with: " + "width / height = [" + videoSize.width + " / " + videoSize.height @@ -3960,7 +3960,7 @@ public final class Player implements } } - private int distanceFromCloseButton(final MotionEvent popupMotionEvent) { + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + closeOverlayBinding.closeButton.getWidth() / 2; final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() @@ -3979,7 +3979,7 @@ public final class Player implements return buttonRadius * 1.2f; } - public boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) { + public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); } //endregion @@ -4099,6 +4099,7 @@ public final class Player implements } } + @Nullable public AppCompatActivity getParentActivity() { // ! instanceof ViewGroup means that view was added via windowManager for Popup if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) { diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java index e55c596b8..25ace1c05 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java @@ -1,5 +1,12 @@ package org.schabi.newpipe.player.event; +import static org.schabi.newpipe.ktx.AnimationType.ALPHA; +import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION; +import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; +import static org.schabi.newpipe.player.Player.STATE_PLAYING; + import android.app.Activity; import android.util.Log; import android.view.MotionEvent; @@ -8,22 +15,15 @@ import android.view.Window; import android.view.WindowManager; import android.widget.ProgressBar; +import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; -import org.jetbrains.annotations.NotNull; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; -import static org.schabi.newpipe.ktx.AnimationType.ALPHA; -import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; -import static org.schabi.newpipe.player.Player.STATE_PLAYING; - /** * GestureListener for the player * @@ -45,8 +45,8 @@ public class PlayerGestureListener } @Override - public void onDoubleTap(@NotNull final MotionEvent event, - @NotNull final DisplayPortion portion) { + public void onDoubleTap(@NonNull final MotionEvent event, + @NonNull final DisplayPortion portion) { if (DEBUG) { Log.d(TAG, "onDoubleTap called with playerType = [" + player.getPlayerType() + "], portion = [" + portion + "]"); @@ -65,7 +65,7 @@ public class PlayerGestureListener } @Override - public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) { + public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) { if (DEBUG) { Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]"); } @@ -85,10 +85,10 @@ public class PlayerGestureListener } @Override - public void onScroll(@NotNull final MainPlayer.PlayerType playerType, - @NotNull final DisplayPortion portion, - @NotNull final MotionEvent initialEvent, - @NotNull final MotionEvent movingEvent, + public void onScroll(@NonNull final MainPlayer.PlayerType playerType, + @NonNull final DisplayPortion portion, + @NonNull final MotionEvent initialEvent, + @NonNull final MotionEvent movingEvent, final float distanceX, final float distanceY) { if (DEBUG) { Log.d(TAG, "onScroll called with playerType = [" @@ -197,8 +197,8 @@ public class PlayerGestureListener } @Override - public void onScrollEnd(@NotNull final MainPlayer.PlayerType playerType, - @NotNull final MotionEvent event) { + public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType, + @NonNull final MotionEvent event) { if (DEBUG) { Log.d(TAG, "onScrollEnd called with playerType = [" + player.getPlayerType() + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index b7584151d..708b72ff2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -1,18 +1,14 @@ package org.schabi.newpipe.player.helper; import android.content.Context; -import android.os.Build; import androidx.annotation.NonNull; -import com.google.android.exoplayer2.source.MediaParserExtractorAdapter; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; -import com.google.android.exoplayer2.source.chunk.MediaParserChunkExtractor; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.hls.MediaParserHlsMediaChunkExtractor; import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.upstream.DataSource; @@ -46,17 +42,10 @@ public class PlayerDataSource { } public HlsMediaSource.Factory getLiveHlsMediaSourceFactory() { - final HlsMediaSource.Factory factory = - new HlsMediaSource.Factory(cachelessDataSourceFactory) - .setAllowChunklessPreparation(true) - .setLoadErrorHandlingPolicy( - new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY); - } - - return factory; + return new HlsMediaSource.Factory(cachelessDataSourceFactory) + .setAllowChunklessPreparation(true) + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(MANIFEST_MINIMUM_RETRY)); } public DashMediaSource.Factory getLiveDashMediaSourceFactory() { @@ -71,26 +60,11 @@ public class PlayerDataSource { private DefaultDashChunkSource.Factory getDefaultDashChunkSourceFactory( final DataSource.Factory dataSourceFactory ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - return new DefaultDashChunkSource.Factory( - MediaParserChunkExtractor.FACTORY, - dataSourceFactory, - 1 - ); - } - return new DefaultDashChunkSource.Factory(dataSourceFactory); } public HlsMediaSource.Factory getHlsMediaSourceFactory() { - final HlsMediaSource.Factory factory = new HlsMediaSource.Factory(cacheDataSourceFactory); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - return factory; - } - - // *** >= Android 11 / R / API 30 *** - return factory.setExtractorFactory(MediaParserHlsMediaChunkExtractor.FACTORY); + return new HlsMediaSource.Factory(cacheDataSourceFactory); } public DashMediaSource.Factory getDashMediaSourceFactory() { @@ -101,18 +75,9 @@ public class PlayerDataSource { } public ProgressiveMediaSource.Factory getExtractorMediaSourceFactory() { - final ProgressiveMediaSource.Factory factory; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - factory = new ProgressiveMediaSource.Factory( - cacheDataSourceFactory, - MediaParserExtractorAdapter.FACTORY - ); - } else { - factory = new ProgressiveMediaSource.Factory(cacheDataSourceFactory); - } - - return factory.setLoadErrorHandlingPolicy( - new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY)); + return new ProgressiveMediaSource.Factory(cacheDataSourceFactory) + .setLoadErrorHandlingPolicy( + new DefaultLoadErrorHandlingPolicy(EXTRACTOR_MINIMUM_RETRY)); } public SingleSampleMediaSource.Factory getSampleMediaSourceFactory() { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java index 46239cab1..10e315667 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java @@ -35,13 +35,13 @@ public final class PlayerHolder { return PlayerHolder.instance; } - private final boolean DEBUG = MainActivity.DEBUG; - private final String TAG = PlayerHolder.class.getSimpleName(); + private static final boolean DEBUG = MainActivity.DEBUG; + private static final String TAG = PlayerHolder.class.getSimpleName(); private PlayerServiceExtendedEventListener listener; private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); - public boolean bound; + private boolean bound; private MainPlayer playerService; private Player player; @@ -70,6 +70,10 @@ public final class PlayerHolder { return player != null; } + public boolean isBound() { + return bound; + } + public int getQueueSize() { return isPlayerOpen() ? player.getPlayQueue().size() : 0; } @@ -148,7 +152,7 @@ public final class PlayerHolder { } startPlayerListener(); } - }; + } private void bind(final Context context) { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java deleted file mode 100644 index 626200ae1..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.schabi.newpipe.player.playererror; - -import android.content.Context; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.preference.PreferenceManager; - -import com.google.android.exoplayer2.ExoPlaybackException; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.error.EnsureExceptionSerializable; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.Info; - -/** - * Handles (exoplayer)errors that occur in the player. - */ -public class PlayerErrorHandler { - // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25) - // or it fails with an IllegalArgumentException - // https://stackoverflow.com/a/54744028 - private static final String TAG = "PlayerErrorHandler"; - - @Nullable - private Toast errorToast; - - @NonNull - private final Context context; - - public PlayerErrorHandler(@NonNull final Context context) { - this.context = context; - } - - public void showPlayerError( - @NonNull final ExoPlaybackException exception, - @NonNull final Info info, - @StringRes final int textResId - ) { - // Hide existing toast message - if (errorToast != null) { - Log.d(TAG, "Trying to cancel previous player error error toast"); - errorToast.cancel(); - errorToast = null; - } - - if (shouldReportError()) { - try { - reportError(exception, info); - // When a report pops up we need no toast - return; - } catch (final Exception ex) { - Log.w(TAG, "Unable to report error:", ex); - // This will show the toast as fallback - } - } - - Log.d(TAG, "Showing player error toast"); - errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT); - errorToast.show(); - } - - private void reportError(@NonNull final ExoPlaybackException exception, - @NonNull final Info info) { - ErrorActivity.reportError( - context, - new ErrorInfo( - EnsureExceptionSerializable.ensureSerializable(exception), - UserAction.PLAY_STREAM, - "Player error[type=" + exception.type + "] occurred while playing: " - + info.getUrl(), - info - ) - ); - } - - private boolean shouldReportError() { - return PreferenceManager - .getDefaultSharedPreferences(context) - .getBoolean( - context.getString(R.string.report_player_errors_key), - false); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java index 8b2bd9c9a..a745861ad 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java @@ -18,7 +18,7 @@ import java.util.Objects; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); - protected final boolean DEBUG = MainActivity.DEBUG; + protected static final boolean DEBUG = MainActivity.DEBUG; SharedPreferences defaultPreferences; diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 6e7e75932..1c8eb5cd2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -20,11 +20,12 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; @@ -73,19 +74,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment { final Preference importDataPreference = requirePreference(R.string.import_data); importDataPreference.setOnPreferenceClickListener((Preference p) -> { - requestImportPathLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestImportPathLauncher, StoredFileHelper.getPicker(requireContext(), - ZIP_MIME_TYPE, getImportExportDataUri())); + ZIP_MIME_TYPE, getImportExportDataUri()), + TAG, + getContext() + ); + return true; }); final Preference exportDataPreference = requirePreference(R.string.export_data); exportDataPreference.setOnPreferenceClickListener((final Preference p) -> { - - requestExportPathLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestExportPathLauncher, StoredFileHelper.getNewPicker(requireContext(), "NewPipeData-" + exportDateFormat.format(new Date()) + ".zip", - ZIP_MIME_TYPE, getImportExportDataUri())); + ZIP_MIME_TYPE, getImportExportDataUri()), + TAG, + getContext() + ); + return true; }); @@ -205,7 +215,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { saveLastImportExportDataUri(exportDataUri); // save export path only on success Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show(); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e); + ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e); } } @@ -247,7 +257,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { finishImport(importDataUri); } } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e); + ErrorUtil.showUiErrorSnackbar(this, "Importing database", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index dfd77f049..681aee409 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -21,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat; import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredDirectoryHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -214,7 +215,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { } private void launchDirectoryPicker(final ActivityResultLauncher launcher) { - launcher.launch(StoredDirectoryHelper.getPicker(ctx)); + NoFileManagerSafeGuard.launchSafe( + launcher, + StoredDirectoryHelper.getPicker(ctx), + TAG, + ctx + ); } private void requestDownloadVideoPathResult(final ActivityResult result) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index cb6ce263d..33e0ba16b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -9,8 +9,8 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.InfoCache; @@ -64,7 +64,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment { .subscribe( howManyDeleted -> Toast.makeText(context, R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(), - throwable -> ErrorActivity.reportError(context, + throwable -> ErrorUtil.openActivity(context, new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Delete playback states"))); } @@ -76,7 +76,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment { .subscribe( howManyDeleted -> Toast.makeText(context, R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(), - throwable -> ErrorActivity.reportError(context, + throwable -> ErrorUtil.openActivity(context, new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Delete from history"))); } @@ -87,7 +87,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment { .observeOn(AndroidSchedulers.mainThread()) .subscribe( howManyDeleted -> { }, - throwable -> ErrorActivity.reportError(context, + throwable -> ErrorUtil.openActivity(context, new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Clear orphaned records"))); } @@ -99,7 +99,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment { .subscribe( howManyDeleted -> Toast.makeText(context, R.string.search_history_deleted, Toast.LENGTH_SHORT).show(), - throwable -> ErrorActivity.reportError(context, + throwable -> ErrorUtil.openActivity(context, new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Delete search history"))); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 2cea04dc3..4340e7ce8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -11,8 +11,8 @@ import io.reactivex.rxjava3.disposables.Disposable import org.schabi.newpipe.R import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionEntity -import org.schabi.newpipe.error.ErrorActivity import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.ErrorUtil import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.local.feed.notifications.NotificationHelper import org.schabi.newpipe.local.feed.notifications.NotificationWorker @@ -118,7 +118,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen } private fun onError(e: Throwable) { - ErrorActivity.reportErrorInSnackbar( + ErrorUtil.showSnackbar( this, ErrorInfo(e, UserAction.SUBSCRIPTION_GET, "Get subscriptions list") ) diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index a0105a11f..116807cbc 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -153,7 +153,7 @@ public class SelectChannelFragment extends DialogFragment { @Override public void onError(@NonNull final Throwable exception) { - ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this, + ErrorUtil.showUiErrorSnackbar(SelectChannelFragment.this, "Loading subscription", exception); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index 9d8736076..a766ee074 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -16,7 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.util.KioskTranslator; @@ -48,7 +48,6 @@ import java.util.Vector; */ public class SelectKioskFragment extends DialogFragment { - private RecyclerView recyclerView = null; private SelectKioskAdapter selectKioskAdapter = null; private OnSelectedListener onSelectedListener = null; @@ -76,12 +75,12 @@ public class SelectKioskFragment extends DialogFragment { public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false); - recyclerView = v.findViewById(R.id.items_list); + final RecyclerView recyclerView = v.findViewById(R.id.items_list); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); try { selectKioskAdapter = new SelectKioskAdapter(); } catch (final Exception e) { - ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e); + ErrorUtil.showUiErrorSnackbar(this, "Selecting kiosk", e); } recyclerView.setAdapter(selectKioskAdapter); diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index f94e391ba..e8491d52c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.settings; -import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -21,8 +20,8 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; @@ -105,8 +104,7 @@ public class SelectPlaylistFragment extends DialogFragment { } protected void onError(final Throwable e) { - final Activity activity = requireActivity(); - ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e, + ErrorUtil.showSnackbar(requireActivity(), new ErrorInfo(e, UserAction.UI_ERROR, "Loading playlists")); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index b0b9567d8..8924ecbe1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -8,8 +8,8 @@ import android.util.Log; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.DeviceUtils; @@ -157,7 +157,7 @@ public final class SettingMigrations { } catch (final Exception e) { // save the version with the last successful migration and report the error sp.edit().putInt(lastPrefVersionKey, currentVersion).apply(); - ErrorActivity.reportError(context, new ErrorInfo( + ErrorUtil.openActivity(context, new ErrorInfo( e, UserAction.PREFERENCES_MIGRATION, "Migrating preferences from version " + lastPrefVersion + " to " diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index c9eb42fca..95f7f50ba 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -27,8 +27,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.settings.SelectChannelFragment; @@ -182,7 +182,7 @@ public class ChooseTabsFragment extends Fragment { final Tab.Type type = typeFrom(tabId); if (type == null) { - ErrorActivity.reportErrorInSnackbar(this, + ErrorUtil.showSnackbar(this, new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId), UserAction.SOMETHING_ELSE, "Choosing tabs on settings")); return; diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index a148255b3..aa03bbfa6 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -12,8 +12,8 @@ import com.grack.nanojson.JsonSink; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem.LocalItemType; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -36,6 +36,10 @@ import java.util.Objects; public abstract class Tab { private static final String JSON_TAB_ID_KEY = "tab_id"; + private static final String NO_NAME = ""; + private static final String NO_ID = ""; + private static final String NO_URL = ""; + Tab() { } @@ -185,7 +189,9 @@ public abstract class Tab { @Override public String getTabName(final Context context) { - return "NewPipe"; //context.getString(R.string.blank_page_summary); + // TODO: find a better name for the blank tab (maybe "blank_tab") or replace it with + // context.getString(R.string.app_name); + return "NewPipe"; // context.getString(R.string.blank_page_summary); } @DrawableRes @@ -309,7 +315,7 @@ public abstract class Tab { private String kioskId; private KioskTab() { - this(-1, ""); + this(-1, NO_ID); } public KioskTab(final int kioskServiceId, final String kioskId) { @@ -357,7 +363,7 @@ public abstract class Tab { @Override protected void readDataFromJson(final JsonObject jsonObject) { kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1); - kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, ""); + kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, NO_ID); } @Override @@ -395,7 +401,7 @@ public abstract class Tab { private String channelName; private ChannelTab() { - this(-1, "", ""); + this(-1, NO_URL, NO_NAME); } public ChannelTab(final int channelServiceId, final String channelUrl, @@ -440,8 +446,8 @@ public abstract class Tab { @Override protected void readDataFromJson(final JsonObject jsonObject) { channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1); - channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, ""); - channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, ""); + channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, NO_URL); + channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, NO_NAME); } @Override @@ -506,7 +512,7 @@ public abstract class Tab { final StreamingService service = NewPipe.getService(kioskServiceId); kioskId = service.getKioskList().getDefaultKioskId(); } catch (final ExtractionException e) { - ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e, + ErrorUtil.showSnackbar(context, new ErrorInfo(e, UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service")); } return kioskId; @@ -527,7 +533,7 @@ public abstract class Tab { private LocalItemType playlistType; private PlaylistTab() { - this(-1, ""); + this(-1, NO_NAME); } public PlaylistTab(final long playlistId, final String playlistName) { @@ -535,7 +541,7 @@ public abstract class Tab { this.playlistId = playlistId; this.playlistType = LocalItemType.PLAYLIST_LOCAL_ITEM; this.playlistServiceId = -1; - this.playlistUrl = ""; + this.playlistUrl = NO_URL; } public PlaylistTab(final int playlistServiceId, final String playlistUrl, @@ -589,8 +595,8 @@ public abstract class Tab { @Override protected void readDataFromJson(final JsonObject jsonObject) { playlistServiceId = jsonObject.getInt(JSON_PLAYLIST_SERVICE_ID_KEY, -1); - playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, ""); - playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, ""); + playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, NO_URL); + playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, NO_NAME); playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1); playlistType = LocalItemType.valueOf( jsonObject.getString(JSON_PLAYLIST_TYPE_KEY, diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java b/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java new file mode 100644 index 000000000..df43c34c1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java @@ -0,0 +1,75 @@ +package org.schabi.newpipe.streams.io; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.os.Build; +import android.util.Log; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.appcompat.app.AlertDialog; + +import org.schabi.newpipe.R; + +/** + * Helper for when no file-manager/activity was found. + */ +public final class NoFileManagerSafeGuard { + private NoFileManagerSafeGuard() { + // No impl + } + + /** + * Shows an alert dialog when no file-manager is found. + * @param context Context + */ + private static void showActivityNotFoundAlert(final Context context) { + if (context == null) { + throw new IllegalArgumentException( + "Unable to open no file manager alert dialog: Context is null"); + } + + final String message; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android 10+ only allows SAF + message = context.getString(R.string.no_appropriate_file_manager_message_android_10); + } else { + message = context.getString( + R.string.no_appropriate_file_manager_message, + context.getString(R.string.downloads_storage_use_saf_title)); + } + + + new AlertDialog.Builder(context) + .setTitle(R.string.no_app_to_open_intent) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show(); + } + + /** + * Launches the file manager safely. + * + * If no file manager is found (which is normally only the case when the user uninstalled + * the default file manager or the OS lacks one) an alert dialog shows up, asking the user + * to fix the situation. + * + * @param activityResultLauncher see {@link ActivityResultLauncher#launch(Object)} + * @param input see {@link ActivityResultLauncher#launch(Object)} + * @param tag Tag used for logging + * @param context Context + * @param see {@link ActivityResultLauncher#launch(Object)} + */ + public static void launchSafe( + final ActivityResultLauncher activityResultLauncher, + final I input, + final String tag, + final Context context + ) { + try { + activityResultLauncher.launch(input); + } catch (final ActivityNotFoundException aex) { + Log.w(tag, "Unable to launch file/directory picker", aex); + NoFileManagerSafeGuard.showActivityNotFoundAlert(context); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index bc6cdca8d..e68009d41 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -168,10 +168,14 @@ public final class NavigationHelper { public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { - if (PlayerHolder.getInstance().getType() != MainPlayer.PlayerType.AUDIO) { - Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) - .show(); - } + Toast.makeText( + context, + PlayerHolder.getInstance().getType() == PlayerType.AUDIO + ? R.string.background_player_already_playing_toast + : R.string.background_player_playing_toast, + Toast.LENGTH_SHORT) + .show(); + final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal()); ContextCompat.startForegroundService(context, intent); diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java index f435653b5..8b8eb265b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java @@ -33,7 +33,9 @@ import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler public final class TextLinkifier { public static final String TAG = TextLinkifier.class.getSimpleName(); - private static final Pattern HASHTAGS_PATTERN = Pattern.compile("(#[A-Za-z0-9_]+)"); + // Looks for hashtags with characters from any language (\p{L}), numbers, or underscores + private static final Pattern HASHTAGS_PATTERN = + Pattern.compile("(#[\\p{L}0-9_]+)"); private TextLinkifier() { } diff --git a/app/src/main/java/us/shandian/giga/io/FileStreamSAF.java b/app/src/main/java/us/shandian/giga/io/FileStreamSAF.java index 000900918..b7dd0a103 100644 --- a/app/src/main/java/us/shandian/giga/io/FileStreamSAF.java +++ b/app/src/main/java/us/shandian/giga/io/FileStreamSAF.java @@ -108,10 +108,12 @@ public class FileStreamSAF extends SharpStream { return true; } + @Override public boolean canSetLength() { return true; } + @Override public boolean canSeek() { return true; } @@ -131,10 +133,12 @@ public class FileStreamSAF extends SharpStream { out.write(buffer, offset, count); } + @Override public void setLength(long length) throws IOException { channel.truncate(length); } + @Override public void seek(long offset) throws IOException { channel.position(offset); } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 057b9cb09..39bdefbe0 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -39,8 +39,8 @@ import com.google.android.material.snackbar.Snackbar; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.Localization; @@ -581,9 +581,9 @@ public class MissionAdapter extends Adapter implements Handler.Callb service = ErrorInfo.SERVICE_NONE; } - ErrorActivity.reportError(mContext, + ErrorUtil.createNotification(mContext, new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action, - service, request.toString(), reason, null)); + service, request.toString(), reason)); } public void clearFinishedDownloads(boolean delete) { diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 2cca3239b..dda2d6dee 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -32,6 +32,7 @@ import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; import org.schabi.newpipe.settings.NewPipeSettings; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -46,6 +47,7 @@ import us.shandian.giga.ui.adapter.MissionAdapter; public class MissionsFragment extends Fragment { + private static final String TAG = "MissionsFragment"; private static final int SPAN_SIZE = 2; private SharedPreferences mPrefs; @@ -257,9 +259,13 @@ public class MissionsFragment extends Fragment { initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - requestDownloadSaveAsLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestDownloadSaveAsLauncher, StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), - mission.storage.getType(), initialPath)); + mission.storage.getType(), initialPath), + TAG, + mContext + ); } @Override diff --git a/app/src/main/res/drawable-night/ic_pin.xml b/app/src/main/res/drawable-night/ic_pin.xml new file mode 100644 index 000000000..6fe406341 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_pin.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml new file mode 100644 index 000000000..70578fbeb --- /dev/null +++ b/app/src/main/res/drawable/ic_pin.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index f6ddc3924..c76444212 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -23,13 +23,25 @@ android:src="@drawable/buddy" tools:ignore="RtlHardcoded" /> + + 网格 NewPipe 可更新! 服务器不接受多线程下载, 使用 @string/msg_threads = 1 重试 - 自动恢复上次播放 + 自动播放 清空数据 播放历史已删除 喜欢 @@ -211,7 +211,7 @@ 时下流行 前 50 最新与热门 - 显示“长按添加”说明 + 显示“长按加入播放队列”提示 在视频详情页中,长按后台播放或悬浮窗播放按钮时显示提示 无法播放此串流 发生无法处理的播放器错误 @@ -534,7 +534,7 @@ 显示结果:%s 从不 仅在 Wi-Fi 下 - 视频开始播放后,自动定位到上次播放时的位置 — %s + 自动开始播放 — %s 播放队列 无法识别此 URL。是否用其他应用打开\? 自动加入播放队列 @@ -610,7 +610,7 @@ 语言 年龄限制 私有性 - 发行许可 + 许可 标签 类别 启用简介中的文本选择功能 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2a9dc2ab4..b69c87f39 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -184,7 +184,7 @@ Tipp anzeigen, wenn der Hintergrundwiedergabe- oder Pop-up-Knopf „Details:“ im Video gedrückt wird Neu und Heiß Halten, um zur Wiedergabeliste hinzuzufügen - „Zum Anhängen gedrückt halten“ Tipp anzeigen + „Halten zum Einreihen“ Tipp anzeigen [Unbekannt] Wiedergabe im Hintergrund starten Wiedergabe in einem Pop-up starten @@ -567,7 +567,7 @@ Wiederholen Nichts Warteschlange abspielen - Automatische Warteschlange + Auto-Einreihung Der Wechsel von einem Player zu einem anderen kann deine Warteschlange überschreiben Überschreiben der Warteschlange bestätigen Die aktive Player-Warteschlange wird ersetzt diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 8f9493bae..108c0474f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -106,7 +106,7 @@ Κρατήστε ιστορικό των αναπαραχθέντων βίντεο Ανάκτηση αναπαραγωγής Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις) - Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη» + Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά» Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο Προεπιλεγμένη χώρα περιεχομένου Αναπαραγωγός @@ -252,7 +252,7 @@ Αφαίρεση Λεπτομέρειες Ρυθμίσεις ήχου - Πιέστε παρατεταμένα για να προστεθεί στην ουρά + Πιέστε παρατεταμένα για προσθήκη στην ουρά Εκκίνηση αναπαραγωγής στο παρασκήνιο Εκκίνηση αναπαραγωγής σε ένα αναδυόμενο παράθυρο Άνοιγμα συρταριού @@ -486,7 +486,7 @@ \nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε. Λειτουργία περιορισμένης πρόσβασης του YouTube Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή; - Αυτόματη ουρά + Αυτόματη προσθήκη στην ουρά Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί Η εναλλαγή από έναν αναπαραγωγό σε άλλον, μπορεί να αντικαταστήσει την ουρά σας Ζητήστε επιβεβαίωση πριν από την εκκαθάριση μιας ουράς diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index dc54807ed..3daa7a5d4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -183,7 +183,7 @@ [Desconocido] Comenzar a reproducir en segundo plano Reproducir en modo emergente - Mostrar la sugerencia \"Mantener pulsado para añadir a la cola\" + Mostrar sugerencia de \"Mantener pulsado para añadir a la cola\" Nuevo y lo mejor Mantener pulsado para añadir a la cola Donar @@ -250,7 +250,7 @@ Forzar informe de excepciones no entregables de RX fuera del fragmento o del ciclo de actividad después del descarte Usar búsqueda rápida e inexacta La búsqueda inexacta permite al reproductor buscar posiciones más rápido con menor precisión. Buscar de a 5, 15 o 25 segundos no funciona - Poner en cola vídeo relacionado siguiente + Poner en cola automáticamente la siguiente transmisión Continuar reproducción sin repetir al añadir de forma automática un vídeo relacionado con el último visto Archivo Archivo movido o borrado @@ -552,7 +552,7 @@ Comenzar reproducción automáticamente — %s Reproducir cola No se pudo reconocer la URL. ¿Abrir con otra aplicación\? - Poner en cola + Poner en cola automáticamente Cambiar de un reproductor a otro puede reemplazar la cola de reproducción La cola de reproducción activa será reemplazada Pedir confirmación antes de vaciar una cola diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0ca5e2699..638695ffe 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -675,4 +675,9 @@ Vérification des mises à jour… Vérifier les mises à jours Nouveaux éléments du flux + Reporter les erreurs du lecteur en détail au lieu de montrer un bref message éphémère (utile pour diagnostiquer les problèmes) + Reporter des erreurs du lecteur + Couper le lecteur + Montrer \"Couper le lecteur\" + Montrer une option couper lors de l\'utilisation du lecteur \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 4eaeefca5..fe26f32b7 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -660,4 +660,9 @@ Periksa manual untuk versi baru Memeriksa pembaruan… Item feed baru + Tampilkan \"hentikan pemain video\" + Menampilkan opsi penghentian ketika menggunakan pemain video + Melaporkan kesalahan pemain video dalam detail yang penuh daripada menampilkan pesan toast yang muncul sebentar (berguna untuk memeriksa masalah) + Laporkan kesalahan pemain video + Hentikan pemain video \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index dae2bf7ab..418b363ee 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -660,4 +660,8 @@ 次をキューに追加 次をキューに追加しました クリエイターの心をこめて + プレーヤーのエラーを、短時間のトーストメッセージではなく、詳細に報告する(問題の診断に役立ちます) + プレイヤーのエラーを報告 + \"プレイヤーがクラッシュ\"を表示 + プレイヤーがクラッシュ \ No newline at end of file diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml index 91617fe63..a6cdcc7df 100644 --- a/app/src/main/res/values-land/dimens.xml +++ b/app/src/main/res/values-land/dimens.xml @@ -38,6 +38,7 @@ 90dp 45dp + 8dp 8dp 4dp diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6570e1044..98e4fb98a 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -563,7 +563,7 @@ Eerste actieknop Schaal de miniatuurafbeelding van de video die getoond wordt in de notificatie van verhouding 16:9 naar 1:1 (dit kan vervorming creëren) Schaal de miniatuurafbeelding tot verhouding 1:1 - Auto-wachtrij + Automatisch in de wachtrij plaatsen Toon memory leaks In de wachtrij geplaatst In de wachtrij plaatsen @@ -668,4 +668,14 @@ Veeg items om ze te verwijderen Start geen video\'s in de minispeler, maar ga direct naar de volledige schermmodus, als automatisch draaien is vergrendeld. Je hebt nog steeds toegang tot de minispeler door de volledige schermmodus af te sluiten Start hoofdspeler als volledig scherm + Rapporteer fouten van de speler in volledig detail in plaats van een kortstondige toastmelding te tonen (handig voor het diagnosticeren van problemen) + Verwerken... Dit kan even duren + Crash de speler + Rapporteer fouten van de speler + Toon \"crash de speler\" + Toon een crash overzicht bij gebruik van de speler + Controleer handmatig op nieuwe versies + Controleer op updates + Bezig met controleren op updates… + Nieuwe feed items \ No newline at end of file diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index f1aa81b6b..91a86aa59 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -1,6 +1,6 @@ - ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਸਰਚ ਦਬਾਓ + ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਵੱਡਦਰਸ਼ੀ ਕੱਚ ਨੂੰ ਦਬਾਓ %1$s ਨੂੰ ਜਾਰੀ ਕੀਤੀ ਗਈ ਕੋਈ ਸਟ੍ਰੀਮ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ। ਤੁਸੀਂ ਵੀਐੱਲਸੀ ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੋਗੇ\? ਸਟ੍ਰੀਮ ਪਲੇਅਰ ਨਹੀਂ ਮਿਲਿਆ (ਤੁਸੀਂ ਵੀਐੱਲਸੀ ਸਥਾਪਤ ਕਰਕੇ ਇਸਨੂੰ ਚਲਾ ਸਕਦੇ ਹੋ)। diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 89cf0e687..7ce6d919b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -252,8 +252,8 @@ Powiększ Wygenerowane automatycznie Monitorowanie wycieków pamięci może powodować niestabilność aplikacji podczas zrzutu pamięci - Zgłoś błędy spoza cyklu życia - Wymuś raportowanie niedostarczonych wyjątków Rx poza cyklem życia fragmentu lub aktywności + Zgłaszaj błędy spoza cyklu życia + Wymuś raportowanie niedostarczonych wyjątków Rx poza cyklem życia fragmentu lub aktywności po usunięciu Używaj szybkiego, niedokładnego przewijania Niedokładne przewijanie umożliwia szybsze przewijanie ze zmniejszoną dokładnością. Przewijanie o 5, 15 lub 25 sekund nie działa w tym przypadku Wczytuj miniatury @@ -544,7 +544,7 @@ Usunąć obejrzane wideo\? Usuń obejrzane Oryginalne teksty z usług będą widoczne w strumieniowanych pozycjach - Pokaż oryginalny czas + Pokazuj oryginalny czas na pozycjach Włącz tryb ograniczonego dostępu YouTube\'a Przez %s Utworzony przez %s @@ -666,8 +666,8 @@ Oznacz jako obejrzane Ładowanie szczegółów kanału… Błąd podczas wyświetlania szczegółów kanału - Pokaż kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci - Pokaż wskaźniki obrazu + Pokazuj kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci + Pokazuj wskaźniki obrazu Zdalne podpowiedzi wyszukiwania Lokalne podpowiedzi wyszukiwania diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6aa6e86fc..8b3044e81 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -672,4 +672,10 @@ Procurar por atualizações Procurar manualmente por novas versões Procurando por atualizações… + Travar o player + Reporta os erros do player em detalhes completos, em vez de mostrar uma mensagem de notificação de curta duração (útil para diagnosticar problemas) + Mostrar \"travar o player\" + Reportar erros do player + Mostra uma opção de travamento ao usar o player + Novos itens do feed \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index d4dc1c3f6..8c10670dc 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -351,7 +351,7 @@ %d hora %d horas - Colocar vídeo seguinte na fila + Enfileirar o próximo stream automaticamente Defina as suas instâncias favoritas PeerTube Exportar histórico, subscrições, listas de reprodução e definições Melhor resolução @@ -468,7 +468,7 @@ Selecionar subscrições Adicionado à lista de reprodução Formato padrão de vídeo - Mostrar dica \"Toque longo para colocar na fila\" + Mostrar dica \"Toque longo para enfileirar\" Escolha uma instância Limpar todos os dados da página web Fechar menu @@ -546,7 +546,7 @@ Iniciar reprodução automaticamente — %s Reproduzir fila URL não reconhecido. Abrir com outra aplicação\? - Colocar na fila automaticamente + Enfileiramento automático A fila do reprodutor ativo será substituída Mudar de um reprodutor para outro pode substituir a sua fila Pedir confirmação antes de limpar uma fila @@ -673,4 +673,9 @@ Verificar manualmente se existe uma nova versão A procurar atualizações… Novos itens + Travar o reprodutor + Relata os erros do reprodutor com todos os detalhes em vez de mostrar uma mensagem de notificação por pouco tempo (útil para diagnosticar problemas) + Relatar erros do reprodutor + Mostrar \"travar o reprodutor\" + Mostra uma opção de travamento ao usar o reprodutor \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 301bb1fec..170628393 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -282,7 +282,7 @@ Ritmo Limpar histórico de visualizações Continuar (sem repetição) a fila de reprodução anexando um vídeo relacionado - Mostrar dica \"Toque longo para colocar na fila\" + Mostrar dica \"Toque longo para enfileirar\" Mostrar dica ao premir em segundo plano ou no botão \"Detalhes:\" da janela popup Canais Listas de reprodução @@ -303,7 +303,7 @@ O projeto NewPipe leva a sua privacidade muito a sério. Por isso, não recolhe nenhum dado sem o seu consentimento. \nA polícia de privacidade do NewPipe explica, em detalhe, os tipos de dados enviados sempre que submete um relatório de erro. Ver política de privacidade - Colocar vídeo seguinte na fila + Enfileirar o próximo stream automaticamente NewPipe é um software livre \"copyleft\": pode utilizar, estudar, partilhar e melhorar a aplicação. Especificamente, pode redistribuir e/ou modificar a aplicação nos termos da GNU General Public License, conforme publicada pela Free Software Foundation, tanto a versão 3 da licença ou (por opção) qualquer versão posterior. Também deseja importar as definições\? Toque longo para colocar na fila @@ -554,7 +554,7 @@ A carregar A fila do reprodutor ativo será substituída URL não reconhecido. Abrir com outra aplicação\? - Colocar na fila automaticamente + Enfileiramento automático Baralhar Apenas em Wi-Fi Nada @@ -673,4 +673,9 @@ Verificar manualmente se existe uma nova versão A procurar atualizações… Novos itens + Travar o reprodutor + Relata os erros do reprodutor com todos os detalhes em vez de mostrar uma mensagem de notificação por pouco tempo (útil para diagnosticar problemas) + Relatar erros do reprodutor + Mostrar \"travar o reprodutor\" + Mostra uma opção de travamento ao usar o reprodutor \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3a72d1f95..d39c7315e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -714,4 +714,7 @@ Уведомлять Вы подписались на канал Переключить все + Показать \"Вызвать сбой плеера\" + Показать функцию вызова сбоя при работе плеера + Вызвать сбой плеера \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 79020f28a..de3e8de9c 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -194,7 +194,7 @@ Увек Само једном [непознато] - Прикажи поруку „задржи ради стављања у ред” + Прикажи поруку „задржи ради заказивања” Донација ЊуПајп развијају волонтери у своје слободно време како би вам пружили најбоље искуство. Узвратите им како би наставили са побољшавањем ЊуПајпа док уживају уз шољицу кафе. Узврати @@ -506,9 +506,9 @@ Контрола осветљености потезом Користите потезе за контролу јачине звука плејера Контрола јачине звука потезом - Аутоматски ред + Самостално заказивање Наставите да завршавате (не понављајући) ред репродукције додавањем повезаног стрима - Аутоматски стави у ред следећи ток + Самостално закажи следећи ток Кеш метаподатака обрисан Искључите за сакривање поља мета-података са додатним информацијама о творцу тока, садржају или захтеву за претрагу Прикажи мета-податке @@ -651,4 +651,15 @@ укљ Режим таблета Прикажи пуштано + Закажи следеће + Заказано је следеће + Местни предлози претраге + Удаљени предлози претраге + Означи као одгледано + Разговори су онемогућени + Обрађујем… Може потрајати пар тренутака + Приказуј указиваче слике + Не покрећи видео у малом прозору, већ пређи одмах у пун приказ заслона, уколико је обртање приказа закључано. И даље можете приступити малом приказу извођача изласком из пуног приказа + Пријави грешке програма извођача видеа + Покрени пуни главни приказ извођача \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5d1cbe819..5cb3bd7f2 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -91,7 +91,7 @@ Håll koll på videor som du tittat på Återuppta uppspelning Fortsätt uppspelning efter avbrott (t.ex. telefonsamtal) - Visa \"Håll för att lägga till\"-tips + Visa \"Håll för att placera i kön\"-tips Visa tips när bakgrunds- eller popup-knappen trycks på sidan för videodetaljer Spelare Beteende @@ -673,4 +673,9 @@ Kolla manuellt efter nya versioner Söker efter uppdateringar… Nya flödes objekt + Visa \"krascha spelaren\" + Rapportera spelarfel + Krascha spelaren + Visar ett kraschalternativ vid användning av spelaren + Rapporterar spelarfel i detalj i stället för att visa ett kortvarigt popup-meddelande (användbart för att diagnostisera problem) \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index da00b0c22..85ed7b5a9 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -18,6 +18,7 @@ 18sp 18sp + 10dp 10dp 14sp diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index f86ca288c..5c93dec4b 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -1,25 +1,25 @@ - ప్రారంభించడానికి శోధనను క్లిక్ - %1$s ప్రచురించబడింది + మొదలుపెట్టుటకు బూతద్దము గురుతుని తట్టండి. + %1$sన ప్రచురించబడింది ఇన్స్టాల్ రద్దు చేయి బ్రౌజర్ లో తెరవండి - షేర్ చేయండి + షేర్ డౌన్లోడ్ వెతుకు - సెట్టింగ్‌లు - అంటే నువ్వు అనేది: %1$s\? - తో పంచు - బాహ్యట ఆడియో ప్లేయర్ని ఉపయోగించండి + అమరికలు + మీ ఉద్దేశ్యం \"%1$s\"\? + పంచుకో + బాహ్య ఆడియో ప్లేయర్ని ఉపయోగించండి సభ్యత్వం సబ్ స్క్రైబ్ - అన్ సబ్స్క్రైబ్ చెసిరు + ఛానెల్ సభ్యత్వం తీసివేయబడినది సబ్ స్క్రైబ్ సాధ్యం కాలేదు సబ్ స్క్రైబ్ నవీకరించలేరు సభ్యత్వం కొత్తది ఏమిటి - వెనకవైపు + వెనుకగా పాపప్ వీడియో డౌన్లోడ్ మార్గం డౌన్లోడ్ చేసిన వీడియోలను నిల్వ చేయడానికి మార్గం @@ -28,26 +28,26 @@ ఆడియో కోసం డౌన్లోడ్ మార్గాన్ని నమోదు చేయండి ఆడియో ఫైల్లకు డౌన్లోడ్ మార్గాన్ని ఇవ్వండి కోడితో ప్లే చేయండి - కోరే అనువర్తనం కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చేయండి + కోరే ఆప్ కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చెయ్యాలా\? కోరే అనువర్తనం కనుగొనబడలేదు. దీన్ని ఇన్స్టాల్ చేయండి ఆడియో డిఫాల్ట్ ఆడియో ఫార్మాట్ థీమ్ ప్రకాశం - చరిత్ర - డౌన్లోడ్ - తదుపరి వీడియో మరియు ఇలాంటి వీడియో + చరిత్రను చూడండి + డౌన్‌లోడ్ + \'తదుపరి\' మరియు \'ఇలాంటి\' వీడియోలను చూపించు చిట్కాను అనుబంధించడానికి హోల్డ్ను చూపు Url మద్దతు లేదు డిఫాల్ట్ భాష ప్లేయర్ ప్రవర్తన వీడియో & ఆడియో - చరిత్ర + చరిత్ర మరియు కాష్ స్వరూపం వెనకవైపులో ఆడుతున్నారు పాపప్ రీతిలో ప్లే చేస్తోంది - కంటెంట్ + విషయము డౌన్ లోడ్ల డౌన్ లోడ్ లోపం నివేదిక @@ -62,17 +62,17 @@ కంటెంట్ అందుబాటులో లేదు డౌన్లోడ్ మెనుని సెటప్ చేయడం సాధ్యపడలేదు ఈ స్ట్రీమ్ని ప్లే చేయడం విఫలమైంది - ఆటగాడు లోపం నుండి పునరుద్ధరించడం + ప్లేయర్ లోపం నుండి పునరుద్ధరించబడుతున్నది క్షమించాలి, అది జరగకూడదు - ఇ-మెయిల్ ద్వారా నివేదన లోపం - క్షమించండి, కొన్ని లోపాలు సంభవించాయి + ఇ-మెయిల్ ద్వారా లోపాన్ని నివేదించుము + క్షమించండి, ఏదో తప్పు జరిగింది. నివేదిక - సమాచారం - ఏం జరిగింది - మీ వ్యాఖ్య(ఆంగ్లం లో) - వివరాలు + సమాచారం: + ఏం జరిగింది: + మీ వ్యాఖ్య(ఆంగ్లం లో): + వివరములు: వీడియో ప్రివ్యూ సూక్ష్మచిత్రం - Video preview thumbnail + వీడియోని ప్లే చేయండి, వ్యవధి: ఇష్టాలు మంది ఇష్టపడలేదు ఫలితాలు లేవు @@ -82,10 +82,10 @@ కె ఎం బి - చందాదారులు లేరు + సభ్యులు లేరు - %s సబ్స్క్రయిబ్ - %s సబ్స్క్రయిబలు + %s సభ్యుడు + %s సభ్యులు వీక్షణలు లేవు @@ -98,7 +98,7 @@ %s వీడియోలు ప్రారంభం - ఆపు + విరామం తొలగించు అలాగే ఫైలుపేరు @@ -112,7 +112,7 @@ డౌన్లోడ్ ఫైల్ పేర్లలో అనుమతించిన అక్షరాలు చెల్లని అక్షరాలు ఈ విలువతో భర్తీ చేయబడతాయి - ప్రత్యామ్నాయం పాత్ర + ప్రత్యామ్నాయ అక్షరం లెటర్స్ మరియు అంకెలు న్యూపిప్ గురించి మూడవ పార్టీ లైసెన్స్ @@ -120,9 +120,9 @@ లైసెన్సుల GitHub పై చూడండి న్యూపెయిప్స్ లైసెన్స్ - మీరు ఆలోచనలు ఉన్నాయా లేదో; అనువాదం, డిజైన్ మార్పులు, కోడ్ క్లీనింగ్ లేదా రియల్ భారీ కోడ్ మార్పులు-సహాయం ఎల్లప్పుడూ స్వాగతం. మరింత అది గెట్స్ మంచి జరుగుతుంది + మీకు ఆలోచనలు ఉన్నాయా; అనువాదం, డిజైన్ మార్పులు, కోడ్ శుభ్రపరచడం లేదా నిజమైన భారీ కోడ్ మార్పులు-సహాయం ఎల్లప్పుడూ స్వాగతం. ఎంత ఎక్కువ చేస్తే అంత మంచిది! లైసెన్స్ చదువు - కాంట్రిబ్యూషన్ + సహకరించటానికి చరిత్ర చరిత్ర మీరు ఈ అంశాన్ని శోధన చరిత్ర నుండి తొలగించాలనుకుంటున్నారా? @@ -138,11 +138,100 @@ వివరాలు ఆడియో సెట్టింగ్లు ఎన్క్యూలో పట్టుకోండి - మీదగార వీడియో కి కావాల్సిన ప్లేయర్ లేదు. VLC ప్లేయర్ ఇన్స్టాల్ చేసుకుంటారా\? - మీదగార వీడియో కి కావాల్సిన ప్లేయర్ లేదు (మీరు VLC ఇసన్తాల్ చేసుకోండి ) - "పాపప్ మోడ్ లో తెరవండి" + మీదగ్గర వీడియోకి కావాల్సిన ప్లేయర్ లేదు. VLC ప్లేయర్ని ఇన్స్టాల్ చేసుకుంటారా\? + మీదగ్గర వీడియోకి కావాల్సిన ప్లేయర్ లేదు (మీరు VLCని ఇన్స్టాల్ చేసుకోండి ). + పాపప్ మోడ్లో తెరవండి డిఫాల్ట్ పాపప్ స్పష్టత - ప్రసార ఫైల్ను డౌన్లోడ్ చేయండి + ప్రసార ఫైలుని డౌన్లోడ్ చేయండి బాహ్య వీడియో ప్లేయర్ని ఉపయోగించండి - కొన్ని రెసొల్యూషన్స్ లో ఆడియో తీసేస్తుంది + కొన్ని స్పష్టతల్లో ఆడియోను తొలగిస్తుంది + పూర్తి స్క్రీన్‌లో ప్రధాన ప్లేయర్‌ని ప్రారంభించండి + డీబగ్ చేయండి + ఏమిలేదు + నోటిఫికేషన్‌లో చూపబడిన వీడియో థంబ్‌నెయిల్‌ను 16:9 నుండి 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి (వక్రీకరణలను ప్రవేశపెట్టవచ్చు) + పునరావృతం చేయండి + వీడియో \"వివరాలు:\"లో బ్యాక్‌గ్రౌండ్ లేదా పాప్‌అప్ బటన్‌ను నొక్కినప్పుడు చిట్కాను చూపు + URLని గుర్తించడం సాధ్యపడలేదు. మరొక యాప్‌తో తెరవాలా\? + నోటిఫికేషన్‌ను రంగులమయం చేయండి + స్థానిక శోధన సూచనలు + రిమోట్ శోధన సూచనలు + ఆటోప్లే + అంతరాయాలు (ఉదా. ఫోన్‌కాల్స్) తర్వాత ప్లే చేయడం కొనసాగించండి + మెటా సమాచారాన్ని చూపు + కోడి మీడియా సెంటర్ ద్వారా వీడియోను ప్లే చేయడానికి ఎంపికను ప్రదర్శించండి + కొన్ని పరికరాలు మాత్రమే 2K/4K వీడియోలను ప్లే చేయగలవు + వీక్షించినట్లు గుర్తుపెట్టుము + దీనితో తెరువుము + దీని కోసం ఫలితాలను చూపుతోంది: %s + డిఫాల్ట్ స్పష్టత + అధిక స్పష్టతను చూపుము + నలుపు + పాప్అప్ లక్షణాలను గుర్తుంచుకో + సూక్ష్మచిత్రాలను లోడ్ చేయండి + వ్యాఖ్యలను చూపించు + వ్యాఖ్యలను దాచడాన్ని ఆఫ్ చేయండి + %sలో మీకు నచ్చిన సందర్భాలను కనుగొనండి + పీర్‌ట్యూబ్ ఉదాహరణలు + మూడవ చర్య బటన్ + థంబ్‌నెయిల్‌లను లోడ్ చేయడం, డేటాను సేవ్ చేయడం మరియు మెమరీ వినియోగాన్ని నిరోధించడానికి ఆఫ్ చేయండి. మార్పులు ఇన్-మెమరీ మరియు ఆన్-డిస్క్ ఇమేజ్ కాష్ రెండింటినీ క్లియర్ చేస్తాయి + ఖచ్చితమైన శోధన తగ్గిన ఖచ్చితత్వంతో వేగంగా స్థానాలను పొందేందుకు ఆటగాడిని అనుమతిస్తుంది. 5, 15 లేదా 25 సెకన్ల పాటు కోరడం దీనితో పని చేయదు + వేగవంతమైన ఖచ్చితమైన శోధనను ఉపయోగించండి + జోడించండి + బుక్‌మార్క్ చేయబడిన వినోదజాబితాలు + ట్యాబ్‌ని ఎంచుకోండి + సభ్యత్వాన్ని తొలగించుము + సమాచారాన్ని చూపుము + సూక్ష్మచిత్రాన్ని 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి + ఒక ప్లేయర్ నుండి మరొక ప్లేయర్‌కు మారడం వలన మీ క్యూను భర్తీ చేయవచ్చు + సంబంధిత స్ట్రీమ్‌ను జోడించడం ద్వారా (పునరావృతం కాని) ప్లేబ్యాక్ క్యూను ముగించడాన్ని కొనసాగించండి + దిగువన ఉన్న ప్రతి నోటిఫికేషన్ చర్యను దానిపై నొక్కడం ద్వారా సవరించండి. కుడివైపు ఉన్న చెక్‌బాక్స్‌లను ఉపయోగించడం ద్వారా కాంపాక్ట్ నోటిఫికేషన్‌లో చూపబడే వాటిలో మూడు వరకు ఎంచుకోండి + పాప్అప్ చివరి పరిమాణం మరియు స్థానాన్ని గుర్తుంచుకోండి + వీడియో వివరణ మరియు అదనపు సమాచారాన్ని దాచడాన్ని ఆఫ్ చేయండి + స్ట్రీమ్ సృష్టికర్త, స్ట్రీమ్ కంటెంట్ లేదా శోధన అభ్యర్థన గురించి అదనపు సమాచారంతో మెటా సమాచార పెట్టెలను దాచడానికి ఆఫ్ చేయండి + ప్లేయర్ ప్రకాశాన్ని నియంత్రించడానికి సంజ్ఞలను ఉపయోగించండి + శోధిస్తున్నప్పుడు చూపాల్సిన సూచనలను ఎంచుకోండి + చివరి ప్లేబ్యాక్ స్థానాన్ని పునరుద్ధరించండి + మీకు ఇష్టమైన పీర్‌ట్యూబ్ సందర్భాలను ఎంచుకోండి + వీక్షించిన వీడియోలను ట్రాక్ చేయండి + మినీ ప్లేయర్‌లో వీడియోలను ప్రారంభించవద్దు, ఆటో రొటేషన్ లాక్ చేయబడితే, నేరుగా పూర్తి స్క్రీన్ మోడ్‌కి మారండి. మీరు పూర్తి స్క్రీన్ నుండి నిష్క్రమించడం ద్వారా ఇప్పటికీ మినీ ప్లేయర్‌ని యాక్సెస్ చేయవచ్చు + సందర్భాన్ని జోడించండి + నవీకరణలు + HTTPS URLలకు మాత్రమే మద్దతు ఉంది + రెండవ చర్య బటన్ + నాల్గవ చర్య బటన్ + స్వల్పకాలిక టోస్ట్ సందేశాన్ని చూపించే బదులు పూర్తి వివరాలతో ప్లేయర్ లోపాలను నివేదిస్తుంది (సమస్యలను నిర్ధారించడానికి ఉపయోగపడుతుంది) + ప్లేయర్ క్రాష్ చేయండి + ప్లేయర్ లోపాలను నివేదించండి + మొదటి చర్య బటన్ + ఐదవ చర్య బటన్ + మీరు కాంపాక్ట్ నోటిఫికేషన్‌లో చూపడానికి గరిష్టంగా మూడు చర్యలను ఎంచుకోవచ్చు! + షఫుల్ చేయండి + బఫరింగ్ + డిఫాల్ట్ వీడియో ఫార్మాట్ + రాత్రి థీమ్ + చీకటి + ఫాస్ట్-ఫార్వర్డ్/-రివైండ్ సీక్ వ్యవధి + క్యూను క్లియర్ చేయడానికి ముందు నిర్ధారణ కోసం అడగండి + క్రియాశీల ప్లేయర్ క్యూ భర్తీ చేయబడుతుంది + వివరణను చూపు + చిత్రం కాష్ తుడిచివేయబడింది + కాష్ చేయబడిన మెటాడేటాను తుడిచివేయండి + కాష్ చేసిన వెబ్‌పేజీ డేటా మొత్తాన్ని తీసివేయండి + మెటాడేటా కాష్ తుడిచివేయబడింది + వాల్యూమ్ సంజ్ఞ నియంత్రణ + ప్లేయర్ వాల్యూమ్‌ను నియంత్రించడానికి సంజ్ఞలను ఉపయోగించండి + ప్రకాశం సంజ్ఞ నియంత్రణ + సూచనలను శోధించండి + శోధన చరిత్ర + శోధన ప్రశ్నలను స్థానికంగా నిల్వ చేయండి + ప్లేబ్యాక్ పునఃప్రారంభించండి + జాబితాలలో స్థానాలు + జాబితాలలో ప్లేబ్యాక్ స్థాన సూచికలను చూపు + డేటాను క్లియర్ చేయండి + ప్లే చేయడం కొనసాగించండి + డిఫాల్ట్ కంటెంట్ దేశం + ఉదాహరణ URLని నమోదు చేయండి + ఉదాహరణను ధృవీకరించడం సాధ్యపడలేదు + ఉదాహరణ ఇప్పటికే ఉంది + నోటిఫికేషన్ \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index cd1bfddb8..5d90ebf40 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -115,7 +115,7 @@ Історія переглядів Відновлювати відтворення Продовжувати відтворення після переривань (напр. телефонних дзвінків) - Показати пораду «Утримуй, щоб додати» + Показувати підказку «Утримуйте, щоб додати в чергу» Типова країна вмісту Програвач Поведінка @@ -130,7 +130,7 @@ Сповіщення NewPipe Сповіщення для програвачів NewPipe на тлі й у вікні [Невідомо] - Перемкнути на тло + Перейти у фоновий режим Перемкнути у вікно Перемкнути на головну Імпортувати базу даних @@ -258,7 +258,7 @@ Примусове звітування про неможливість доставлення Rx-винятків, які відбуваються за межами фрагменту або діяльності життєвого циклу після усунення Викор. швидкий неточний пошук Неточний пошук дозволяє програвачеві рухатися позиціями швидше, проте з меншою точністю - Автододавання в чергу наступного запису + Автододавання в чергу наступної трансляції Продовжити при завершені (не повторюваної) черги, додавши повʼязаний запис Файл Такої теки не існує @@ -583,7 +583,7 @@ Коментарі Налаштувати повідомлення про відтворюваний наразі потік Не розпізнано URL. Відкрити через іншу програму\? - Самододавання в чергу + Автоматична черга Показувати метадані Показувати описи Нічна тема diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5c1d8aa70..f3d29b605 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -71,6 +71,8 @@ 12sp 32dp + 18sp + 18sp 16dp 18sp 18sp @@ -79,6 +81,7 @@ 35dp 60dp + 5dp 5dp 5dp 50dp diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 16c62ec2e..a28f96d97 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -89,8 +89,6 @@ @string/never - report_player_errors_key - seekbar_preview_thumbnail_key seekbar_preview_thumbnail_high_quality seekbar_preview_thumbnail_low_quality @@ -188,10 +186,12 @@ allow_disposed_exceptions_key show_original_time_ago_key disable_media_tunneling_key - crash_the_app_key show_image_indicators_key show_crash_the_player_key check_new_streams + crash_the_app_key + show_error_snackbar_key + create_error_notification_key theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6564e3bf..3bd70f26f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -53,8 +53,6 @@ Show \"Play with Kodi\" option Display an option to play a video via Kodi media center Crash the player - Report player errors - Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems) Scale thumbnail to 1:1 aspect ratio Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions) First action button @@ -99,9 +97,9 @@ Wipe cached metadata Remove all cached webpage data Metadata cache wiped - Auto-queue next stream + Auto-enqueue next stream Continue ending (non-repeating) playback queue by appending a related stream - Auto-queue + Auto-enqueuing Volume gesture control Use gestures to control player volume Brightness gesture control @@ -125,7 +123,7 @@ Start main player in fullscreen Do not start videos in the mini player, but turn to fullscreen mode directly, if auto rotation is locked. You can still access the mini player by exiting fullscreen Autoplay - Show \"Hold to append\" tip + Show \"Hold to enqueue\" tip Show tip when pressing the background or the popup button in video \"Details:\" Unsupported URL Could not recognize the URL. Open with another app? @@ -150,6 +148,7 @@ Player notification Configure current playing stream notification Playing in background + Already playing in background Playing in popup mode Content Show age restricted content @@ -184,17 +183,20 @@ File Notifications newpipe - NewPipe Notification - Notifications for NewPipe background and popup players + NewPipe notification + Notifications for NewPipe\'s player newpipeAppUpdate - App Update Notification - Notifications for new NewPipe version + App update notification + Notifications for new NewPipe versions newpipeHash - Video Hash Notification + Video hash notification Notifications for video hashing progress newpipeNewStreams New streams Notifications about new streams for subscriptions + newpipeErrorReport + Error report notification + Notifications to report errors [Unknown] Switch to Background Switch to Popup @@ -248,6 +250,8 @@ Do you want to restore defaults? Give permission to display over other apps + NewPipe encountered an error, tap to report + An error occurred, see the notification Sorry, that should not have happened. Guru Meditation. Report this error via e-mail @@ -484,10 +488,12 @@ Disable media tunneling if you experience a black screen or stuttering on video playback Show image indicators Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory - Crash the app Show \"crash the player\" Shows a crash option when using the player Run check for new streams + Crash the app + Show an error snackbar + Create an error notification Import Import from @@ -697,6 +703,8 @@ Recent Chapters No app on your device can open this + No appropriate file manager was found for this action.\nPlease install a file manager or try to disable \'%s\' in the download settings. + No appropriate file manager was found for this action.\nPlease install a Storage Access Framework compatible file manager. This content is not available in your country. This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe. This content is private, so it cannot be streamed or downloaded by NewPipe. @@ -726,6 +734,7 @@ Unlisted Private Internal + Pinned comment Hearted by creator Open website Tablet mode diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 3d7732782..7405e47ac 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -51,10 +51,9 @@ - + + diff --git a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt index 2311b41ce..ec41a77f8 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ContentSettingsManagerTest.kt @@ -9,12 +9,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyString import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.Mockito.withSettings import org.mockito.junit.MockitoJUnitRunner import org.schabi.newpipe.streams.io.StoredFileHelper diff --git a/build.gradle b/build.gradle index 145515c1e..f7717a4ff 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/fastlane/metadata/android/de/changelogs/958.txt b/fastlane/metadata/android/de/changelogs/958.txt index 2fe07fd71..4b41f7d70 100644 --- a/fastlane/metadata/android/de/changelogs/958.txt +++ b/fastlane/metadata/android/de/changelogs/958.txt @@ -1,14 +1,15 @@ -Neu und verbessert: -• Einstellung, kein Vorschaubild auf dem Lockscreen anzuzeigen, wurde hinzugefügt -• Feeds durch Gesten aktualisieren -• Verbesserte Performance beim laden von lokalen Listen +Neu+verbessert +•Option Miniansicht Ausblenden auf Sperrbildschirm wieder hinzugefügt +•Ziehen zum Feed aktualisieren +•Verbesserte Leistung beim Abruf lokaler Listen -Behoben: -• Absturz beim Starten von NewPipe ohne Internetverbindung -• Absturz beim Wiederherstellen von NewPipe aus dem Arbeitsspeicher -• [YouTube] Lange Wiedergabelisten +Behoben +•Absturz, Start von NewPipe, nachdem es aus dem RAM entfernt wurde +•Absturz, Starten von NewPipe ohne Internetverbindung +•Einstellungen Helligkeits- und Lautstärkegesten +•[YT] Lange Wiedergabelisten -Anderes: -• Code Verbesserungen und mehrere interne Verbesserungen -• Bibliotheken wurden aktualisiert -• aktualisierte Übersetzungen +Sonstiges +•Codebereinigung, verschiedene interne Verbesserungen +•Aktualisierung Abhängigkeiten +•Aktualisierte Übersetzungen diff --git a/fastlane/metadata/android/en-US/changelogs/981.txt b/fastlane/metadata/android/en-US/changelogs/981.txt new file mode 100644 index 000000000..9a2230ade --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/981.txt @@ -0,0 +1,2 @@ +Removed MediaParser support to fix failing playback resume after buffering on Android 11+. +Disabled media tunneling on Philips QM16XE to fix playback problems. diff --git a/fastlane/metadata/android/he/changelogs/980.txt b/fastlane/metadata/android/he/changelogs/980.txt new file mode 100644 index 000000000..7b74d9af0 --- /dev/null +++ b/fastlane/metadata/android/he/changelogs/980.txt @@ -0,0 +1,13 @@ +חדש +• נוספה האפשרות „הוספה לרשימת נגינה” לתפריט השיתוף +• נוספה תמיכה ב־y2u.be וקישורים מקוצרים של PeerTube + +שיפורים +• פקדי מהירות הנגינה צומצמו +• ההזנה מדגישה מעתה פריטים חדשים +• האפשרות „הצגת פריטים שנצפו” בהזנה נשמרת מעתה + +תיקונים +• תוקן חילוץ הלייקים והלא לייקים ב־YouTube +• תוקנה נגינה חוזרת אוטומטית בחזרה מעבודה ברקע +קצרה היריעה מלהכיל diff --git a/fastlane/metadata/android/hu/changelogs/63.txt b/fastlane/metadata/android/hu/changelogs/63.txt new file mode 100644 index 000000000..e9b4b461c --- /dev/null +++ b/fastlane/metadata/android/hu/changelogs/63.txt @@ -0,0 +1,8 @@ +### Fejlesztések +- Beállítások importálása/exportálása #1333 +- Túlrajzolás csökkentése (teljesítménybeli javítás) #1371 +- Apró kódfejlesztések #1375 +- GDPR információk hozzáadása #1420 + +### Javítva +- Letöltés: A befejezetlen .giga fájlokból történő betöltés közbeni összeomlás javítása #1407 diff --git a/fastlane/metadata/android/hu/full_description.txt b/fastlane/metadata/android/hu/full_description.txt index c1cf87b83..df1f8cb77 100644 --- a/fastlane/metadata/android/hu/full_description.txt +++ b/fastlane/metadata/android/hu/full_description.txt @@ -1 +1 @@ -A NewPipe nem használ semmilyen Google vázkönyvtárat vagy a YouTube API-t. Csupán a weboldalt elemzi, hogy megszerezze a szükséges információt. Így ez az alkalmazás a Google Szolgáltatások nélkül futó ezközökön is használható. Emellett, a NewPipe használatához nincs szükséged YouTube fiókra, +A NewPipe nem használ semmilyen Google keretrendszer könyvtárat, sem a YouTube API-t. Csupán a weboldalt dolgozza fel, hogy kinyerje a szükséges információkat. Így ez az alkalmazás a Google Szolgáltatások nélkül futó eszközökön is használható. Továbbá a NewPipe használatához nincs szükség YouTube-fiókra sem. A NewPipe szabad és nyílt forráskódú szoftver. diff --git a/fastlane/metadata/android/pa/changelogs/63.txt b/fastlane/metadata/android/pa/changelogs/63.txt new file mode 100644 index 000000000..eacfe0cd7 --- /dev/null +++ b/fastlane/metadata/android/pa/changelogs/63.txt @@ -0,0 +1,8 @@ +### ਸੁਧਾਰ +- ਆਯਾਤ/ਨਿਰਯਾਤ ਸੈਟਿੰਗਾਂ #1333 +- ਓਵਰ ਡਰਾਅ ਘਟਾਇਆ ਗਿਆ (ਕਾਰਗੁਜ਼ਾਰੀ ਵਿੱਚ ਸੁਧਾਰ) #1371 +- ਨਿੱਕੇ ਕੋਡ ਸੁਧਾਰ #1375 +- ਜੀ.ਡੀ. ਪੀ.ਆਰ. ਬਾਰੇ ਸਾਰਾ ਕੁਝ ਜੋੜਿਆ #1420 + +### ਸਹੀ ਕੀਤਾ ਗਿਆ +- ਡਾਊਨਲੋਡਰ: .giga ਨਾਮਕ ਫਾਈਲਾਂ ਤੋਂ ਅਧੂਰੇ ਡਾਊਨਲੋਡ ਲੋਡ ਕਰਨ ਤੇ ਬੰਦ ਹੋਣਾ ਸਹੀ ਕੀਤਾ ਗਿਆ diff --git a/fastlane/metadata/android/pa/changelogs/64.txt b/fastlane/metadata/android/pa/changelogs/64.txt new file mode 100644 index 000000000..087c5886a --- /dev/null +++ b/fastlane/metadata/android/pa/changelogs/64.txt @@ -0,0 +1,8 @@ +### ਸੁਧਾਰ +- ਮੋਬਾਈਲ ਡਾਟਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਤੇ ਵੀਡੀਓ ਗੁਣਤਾ ਨੂੰ ਸੀਮਤ ਕਰਨ ਦੀ ਸਮਰੱਥਾ ਨੂੰ ਜੋੜਿਆ। #1339 +- ਸੈਸ਼ਨ ਲਈ ਚਮਕ ਯਾਦ ਰੱਖੋ #1442 +- ਕਮਜ਼ੋਰ ਸੀ ਪੀ ਯੂ #1431 ਲਈ ਡਾਊਨਲੋਡ ਪ੍ਰਦਰਸ਼ਨ ਵਿੱਚ ਸੁਧਾਰ ਕੀਤਾ +- ਮੀਡੀਆ ਸੈਸ਼ਨ #1433 ਲਈ (ਕੰਮ ਕਰਨਾ) ਸਮਰਥਨ ਸ਼ਾਮਲ ਕਰੋ + +### ਸਹੀ ਕੀਤਾ ਗਿਆ +- ਸ਼ੁਰੂਆਤੀ ਡਾਊਨਲੋਡਾਂ 'ਤੇ ਕਰੈਸ਼ ਨੂੰ ਠੀਕ ਕੀਤਾ (ਰਿਲੀਜ਼ ਬਿਲਡਾਂ ਲਈ ਹੁਣ ਇਹ ਫਿਕਸ ਉਪਲਬਧ ਹੈ) #1441 diff --git a/fastlane/metadata/android/pl/changelogs/980.txt b/fastlane/metadata/android/pl/changelogs/980.txt new file mode 100644 index 000000000..29e051247 --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/980.txt @@ -0,0 +1,13 @@ +Nowe +• Dodano opcję „Dodaj do playlisty” do menu kontekstowego +• Dodano obsługę krótkich linków dla y2u.be and PeerTube + +Poprawione +• Bardziej kompaktowe sterowanie prędkością odtwarzania +• Kanał wyróżnia teraz nowe pozycje +• Opcja „Pokaż obejrzane pozycje” w kanale jest teraz zapisywana + +Naprawione +• Naprawiono wyciąganie polubień i łapek w dół z YouTube'a +• Naprawiono automatyczne odtwarzanie po powracaniu z tła +I wiele więcej diff --git a/fastlane/metadata/android/ta/changelogs/63.txt b/fastlane/metadata/android/ta/changelogs/63.txt new file mode 100644 index 000000000..6a90173ef --- /dev/null +++ b/fastlane/metadata/android/ta/changelogs/63.txt @@ -0,0 +1,8 @@ +### மேம்பாடுகள் +- இறக்குமதி/ஏற்றுமதி அமைப்புகள் #1333 +- ஓவர் டிராவைக் குறைக்கவும் (செயல்திறன் மேம்பாடு) #1371 +- சிறிய குறியீடு மேம்பாடுகள் #1375 +- GDPR #1420 பற்றிய அனைத்தையும் சேர்க்கவும் + +### சரி செய்யப்பட்டது +- டவுன்லோடர்: .giga கோப்புகள் #1407 இலிருந்து முடிக்கப்படாத பதிவிறக்கங்களை ஏற்றுவதில் ஏற்படும் செயலிழப்பை சரிசெய்யவும் diff --git a/fastlane/metadata/android/ta/changelogs/64.txt b/fastlane/metadata/android/ta/changelogs/64.txt new file mode 100644 index 000000000..04336a3cd --- /dev/null +++ b/fastlane/metadata/android/ta/changelogs/64.txt @@ -0,0 +1,8 @@ +### மேம்பாடுகள் +- மொபைல் டேட்டாவைப் பயன்படுத்தினால் வீடியோ தரத்தைக் கட்டுப்படுத்தும் திறன் சேர்க்கப்பட்டது. #1339 +- அமர்வு #1442 க்கான பிரகாசத்தை நினைவில் கொள்க +- பலவீனமான CPUகளுக்கான பதிவிறக்க செயல்திறனை மேம்படுத்தவும் #1431 +- மீடியா அமர்வு #1433க்கு (வேலை செய்யும்) ஆதரவைச் சேர்க்கவும் + +### சரி +- பதிவிறக்கங்களைத் திறப்பதில் ஏற்படும் செயலிழப்பைச் சரிசெய்தல் (வெளியீட்டு உருவாக்கங்களுக்கு இப்போது சரிசெய்தல் கிடைக்கிறது) #1441 diff --git a/fastlane/metadata/android/ta/full_description.txt b/fastlane/metadata/android/ta/full_description.txt new file mode 100644 index 000000000..12e0e08ef --- /dev/null +++ b/fastlane/metadata/android/ta/full_description.txt @@ -0,0 +1 @@ +NewPipe எந்த Google கட்டமைப்பு நூலகங்களையும் அல்லது YouTube API ஐயும் பயன்படுத்துவதில்லை. இணையத்தளத்திற்குத் தேவையான தகவல்களைப் பெறுவதற்காக மட்டுமே இது அலசுகிறது. எனவே Google சேவைகள் நிறுவப்படாத சாதனங்களில் இந்தப் பயன்பாட்டைப் பயன்படுத்தலாம். மேலும், NewPipe ஐப் பயன்படுத்த உங்களுக்கு YouTube கணக்கு தேவையில்லை, அது FLOSS ஆகும். diff --git a/fastlane/metadata/android/te/changelogs/63.txt b/fastlane/metadata/android/te/changelogs/63.txt new file mode 100644 index 000000000..d78af57ac --- /dev/null +++ b/fastlane/metadata/android/te/changelogs/63.txt @@ -0,0 +1,8 @@ +### మెరుగుదలలు +- దిగుమతి/ఎగుమతి సెట్టింగ్‌లు #1333 +- ఓవర్‌డ్రాను తగ్గించండి (పనితీరు మెరుగుదల) #1371 +- చిన్న కోడ్ మెరుగుదలలు #1375 +- GDPR #1420 గురించి అన్నింటినీ జోడించండి + +### పరిష్కరించబడినవి +- డౌన్‌లోడర్: .giga ఫైల్స్ #1407 నుండి అసంపూర్తి డౌన్‌లోడ్‌లను లోడ్ చేయడంలో క్రాష్‌ని పరిష్కరించండి diff --git a/fastlane/metadata/android/te/changelogs/64.txt b/fastlane/metadata/android/te/changelogs/64.txt new file mode 100644 index 000000000..b704b531a --- /dev/null +++ b/fastlane/metadata/android/te/changelogs/64.txt @@ -0,0 +1,8 @@ +### మెరుగులు +- మొబైల్ డేటాను ఉపయోగిస్తుంటే వీడియో నాణ్యతను పరిమితం చేసే సామర్థ్యం జోడించబడింది. #1339 +- సెషన్ #1442 కోసం ప్రకాశాన్ని గుర్తుంచుకోండి +- బలహీనమైన CPUల కోసం డౌన్‌లోడ్ పనితీరును మెరుగుపరచండి #1431 +- మీడియా సెషన్ #1433 కోసం (పని) మద్దతును జోడించండి + +### పరిష్కరించండి +- డౌన్‌లోడ్‌లను తెరవడంపై క్రాష్‌ని పరిష్కరించండి (విడుదల బిల్డ్‌ల కోసం ఇప్పుడు అందుబాటులో ఉంది) #1441 diff --git a/fastlane/metadata/android/te/full_description.txt b/fastlane/metadata/android/te/full_description.txt new file mode 100644 index 000000000..64a04af4b --- /dev/null +++ b/fastlane/metadata/android/te/full_description.txt @@ -0,0 +1 @@ +NewPipe ఏ Google ఫ్రేమ్‌వర్క్ లైబ్రరీలను లేదా YouTube APIని ఉపయోగించదు. ఇది వెబ్‌సైట్‌కు అవసరమైన సమాచారాన్ని పొందడం కోసం మాత్రమే అన్వయిస్తుంది. అందువల్ల ఈ యాప్‌ను Google సేవలు ఇన్‌స్టాల్ చేయని పరికరాలలో ఉపయోగించవచ్చు. అలాగే, NewPipeని ఉపయోగించడానికి మీకు YouTube ఖాతా అవసరం లేదు మరియు ఇది FLOSS. diff --git a/fastlane/metadata/android/te/short_description.txt b/fastlane/metadata/android/te/short_description.txt new file mode 100644 index 000000000..62c769a30 --- /dev/null +++ b/fastlane/metadata/android/te/short_description.txt @@ -0,0 +1 @@ +ఆండ్రాయిడ్ కోసం ఉచిత తేలికపాటి యూట్యూబ్ ఫ్రంటెండ్. diff --git a/fastlane/metadata/android/uk/changelogs/980.txt b/fastlane/metadata/android/uk/changelogs/980.txt new file mode 100644 index 000000000..8cd6d0bb2 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/980.txt @@ -0,0 +1,13 @@ +Новий +• Додано опцію «Додати в добірку» до меню спільного доступу +• Додано підтримку коротких посилань y2u.be та PeerTube + +Удосконалено +• Елементи керування швидкістю відтворення стали компактнішими +• У стрічці відтепер виділяються нові елементи +• Опція «Показати переглянуті елементи» у стрічці тепер збережена + +Виправлено +• Виправлено підтягування вподобайок YouTube +• Виправлено автовідтворення після повернення з фонового режиму +І багато іншого diff --git a/fastlane/metadata/android/zh-Hans/changelogs/980.txt b/fastlane/metadata/android/zh-Hans/changelogs/980.txt new file mode 100644 index 000000000..7215efe42 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/980.txt @@ -0,0 +1,13 @@ +新增 +- 增加 在共享菜单中新增 "添加到播放列表 "选项 +- 增加 对y2u.be和PeerTube短链接的支持 + +改进 +- 播放速度控制更加紧凑 +- Feed突出显示新项目 +- 保存feed中的 "显示观看过的项目 "选项 + +修复 +- 修复 YouTube 顶与踩 计数 的解析 +- 修复 从后台返回后的自动重放 +以及更多 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a3bc6ec0f..0d8da6971 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip -distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip +distributionSha256Sum=b75392c5625a88bccd58a574552a5a323edca82dab5942d2d41097f809c6bcce zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed..c53aefaa5 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions $var, ${var}, ${var:-default}, ${var+SET}, +# ${var#prefix}, ${var%suffix}, and $( cmd ); +# * compound commands having a testable exit status, especially case; +# * various built-in commands including command, set, and ulimit. +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@"