Merge ccfb164abafe0a374b5bed806076381f5f84d180 into addf1e23b36d942aeb257c2be4d5d8ab82bdcc11

This commit is contained in:
malania02 2026-01-28 01:47:57 -08:00 committed by GitHub
commit 4a4a0f68b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 284 additions and 12 deletions

View File

@ -299,7 +299,7 @@ public class DownloadDialog extends DialogFragment
}
dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
currentInfo.getName()));
getFileName()));
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(),
getWrappedAudioStreams().getStreamsList());
@ -612,7 +612,7 @@ public class DownloadDialog extends DialogFragment
}
private void onItemSelectedSetFileName() {
final String fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
final String fileName = FilenameUtils.createFilename(getContext(), getFileName());
final String prevFileName = Optional.ofNullable(dialogBinding.fileName.getText())
.map(Object::toString)
.orElse("");
@ -743,7 +743,24 @@ public class DownloadDialog extends DialogFragment
final String str = Objects.requireNonNull(dialogBinding.fileName.getText()).toString()
.trim();
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
return FilenameUtils.createFilename(context, str.isEmpty() ? getFileName() : str);
}
private String getFileName() {
final SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
final boolean includeUploader = sharedPreferences.getBoolean(
context.getString(R.string.settings_file_name_include_uploader_key), false);
final String name = currentInfo.getName();
if (includeUploader) {
final String uploader = currentInfo.getUploaderName();
if (uploader != null && !uploader.isEmpty()) {
return name + " - " + uploader;
}
}
return name;
}
private void showFailedDialog(@StringRes final int msg) {

View File

@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission;
@ -600,12 +601,25 @@ public class DownloadManager {
boolean hasFinished = false;
private boolean filteringEnabled = false;
private String currentFilter = "";
private MissionIterator() {
hidden = new ArrayList<>(2);
current = null;
snapshot = getSpecialItems();
}
public void filter(String query) {
currentFilter = query.trim().toLowerCase(Locale.getDefault());
filteringEnabled = !currentFilter.isEmpty();
}
public void clearFilter() {
currentFilter = "";
filteringEnabled = false;
}
private ArrayList<Object> getSpecialItems() {
synchronized (DownloadManager.this) {
ArrayList<Mission> pending = new ArrayList<>(mMissionsPending);
@ -620,6 +634,11 @@ public class DownloadManager {
return pending.remove(mission) || finished.remove(mission);
});
if (filteringEnabled && currentFilter != null && !currentFilter.isEmpty()) {
pending.removeIf(m -> !matchesFilter(m, currentFilter));
finished.removeIf(m -> !matchesFilter(m, currentFilter));
}
int fakeTotal = pending.size();
if (fakeTotal > 0) fakeTotal++;
@ -642,6 +661,11 @@ public class DownloadManager {
}
}
private boolean matchesFilter(Mission mission, String query) {
String name = mission.storage.getName().toLowerCase(Locale.getDefault());
return name.contains(query);
}
public MissionItem getItem(int position) {
Object object = snapshot.get(position);
@ -729,6 +753,10 @@ public class DownloadManager {
Object x = snapshot.get(oldItemPosition);
Object y = current.get(newItemPosition);
// Necessary to avoid flickering of headers when filtering
if (x == PENDING && y == PENDING) return true;
if (x == FINISHED && y == FINISHED) return true;
if (x instanceof Mission && y instanceof Mission) {
return ((Mission) x).storage.equals(((Mission) y).storage);
}

View File

@ -117,6 +117,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
private final View mView;
private final ArrayList<Mission> mHidden;
private Snackbar mSnackbar;
private boolean showButtons = true;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@ -186,7 +187,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
str = R.string.missions_header_pending;
} else {
str = R.string.missions_header_finished;
if (mClear != null) mClear.setVisible(true);
if (mClear != null) mClear.setVisible(showButtons);
}
((ViewHolderHeader) view).header.setText(str);
@ -731,13 +732,25 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
}
}
public void filter(String query) {
if (query == null) return;
String currentFilter = query.trim();
if (currentFilter.isEmpty()) {
mIterator.clearFilter();
} else {
mIterator.filter(currentFilter);
}
applyChanges();
}
public void applyChanges() {
mIterator.start();
DiffUtil.calculateDiff(mIterator, true).dispatchUpdatesTo(this);
mIterator.end();
checkEmptyMessageVisibility();
if (mClear != null) mClear.setVisible(mIterator.hasFinishedMissions());
if (mClear != null) mClear.setVisible(showButtons && mIterator.hasFinishedMissions());
}
public void forceUpdate() {
@ -757,7 +770,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
public void setClearButton(MenuItem clearButton) {
if (mClear == null)
clearButton.setVisible(mIterator.hasFinishedMissions());
clearButton.setVisible(showButtons && mIterator.hasFinishedMissions());
mClear = clearButton;
}
@ -771,6 +784,18 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
if (init) checkMasterButtonsVisibility();
}
public void showMenuButtons() {
showButtons = true;
if (mClear != null) mClear.setVisible(mIterator.hasFinishedMissions());
checkMasterButtonsVisibility();
}
public void hideMenuButtons() {
showButtons = false;
if (mClear != null) mClear.setVisible(false);
checkMasterButtonsVisibility();
}
private void checkEmptyMessageVisibility() {
int flag = mIterator.getOldListSize() > 0 ? View.GONE : View.VISIBLE;
if (mEmptyMessage.getVisibility() != flag) mEmptyMessage.setVisibility(flag);
@ -779,12 +804,12 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
public void checkMasterButtonsVisibility() {
boolean[] state = mIterator.hasValidPendingMissions();
Log.d(TAG, "checkMasterButtonsVisibility() running=" + state[0] + " paused=" + state[1]);
setButtonVisible(mPauseButton, state[0]);
setButtonVisible(mStartButton, state[1]);
setButtonVisible(mPauseButton, showButtons && state[0]);
setButtonVisible(mStartButton, showButtons && state[1]);
}
private static void setButtonVisible(MenuItem button, boolean visible) {
if (button.isVisible() != visible)
if (button != null && button.isVisible() != visible)
button.setVisible(visible);
}

View File

@ -10,24 +10,36 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.CharacterStyle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.R;
@ -35,6 +47,7 @@ import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.KeyboardUtil;
import java.io.File;
import java.io.IOException;
@ -52,6 +65,7 @@ public class MissionsFragment extends Fragment {
private SharedPreferences mPrefs;
private boolean mLinear;
private MenuItem mSearch;
private MenuItem mSwitch;
private MenuItem mClear = null;
private MenuItem mStart = null;
@ -64,9 +78,19 @@ public class MissionsFragment extends Fragment {
private LinearLayoutManager mLinearManager;
private Context mContext;
private View searchToolbarContainer;
private EditText searchEditText;
private View searchClear;
private TextWatcher textWatcher;
private DownloadManagerBinder mBinder;
private boolean mForceUpdate;
@State
String searchString;
@State
boolean wasSearchActive;
private DownloadMission unsafeMissionTarget = null;
private final ActivityResultLauncher<Intent> requestDownloadSaveAsLauncher =
registerForActivityResult(new StartActivityForResult(), this::requestDownloadSaveAsResult);
@ -87,6 +111,11 @@ public class MissionsFragment extends Fragment {
mBinder.enableNotifications(false);
updateList();
if (isSearchActive()) {
mAdapter.hideMenuButtons();
mAdapter.filter(getSearchEditString());
}
}
@Override
@ -132,6 +161,48 @@ public class MissionsFragment extends Fragment {
return v;
}
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
initSearchViews();
initSearchListeners();
Bridge.restoreInstanceState(this, savedInstanceState);
if (savedInstanceState != null) {
if (wasSearchActive) {
searchEditText.setText(searchString);
showSearch();
}
}
requireActivity().getOnBackPressedDispatcher().addCallback(
getViewLifecycleOwner(),
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (isSearchActive() && !TextUtils.isEmpty(getSearchEditString())) {
hideSearch();
} else {
setEnabled(false);
hideKeyboardSearch();
requireActivity().onBackPressed();
}
}
}
);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle bundle) {
if (searchEditText != null) {
searchString = getSearchEditString();
wasSearchActive = isSearchActive();
}
super.onSaveInstanceState(bundle);
Bridge.saveInstanceState(this, bundle);
}
/**
* Added in API level 23.
*/
@ -174,12 +245,14 @@ public class MissionsFragment extends Fragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
mSearch = menu.findItem(R.id.action_search);
mSwitch = menu.findItem(R.id.switch_mode);
mClear = menu.findItem(R.id.clear_list);
mStart = menu.findItem(R.id.start_downloads);
mPause = menu.findItem(R.id.pause_downloads);
if (mAdapter != null) setAdapterButtons();
if (mSearch != null) mSearch.setVisible(!isSearchActive());
super.onPrepareOptionsMenu(menu);
}
@ -200,6 +273,10 @@ public class MissionsFragment extends Fragment {
case R.id.pause_downloads:
mBinder.getDownloadManager().pauseAllMissions(false);
mAdapter.refreshMissionItems();// update items view
return true;
case R.id.action_search:
showSearch();
return true;
default:
return super.onOptionsItemSelected(item);
}
@ -246,8 +323,8 @@ public class MissionsFragment extends Fragment {
if (mSwitch != null) {
mSwitch.setIcon(mLinear
? R.drawable.ic_apps
: R.drawable.ic_list);
? R.drawable.ic_apps
: R.drawable.ic_list);
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
mPrefs.edit().putBoolean("linear", mLinear).apply();
}
@ -338,4 +415,110 @@ public class MissionsFragment extends Fragment {
Toast.makeText(mContext, R.string.general_error, Toast.LENGTH_LONG).show();
}
}
private void initSearchViews() {
searchToolbarContainer = requireActivity().findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
}
private void initSearchListeners() {
searchClear.setOnClickListener(v -> {
if (TextUtils.isEmpty(getSearchEditString())) {
hideSearch();
return;
}
searchEditText.setText("");
showKeyboardSearch();
});
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
if (textWatcher != null) {
searchEditText.removeTextChangedListener(textWatcher);
}
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
// Do nothing, old text is already clean
}
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
// Changes are handled in afterTextChanged; CharSequence cannot be changed here.
}
@Override
public void afterTextChanged(final Editable s) {
// Remove rich text formatting
for (final CharacterStyle span : s.getSpans(0, s.length(), CharacterStyle.class)) {
s.removeSpan(span);
}
if (mAdapter != null) mAdapter.filter(s.toString());
}
};
searchEditText.addTextChangedListener(textWatcher);
searchEditText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
(event != null
&& event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_DOWN)) {
hideKeyboardSearch();
return true;
}
return false;
});
}
private void showSearch() {
if (mSearch != null) mSearch.setVisible(false);
if (mAdapter != null) mAdapter.hideMenuButtons();
showKeyboardSearch();
if (TextUtils.isEmpty(getSearchEditString())) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
searchToolbarContainer.animate()
.translationX(0)
.alpha(1.0f)
.setDuration(200)
.setInterpolator(new DecelerateInterpolator()).start();
} else {
searchToolbarContainer.setTranslationX(0);
searchToolbarContainer.setAlpha(1.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
}
}
private void hideSearch() {
hideKeyboardSearch();
searchToolbarContainer.setVisibility(View.GONE);
if (!TextUtils.isEmpty(getSearchEditString())) searchEditText.setText("");
if (mSearch != null) mSearch.setVisible(true);
if (mAdapter != null) mAdapter.showMenuButtons();
}
private boolean isSearchActive() {
return searchToolbarContainer.getVisibility() == View.VISIBLE;
}
private String getSearchEditString() {
return searchEditText.getText().toString();
}
private void showKeyboardSearch() {
KeyboardUtil.showKeyboard(requireActivity(), searchEditText);
}
private void hideKeyboardSearch() {
KeyboardUtil.hideKeyboard(requireActivity(), searchEditText);
}
}

