mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-11-04 01:03:00 +00:00 
			
		
		
		
	Merge pull request #6716 from TeamNewPipe/release_0.21.7
Hotfix release 0.21.7
This commit is contained in:
		@@ -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'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								fastlane/metadata/android/en-US/changelogs/973.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fastlane/metadata/android/en-US/changelogs/973.txt
									
									
									
									
									
										Normal 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
 | 
				
			||||||
		Reference in New Issue
	
	Block a user