diff --git a/app/build.gradle b/app/build.gradle index 0fb1437fd..c5c86e12e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { resValue "string", "app_name", "NewPipe" minSdkVersion 19 targetSdkVersion 29 - versionCode 972 - versionName "0.21.6" + versionCode 973 + versionName "0.21.7" multiDexEnabled true @@ -201,7 +201,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.documentfile:documentfile:1.0.1' - implementation 'androidx.fragment:fragment-ktx:1.3.4' + implementation 'androidx.fragment:fragment-ktx:1.3.5' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 0c6165084..7a379795c 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -589,9 +589,9 @@ public class RouterActivity extends AppCompatActivity { downloadDialog.setVideoStreams(sortedVideoStreams); downloadDialog.setAudioStreams(result.getAudioStreams()); downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); + downloadDialog.setOnDismissListener(dialog -> finish()); downloadDialog.show(fm, "downloadDialog"); fm.executePendingTransactions(); - downloadDialog.requireDialog().setOnDismissListener(dialog -> finish()); }, throwable -> showUnsupportedUrlDialog(currentUrl))); } 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 c055f8f23..628496c01 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -3,6 +3,8 @@ package org.schabi.newpipe.download; import android.app.Activity; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; @@ -20,6 +22,9 @@ import android.widget.RadioGroup; import android.widget.SeekBar; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,7 +40,6 @@ import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.databinding.DownloadDialogBinding; import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; @@ -82,9 +86,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { private static final String TAG = "DialogFragment"; private static final boolean DEBUG = MainActivity.DEBUG; - private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; - private static final int REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER = 0x789E; - private static final int REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER = 0x789F; @State StreamInfo currentInfo; @@ -101,6 +102,9 @@ public class DownloadDialog extends DialogFragment @State int selectedSubtitleIndex = 0; + @Nullable + private OnDismissListener onDismissListener = null; + private StoredDirectoryHelper mainStorageAudio = null; private StoredDirectoryHelper mainStorageVideo = null; private DownloadManager downloadManager = null; @@ -122,6 +126,21 @@ public class DownloadDialog extends DialogFragment private String filenameTmp; private String mimeTmp; + private final ActivityResultLauncher requestDownloadSaveAsLauncher = + registerForActivityResult( + new StartActivityForResult(), this::requestDownloadSaveAsResult); + private final ActivityResultLauncher requestDownloadPickAudioFolderLauncher = + registerForActivityResult( + new StartActivityForResult(), this::requestDownloadPickAudioFolderResult); + private final ActivityResultLauncher requestDownloadPickVideoFolderLauncher = + registerForActivityResult( + new StartActivityForResult(), this::requestDownloadPickVideoFolderResult); + + + /*////////////////////////////////////////////////////////////////////////// + // Instance creation + //////////////////////////////////////////////////////////////////////////*/ + public static DownloadDialog newInstance(final StreamInfo info) { final DownloadDialog dialog = new DownloadDialog(); dialog.setInfo(info); @@ -143,6 +162,11 @@ public class DownloadDialog extends DialogFragment return instance; } + + /*////////////////////////////////////////////////////////////////////////// + // Setters + //////////////////////////////////////////////////////////////////////////*/ + private void setInfo(final StreamInfo info) { this.currentInfo = info; } @@ -184,6 +208,14 @@ public class DownloadDialog extends DialogFragment this.selectedSubtitleIndex = ssi; } + public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) { + this.onDismissListener = onDismissListener; + } + + /*////////////////////////////////////////////////////////////////////////// + // Android lifecycle + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -194,7 +226,7 @@ public class DownloadDialog extends DialogFragment if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { - getDialog().dismiss(); + dismiss(); return; } @@ -253,10 +285,6 @@ public class DownloadDialog extends DialogFragment }, Context.BIND_AUTO_CREATE); } - /*////////////////////////////////////////////////////////////////////////// - // Inits - //////////////////////////////////////////////////////////////////////////*/ - @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { @@ -312,6 +340,60 @@ public class DownloadDialog extends DialogFragment fetchStreamsSize(); } + private void initToolbar(final Toolbar toolbar) { + if (DEBUG) { + Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); + } + + toolbar.setTitle(R.string.download_dialog_title); + toolbar.setNavigationIcon(R.drawable.ic_arrow_back); + toolbar.inflateMenu(R.menu.dialog_url); + toolbar.setNavigationOnClickListener(v -> dismiss()); + toolbar.setNavigationContentDescription(R.string.cancel); + + okButton = toolbar.findViewById(R.id.okay); + okButton.setEnabled(false); // disable until the download service connection is done + + toolbar.setOnMenuItemClickListener(item -> { + if (item.getItemId() == R.id.okay) { + prepareSelectedDownload(); + return true; + } + return false; + }); + } + + @Override + public void onDismiss(@NonNull final DialogInterface dialog) { + super.onDismiss(dialog); + if (onDismissListener != null) { + onDismissListener.onDismiss(dialog); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + disposables.clear(); + } + + @Override + public void onDestroyView() { + dialogBinding = null; + super.onDestroyView(); + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + Icepick.saveInstanceState(this, outState); + } + + + /*////////////////////////////////////////////////////////////////////////// + // Video, audio and subtitle spinners + //////////////////////////////////////////////////////////////////////////*/ + private void fetchStreamsSize() { disposables.clear(); disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams) @@ -346,126 +428,6 @@ public class DownloadDialog extends DialogFragment currentInfo.getServiceId())))); } - @Override - public void onDestroy() { - super.onDestroy(); - disposables.clear(); - } - - @Override - public void onDestroyView() { - dialogBinding = null; - super.onDestroyView(); - } - - /*////////////////////////////////////////////////////////////////////////// - // Radio group Video&Audio options - Listener - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { - super.onSaveInstanceState(outState); - Icepick.saveInstanceState(this, outState); - } - - /*////////////////////////////////////////////////////////////////////////// - // Streams Spinner Listener - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (resultCode != Activity.RESULT_OK) { - return; - } - - if (data.getData() == null) { - showFailedDialog(R.string.general_error); - return; - } - - if (requestCode == REQUEST_DOWNLOAD_SAVE_AS) { - if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) { - final File file = Utils.getFileForUri(data.getData()); - checkSelectedDownload(null, Uri.fromFile(file), file.getName(), - StoredFileHelper.DEFAULT_MIME); - return; - } - - final DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData()); - if (docFile == null) { - showFailedDialog(R.string.general_error); - return; - } - - // check if the selected file was previously used - checkSelectedDownload(null, data.getData(), docFile.getName(), - docFile.getType()); - } else if (requestCode == REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER - || requestCode == REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER) { - Uri uri = data.getData(); - if (FilePickerActivityHelper.isOwnFileUri(context, uri)) { - uri = Uri.fromFile(Utils.getFileForUri(uri)); - } else { - context.grantUriPermission(context.getPackageName(), uri, - StoredDirectoryHelper.PERMISSION_FLAGS); - } - - final String key; - final String tag; - if (requestCode == REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER) { - key = getString(R.string.download_path_audio_key); - tag = DownloadManager.TAG_AUDIO; - } else { - key = getString(R.string.download_path_video_key); - tag = DownloadManager.TAG_VIDEO; - } - - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(key, uri.toString()).apply(); - - try { - final StoredDirectoryHelper mainStorage - = new StoredDirectoryHelper(context, uri, tag); - checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), - filenameTmp, mimeTmp); - } catch (final IOException e) { - showFailedDialog(R.string.general_error); - } - } - } - - private void initToolbar(final Toolbar toolbar) { - if (DEBUG) { - Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); - } - - toolbar.setTitle(R.string.download_dialog_title); - toolbar.setNavigationIcon(R.drawable.ic_arrow_back); - toolbar.inflateMenu(R.menu.dialog_url); - toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss()); - toolbar.setNavigationContentDescription(R.string.cancel); - - okButton = toolbar.findViewById(R.id.okay); - okButton.setEnabled(false); // disable until the download service connection is done - - toolbar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.okay) { - prepareSelectedDownload(); - if (getActivity() instanceof RouterActivity) { - getActivity().finish(); - } - return true; - } - return false; - }); - } - - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - private void setupAudioSpinner() { if (getContext() == null) { return; @@ -496,6 +458,88 @@ public class DownloadDialog extends DialogFragment setRadioButtonsState(true); } + + /*////////////////////////////////////////////////////////////////////////// + // Activity results + //////////////////////////////////////////////////////////////////////////*/ + + private void requestDownloadPickAudioFolderResult(final ActivityResult result) { + requestDownloadPickFolderResult( + result, getString(R.string.download_path_audio_key), DownloadManager.TAG_AUDIO); + } + + private void requestDownloadPickVideoFolderResult(final ActivityResult result) { + requestDownloadPickFolderResult( + result, getString(R.string.download_path_video_key), DownloadManager.TAG_VIDEO); + } + + private void requestDownloadSaveAsResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK) { + return; + } + + if (result.getData() == null || result.getData().getData() == null) { + showFailedDialog(R.string.general_error); + return; + } + + if (FilePickerActivityHelper.isOwnFileUri(context, result.getData().getData())) { + final File file = Utils.getFileForUri(result.getData().getData()); + checkSelectedDownload(null, Uri.fromFile(file), file.getName(), + StoredFileHelper.DEFAULT_MIME); + return; + } + + final DocumentFile docFile + = DocumentFile.fromSingleUri(context, result.getData().getData()); + if (docFile == null) { + showFailedDialog(R.string.general_error); + return; + } + + // check if the selected file was previously used + checkSelectedDownload(null, result.getData().getData(), docFile.getName(), + docFile.getType()); + } + + private void requestDownloadPickFolderResult(final ActivityResult result, + final String key, + final String tag) { + if (result.getResultCode() != Activity.RESULT_OK) { + return; + } + + if (result.getData() == null || result.getData().getData() == null) { + showFailedDialog(R.string.general_error); + return; + } + + Uri uri = result.getData().getData(); + if (FilePickerActivityHelper.isOwnFileUri(context, uri)) { + uri = Uri.fromFile(Utils.getFileForUri(uri)); + } else { + context.grantUriPermission(context.getPackageName(), uri, + StoredDirectoryHelper.PERMISSION_FLAGS); + } + + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(key, uri.toString()).apply(); + + try { + final StoredDirectoryHelper mainStorage + = new StoredDirectoryHelper(context, uri, tag); + checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), + filenameTmp, mimeTmp); + } catch (final IOException e) { + showFailedDialog(R.string.general_error); + } + } + + + /*////////////////////////////////////////////////////////////////////////// + // Listeners + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) { if (DEBUG) { @@ -545,6 +589,11 @@ public class DownloadDialog extends DialogFragment public void onNothingSelected(final AdapterView parent) { } + + /*////////////////////////////////////////////////////////////////////////// + // Download + //////////////////////////////////////////////////////////////////////////*/ + protected void setupDownloadOptions() { setRadioButtonsState(false); @@ -557,7 +606,7 @@ public class DownloadDialog extends DialogFragment dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE); - prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type), getString(R.string.last_download_type_video_key)); @@ -585,7 +634,7 @@ public class DownloadDialog extends DialogFragment } else { Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show(); - getDialog().dismiss(); + dismiss(); } } @@ -637,6 +686,10 @@ public class DownloadDialog extends DialogFragment .show(); } + private void launchDirectoryPicker(final ActivityResultLauncher launcher) { + launcher.launch(StoredDirectoryHelper.getPicker(context)); + } + private void prepareSelectedDownload() { final StoredDirectoryHelper mainStorage; final MediaFormat format; @@ -691,11 +744,9 @@ public class DownloadDialog extends DialogFragment Toast.LENGTH_LONG).show(); if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) { - startActivityForResult(StoredDirectoryHelper.getPicker(context), - REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER); + launchDirectoryPicker(requestDownloadPickAudioFolderLauncher); } else { - startActivityForResult(StoredDirectoryHelper.getPicker(context), - REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER); + launchDirectoryPicker(requestDownloadPickVideoFolderLauncher); } return; @@ -715,8 +766,8 @@ public class DownloadDialog extends DialogFragment initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - startActivityForResult(StoredFileHelper.getNewPicker(context, - filenameTmp, mimeTmp, initialPath), REQUEST_DOWNLOAD_SAVE_AS); + requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context, + filenameTmp, mimeTmp, initialPath)); return; } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 32a1d414e..8790c3059 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.local; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; @@ -26,6 +25,7 @@ import org.schabi.newpipe.fragments.list.ListViewContract; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; +import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; /** * This fragment is design to be used with persistent data such as @@ -77,7 +77,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment super.onResume(); if (updateFlags != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { - final boolean useGrid = isGridLayout(); + final boolean useGrid = shouldUseGridLayout(requireContext()); itemsList.setLayoutManager( useGrid ? getGridLayoutManager() : getListLayoutManager()); itemListAdapter.setUseGridVariant(useGrid); @@ -121,7 +121,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment itemListAdapter = new LocalItemListAdapter(activity); - final boolean useGrid = isGridLayout(); + final boolean useGrid = shouldUseGridLayout(requireContext()); itemsList = rootView.findViewById(R.id.items_list); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); @@ -260,17 +260,4 @@ public abstract class BaseLocalListFragment extends BaseStateFragment updateFlags |= LIST_MODE_UPDATE_FLAG; } } - - protected boolean isGridLayout() { - final String listMode = PreferenceManager.getDefaultSharedPreferences(activity) - .getString(getString(R.string.list_view_mode_key), - getString(R.string.list_view_mode_value)); - if ("auto".equals(listMode)) { - final Configuration configuration = getResources().getConfiguration(); - return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE - && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); - } else { - return "grid".equals(listMode); - } - } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 4c1bb0732..a48dc305e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -23,7 +23,6 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.SharedPreferences -import android.content.res.Configuration import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -74,10 +73,10 @@ import org.schabi.newpipe.player.helper.PlayerHolder import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.StreamDialogEntry +import org.schabi.newpipe.util.ThemeHelper.getGridSpanCount +import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import java.time.OffsetDateTime import java.util.ArrayList -import kotlin.math.floor -import kotlin.math.max class FeedFragment : BaseStateFragment() { private var _feedBinding: FragmentFeedBinding? = null @@ -161,7 +160,7 @@ class FeedFragment : BaseStateFragment() { fun setupListViewMode() { // does everything needed to setup the layouts for grid or list modes - groupAdapter.spanCount = if (shouldUseGridLayout()) getGridSpanCount() else 1 + groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCount(context) else 1 feedBinding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply { spanSizeLookup = groupAdapter.spanSizeLookup } @@ -384,7 +383,7 @@ class FeedFragment : BaseStateFragment() { @SuppressLint("StringFormatMatches") private fun handleLoadedState(loadedState: FeedState.LoadedState) { - val itemVersion = if (shouldUseGridLayout()) { + val itemVersion = if (shouldUseGridLayout(context)) { StreamItem.ItemVersion.GRID } else { StreamItem.ItemVersion.NORMAL @@ -528,35 +527,6 @@ class FeedFragment : BaseStateFragment() { listState = null } - // ///////////////////////////////////////////////////////////////////////// - // Grid Mode - // ///////////////////////////////////////////////////////////////////////// - - // TODO: Move these out of this class, as it can be reused - - private fun shouldUseGridLayout(): Boolean { - val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)) - - return when (listMode) { - getString(R.string.list_view_mode_auto_key) -> { - val configuration = resources.configuration - - ( - configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && - configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE) - ) - } - getString(R.string.list_view_mode_grid_key) -> true - else -> false - } - } - - private fun getGridSpanCount(): Int { - val minWidth = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width) - return max(1, floor(resources.displayMetrics.widthPixels / minWidth.toDouble()).toInt()) - } - companion object { const val KEY_GROUP_ID = "ARG_GROUP_ID" const val KEY_GROUP_NAME = "ARG_GROUP_NAME" diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index cefc63c0d..d63731d85 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -68,6 +68,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; public class LocalPlaylistFragment extends BaseLocalListFragment, Void> { // Save the list 10 seconds after the last change occurred @@ -678,7 +679,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { private var _binding: FragmentSubscriptionBinding? = null @@ -279,8 +277,7 @@ class SubscriptionFragment : BaseStateFragment() { super.initViews(rootView, savedInstanceState) _binding = FragmentSubscriptionBinding.bind(rootView) - val shouldUseGridLayout = shouldUseGridLayout() - groupAdapter.spanCount = if (shouldUseGridLayout) getGridSpanCount() else 1 + groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCount(context) else 1 binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply { spanSizeLookup = groupAdapter.spanSizeLookup } @@ -359,7 +356,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun handleResult(result: SubscriptionState) { super.handleResult(result) - val shouldUseGridLayout = shouldUseGridLayout() + val shouldUseGridLayout = shouldUseGridLayout(context) when (result) { is SubscriptionState.LoadedState -> { result.subscriptions.forEach { @@ -420,30 +417,4 @@ class SubscriptionFragment : BaseStateFragment() { super.hideLoading() binding.itemsList.animate(true, 200) } - - // ///////////////////////////////////////////////////////////////////////// - // Grid Mode - // ///////////////////////////////////////////////////////////////////////// - - // TODO: Move these out of this class, as it can be reused - - private fun shouldUseGridLayout(): Boolean { - val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)) - - return when (listMode) { - getString(R.string.list_view_mode_auto_key) -> { - val configuration = resources.configuration - configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && - configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE) - } - getString(R.string.list_view_mode_grid_key) -> true - else -> false - } - } - - private fun getGridSpanCount(): Int { - val minWidth = resources.getDimensionPixelSize(R.dimen.channel_item_grid_min_width) - return max(1, floor(resources.displayMetrics.widthPixels / minWidth.toDouble()).toInt()) - } } 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 a882acf3b..b4af0e43b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -9,6 +9,9 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; @@ -18,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat; import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; +import org.schabi.newpipe.streams.io.StoredDirectoryHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; import java.io.File; @@ -27,14 +31,10 @@ import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import org.schabi.newpipe.streams.io.StoredDirectoryHelper; - import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; public class DownloadSettingsFragment extends BasePreferenceFragment { public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; - private static final int REQUEST_DOWNLOAD_VIDEO_PATH = 0x1235; - private static final int REQUEST_DOWNLOAD_AUDIO_PATH = 0x1236; private String downloadPathVideoPreference; private String downloadPathAudioPreference; private String storageUseSafPreference; @@ -44,6 +44,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { private Preference prefStorageAsk; private Context ctx; + private final ActivityResultLauncher requestDownloadVideoPathLauncher = + registerForActivityResult( + new StartActivityForResult(), this::requestDownloadVideoPathResult); + private final ActivityResultLauncher requestDownloadAudioPathLauncher = + registerForActivityResult( + new StartActivityForResult(), this::requestDownloadAudioPathResult); @Override public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { @@ -185,7 +191,6 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { } final String key = preference.getKey(); - final int request; if (key.equals(storageUseSafPreference)) { if (!NewPipeSettings.useStorageAccessFramework(ctx)) { @@ -198,43 +203,39 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { updatePreferencesSummary(); return true; } else if (key.equals(downloadPathVideoPreference)) { - request = REQUEST_DOWNLOAD_VIDEO_PATH; + launchDirectoryPicker(requestDownloadVideoPathLauncher); } else if (key.equals(downloadPathAudioPreference)) { - request = REQUEST_DOWNLOAD_AUDIO_PATH; + launchDirectoryPicker(requestDownloadAudioPathLauncher); } else { return super.onPreferenceTreeClick(preference); } - startActivityForResult(StoredDirectoryHelper.getPicker(ctx), request); - return true; } - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { + private void launchDirectoryPicker(final ActivityResultLauncher launcher) { + launcher.launch(StoredDirectoryHelper.getPicker(ctx)); + } + + private void requestDownloadVideoPathResult(final ActivityResult result) { + requestDownloadPathResult(result, downloadPathVideoPreference); + } + + private void requestDownloadAudioPathResult(final ActivityResult result) { + requestDownloadPathResult(result, downloadPathAudioPreference); + } + + private void requestDownloadPathResult(final ActivityResult result, final String key) { assureCorrectAppLanguage(getContext()); - super.onActivityResult(requestCode, resultCode, data); - if (DEBUG) { - Log.d(TAG, "onActivityResult() called with: " - + "requestCode = [" + requestCode + "], " - + "resultCode = [" + resultCode + "], data = [" + data + "]" - ); - } - if (resultCode != Activity.RESULT_OK) { + if (result.getResultCode() != Activity.RESULT_OK) { return; } - final String key; - if (requestCode == REQUEST_DOWNLOAD_VIDEO_PATH) { - key = downloadPathVideoPreference; - } else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) { - key = downloadPathAudioPreference; - } else { - return; + Uri uri = null; + if (result.getData() != null) { + uri = result.getData().getData(); } - - Uri uri = data.getData(); if (uri == null) { showMessageDialog(R.string.general_error, R.string.invalid_directory); return; diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 907bcf0ba..f3ae002dd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -298,4 +298,43 @@ public final class ThemeHelper { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); } } + + + /** + * Returns whether the grid layout or the list layout should be used. If the user set "auto" + * mode in settings, decides based on screen orientation (landscape) and size. + * + * @param context the context to use + * @return true:use grid layout, false:use list layout + */ + public static boolean shouldUseGridLayout(final Context context) { + final String listMode = PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.list_view_mode_key), + context.getString(R.string.list_view_mode_value)); + + if (listMode.equals(context.getString(R.string.list_view_mode_list_key))) { + return false; + } else if (listMode.equals(context.getString(R.string.list_view_mode_grid_key))) { + return true; + } else { + final Configuration configuration = context.getResources().getConfiguration(); + return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } + } + + /** + * Calculates the number of grid items that can fit horizontally on the screen. The width of a + * grid item is obtained from the thumbnail width plus the right and left paddings. + * + * @param context the context to use + * @return the span count of grid list items + */ + public static int getGridSpanCount(final Context context) { + final Resources res = context.getResources(); + final int minWidth + = res.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width) + + res.getDimensionPixelSize(R.dimen.video_item_search_padding) * 2; + return Math.max(1, res.getDisplayMetrics().widthPixels / minWidth); + } } 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 793d147b5..2cca3239b 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 @@ -17,6 +17,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; @@ -44,7 +47,6 @@ import us.shandian.giga.ui.adapter.MissionAdapter; public class MissionsFragment extends Fragment { private static final int SPAN_SIZE = 2; - private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; private SharedPreferences mPrefs; private boolean mLinear; @@ -64,7 +66,8 @@ public class MissionsFragment extends Fragment { private boolean mForceUpdate; private DownloadMission unsafeMissionTarget = null; - + private final ActivityResultLauncher requestDownloadSaveAsLauncher = + registerForActivityResult(new StartActivityForResult(), this::requestDownloadSaveAsResult); private final ServiceConnection mConnection = new ServiceConnection() { @Override @@ -254,8 +257,9 @@ public class MissionsFragment extends Fragment { initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - startActivityForResult(StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), - mission.storage.getType(), initialPath), REQUEST_DOWNLOAD_SAVE_AS); + requestDownloadSaveAsLauncher.launch( + StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), + mission.storage.getType(), initialPath)); } @Override @@ -289,18 +293,17 @@ public class MissionsFragment extends Fragment { if (mBinder != null) mBinder.enableNotifications(true); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); + private void requestDownloadSaveAsResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK) { + return; + } - if (requestCode != REQUEST_DOWNLOAD_SAVE_AS || resultCode != Activity.RESULT_OK) return; - - if (unsafeMissionTarget == null || data.getData() == null) { + if (unsafeMissionTarget == null || result.getData() == null) { return; } try { - Uri fileUri = data.getData(); + Uri fileUri = result.getData().getData(); if (fileUri.getAuthority() != null && FilePickerActivityHelper.isOwnFileUri(mContext, fileUri)) { fileUri = Uri.fromFile(Utils.getFileForUri(fileUri)); } diff --git a/fastlane/metadata/android/en-US/changelogs/973.txt b/fastlane/metadata/android/en-US/changelogs/973.txt new file mode 100644 index 000000000..120359a24 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/973.txt @@ -0,0 +1,4 @@ +Hotfix +• Fix thumbnails and titles being trimmed in grid layout, due to a wrong calculation of how many videos can fit in one row +• Fix download dialog disappearing without doing anything if opened from the share menu +• Update a library related to opening external activities such as the Storage Access Framework file picker