Merge pull request #6716 from TeamNewPipe/release_0.21.7

Hotfix release 0.21.7
This commit is contained in:
Tobi 2021-07-20 20:05:10 +02:00 committed by GitHub
commit b58f7856a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 291 additions and 264 deletions

View File

@ -17,8 +17,8 @@ android {
resValue "string", "app_name", "NewPipe" resValue "string", "app_name", "NewPipe"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 29 targetSdkVersion 29
versionCode 972 versionCode 973
versionName "0.21.6" versionName "0.21.7"
multiDexEnabled true multiDexEnabled true
@ -201,7 +201,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1' 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-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

View File

@ -589,9 +589,9 @@ public class RouterActivity extends AppCompatActivity {
downloadDialog.setVideoStreams(sortedVideoStreams); downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(result.getAudioStreams()); downloadDialog.setAudioStreams(result.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setOnDismissListener(dialog -> finish());
downloadDialog.show(fm, "downloadDialog"); downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions(); fm.executePendingTransactions();
downloadDialog.requireDialog().setOnDismissListener(dialog -> finish());
}, throwable -> }, throwable ->
showUnsupportedUrlDialog(currentUrl))); showUnsupportedUrlDialog(currentUrl)));
} }

View File

@ -3,6 +3,8 @@ package org.schabi.newpipe.download;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -20,6 +22,9 @@ import android.widget.RadioGroup;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.Toast; 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.IdRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -35,7 +40,6 @@ import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.RouterActivity;
import org.schabi.newpipe.databinding.DownloadDialogBinding; import org.schabi.newpipe.databinding.DownloadDialogBinding;
import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorInfo;
@ -82,9 +86,6 @@ public class DownloadDialog extends DialogFragment
implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment"; private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG; 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 @State
StreamInfo currentInfo; StreamInfo currentInfo;
@ -101,6 +102,9 @@ public class DownloadDialog extends DialogFragment
@State @State
int selectedSubtitleIndex = 0; int selectedSubtitleIndex = 0;
@Nullable
private OnDismissListener onDismissListener = null;
private StoredDirectoryHelper mainStorageAudio = null; private StoredDirectoryHelper mainStorageAudio = null;
private StoredDirectoryHelper mainStorageVideo = null; private StoredDirectoryHelper mainStorageVideo = null;
private DownloadManager downloadManager = null; private DownloadManager downloadManager = null;
@ -122,6 +126,21 @@ public class DownloadDialog extends DialogFragment
private String filenameTmp; private String filenameTmp;
private String mimeTmp; private String mimeTmp;
private final ActivityResultLauncher<Intent> requestDownloadSaveAsLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadSaveAsResult);
private final ActivityResultLauncher<Intent> requestDownloadPickAudioFolderLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadPickAudioFolderResult);
private final ActivityResultLauncher<Intent> requestDownloadPickVideoFolderLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);
/*//////////////////////////////////////////////////////////////////////////
// Instance creation
//////////////////////////////////////////////////////////////////////////*/
public static DownloadDialog newInstance(final StreamInfo info) { public static DownloadDialog newInstance(final StreamInfo info) {
final DownloadDialog dialog = new DownloadDialog(); final DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info); dialog.setInfo(info);
@ -143,6 +162,11 @@ public class DownloadDialog extends DialogFragment
return instance; return instance;
} }
/*//////////////////////////////////////////////////////////////////////////
// Setters
//////////////////////////////////////////////////////////////////////////*/
private void setInfo(final StreamInfo info) { private void setInfo(final StreamInfo info) {
this.currentInfo = info; this.currentInfo = info;
} }
@ -184,6 +208,14 @@ public class DownloadDialog extends DialogFragment
this.selectedSubtitleIndex = ssi; this.selectedSubtitleIndex = ssi;
} }
public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
}
/*//////////////////////////////////////////////////////////////////////////
// Android lifecycle
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -194,7 +226,7 @@ public class DownloadDialog extends DialogFragment
if (!PermissionHelper.checkStoragePermissions(getActivity(), if (!PermissionHelper.checkStoragePermissions(getActivity(),
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss(); dismiss();
return; return;
} }
@ -253,10 +285,6 @@ public class DownloadDialog extends DialogFragment
}, Context.BIND_AUTO_CREATE); }, Context.BIND_AUTO_CREATE);
} }
/*//////////////////////////////////////////////////////////////////////////
// Inits
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) { final Bundle savedInstanceState) {
@ -312,6 +340,60 @@ public class DownloadDialog extends DialogFragment
fetchStreamsSize(); 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() { private void fetchStreamsSize() {
disposables.clear(); disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams) disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
@ -346,126 +428,6 @@ public class DownloadDialog extends DialogFragment
currentInfo.getServiceId())))); 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() { private void setupAudioSpinner() {
if (getContext() == null) { if (getContext() == null) {
return; return;
@ -496,6 +458,88 @@ public class DownloadDialog extends DialogFragment
setRadioButtonsState(true); 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 @Override
public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) { public void onCheckedChanged(final RadioGroup group, @IdRes final int checkedId) {
if (DEBUG) { if (DEBUG) {
@ -545,6 +589,11 @@ public class DownloadDialog extends DialogFragment
public void onNothingSelected(final AdapterView<?> parent) { public void onNothingSelected(final AdapterView<?> parent) {
} }
/*//////////////////////////////////////////////////////////////////////////
// Download
//////////////////////////////////////////////////////////////////////////*/
protected void setupDownloadOptions() { protected void setupDownloadOptions() {
setRadioButtonsState(false); setRadioButtonsState(false);
@ -557,7 +606,7 @@ public class DownloadDialog extends DialogFragment
dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable dialogBinding.subtitleButton.setVisibility(isSubtitleStreamsAvailable
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type), final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
getString(R.string.last_download_type_video_key)); getString(R.string.last_download_type_video_key));
@ -585,7 +634,7 @@ public class DownloadDialog extends DialogFragment
} else { } else {
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.makeText(getContext(), R.string.no_streams_available_download,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
getDialog().dismiss(); dismiss();
} }
} }
@ -637,6 +686,10 @@ public class DownloadDialog extends DialogFragment
.show(); .show();
} }
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
launcher.launch(StoredDirectoryHelper.getPicker(context));
}
private void prepareSelectedDownload() { private void prepareSelectedDownload() {
final StoredDirectoryHelper mainStorage; final StoredDirectoryHelper mainStorage;
final MediaFormat format; final MediaFormat format;
@ -691,11 +744,9 @@ public class DownloadDialog extends DialogFragment
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) { if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
startActivityForResult(StoredDirectoryHelper.getPicker(context), launchDirectoryPicker(requestDownloadPickAudioFolderLauncher);
REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER);
} else { } else {
startActivityForResult(StoredDirectoryHelper.getPicker(context), launchDirectoryPicker(requestDownloadPickVideoFolderLauncher);
REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER);
} }
return; return;
@ -715,8 +766,8 @@ public class DownloadDialog extends DialogFragment
initialPath = Uri.parse(initialSavePath.getAbsolutePath()); initialPath = Uri.parse(initialSavePath.getAbsolutePath());
} }
startActivityForResult(StoredFileHelper.getNewPicker(context, requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context,
filenameTmp, mimeTmp, initialPath), REQUEST_DOWNLOAD_SAVE_AS); filenameTmp, mimeTmp, initialPath));
return; return;
} }