View File

@ -3,6 +3,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
android:title="@string/search"
app:showAsAction="always" />
<item
android:id="@+id/switch_mode"
android:icon="@drawable/ic_apps"

View File

@ -101,7 +101,7 @@
<string name="popup_remember_size_pos_summary">Letzte Größe und Position des Pop-ups merken</string>
<string name="show_search_suggestions_title">Suchvorschläge</string>
<string name="show_search_suggestions_summary">Vorschläge auswählen, die bei der Suche angezeigt werden sollen</string>
<string name="clear">löschen</string>
<string name="clear">Löschen</string>
<string name="best_resolution">Beste Auflösung</string>
<string name="title_activity_about">Über NewPipe</string>
<string name="tab_licenses">Lizenzen</string>
@ -118,6 +118,8 @@
<string name="settings_file_charset_title">Erlaubte Zeichen im Dateinamen</string>
<string name="settings_file_replacement_character_summary">Ungültige Zeichen werden durch dieses Zeichen ersetzt</string>
<string name="settings_file_replacement_character_title">Ersetzungszeichen</string>
<string name="settings_file_name_include_uploader_key_summary">Name des Uploaders an den Dateinamen anhängen</string>
<string name="settings_file_name_include_uploader_key_title">Uploader im Dateinamen</string>
<string name="charset_letters_and_digits">Buchstaben und Zahlen</string>
<string name="subscribe_button_title">Abonnieren</string>
<string name="subscribed_button_title">Abonniert</string>

