Fix inconsistency when LocalPlaylist is used as MainFragment tab

This commit is contained in:
TobiGr 2023-08-16 23:28:02 +02:00
parent 1cf670dad9
commit 3d5a8af52b
3 changed files with 52 additions and 8 deletions

View File

@ -38,6 +38,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentMainBinding; import org.schabi.newpipe.databinding.FragmentMainBinding;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
@ -217,6 +218,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
setTitle(tabsList.get(tabPosition).getTabName(requireContext())); setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
} }
public void commitPlaylistTabs() {
pagerAdapter.getLocalPlaylistFragments()
.stream()
.forEach(LocalPlaylistFragment::commitChanges);
}
private void updateTabLayoutPosition() { private void updateTabLayoutPosition() {
final ScrollableTabLayout tabLayout = binding.mainTabLayout; final ScrollableTabLayout tabLayout = binding.mainTabLayout;
final ViewPager viewPager = binding.pager; final ViewPager viewPager = binding.pager;
@ -268,10 +275,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
updateTitleForTab(tab.getPosition()); updateTitleForTab(tab.getPosition());
} }
private static final class SelectedTabsPagerAdapter public static final class SelectedTabsPagerAdapter
extends FragmentStatePagerAdapterMenuWorkaround { extends FragmentStatePagerAdapterMenuWorkaround {
private final Context context; private final Context context;
private final List<Tab> internalTabsList; private final List<Tab> internalTabsList;
/**
* Keep reference to LocalPlaylistFragments, because their data can be modified by the user
* during runtime and changes are not committed immediately. However, in some cases,
* the changes need to be committed immediately by calling
* {@link LocalPlaylistFragment#commitChanges()}.
* The fragments are removed when {@link LocalPlaylistFragment#onDestroy()} is called.
*/
private final List<LocalPlaylistFragment> localPlaylistFragments = new ArrayList<>();
private SelectedTabsPagerAdapter(final Context context, private SelectedTabsPagerAdapter(final Context context,
final FragmentManager fragmentManager, final FragmentManager fragmentManager,
@ -298,9 +313,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
((BaseFragment) fragment).useAsFrontPage(true); ((BaseFragment) fragment).useAsFrontPage(true);
} }
if (fragment instanceof LocalPlaylistFragment) {
localPlaylistFragments.add((LocalPlaylistFragment) fragment);
}
return fragment; return fragment;
} }
public List<LocalPlaylistFragment> getLocalPlaylistFragments() {
return localPlaylistFragments;
}
@Override @Override
public int getItemPosition(@NonNull final Object object) { public int getItemPosition(@NonNull final Object object) {
// Causes adapter to reload all Fragments when // Causes adapter to reload all Fragments when

View File

@ -85,6 +85,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.EmptyFragment; import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.ktx.AnimationType;
@ -482,6 +483,8 @@ public final class VideoDetailFragment
// commit previous pending changes to database // commit previous pending changes to database
if (fragment instanceof LocalPlaylistFragment) { if (fragment instanceof LocalPlaylistFragment) {
((LocalPlaylistFragment) fragment).commitChanges(); ((LocalPlaylistFragment) fragment).commitChanges();
} else if (fragment instanceof MainFragment) {
((MainFragment) fragment).commitPlaylistTabs();
} }
disposables.add(PlaylistDialog.createCorrespondingDialog(requireContext(), disposables.add(PlaylistDialog.createCorrespondingDialog(requireContext(),

View File

@ -41,6 +41,7 @@ import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
@ -71,7 +72,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void>
implements PlaylistControlViewHolder { implements PlaylistControlViewHolder {
// Save the list 10 seconds after the last change occurred /** Save the list 10 seconds after the last change occurred. */
private static final long SAVE_DEBOUNCE_MILLIS = 10000; private static final long SAVE_DEBOUNCE_MILLIS = 10000;
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12; private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
@State @State
@ -92,13 +93,20 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private PublishSubject<Long> debouncedSaveSignal; private PublishSubject<Long> debouncedSaveSignal;
private CompositeDisposable disposables; private CompositeDisposable disposables;
/* Has the playlist been fully loaded from db */ /** Whether the playlist has been fully loaded from db. */
private AtomicBoolean isLoadingComplete; private AtomicBoolean isLoadingComplete;
/* Has the playlist been modified (e.g. items reordered or deleted) */ /** Whether the playlist has been modified (e.g. items reordered or deleted) */
private AtomicBoolean isModified; private AtomicBoolean isModified;
/* Flag to prevent simultaneous rewrites of the playlist */ /** Flag to prevent simultaneous rewrites of the playlist. */
private boolean isRewritingPlaylist = false; private boolean isRewritingPlaylist = false;
/**
* The pager adapter that the fragment is created from when it is used as frontpage, i.e.
* {@link #useAsFrontPage} is {@link true}.
*/
@Nullable
private MainFragment.SelectedTabsPagerAdapter tabsPagerAdapter = null;
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) { public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
final LocalPlaylistFragment instance = new LocalPlaylistFragment(); final LocalPlaylistFragment instance = new LocalPlaylistFragment();
instance.setInitialData(playlistId, name); instance.setInitialData(playlistId, name);
@ -158,9 +166,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
return headerBinding; return headerBinding;
} }
// Commit changes immediately when the user turns to the player. /**
// Delete operations will be committed to ensure that the database * <p>Commit changes immediately if the playlist has been modified.</p>
// is up to date when the user adds the just deleted stream by the player. * Delete operations and other modifications will be committed to ensure that the database
* is up to date, e.g. when the user adds the just deleted stream from another fragment.
*/
public void commitChanges() { public void commitChanges() {
if (isModified != null && isModified.get()) { if (isModified != null && isModified.get()) {
saveImmediate(); saveImmediate();
@ -300,6 +310,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
if (disposables != null) { if (disposables != null) {
disposables.dispose(); disposables.dispose();
} }
if (tabsPagerAdapter != null) {
tabsPagerAdapter.getLocalPlaylistFragments().remove(this);
}
debouncedSaveSignal = null; debouncedSaveSignal = null;
playlistManager = null; playlistManager = null;
@ -886,5 +899,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
) )
.show(); .show();
} }
public void setTabsPagerAdapter(
@Nullable final MainFragment.SelectedTabsPagerAdapter tabsPagerAdapter) {
this.tabsPagerAdapter = tabsPagerAdapter;
}
} }