View File

@ -1,7 +1,6 @@
package org.schabi.newpipe.local; package org.schabi.newpipe.local;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; 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.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; 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 * This fragment is design to be used with persistent data such as
@ -77,7 +77,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
super.onResume(); super.onResume();
if (updateFlags != 0) { if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout(); final boolean useGrid = shouldUseGridLayout(requireContext());
itemsList.setLayoutManager( itemsList.setLayoutManager(
useGrid ? getGridLayoutManager() : getListLayoutManager()); useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setUseGridVariant(useGrid); itemListAdapter.setUseGridVariant(useGrid);
@ -121,7 +121,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
itemListAdapter = new LocalItemListAdapter(activity); itemListAdapter = new LocalItemListAdapter(activity);
final boolean useGrid = isGridLayout(); final boolean useGrid = shouldUseGridLayout(requireContext());
itemsList = rootView.findViewById(R.id.items_list); itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
@ -260,17 +260,4 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
updateFlags |= LIST_MODE_UPDATE_FLAG; 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);
}
}
} }

View File

@ -23,7 +23,6 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater 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.Localization
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.StreamDialogEntry 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.time.OffsetDateTime
import java.util.ArrayList import java.util.ArrayList
import kotlin.math.floor
import kotlin.math.max
class FeedFragment : BaseStateFragment<FeedState>() { class FeedFragment : BaseStateFragment<FeedState>() {
private var _feedBinding: FragmentFeedBinding? = null private var _feedBinding: FragmentFeedBinding? = null
@ -161,7 +160,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
fun setupListViewMode() { fun setupListViewMode() {
// does everything needed to setup the layouts for grid or list modes // 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 { feedBinding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup spanSizeLookup = groupAdapter.spanSizeLookup
} }
@ -384,7 +383,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
private fun handleLoadedState(loadedState: FeedState.LoadedState) { private fun handleLoadedState(loadedState: FeedState.LoadedState) {
val itemVersion = if (shouldUseGridLayout()) { val itemVersion = if (shouldUseGridLayout(context)) {
StreamItem.ItemVersion.GRID StreamItem.ItemVersion.GRID
} else { } else {
StreamItem.ItemVersion.NORMAL StreamItem.ItemVersion.NORMAL
@ -528,35 +527,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
listState = null 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 { companion object {
const val KEY_GROUP_ID = "ARG_GROUP_ID" const val KEY_GROUP_ID = "ARG_GROUP_ID"
const val KEY_GROUP_NAME = "ARG_GROUP_NAME" const val KEY_GROUP_NAME = "ARG_GROUP_NAME"

View File

@ -68,6 +68,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> { public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
// Save the list 10 seconds after the last change occurred // Save the list 10 seconds after the last change occurred
@ -678,7 +679,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private ItemTouchHelper.SimpleCallback getItemTouchCallback() { private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
if (isGridLayout()) { if (shouldUseGridLayout(requireContext())) {
directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} }
return new ItemTouchHelper.SimpleCallback(directions, return new ItemTouchHelper.SimpleCallback(directions,

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -20,7 +19,6 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.xwray.groupie.Group import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter import com.xwray.groupie.GroupAdapter
@ -60,12 +58,12 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCount
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.external_communication.ShareUtils
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() { class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private var _binding: FragmentSubscriptionBinding? = null private var _binding: FragmentSubscriptionBinding? = null
@ -279,8 +277,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
super.initViews(rootView, savedInstanceState) super.initViews(rootView, savedInstanceState)
_binding = FragmentSubscriptionBinding.bind(rootView) _binding = FragmentSubscriptionBinding.bind(rootView)
val shouldUseGridLayout = shouldUseGridLayout() groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCount(context) else 1
groupAdapter.spanCount = if (shouldUseGridLayout) getGridSpanCount() else 1
binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply { binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup spanSizeLookup = groupAdapter.spanSizeLookup
} }
@ -359,7 +356,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
override fun handleResult(result: SubscriptionState) { override fun handleResult(result: SubscriptionState) {
super.handleResult(result) super.handleResult(result)
val shouldUseGridLayout = shouldUseGridLayout() val shouldUseGridLayout = shouldUseGridLayout(context)
when (result) { when (result) {
is SubscriptionState.LoadedState -> { is SubscriptionState.LoadedState -> {
result.subscriptions.forEach { result.subscriptions.forEach {
@ -420,30 +417,4 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
super.hideLoading() super.hideLoading()
binding.itemsList.animate(true, 200) 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())
}
} }

View File

@ -9,6 +9,9 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; 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.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@ -18,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
import java.io.File; import java.io.File;
@ -27,14 +31,10 @@ import java.net.URI;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class DownloadSettingsFragment extends BasePreferenceFragment { public class DownloadSettingsFragment extends BasePreferenceFragment {
public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; 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 downloadPathVideoPreference;
private String downloadPathAudioPreference; private String downloadPathAudioPreference;
private String storageUseSafPreference; private String storageUseSafPreference;
@ -44,6 +44,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
private Preference prefStorageAsk; private Preference prefStorageAsk;
private Context ctx; private Context ctx;
private final ActivityResultLauncher<Intent> requestDownloadVideoPathLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadVideoPathResult);
private final ActivityResultLauncher<Intent> requestDownloadAudioPathLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadAudioPathResult);
@Override @Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
@ -185,7 +191,6 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
} }
final String key = preference.getKey(); final String key = preference.getKey();
final int request;
if (key.equals(storageUseSafPreference)) { if (key.equals(storageUseSafPreference)) {
if (!NewPipeSettings.useStorageAccessFramework(ctx)) { if (!NewPipeSettings.useStorageAccessFramework(ctx)) {
@ -198,43 +203,39 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
updatePreferencesSummary(); updatePreferencesSummary();
return true; return true;
} else if (key.equals(downloadPathVideoPreference)) { } else if (key.equals(downloadPathVideoPreference)) {
request = REQUEST_DOWNLOAD_VIDEO_PATH; launchDirectoryPicker(requestDownloadVideoPathLauncher);
} else if (key.equals(downloadPathAudioPreference)) { } else if (key.equals(downloadPathAudioPreference)) {
request = REQUEST_DOWNLOAD_AUDIO_PATH; launchDirectoryPicker(requestDownloadAudioPathLauncher);
} else { } else {
return super.onPreferenceTreeClick(preference); return super.onPreferenceTreeClick(preference);
} }
startActivityForResult(StoredDirectoryHelper.getPicker(ctx), request);
return true; return true;
} }
@Override private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { 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()); 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; return;
} }
final String key; Uri uri = null;
if (requestCode == REQUEST_DOWNLOAD_VIDEO_PATH) { if (result.getData() != null) {
key = downloadPathVideoPreference; uri = result.getData().getData();
} else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) {
key = downloadPathAudioPreference;
} else {
return;
} }
Uri uri = data.getData();
if (uri == null) { if (uri == null) {
showMessageDialog(R.string.general_error, R.string.invalid_directory); showMessageDialog(R.string.general_error, R.string.invalid_directory);
return; return;

View File

@ -298,4 +298,43 @@ public final class ThemeHelper {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); 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);
}
} }

View File

@ -17,6 +17,9 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; 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.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -44,7 +47,6 @@ import us.shandian.giga.ui.adapter.MissionAdapter;
public class MissionsFragment extends Fragment { public class MissionsFragment extends Fragment {
private static final int SPAN_SIZE = 2; private static final int SPAN_SIZE = 2;
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
private SharedPreferences mPrefs; private SharedPreferences mPrefs;
private boolean mLinear; private boolean mLinear;
@ -64,7 +66,8 @@ public class MissionsFragment extends Fragment {
private boolean mForceUpdate; private boolean mForceUpdate;
private DownloadMission unsafeMissionTarget = null; private DownloadMission unsafeMissionTarget = null;
private final ActivityResultLauncher<Intent> requestDownloadSaveAsLauncher =
registerForActivityResult(new StartActivityForResult(), this::requestDownloadSaveAsResult);
private final ServiceConnection mConnection = new ServiceConnection() { private final ServiceConnection mConnection = new ServiceConnection() {
@Override @Override
@ -254,8 +257,9 @@ public class MissionsFragment extends Fragment {
initialPath = Uri.parse(initialSavePath.getAbsolutePath()); initialPath = Uri.parse(initialSavePath.getAbsolutePath());
} }
startActivityForResult(StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), requestDownloadSaveAsLauncher.launch(
mission.storage.getType(), initialPath), REQUEST_DOWNLOAD_SAVE_AS); StoredFileHelper.getNewPicker(mContext, mission.storage.getName(),
mission.storage.getType(), initialPath));
} }
@Override @Override
@ -289,18 +293,17 @@ public class MissionsFragment extends Fragment {
if (mBinder != null) mBinder.enableNotifications(true); if (mBinder != null) mBinder.enableNotifications(true);
} }
@Override private void requestDownloadSaveAsResult(final ActivityResult result) {
public void onActivityResult(int requestCode, int resultCode, Intent data) { if (result.getResultCode() != Activity.RESULT_OK) {
super.onActivityResult(requestCode, resultCode, data); return;
}
if (requestCode != REQUEST_DOWNLOAD_SAVE_AS || resultCode != Activity.RESULT_OK) return; if (unsafeMissionTarget == null || result.getData() == null) {
if (unsafeMissionTarget == null || data.getData() == null) {
return; return;
} }
try { try {
Uri fileUri = data.getData(); Uri fileUri = result.getData().getData();
if (fileUri.getAuthority() != null && FilePickerActivityHelper.isOwnFileUri(mContext, fileUri)) { if (fileUri.getAuthority() != null && FilePickerActivityHelper.isOwnFileUri(mContext, fileUri)) {
fileUri = Uri.fromFile(Utils.getFileForUri(fileUri)); fileUri = Uri.fromFile(Utils.getFileForUri(fileUri));
} }

View File

@ -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