View File

@ -432,6 +432,7 @@
<string name="settings_file_charset_key">file_rename_charset</string>
<string name="settings_file_replacement_character_key">file_replacement_character</string>
<string name="settings_file_replacement_character_default_value">_</string>
<string name="settings_file_name_include_uploader_key">file_include_uploader_name</string>
<string name="charset_letters_and_digits_value">CHARSET_LETTERS_AND_DIGITS</string>

View File

@ -365,6 +365,8 @@
<string name="settings_file_charset_title">Allowed characters in filenames</string>
<string name="settings_file_replacement_character_summary">Invalid characters are replaced with this value</string>
<string name="settings_file_replacement_character_title">Replacement character</string>
<string name="settings_file_name_include_uploader_key_summary">Append uploader name to filename</string>
<string name="settings_file_name_include_uploader_key_title">Uploader in filename</string>
<string name="charset_letters_and_digits">Letters and digits</string>
<string name="charset_most_special_characters">Most special characters</string>
<!-- About -->

View File

@ -53,6 +53,14 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/settings_file_name_include_uploader_key"
android:summary="@string/settings_file_name_include_uploader_key_summary"
android:title="@string/settings_file_name_include_uploader_key_title"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="@string/downloads_maximum_retry_default"
android:entries="@array/downloads_maximum_retry_list"