diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/braveLegacy/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 66fdff13b..e3971f4d5 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/braveLegacy/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -72,8 +72,8 @@ import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.Image; -import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.AudioStream; @@ -108,15 +108,16 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ReturnYouTubeDislikeUtils; import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.InfoCache; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; -import org.schabi.newpipe.util.image.PicassoHelper; +import org.schabi.newpipe.util.PlayButtonHelper; import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.util.PlayButtonHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import java.util.ArrayList; import java.util.Iterator; @@ -482,7 +483,7 @@ public final class VideoDetailFragment // commit previous pending changes to database if (fragment instanceof LocalPlaylistFragment) { - ((LocalPlaylistFragment) fragment).commitChanges(); + ((LocalPlaylistFragment) fragment).saveImmediate(); } else if (fragment instanceof MainFragment) { ((MainFragment) fragment).commitPlaylistTabs(); } @@ -1013,6 +1014,20 @@ public final class VideoDetailFragment updateTabLayoutVisibility(); } + public void scrollToComment(final CommentsInfoItem comment) { + final int commentsTabPos = pageAdapter.getItemPositionByTitle(COMMENTS_TAB_TAG); + final Fragment fragment = pageAdapter.getItem(commentsTabPos); + if (!(fragment instanceof CommentsFragment)) { + return; + } + + // unexpand the app bar only if scrolling to the comment succeeded + if (((CommentsFragment) fragment).scrollToComment(comment)) { + binding.appBarLayout.setExpanded(false, false); + binding.viewPager.setCurrentItem(commentsTabPos, false); + } + } + /*////////////////////////////////////////////////////////////////////////// // Play Utils //////////////////////////////////////////////////////////////////////////*/ @@ -1431,7 +1446,7 @@ public final class VideoDetailFragment super.showLoading(); //if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required - if (!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)) { + if (!ExtractorHelper.isCached(serviceId, url, InfoCache.Type.STREAM)) { binding.detailContentRootHiding.setVisibility(View.INVISIBLE); } diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/braveLegacy/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 595e9cc4e..483871345 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/braveLegacy/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.fragments.list.search; import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; @@ -436,7 +437,7 @@ public class SearchFragment extends BaseListFragment suggestionPublisher - .onNext(searchEditText.getText().toString()), + .onNext(getSearchEditString()), throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Deleting item failed"))); @@ -736,9 +739,9 @@ public class SearchFragment extends BaseListFragment - searchHistoryEntries.stream() - .map(entry -> new SuggestionItem(true, entry)) - .collect(Collectors.toList())); + searchHistoryEntries.stream() + .map(entry -> new SuggestionItem(true, entry)) + .collect(Collectors.toList())); } private Observable> getRemoteSuggestionsObservable(final String query) { @@ -805,12 +808,12 @@ public class SearchFragment extends BaseListFragment showSnackBarError(new ErrorInfo( - throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId))); + throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId))); } @Override @@ -818,7 +821,11 @@ public class SearchFragment extends BaseListFragment NavigationHelper.getIntentByLink(activity, - streamingService, theSearchString)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(intent -> { - getFM().popBackStackImmediate(); - activity.startActivity(intent); - }, throwable -> showTextError(getString(R.string.unsupported_url)))); - return; - } + showLoading(); + disposables.add(Observable + .fromCallable(() -> NavigationHelper.getIntentByLink(activity, + streamingService, theSearchString)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(intent -> { + getFM().popBackStackImmediate(); + activity.startActivity(intent); + }, throwable -> showTextError(getString(R.string.unsupported_url)))); + return; } catch (final Exception ignored) { // Exception occurred, it's not a url } + // prepare search lastSearchedString = this.searchString; this.searchString = theSearchString; infoListAdapter.clearStreamItemList(); @@ -853,13 +861,17 @@ public class SearchFragment extends BaseListFragment { }, + ignored -> { + }, throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED, theSearchString, serviceId)) )); + + // load search results suggestionPublisher.onNext(theSearchString); startLoading(false); } @@ -944,6 +956,14 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers @@ -1086,7 +1109,7 @@ public class SearchFragment extends BaseListFragment suggestionPublisher - .onNext(searchEditText.getText().toString()), + .onNext(getSearchEditString()), throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, "Deleting item failed"))); disposables.add(onDelete); diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/braveLegacy/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index e365ee181..19c4482b5 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/braveLegacy/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -57,10 +57,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable { private var groupSortOrder: Long = -1 sealed class ScreenState : Serializable { - object InitialScreen : ScreenState() - object IconPickerScreen : ScreenState() - object SubscriptionsPickerScreen : ScreenState() - object DeleteScreen : ScreenState() + data object InitialScreen : ScreenState() + data object IconPickerScreen : ScreenState() + data object SubscriptionsPickerScreen : ScreenState() + data object DeleteScreen : ScreenState() } @State @JvmField var selectedIcon: FeedGroupIcon? = null @@ -380,7 +380,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable { private fun setupIconPicker() { val groupAdapter = GroupieAdapter() - groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(it) }) + groupAdapter.addAll(FeedGroupIcon.entries.map { PickerIconItem(it) }) feedGroupCreateBinding.iconSelector.apply { layoutManager = GridLayoutManager(requireContext(), 7, RecyclerView.VERTICAL, false) diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/braveLegacy/java/org/schabi/newpipe/settings/NewPipeSettings.java index bce64474e..45dbf61ca 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/braveLegacy/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.util.DeviceUtils; @@ -44,14 +45,8 @@ public final class NewPipeSettings { private NewPipeSettings() { } public static void initSettings(final Context context) { - // check if the last used preference version is set - // to determine whether this is the first app run - final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(context) - .getInt(context.getString(R.string.last_used_preferences_version), -1); - final boolean isFirstRun = lastUsedPrefVersion == -1; - // first run migrations, then setDefaultValues, since the latter requires the correct types - SettingMigrations.runMigrationsIfNeeded(context, isFirstRun); + SettingMigrations.runMigrationsIfNeeded(context); // readAgain is true so that if new settings are added their default value is set PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); @@ -64,11 +59,12 @@ public final class NewPipeSettings { PreferenceManager.setDefaultValues(context, R.xml.update_settings, true); PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true); PreferenceManager.setDefaultValues(context, R.xml.sponsor_block_category_settings, true); + PreferenceManager.setDefaultValues(context, R.xml.backup_restore_settings, true); saveDefaultVideoDownloadDirectory(context); saveDefaultAudioDownloadDirectory(context); - disableMediaTunnelingIfNecessary(context, isFirstRun); + disableMediaTunnelingIfNecessary(context); } static void saveDefaultVideoDownloadDirectory(final Context context) { @@ -146,8 +142,7 @@ public final class NewPipeSettings { R.string.show_remote_search_suggestions_key); } - private static void disableMediaTunnelingIfNecessary(@NonNull final Context context, - final boolean isFirstRun) { + private static void disableMediaTunnelingIfNecessary(@NonNull final Context context) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String disabledTunnelingKey = context.getString(R.string.disable_media_tunneling_key); final String disabledTunnelingAutomaticallyKey = @@ -162,7 +157,7 @@ public final class NewPipeSettings { prefs.getInt(disabledTunnelingAutomaticallyKey, -1) == 0 && !prefs.getBoolean(disabledTunnelingKey, false); - if (Boolean.TRUE.equals(isFirstRun) + if (App.getApp().isFirstRun() || (wasDeviceBlacklistUpdated && !wasMediaTunnelingEnabledByUser)) { setMediaTunneling(context); } diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/braveLegacy/java/org/schabi/newpipe/settings/SettingMigrations.java index 4ae700dff..ce270a2cf 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/braveLegacy/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -8,6 +8,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; @@ -164,15 +165,14 @@ public final class SettingMigrations { private static final int VERSION = 6; - public static void runMigrationsIfNeeded(@NonNull final Context context, - final boolean isFirstRun) { + public static void runMigrationsIfNeeded(@NonNull final Context context) { // setup migrations and check if there is something to do sp = PreferenceManager.getDefaultSharedPreferences(context); final String lastPrefVersionKey = context.getString(R.string.last_used_preferences_version); final int lastPrefVersion = sp.getInt(lastPrefVersionKey, 0); // no migration to run, already up to date - if (isFirstRun) { + if (App.getApp().isFirstRun()) { sp.edit().putInt(lastPrefVersionKey, VERSION).apply(); return; } else if (lastPrefVersion == VERSION) { diff --git a/app/src/braveLegacy/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/braveLegacy/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index 80b675ee1..dd2c366b9 100644 --- a/app/src/braveLegacy/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/braveLegacy/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -1,12 +1,19 @@ package org.schabi.newpipe.streams.io; +import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; +import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; +import android.system.Os; +import android.system.StructStatVfs; import android.util.Log; import androidx.annotation.NonNull; @@ -16,6 +23,7 @@ import androidx.documentfile.provider.DocumentFile; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; +import java.io.FileDescriptor; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -27,10 +35,6 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; -import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; - public class StoredDirectoryHelper { private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -39,6 +43,10 @@ public class StoredDirectoryHelper { private Path ioTree; private DocumentFile docTree; + /** + * Context is `null` for non-SAF files, i.e. files that use `ioTree`. + */ + @Nullable private Context context; private final String tag; @@ -173,6 +181,46 @@ public class StoredDirectoryHelper { return docTree == null; } + /** + * Get free memory of the storage partition this file belongs to (root of the directory). + * See StackOverflow and + * + * {@code statvfs()} and {@code fstatvfs()} docs + * + * @return amount of free memory in the volume of current directory (bytes), or {@link + * Long#MAX_VALUE} if an error occurred + */ + public long getFreeStorageSpace() { + try { + final StructStatVfs stat; + + if (ioTree != null) { + // non-SAF file, use statvfs with the path directly (also, `context` would be null + // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway) + stat = Os.statvfs(ioTree.toString()); + + } else { + // SAF file, we can't get a path directly, so obtain a file descriptor first + // and then use fstatvfs with the file descriptor + try (ParcelFileDescriptor parcelFileDescriptor = + context.getContentResolver().openFileDescriptor(getUri(), "r")) { + if (parcelFileDescriptor == null) { + return Long.MAX_VALUE; + } + final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + stat = Os.fstatvfs(fileDescriptor); + } + } + + // this is the same formula used inside the FsStat class + return stat.f_bavail * stat.f_frsize; + } catch (final Throwable e) { + // ignore any error + Log.e(TAG, "Could not get free storage space", e); + return Long.MAX_VALUE; + } + } + /** * Only using Java I/O. Creates the directory named by this abstract pathname, including any * necessary but nonexistent parent directories. diff --git a/app/src/braveLegacy/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/braveLegacy/java/us/shandian/giga/service/DownloadManagerService.java index 25aa249e9..601f7984a 100755 --- a/app/src/braveLegacy/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/braveLegacy/java/us/shandian/giga/service/DownloadManagerService.java @@ -26,7 +26,6 @@ import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; -import android.os.Parcelable; import android.util.Log; import android.widget.Toast; @@ -39,6 +38,7 @@ import androidx.core.app.NotificationCompat.Builder; import androidx.core.app.PendingIntentCompat; import androidx.core.app.ServiceCompat; import androidx.core.content.ContextCompat; +import androidx.core.content.IntentCompat; import androidx.preference.PreferenceManager; import com.grack.nanojson.JsonStringWriter; @@ -56,6 +56,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.MissionRecoveryInfo; @@ -396,31 +397,31 @@ public class DownloadManagerService extends Service { */ public static void startMission(Context context, String[] urls, StoredFileHelper storage, char kind, int threads, String source, String psName, - String[] psArgs, long nearLength, MissionRecoveryInfo[] recoveryInfo, + String[] psArgs, long nearLength, + ArrayList recoveryInfo, VideoSegment[] segments) { - Intent intent = new Intent(context, DownloadManagerService.class); - intent.setAction(Intent.ACTION_RUN); - intent.putExtra(EXTRA_URLS, urls); - intent.putExtra(EXTRA_KIND, kind); - intent.putExtra(EXTRA_THREADS, threads); - intent.putExtra(EXTRA_SOURCE, source); - intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName); - intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs); - intent.putExtra(EXTRA_NEAR_LENGTH, nearLength); - intent.putExtra(EXTRA_RECOVERY_INFO, recoveryInfo); - - intent.putExtra(EXTRA_PARENT_PATH, storage.getParentUri()); - intent.putExtra(EXTRA_PATH, storage.getUri()); - intent.putExtra(EXTRA_STORAGE_TAG, storage.getTag()); - intent.putExtra(EXTRA_SEGMENTS, segments); + final Intent intent = new Intent(context, DownloadManagerService.class) + .setAction(Intent.ACTION_RUN) + .putExtra(EXTRA_URLS, urls) + .putExtra(EXTRA_KIND, kind) + .putExtra(EXTRA_THREADS, threads) + .putExtra(EXTRA_SOURCE, source) + .putExtra(EXTRA_POSTPROCESSING_NAME, psName) + .putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs) + .putExtra(EXTRA_NEAR_LENGTH, nearLength) + .putExtra(EXTRA_RECOVERY_INFO, recoveryInfo) + .putExtra(EXTRA_PARENT_PATH, storage.getParentUri()) + .putExtra(EXTRA_PATH, storage.getUri()) + .putExtra(EXTRA_SEGMENTS, segments) + .putExtra(EXTRA_STORAGE_TAG, storage.getTag()); context.startService(intent); } private void startMission(Intent intent) { String[] urls = intent.getStringArrayExtra(EXTRA_URLS); - Uri path = intent.getParcelableExtra(EXTRA_PATH); - Uri parentPath = intent.getParcelableExtra(EXTRA_PARENT_PATH); + Uri path = IntentCompat.getParcelableExtra(intent, EXTRA_PATH, Uri.class); + Uri parentPath = IntentCompat.getParcelableExtra(intent, EXTRA_PARENT_PATH, Uri.class); int threads = intent.getIntExtra(EXTRA_THREADS, 1); char kind = intent.getCharExtra(EXTRA_KIND, '?'); String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME); @@ -428,7 +429,10 @@ public class DownloadManagerService extends Service { String source = intent.getStringExtra(EXTRA_SOURCE); long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0); String tag = intent.getStringExtra(EXTRA_STORAGE_TAG); - Parcelable[] parcelRecovery = intent.getParcelableArrayExtra(EXTRA_RECOVERY_INFO); + final var recovery = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_RECOVERY_INFO, + MissionRecoveryInfo.class); + Objects.requireNonNull(recovery); + VideoSegment[] segments = null; if (intent.hasExtra(EXTRA_SEGMENTS) && intent.getSerializableExtra(EXTRA_SEGMENTS) instanceof VideoSegment[]) { @@ -448,15 +452,11 @@ public class DownloadManagerService extends Service { else ps = Postprocessing.getAlgorithm(psName, psArgs); - MissionRecoveryInfo[] recovery = new MissionRecoveryInfo[parcelRecovery.length]; - for (int i = 0; i < parcelRecovery.length; i++) - recovery[i] = (MissionRecoveryInfo) parcelRecovery[i]; - final DownloadMission mission = new DownloadMission(urls, storage, kind, ps); mission.threadCount = threads; mission.source = source; mission.nearLength = nearLength; - mission.recoveryInfo = recovery; + mission.recoveryInfo = recovery.toArray(new MissionRecoveryInfo[0]); if (segments != null && segments.length > 0) { try { diff --git a/app/src/braveLegacy/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/braveLegacy/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 6ce6c96ea..08e072653 100644 --- a/app/src/braveLegacy/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/braveLegacy/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -521,7 +521,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb showError(mission, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed); return; case ERROR_INSUFFICIENT_STORAGE: - msg = R.string.error_insufficient_storage; + msg = R.string.error_insufficient_storage_left; break; case ERROR_UNKNOWN_EXCEPTION: if (mission.errObject != null) {