diff options
34 files changed, 527 insertions, 900 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java index df3dbb7f8..70282a6f3 100644 --- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -172,12 +172,16 @@ public class EspressoTestUtils { RatingDialog.saveRated(); } - public static void setLastNavFragment(String tag) { + public static void setLaunchScreen(String tag) { InstrumentationRegistry.getInstrumentation().getTargetContext() .getSharedPreferences(NavDrawerFragment.PREF_NAME, Context.MODE_PRIVATE) .edit() .putString(NavDrawerFragment.PREF_LAST_FRAGMENT_TAG, tag) .commit(); + PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().getTargetContext()) + .edit() + .putString(UserPreferences.PREF_DEFAULT_PAGE, UserPreferences.DEFAULT_PAGE_REMEMBER) + .commit(); } public static void clearDatabase() { diff --git a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java index c68e13438..49a252ea1 100644 --- a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java +++ b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java @@ -48,7 +48,7 @@ public class ShareDialogTest { context = InstrumentationRegistry.getInstrumentation().getTargetContext(); EspressoTestUtils.clearPreferences(); EspressoTestUtils.clearDatabase(); - EspressoTestUtils.setLastNavFragment(AllEpisodesFragment.TAG); + EspressoTestUtils.setLaunchScreen(AllEpisodesFragment.TAG); UITestUtils uiTestUtils = new UITestUtils(context); uiTestUtils.setup(); uiTestUtils.addLocalFeedData(true); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java index 81d7731c5..a55670ed6 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java @@ -1,16 +1,15 @@ package de.test.antennapod.ui; -import android.app.Activity; import android.content.Intent; - import androidx.test.espresso.Espresso; import androidx.test.espresso.intent.rule.IntentsTestRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - -import com.robotium.solo.Solo; - +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.storage.database.PodDBAdapter; +import de.test.antennapod.EspressoTestUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -19,27 +18,14 @@ import org.junit.runner.RunWith; import java.io.IOException; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.model.feed.Feed; -import de.test.antennapod.EspressoTestUtils; - import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.replaceText; import static androidx.test.espresso.action.ViewActions.scrollTo; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.contrib.ActivityResultMatchers.hasResultCode; -import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static de.test.antennapod.EspressoTestUtils.clickPreference; import static de.test.antennapod.EspressoTestUtils.openNavDrawer; import static de.test.antennapod.EspressoTestUtils.waitForViewGlobally; -import static org.hamcrest.Matchers.allOf; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * User interface tests for MainActivity. @@ -47,7 +33,6 @@ import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class MainActivityTest { - private Solo solo; private UITestUtils uiTestUtils; @Rule @@ -62,8 +47,6 @@ public class MainActivityTest { uiTestUtils = new UITestUtils(InstrumentationRegistry.getInstrumentation().getTargetContext()); uiTestUtils.setup(); - - solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityRule.getActivity()); } @After @@ -91,79 +74,4 @@ public class MainActivityTest { // wait for podcast feed item list waitForViewGlobally(withId(R.id.butShowSettings), 15000); } - - @Test - public void testBackButtonBehaviorGoToPage() { - openNavDrawer(); - onView(withText(R.string.settings_label)).perform(click()); - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - - onView(withText(R.string.back_button_go_to_page)).perform(click()); - onView(withText(R.string.subscriptions_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - - solo.goBackToActivity(MainActivity.class.getSimpleName()); - solo.goBack(); - solo.goBack(); - onView(allOf(withId(R.id.toolbar), isDisplayed())).check( - matches(hasDescendant(withText(R.string.subscriptions_label)))); - solo.goBack(); - assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); - } - - @Test - public void testBackButtonBehaviorOpenDrawer() { - openNavDrawer(); - onView(withText(R.string.settings_label)).perform(click()); - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_open_drawer)).perform(click()); - solo.goBackToActivity(MainActivity.class.getSimpleName()); - solo.goBack(); - solo.goBack(); - assertTrue(((MainActivity) solo.getCurrentActivity()).isDrawerOpen()); - } - - @Test - public void testBackButtonBehaviorDoubleTap() { - openNavDrawer(); - onView(withText(R.string.settings_label)).perform(click()); - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_double_tap)).perform(click()); - solo.goBackToActivity(MainActivity.class.getSimpleName()); - solo.goBack(); - solo.goBack(); - solo.goBack(); - assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); - } - - @Test - public void testBackButtonBehaviorPrompt() throws Exception { - openNavDrawer(); - onView(withText(R.string.settings_label)).perform(click()); - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_show_prompt)).perform(click()); - solo.goBackToActivity(MainActivity.class.getSimpleName()); - solo.goBack(); - solo.goBack(); - onView(withText(R.string.yes)).perform(click()); - Thread.sleep(100); - assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); - } - - @Test - public void testBackButtonBehaviorDefault() { - openNavDrawer(); - onView(withText(R.string.settings_label)).perform(click()); - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_default)).perform(click()); - solo.goBackToActivity(MainActivity.class.getSimpleName()); - solo.goBack(); - solo.goBack(); - assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED)); - } } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index b8f2faa63..25eb60ff4 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -28,9 +28,6 @@ import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm; import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm; -import de.danoeh.antennapod.fragment.AllEpisodesFragment; -import de.danoeh.antennapod.fragment.QueueFragment; -import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.test.antennapod.EspressoTestUtils; import static androidx.test.espresso.Espresso.onData; @@ -504,35 +501,6 @@ public class PreferencesTest { } @Test - public void testBackButtonBehaviorGoToPageSelector() { - clickPreference(R.string.user_interface_label); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_go_to_page)).perform(click()); - onView(withText(R.string.queue_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonGoToPage().equals(QueueFragment.TAG)); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_go_to_page)).perform(click()); - onView(withText(R.string.episodes_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonGoToPage().equals(AllEpisodesFragment.TAG)); - clickPreference(R.string.pref_back_button_behavior_title); - onView(withText(R.string.back_button_go_to_page)).perform(click()); - onView(withText(R.string.subscriptions_label)).perform(click()); - onView(withText(R.string.confirm_label)).perform(click()); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE); - Awaitility.await().atMost(1000, MILLISECONDS) - .until(() -> UserPreferences.getBackButtonGoToPage().equals(SubscriptionFragment.TAG)); - } - - @Test public void testDeleteRemovesFromQueue() { clickPreference(R.string.storage_pref); if (!UserPreferences.shouldDeleteRemoveFromQueue()) { diff --git a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java index 460dfdb3e..da323af76 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java @@ -33,7 +33,7 @@ public class QueueFragmentTest { public void setUp() { EspressoTestUtils.clearPreferences(); EspressoTestUtils.clearDatabase(); - EspressoTestUtils.setLastNavFragment(QueueFragment.TAG); + EspressoTestUtils.setLaunchScreen(QueueFragment.TAG); activityRule.launchActivity(new Intent()); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 5e570828c..d453af615 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -16,11 +16,9 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; import androidx.drawerlayout.widget.DrawerLayout; @@ -88,7 +86,6 @@ public class MainActivity extends CastEnabledActivity { private @Nullable ActionBarDrawerToggle drawerToggle; private View navDrawer; private LockableBottomSheetBehavior sheetBehavior; - private long lastBackButtonPressTime = 0; private RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool(); private int lastTheme = 0; @@ -117,17 +114,21 @@ public class MainActivity extends CastEnabledActivity { final FragmentManager fm = getSupportFragmentManager(); if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) { - String lastFragment = NavDrawerFragment.getLastNavFragment(this); - if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) { - loadFragment(lastFragment, null); + if (!UserPreferences.DEFAULT_PAGE_REMEMBER.equals(UserPreferences.getDefaultPage())) { + loadFragment(UserPreferences.getDefaultPage(), null); } else { - try { - loadFeedFragmentById(Integer.parseInt(lastFragment), null); - } catch (NumberFormatException e) { - // it's not a number, this happens if we removed - // a label from the NAV_DRAWER_TAGS - // give them a nice default... - loadFragment(QueueFragment.TAG, null); + String lastFragment = NavDrawerFragment.getLastNavFragment(this); + if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) { + loadFragment(lastFragment, null); + } else { + try { + loadFeedFragmentById(Integer.parseInt(lastFragment), null); + } catch (NumberFormatException e) { + // it's not a number, this happens if we removed + // a label from the NAV_DRAWER_TAGS + // give them a nice default... + loadFragment(HomeFragment.TAG, null); + } } } } @@ -467,36 +468,12 @@ public class MainActivity extends CastEnabledActivity { } else if (getSupportFragmentManager().getBackStackEntryCount() != 0) { super.onBackPressed(); } else { - switch (UserPreferences.getBackButtonBehavior()) { - case OPEN_DRAWER: - if (drawerLayout != null) { // Tablet layout does not have drawer - drawerLayout.openDrawer(navDrawer); - } - break; - case SHOW_PROMPT: - new AlertDialog.Builder(this) - .setMessage(R.string.close_prompt) - .setPositiveButton(R.string.yes, (dialogInterface, i) -> MainActivity.super.onBackPressed()) - .setNegativeButton(R.string.no, null) - .setCancelable(false) - .show(); - break; - case DOUBLE_TAP: - if (lastBackButtonPressTime < System.currentTimeMillis() - 2000) { - Toast.makeText(this, R.string.double_tap_toast, Toast.LENGTH_SHORT).show(); - lastBackButtonPressTime = System.currentTimeMillis(); - } else { - super.onBackPressed(); - } - break; - case GO_TO_PAGE: - if (NavDrawerFragment.getLastNavFragment(this).equals(UserPreferences.getBackButtonGoToPage())) { - super.onBackPressed(); - } else { - loadFragment(UserPreferences.getBackButtonGoToPage(), null); - } - break; - default: super.onBackPressed(); + String toPage = UserPreferences.getDefaultPage(); + if (NavDrawerFragment.getLastNavFragment(this).equals(toPage) + || UserPreferences.DEFAULT_PAGE_REMEMBER.equals(toPage)) { + super.onBackPressed(); + } else { + loadFragment(toPage, null); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java index 4ebfcff5b..385360790 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java @@ -52,6 +52,7 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription private List<NavDrawerData.DrawerItem> listItems; private NavDrawerData.DrawerItem selectedItem = null; int longPressedPosition = 0; // used to init actionMode + private int dummyViews = 0; public SubscriptionsRecyclerAdapter(MainActivity mainActivity) { super(mainActivity); @@ -95,6 +96,11 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription @Override public void onBindViewHolder(@NonNull SubscriptionViewHolder holder, int position) { + if (position >= listItems.size()) { + holder.selectView.setVisibility(View.GONE); + holder.bindDummy(); + return; + } NavDrawerData.DrawerItem drawerItem = listItems.get(position); boolean isFeed = drawerItem.type == NavDrawerData.DrawerItem.Type.FEED; holder.bind(drawerItem); @@ -157,11 +163,14 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription @Override public int getItemCount() { - return listItems.size(); + return listItems.size() + dummyViews; } @Override public long getItemId(int position) { + if (position >= listItems.size()) { + return RecyclerView.NO_ID; // Dummy views + } return listItems.get(position).id; } @@ -202,6 +211,10 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription return items; } + public void setDummyViews(int dummyViews) { + this.dummyViews = dummyViews; + } + public void setItems(List<NavDrawerData.DrawerItem> listItems) { this.listItems = listItems; } @@ -270,6 +283,17 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription .load(); } } + + public void bindDummy() { + feedTitle.setText("███████"); + feedTitle.setVisibility(View.VISIBLE); + count.setVisibility(View.GONE); + new CoverLoader(mainActivityRef.get()) + .withResource(android.R.color.transparent) + .withPlaceholderView(feedTitle, false) + .withCoverView(imageView) + .load(); + } } public static float convertDpToPixel(Context context, float dp) { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/DrawerPreferencesDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/DrawerPreferencesDialog.java new file mode 100644 index 000000000..9fa9e583d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/DrawerPreferencesDialog.java @@ -0,0 +1,50 @@ +package de.danoeh.antennapod.dialog; + +import android.content.Context; +import androidx.appcompat.app.AlertDialog; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.fragment.NavDrawerFragment; + +import java.util.List; + +public class DrawerPreferencesDialog { + public static void show(Context context, Runnable callback) { + final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); + final String[] navTitles = context.getResources().getStringArray(R.array.nav_drawer_titles); + boolean[] checked = new boolean[NavDrawerFragment.NAV_DRAWER_TAGS.length]; + for (int i = 0; i < NavDrawerFragment.NAV_DRAWER_TAGS.length; i++) { + String tag = NavDrawerFragment.NAV_DRAWER_TAGS[i]; + if (!hiddenDrawerItems.contains(tag)) { + checked[i] = true; + } + } + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.drawer_preferences); + builder.setMultiChoiceItems(navTitles, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NavDrawerFragment.NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NavDrawerFragment.NAV_DRAWER_TAGS[which]); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); + + if (hiddenDrawerItems.contains(UserPreferences.getDefaultPage())) { + for (String tag : NavDrawerFragment.NAV_DRAWER_TAGS) { + if (!hiddenDrawerItems.contains(tag)) { + UserPreferences.setDefaultPage(tag); + break; + } + } + } + + if (callback != null) { + callback.run(); + } + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java index fd0a32e03..6849e641b 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java @@ -60,13 +60,15 @@ public class SwipeActionsDialog { switch (tag) { case InboxFragment.TAG: forFragment = context.getString(R.string.inbox_label); - keys = Stream.of(keys).filter(a -> !a.getId().equals(SwipeAction.TOGGLE_PLAYED)).toList(); + keys = Stream.of(keys).filter(a -> !a.getId().equals(SwipeAction.TOGGLE_PLAYED) + && !a.getId().equals(SwipeAction.DELETE)).toList(); break; case AllEpisodesFragment.TAG: forFragment = context.getString(R.string.episodes_label); break; case CompletedDownloadsFragment.TAG: forFragment = context.getString(R.string.downloads_label); + keys = Stream.of(keys).filter(a -> !a.getId().equals(SwipeAction.REMOVE_FROM_INBOX)).toList(); break; case FeedItemlistFragment.TAG: forFragment = context.getString(R.string.feeds_label); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index b4e4bebb9..b781659dc 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.fragment; +import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; @@ -7,7 +8,6 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; @@ -46,6 +46,7 @@ import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -56,12 +57,12 @@ public class CompletedDownloadsFragment extends Fragment public static final String TAG = "DownloadsFragment"; public static final String ARG_SHOW_LOGS = "show_logs"; private static final String KEY_UP_ARROW = "up_arrow"; + private static final String PREF_PREVIOUS_EPISODE_COUNT = "episodeCount"; private long[] runningDownloads = new long[0]; private List<FeedItem> items = new ArrayList<>(); private CompletedDownloadsListAdapter adapter; private EpisodeItemListRecyclerView recyclerView; - private ProgressBar progressBar; private Disposable disposable; private EmptyViewHandler emptyView; private boolean displayUpArrow; @@ -91,11 +92,12 @@ public class CompletedDownloadsFragment extends Fragment recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity()); adapter.setOnSelectModeListener(this); + int previousEpisodesCount = getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE) + .getInt(PREF_PREVIOUS_EPISODE_COUNT, 5); + adapter.setDummyViews(Math.max(1, previousEpisodesCount)); recyclerView.setAdapter(adapter); swipeActions = new SwipeActions(this, TAG).attachTo(recyclerView); swipeActions.setFilter(new FeedItemFilter(FeedItemFilter.DOWNLOADED)); - progressBar = root.findViewById(R.id.progLoading); - progressBar.setVisibility(View.VISIBLE); speedDialView = root.findViewById(R.id.fabSD); speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); @@ -104,6 +106,7 @@ public class CompletedDownloadsFragment extends Fragment speedDialView.removeActionItemById(R.id.mark_read_batch); speedDialView.removeActionItemById(R.id.mark_unread_batch); speedDialView.removeActionItemById(R.id.remove_from_queue_batch); + speedDialView.removeActionItemById(R.id.remove_all_inbox_item); speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() { @Override public boolean onMainActionSelected() { @@ -159,6 +162,9 @@ public class CompletedDownloadsFragment extends Fragment if (disposable != null) { disposable.dispose(); } + getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE).edit() + .putInt(PREF_PREVIOUS_EPISODE_COUNT, adapter.getItemCount()) + .apply(); } @Override @@ -291,11 +297,16 @@ public class CompletedDownloadsFragment extends Fragment }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - items = result; - adapter.updateItems(result); - progressBar.setVisibility(View.GONE); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe( + result -> { + items = result; + adapter.setDummyViews(0); + adapter.updateItems(result); + }, error -> { + adapter.setDummyViews(0); + adapter.updateItems(Collections.emptyList()); + Log.e(TAG, Log.getStackTraceString(error)); + }); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 5b030f0c2..94a697893 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -6,116 +6,81 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.ClipData; import android.content.ClipboardManager; -import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Space; -import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.core.graphics.BlendModeColorFilterCompat; import androidx.core.graphics.BlendModeCompat; import androidx.fragment.app.Fragment; - import com.bumptech.glide.Glide; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; - -import org.apache.commons.lang3.StringUtils; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; -import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.DateFormatter; -import de.danoeh.antennapod.model.feed.EmbeddedChapterImage; import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.databinding.CoverFragmentBinding; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.model.feed.Chapter; +import de.danoeh.antennapod.model.feed.EmbeddedChapterImage; +import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.Playable; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT; +import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; /** * Displays the cover and the title of a FeedItem. */ public class CoverFragment extends Fragment { - private static final String TAG = "CoverFragment"; - static final double SIXTEEN_BY_NINE = 1.7; - - private View root; - private TextView txtvPodcastTitle; - private TextView txtvEpisodeTitle; - private ImageView imgvCover; - private LinearLayout openDescription; - private Space counterweight; - private Space spacer; - private ImageButton butPrevChapter; - private ImageButton butNextChapter; - private LinearLayout episodeDetails; - private LinearLayout chapterControl; + private CoverFragmentBinding viewBinding; private PlaybackController controller; private Disposable disposable; private int displayedChapterIndex = -1; private Playable media; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - setRetainInstance(true); - root = inflater.inflate(R.layout.cover_fragment, container, false); - txtvPodcastTitle = root.findViewById(R.id.txtvPodcastTitle); - txtvEpisodeTitle = root.findViewById(R.id.txtvEpisodeTitle); - imgvCover = root.findViewById(R.id.imgvCover); - episodeDetails = root.findViewById(R.id.episode_details); - final ImageView descriptionIcon = root.findViewById(R.id.description_icon); - chapterControl = root.findViewById(R.id.chapterButton); - butPrevChapter = root.findViewById(R.id.butPrevChapter); - butNextChapter = root.findViewById(R.id.butNextChapter); - - imgvCover.setOnClickListener(v -> onPlayPause()); - openDescription = root.findViewById(R.id.openDescription); - counterweight = root.findViewById(R.id.counterweight); - spacer = root.findViewById(R.id.details_spacer); - View.OnClickListener scrollToDesc = view -> - ((AudioPlayerFragment) requireParentFragment()).scrollToPage(AudioPlayerFragment.POS_DESCRIPTION, true); - openDescription.setOnClickListener(scrollToDesc); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + viewBinding = CoverFragmentBinding.inflate(inflater); + viewBinding.imgvCover.setOnClickListener(v -> onPlayPause()); + viewBinding.openDescription.setOnClickListener(view -> ((AudioPlayerFragment) requireParentFragment()) + .scrollToPage(AudioPlayerFragment.POS_DESCRIPTION, true)); ColorFilter colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat( - txtvPodcastTitle.getCurrentTextColor(), BlendModeCompat.SRC_IN); - butNextChapter.setColorFilter(colorFilter); - butPrevChapter.setColorFilter(colorFilter); - descriptionIcon.setColorFilter(colorFilter); - chapterControl.setOnClickListener(v -> + viewBinding.txtvPodcastTitle.getCurrentTextColor(), BlendModeCompat.SRC_IN); + viewBinding.butNextChapter.setColorFilter(colorFilter); + viewBinding.butPrevChapter.setColorFilter(colorFilter); + viewBinding.descriptionIcon.setColorFilter(colorFilter); + viewBinding.chapterButton.setOnClickListener(v -> new ChaptersFragment().show(getChildFragmentManager(), ChaptersFragment.TAG)); - butPrevChapter.setOnClickListener(v -> seekToPrevChapter()); - butNextChapter.setOnClickListener(v -> seekToNextChapter()); - - return root; + viewBinding.butPrevChapter.setOnClickListener(v -> seekToPrevChapter()); + viewBinding.butNextChapter.setOnClickListener(v -> seekToNextChapter()); + return viewBinding.getRoot(); } @Override @@ -150,7 +115,7 @@ public class CoverFragment extends Fragment { private void displayMediaInfo(@NonNull Playable media) { String pubDateStr = DateFormatter.formatAbbrev(getActivity(), media.getPubDate()); - txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle()) + viewBinding.txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle()) + "\u00A0" + "・" + "\u00A0" @@ -158,33 +123,35 @@ public class CoverFragment extends Fragment { if (media instanceof FeedMedia) { Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), ((FeedMedia) media).getItem().getFeedId()); - txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + viewBinding.txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); } else { - txtvPodcastTitle.setOnClickListener(null); + viewBinding.txtvPodcastTitle.setOnClickListener(null); } - txtvPodcastTitle.setOnLongClickListener(v -> copyText(media.getFeedTitle())); - txtvEpisodeTitle.setText(media.getEpisodeTitle()); - txtvEpisodeTitle.setOnLongClickListener(v -> copyText(media.getEpisodeTitle())); - txtvEpisodeTitle.setOnClickListener(v -> { - int lines = txtvEpisodeTitle.getLineCount(); + viewBinding.txtvPodcastTitle.setOnLongClickListener(v -> copyText(media.getFeedTitle())); + viewBinding.txtvEpisodeTitle.setText(media.getEpisodeTitle()); + viewBinding.txtvEpisodeTitle.setOnLongClickListener(v -> copyText(media.getEpisodeTitle())); + viewBinding.txtvEpisodeTitle.setOnClickListener(v -> { + int lines = viewBinding.txtvEpisodeTitle.getLineCount(); int animUnit = 1500; - if (lines > txtvEpisodeTitle.getMaxLines()) { + if (lines > viewBinding.txtvEpisodeTitle.getMaxLines()) { + int titleHeight = viewBinding.txtvEpisodeTitle.getHeight() + - viewBinding.txtvEpisodeTitle.getPaddingTop() + - viewBinding.txtvEpisodeTitle.getPaddingBottom(); ObjectAnimator verticalMarquee = ObjectAnimator.ofInt( - txtvEpisodeTitle, "scrollY", 0, (lines - txtvEpisodeTitle.getMaxLines()) * ( - (txtvEpisodeTitle.getHeight() - txtvEpisodeTitle.getPaddingTop() - - txtvEpisodeTitle.getPaddingBottom()) / txtvEpisodeTitle.getMaxLines())) + viewBinding.txtvEpisodeTitle, "scrollY", 0, (lines - viewBinding.txtvEpisodeTitle.getMaxLines()) + * (titleHeight / viewBinding.txtvEpisodeTitle.getMaxLines())) .setDuration(lines * animUnit); ObjectAnimator fadeOut = ObjectAnimator.ofFloat( - txtvEpisodeTitle, "alpha", 0); + viewBinding.txtvEpisodeTitle, "alpha", 0); fadeOut.setStartDelay(animUnit); fadeOut.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - txtvEpisodeTitle.scrollTo(0, 0); + viewBinding.txtvEpisodeTitle.scrollTo(0, 0); } }); ObjectAnimator fadeBackIn = ObjectAnimator.ofFloat( - txtvEpisodeTitle, "alpha", 1); + viewBinding.txtvEpisodeTitle, "alpha", 1); AnimatorSet set = new AnimatorSet(); set.playSequentially(verticalMarquee, fadeOut, fadeBackIn); set.start(); @@ -206,9 +173,9 @@ public class CoverFragment extends Fragment { chapterControlVisible = fm.getItem() != null && fm.getItem().hasChapters(); } int newVisibility = chapterControlVisible ? View.VISIBLE : View.GONE; - if (chapterControl.getVisibility() != newVisibility) { - chapterControl.setVisibility(newVisibility); - ObjectAnimator.ofFloat(chapterControl, + if (viewBinding.chapterButton.getVisibility() != newVisibility) { + viewBinding.chapterButton.setVisibility(newVisibility); + ObjectAnimator.ofFloat(viewBinding.chapterButton, "alpha", chapterControlVisible ? 0 : 1, chapterControlVisible ? 1 : 0) @@ -220,10 +187,10 @@ public class CoverFragment extends Fragment { if (chapterIndex > -1) { if (media.getPosition() > media.getDuration() || chapterIndex >= media.getChapters().size() - 1) { displayedChapterIndex = media.getChapters().size() - 1; - butNextChapter.setVisibility(View.INVISIBLE); + viewBinding.butNextChapter.setVisibility(View.INVISIBLE); } else { displayedChapterIndex = chapterIndex; - butNextChapter.setVisibility(View.VISIBLE); + viewBinding.butNextChapter.setVisibility(View.VISIBLE); } } @@ -266,13 +233,6 @@ public class CoverFragment extends Fragment { } @Override - public void onDestroy() { - super.onDestroy(); - // prevent memory leaks - root = null; - } - - @Override public void onStart() { super.onStart(); controller = new PlaybackController(getActivity()) { @@ -313,91 +273,51 @@ public class CoverFragment extends Fragment { .transform(new FitCenter(), new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))); - RequestBuilder<Drawable> cover = Glide.with(this) - .load(media.getImageLocation()) - .error(Glide.with(this) - .load(ImageResourceUtils.getFallbackImageLocation(media)) - .apply(options)) - .apply(options); + RequestBuilder<Drawable> cover = Glide.with(this) + .load(media.getImageLocation()) + .error(Glide.with(this) + .load(ImageResourceUtils.getFallbackImageLocation(media)) + .apply(options)) + .apply(options); if (displayedChapterIndex == -1 || media == null || media.getChapters() == null || TextUtils.isEmpty(media.getChapters().get(displayedChapterIndex).getImageUrl())) { - cover.into(imgvCover); + cover.into(viewBinding.imgvCover); } else { Glide.with(this) .load(EmbeddedChapterImage.getModelFor(media, displayedChapterIndex)) .apply(options) .thumbnail(cover) .error(cover) - .into(imgvCover); + .into(viewBinding.imgvCover); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - configureForOrientation(newConfig); } - public float convertDpToPixel(float dp) { - Context context = this.getActivity().getApplicationContext(); - return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); - } - private void configureForOrientation(Configuration newConfig) { - LinearLayout mainContainer = getView().findViewById(R.id.cover_fragment); - LinearLayout textContainer = getView().findViewById(R.id.cover_fragment_text_container); + boolean isPortrait = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT; - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) imgvCover.getLayoutParams(); - LinearLayout.LayoutParams textParams = (LinearLayout.LayoutParams) textContainer.getLayoutParams(); - double ratio = (float) newConfig.screenHeightDp / (float) newConfig.screenWidthDp; + viewBinding.coverFragment.setOrientation(isPortrait ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); - boolean spacerVisible = true; - ViewGroup detailsParent = (ViewGroup) getView(); - int detailsWidth = ViewGroup.LayoutParams.MATCH_PARENT; - - if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - double percentageWidth = 0.8; - if (ratio <= SIXTEEN_BY_NINE) { - percentageWidth = (ratio / SIXTEEN_BY_NINE) * percentageWidth * 0.8; - } - mainContainer.setOrientation(LinearLayout.VERTICAL); - if (newConfig.screenWidthDp > 0) { - params.width = (int) (convertDpToPixel(newConfig.screenWidthDp) * percentageWidth); - params.height = params.width; - textParams.weight = 0; - imgvCover.setLayoutParams(params); - } + if (isPortrait) { + viewBinding.coverHolder.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1)); + viewBinding.coverFragmentTextContainer.setLayoutParams( + new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } else { - double percentageHeight = ratio * 0.6; - mainContainer.setOrientation(LinearLayout.HORIZONTAL); - if (newConfig.screenHeightDp > 0) { - params.height = (int) (convertDpToPixel(newConfig.screenHeightDp) * percentageHeight); - params.width = params.height; - textParams.weight = 1; - imgvCover.setLayoutParams(params); - } - - spacerVisible = false; - detailsParent = textContainer; - detailsWidth = ViewGroup.LayoutParams.WRAP_CONTENT; + viewBinding.coverHolder.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, 1)); + viewBinding.coverFragmentTextContainer.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, 1)); } - if (displayedChapterIndex == -1) { - detailsWidth = ViewGroup.LayoutParams.WRAP_CONTENT; - } - - spacer.setVisibility(spacerVisible ? View.VISIBLE : View.GONE); - counterweight.setVisibility(spacerVisible ? View.VISIBLE : View.GONE); - LinearLayout.LayoutParams wrapHeight = - new LinearLayout.LayoutParams(detailsWidth, ViewGroup.LayoutParams.WRAP_CONTENT); - episodeDetails.setLayoutParams(wrapHeight); - getView().findViewById(R.id.vertical_divider).setVisibility(spacerVisible ? View.GONE : View.VISIBLE); - - if (episodeDetails.getParent() != detailsParent) { - ((ViewGroup) episodeDetails.getParent()).removeView(episodeDetails); - detailsParent.addView(episodeDetails); + ((ViewGroup) viewBinding.episodeDetails.getParent()).removeView(viewBinding.episodeDetails); + if (isPortrait) { + viewBinding.coverFragment.addView(viewBinding.episodeDetails); + } else { + viewBinding.coverFragmentTextContainer.addView(viewBinding.episodeDetails); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 03764f61c..d8b05d207 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.fragment; +import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; @@ -11,7 +12,6 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; @@ -56,6 +56,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -65,16 +66,15 @@ public abstract class EpisodesListFragment extends Fragment implements EpisodeItemListAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener { public static final String TAG = "EpisodesListFragment"; private static final String KEY_UP_ARROW = "up_arrow"; + private static final String PREF_PREVIOUS_EPISODE_COUNT = "episodeCount"; protected static final int EPISODES_PER_PAGE = 150; protected int page = 1; protected boolean isLoadingMore = false; - protected boolean hasMoreItems = true; + protected boolean hasMoreItems = false; private boolean displayUpArrow; EpisodeItemListRecyclerView recyclerView; EpisodeItemListAdapter listAdapter; - ProgressBar progLoading; - View loadingMoreView; EmptyViewHandler emptyView; SpeedDialView speedDialView; Toolbar toolbar; @@ -113,6 +113,9 @@ public abstract class EpisodesListFragment extends Fragment if (disposable != null) { disposable.dispose(); } + getContext().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE).edit() + .putInt(PREF_PREVIOUS_EPISODE_COUNT, episodes.size()) + .apply(); } @Override @@ -187,17 +190,28 @@ public abstract class EpisodesListFragment extends Fragment getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms)); }); - progLoading = root.findViewById(R.id.progLoading); - progLoading.setVisibility(View.VISIBLE); - loadingMoreView = root.findViewById(R.id.loadingMore); + listAdapter = new EpisodeItemListAdapter((MainActivity) getActivity()) { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if (!inActionMode()) { + menu.findItem(R.id.multi_select).setVisible(true); + } + MenuItemUtils.setOnClickListeners(menu, EpisodesListFragment.this::onContextItemSelected); + } + }; + listAdapter.setOnSelectModeListener(this); + int previousEpisodesCount = getContext().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE) + .getInt(PREF_PREVIOUS_EPISODE_COUNT, 5); + listAdapter.setDummyViews(Math.max(1, previousEpisodesCount)); + recyclerView.setAdapter(listAdapter); emptyView = new EmptyViewHandler(getContext()); emptyView.attachToRecyclerView(recyclerView); emptyView.setIcon(R.drawable.ic_feed); emptyView.setTitle(R.string.no_all_episodes_head_label); emptyView.setMessage(R.string.no_all_episodes_label); - - createRecycleAdapter(recyclerView, emptyView); + emptyView.updateAdapter(listAdapter); emptyView.hide(); speedDialView = root.findViewById(R.id.fabSD); @@ -286,68 +300,36 @@ public abstract class EpisodesListFragment extends Fragment disposable.dispose(); } isLoadingMore = true; - loadingMoreView.setVisibility(View.VISIBLE); + listAdapter.setDummyViews(1); + listAdapter.notifyItemInserted(listAdapter.getItemCount() - 1); disposable = Observable.fromCallable(() -> loadMoreData(page)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(data -> { - if (data.size() < EPISODES_PER_PAGE) { - hasMoreItems = false; - } - episodes.addAll(data); - updateAdapterWithNewItems(); - if (listAdapter.shouldSelectLazyLoadedItems()) { - listAdapter.setSelected(episodes.size() - data.size(), episodes.size(), true); - } - }, error -> Log.e(TAG, Log.getStackTraceString(error)), - () -> { - recyclerView.post(() -> isLoadingMore = false); // Make sure to not always load 2 pages at once - progLoading.setVisibility(View.GONE); - loadingMoreView.setVisibility(View.GONE); - }); - } - - protected void updateAdapterWithNewItems() { - boolean restoreScrollPosition = listAdapter.getItemCount() == 0; - if (episodes.size() == 0) { - createRecycleAdapter(recyclerView, emptyView); - } else { - listAdapter.updateItems(episodes); - } - if (restoreScrollPosition) { - recyclerView.restoreScrollPosition(getPrefName()); - } - } - - /** - * Currently, we need to recreate the list adapter in order to be able to undo last item via the - * snackbar. See #3084 for details. - */ - private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) { - MainActivity mainActivity = (MainActivity) getActivity(); - listAdapter = new EpisodeItemListAdapter(mainActivity) { - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - if (!inActionMode()) { - menu.findItem(R.id.multi_select).setVisible(true); - } - MenuItemUtils.setOnClickListeners(menu, EpisodesListFragment.this::onContextItemSelected); - } - }; - listAdapter.setOnSelectModeListener(this); - listAdapter.updateItems(episodes); - recyclerView.setAdapter(listAdapter); - emptyViewHandler.updateAdapter(listAdapter); + .subscribe( + data -> { + if (data.size() < EPISODES_PER_PAGE) { + hasMoreItems = false; + } + episodes.addAll(data); + listAdapter.setDummyViews(0); + listAdapter.updateItems(episodes); + if (listAdapter.shouldSelectLazyLoadedItems()) { + listAdapter.setSelected(episodes.size() - data.size(), episodes.size(), true); + } + }, error -> { + listAdapter.setDummyViews(0); + listAdapter.updateItems(Collections.emptyList()); + Log.e(TAG, Log.getStackTraceString(error)); + }, () -> { + // Make sure to not always load 2 pages at once + recyclerView.post(() -> isLoadingMore = false); + }); } @Override public void onDestroyView() { super.onDestroyView(); - if (listAdapter != null) { - listAdapter.endSelectMode(); - } - listAdapter = null; + listAdapter.endSelectMode(); } @Override @@ -380,13 +362,11 @@ public abstract class EpisodesListFragment extends Fragment @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlaybackPositionEvent event) { - if (listAdapter != null) { - for (int i = 0; i < listAdapter.getItemCount(); i++) { - EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); - if (holder != null && holder.isCurrentlyPlayingItem()) { - holder.notifyPlaybackPositionUpdated(event); - break; - } + for (int i = 0; i < listAdapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; } } } @@ -445,16 +425,20 @@ public abstract class EpisodesListFragment extends Fragment disposable = Observable.fromCallable(() -> new Pair<>(loadData(), loadTotalItemCount())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(data -> { - progLoading.setVisibility(View.GONE); - loadingMoreView.setVisibility(View.GONE); - hasMoreItems = true; - episodes = data.first; - listAdapter.notifyDataSetChanged(); - listAdapter.setTotalNumberOfItems(data.second); - updateAdapterWithNewItems(); - updateToolbar(); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe( + data -> { + episodes = data.first; + hasMoreItems = !(page == 1 && episodes.size() < EPISODES_PER_PAGE); + listAdapter.setDummyViews(0); + listAdapter.updateItems(episodes); + listAdapter.setTotalNumberOfItems(data.second); + recyclerView.restoreScrollPosition(getPrefName()); + updateToolbar(); + }, error -> { + listAdapter.setDummyViews(0); + listAdapter.updateItems(Collections.emptyList()); + Log.e(TAG, Log.getStackTraceString(error)); + }); } @NonNull diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index dfae22491..87a9516bb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -74,6 +74,7 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.Collections; import java.util.List; /** @@ -142,7 +143,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem updateToolbar(); viewBinding.recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); - viewBinding.progLoading.setVisibility(View.VISIBLE); + adapter = new FeedItemListAdapter((MainActivity) getActivity()); + adapter.setOnSelectModeListener(this); + adapter.setDummyViews(10); + viewBinding.recyclerView.setAdapter(adapter); + swipeActions = new SwipeActions(this, TAG).attachTo(viewBinding.recyclerView); + ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager( getContext(), viewBinding.toolbar, viewBinding.collapsingToolbar) { @Override @@ -221,10 +227,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (disposable != null) { disposable.dispose(); } - if (adapter != null) { - adapter.endSelectMode(); - } - adapter = null; + adapter.endSelectMode(); } @Override @@ -294,9 +297,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (adapter == null) { - return; - } MainActivity activity = (MainActivity) getActivity(); long[] ids = FeedItemUtil.getIds(feed.getItems()); activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); @@ -315,9 +315,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); if (feed == null || feed.getItems() == null) { return; - } else if (adapter == null) { - loadItems(); - return; } for (int i = 0, size = event.items.size(); i < size; i++) { FeedItem item = event.items.get(i); @@ -335,7 +332,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; updateToolbar(); - if (adapter != null && update.mediaIds.length > 0 && feed != null) { + if (update.mediaIds.length > 0 && feed != null) { for (long mediaId : update.mediaIds) { int pos = FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId); if (pos >= 0) { @@ -347,14 +344,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(PlaybackPositionEvent event) { - if (adapter != null) { - for (int i = 0; i < adapter.getItemCount(); i++) { - EpisodeItemViewHolder holder = (EpisodeItemViewHolder) - viewBinding.recyclerView.findViewHolderForAdapterPosition(i); - if (holder != null && holder.isCurrentlyPlayingItem()) { - holder.notifyPlaybackPositionUpdated(event); - break; - } + for (int i = 0; i < adapter.getItemCount(); i++) { + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) + viewBinding.recyclerView.findViewHolderForAdapterPosition(i); + if (holder != null && holder.isCurrentlyPlayingItem()) { + holder.notifyPlaybackPositionUpdated(event); + break; } } } @@ -376,6 +371,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem speedDialBinding.fabSD.removeActionItemById(R.id.download_batch); speedDialBinding.fabSD.removeActionItemById(R.id.delete_batch); } + speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item); speedDialBinding.fabSD.setVisibility(View.VISIBLE); updateToolbar(); } @@ -417,28 +413,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem nextPageLoader.setLoadingState(DownloadService.isDownloadingFeeds()); } - private void displayList() { - if (getView() == null) { - Log.e(TAG, "Required root view is not yet created. Stop binding data to UI."); - return; - } - if (adapter == null) { - viewBinding.recyclerView.setAdapter(null); - adapter = new FeedItemListAdapter((MainActivity) getActivity()); - adapter.setOnSelectModeListener(this); - viewBinding.recyclerView.setAdapter(adapter); - swipeActions = new SwipeActions(this, TAG).attachTo(viewBinding.recyclerView); - } - viewBinding.progLoading.setVisibility(View.GONE); - if (feed != null) { - adapter.updateItems(feed.getItems()); - swipeActions.setFilter(feed.getItemFilter()); - } - - updateToolbar(); - updateSyncProgressBarVisibility(); - } - private void refreshHeaderView() { setupHeaderView(); if (viewBinding == null || feed == null) { @@ -557,12 +531,19 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem .subscribe( result -> { feed = result; + swipeActions.setFilter(feed.getItemFilter()); refreshHeaderView(); - displayList(); + adapter.setDummyViews(0); + adapter.updateItems(feed.getItems()); + updateToolbar(); + updateSyncProgressBarVisibility(); }, error -> { feed = null; refreshHeaderView(); - displayList(); + adapter.setDummyViews(0); + adapter.updateItems(Collections.emptyList()); + updateToolbar(); + updateSyncProgressBarVisibility(); Log.e(TAG, Log.getStackTraceString(error)); }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index 7d8cadd02..8339b050c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -16,7 +16,6 @@ import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; import androidx.core.util.Pair; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -32,6 +31,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.NavDrawerData; +import de.danoeh.antennapod.dialog.DrawerPreferencesDialog; import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.RenameItemDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; @@ -219,35 +219,6 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS loadData(); } - private void showDrawerPreferencesDialog() { - final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); - String[] navLabels = new String[NAV_DRAWER_TAGS.length]; - final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length]; - for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) { - String tag = NAV_DRAWER_TAGS[i]; - navLabels[i] = navAdapter.getLabel(tag); - if (!hiddenDrawerItems.contains(tag)) { - checked[i] = true; - } - } - - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { - if (isChecked) { - hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); - } else { - hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); - } - }); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); - navAdapter.notifyDataSetChanged(); // Update selection - }); - builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - } - private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { @Override public int getCount() { @@ -367,7 +338,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS @Override public boolean onItemLongClick(int position) { if (position < navAdapter.getFragmentTags().size()) { - showDrawerPreferencesDialog(); + DrawerPreferencesDialog.show(getContext(), () -> navAdapter.notifyDataSetChanged()); return true; } else { contextPressedItem = flatItemList.get(position - navAdapter.getSubscriptionOffset()); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 467299e4c..70378a4a9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -14,7 +14,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; -import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -24,10 +23,8 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; @@ -35,26 +32,26 @@ import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.event.FeedItemEvent; -import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; -import de.danoeh.antennapod.event.PlayerStatusEvent; -import de.danoeh.antennapod.event.QueueEvent; -import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; -import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; -import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; -import de.danoeh.antennapod.fragment.swipeactions.SwipeActions; -import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; +import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.model.feed.FeedItemFilter; -import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; +import de.danoeh.antennapod.fragment.swipeactions.SwipeActions; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.model.feed.SortOrder; import de.danoeh.antennapod.view.EmptyViewHandler; import de.danoeh.antennapod.view.EpisodeItemListRecyclerView; import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; @@ -81,7 +78,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi private EpisodeItemListRecyclerView recyclerView; private QueueRecyclerAdapter recyclerAdapter; private EmptyViewHandler emptyView; - private ProgressBar progLoading; private Toolbar toolbar; private boolean displayUpArrow; @@ -89,6 +85,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi private static final String PREFS = "QueueFragment"; private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning"; + private static final String PREF_PREVIOUS_EPISODE_COUNT = "episodeCount"; private Disposable disposable; private SwipeActions swipeActions; @@ -107,7 +104,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi public void onStart() { super.onStart(); if (queue != null) { - onFragmentLoaded(true); + recyclerView.restoreScrollPosition(QueueFragment.TAG); } loadItems(true); EventBus.getDefault().register(this); @@ -126,6 +123,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi if (disposable != null) { disposable.dispose(); } + prefs.edit().putInt(PREF_PREVIOUS_EPISODE_COUNT, queue.size()).apply(); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -161,7 +159,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi return; } recyclerView.saveScrollPosition(QueueFragment.TAG); - onFragmentLoaded(false); + refreshInfoBar(); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -456,6 +454,17 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); registerForContextMenu(recyclerView); + recyclerAdapter = new QueueRecyclerAdapter((MainActivity) getActivity(), swipeActions) { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuItemUtils.setOnClickListeners(menu, QueueFragment.this::onContextItemSelected); + } + }; + recyclerAdapter.setOnSelectModeListener(this); + recyclerAdapter.setDummyViews(Math.max(1, prefs.getInt(PREF_PREVIOUS_EPISODE_COUNT, 5))); + recyclerView.setAdapter(recyclerAdapter); + SwipeRefreshLayout swipeRefreshLayout = root.findViewById(R.id.swipeRefresh); swipeRefreshLayout.setDistanceToTriggerSync(getResources().getInteger(R.integer.swipe_refresh_distance)); swipeRefreshLayout.setOnRefreshListener(() -> { @@ -473,9 +482,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi emptyView.setIcon(R.drawable.ic_playlist_play); emptyView.setTitle(R.string.no_items_header_label); emptyView.setMessage(R.string.no_items_label); - - progLoading = root.findViewById(R.id.progLoading); - progLoading.setVisibility(View.VISIBLE); + emptyView.updateAdapter(recyclerAdapter); speedDialView = root.findViewById(R.id.fabSD); speedDialView.setOverlayLayout(root.findViewById(R.id.fabSDOverlay)); @@ -483,6 +490,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi speedDialView.removeActionItemById(R.id.mark_read_batch); speedDialView.removeActionItemById(R.id.mark_unread_batch); speedDialView.removeActionItemById(R.id.add_to_queue_batch); + speedDialView.removeActionItemById(R.id.remove_all_inbox_item); speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() { @Override public boolean onMainActionSelected() { @@ -513,38 +521,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi super.onSaveInstanceState(outState); } - private void onFragmentLoaded(final boolean restoreScrollPosition) { - if (queue != null) { - if (recyclerAdapter == null) { - MainActivity activity = (MainActivity) getActivity(); - recyclerAdapter = new QueueRecyclerAdapter(activity, swipeActions) { - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuItemUtils.setOnClickListeners(menu, QueueFragment.this::onContextItemSelected); - } - }; - recyclerAdapter.setOnSelectModeListener(this); - recyclerView.setAdapter(recyclerAdapter); - emptyView.updateAdapter(recyclerAdapter); - } - recyclerAdapter.updateItems(queue); - } else { - recyclerAdapter = null; - emptyView.updateAdapter(null); - } - - if (restoreScrollPosition) { - recyclerView.restoreScrollPosition(QueueFragment.TAG); - } - - // we need to refresh the options menu because it sometimes - // needs data that may have just been loaded. - refreshToolbarState(); - - refreshInfoBar(); - } - private void refreshInfoBar() { String info = String.format(Locale.getDefault(), "%d%s", queue.size(), getString(R.string.episodes_suffix)); @@ -574,18 +550,18 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi } if (queue == null) { emptyView.hide(); - progLoading.setVisibility(View.VISIBLE); } disposable = Observable.fromCallable(DBReader::getQueue) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(items -> { - progLoading.setVisibility(View.GONE); queue = items; - onFragmentLoaded(restoreScrollPosition); - if (recyclerAdapter != null) { - recyclerAdapter.notifyDataSetChanged(); + recyclerAdapter.setDummyViews(0); + recyclerAdapter.updateItems(queue); + if (restoreScrollPosition) { + recyclerView.restoreScrollPosition(QueueFragment.TAG); } + refreshInfoBar(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java index 439f8a2cc..f5bfed324 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.text.TextUtils; import android.util.DisplayMetrics; import androidx.fragment.app.Fragment; @@ -14,7 +15,6 @@ import android.widget.AdapterView; import android.widget.Button; import android.widget.GridView; import android.widget.LinearLayout; -import android.widget.ProgressBar; import android.widget.TextView; import de.danoeh.antennapod.net.discovery.ItunesTopListLoader; @@ -41,7 +41,6 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. private static final String TAG = "FeedDiscoveryFragment"; private static final int NUM_SUGGESTIONS = 12; - private ProgressBar progressBar; private Disposable disposable; private FeedDiscoverAdapter adapter; private GridView discoverGridLayout; @@ -59,7 +58,6 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. ((MainActivity) getActivity()).loadChildFragment(new DiscoveryFragment())); discoverGridLayout = root.findViewById(R.id.discover_grid); - progressBar = root.findViewById(R.id.discover_progress_bar); errorView = root.findViewById(R.id.discover_error); errorTextView = root.findViewById(R.id.discover_error_txtV); errorRetry = root.findViewById(R.id.discover_error_retry_btn); @@ -108,8 +106,6 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. } private void loadToplist() { - progressBar.setVisibility(View.VISIBLE); - discoverGridLayout.setVisibility(View.INVISIBLE); errorView.setVisibility(View.GONE); errorRetry.setVisibility(View.INVISIBLE); poweredByTextView.setVisibility(View.VISIBLE); @@ -121,7 +117,6 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. if (countryCode.equals(ItunesTopListLoader.DISCOVER_HIDE_FAKE_COUNTRY_CODE)) { errorTextView.setText(R.string.discover_is_hidden); errorView.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.GONE); discoverGridLayout.setVisibility(View.GONE); errorRetry.setVisibility(View.GONE); poweredByTextView.setVisibility(View.GONE); @@ -132,20 +127,18 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. .subscribe( podcasts -> { errorView.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - discoverGridLayout.setVisibility(View.VISIBLE); if (podcasts.size() == 0) { errorTextView.setText(getResources().getText(R.string.search_status_no_results)); errorView.setVisibility(View.VISIBLE); discoverGridLayout.setVisibility(View.INVISIBLE); } else { + discoverGridLayout.setVisibility(View.VISIBLE); adapter.updateData(podcasts); } }, error -> { Log.e(TAG, Log.getStackTraceString(error)); errorTextView.setText(error.getLocalizedMessage()); errorView.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.GONE); discoverGridLayout.setVisibility(View.INVISIBLE); errorRetry.setVisibility(View.VISIBLE); }); @@ -154,7 +147,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView. @Override public void onItemClick(AdapterView<?> parent, final View view, int position, long id) { PodcastSearchResult podcast = adapter.getItem(position); - if (podcast.feedUrl == null) { + if (TextUtils.isEmpty(podcast.feedUrl)) { return; } Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index bb7d9ff30..252ef8269 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -13,7 +13,6 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; @@ -75,6 +74,7 @@ public class SubscriptionFragment extends Fragment public static final String TAG = "SubscriptionFragment"; private static final String PREFS = "SubscriptionFragment"; private static final String PREF_NUM_COLUMNS = "columns"; + private static final String PREF_PREVIOUS_EPISODE_COUNT = "episodeCount"; private static final String KEY_UP_ARROW = "up_arrow"; private static final String ARGUMENT_FOLDER = "folder"; @@ -88,7 +88,6 @@ public class SubscriptionFragment extends Fragment private RecyclerView subscriptionRecycler; private SubscriptionsRecyclerAdapter subscriptionAdapter; private FloatingActionButton subscriptionAddButton; - private ProgressBar progressBar; private EmptyViewHandler emptyView; private TextView feedsFilteredMsg; private Toolbar toolbar; @@ -153,8 +152,24 @@ public class SubscriptionFragment extends Fragment setColumnNumber(prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns())); subscriptionRecycler.addItemDecoration(new SubscriptionsRecyclerAdapter.GridDividerItemDecorator()); registerForContextMenu(subscriptionRecycler); + subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()) { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuItemUtils.setOnClickListeners(menu, SubscriptionFragment.this::onContextItemSelected); + } + }; + subscriptionAdapter.setOnSelectModeListener(this); + subscriptionAdapter.setDummyViews(Math.max(1, prefs.getInt(PREF_PREVIOUS_EPISODE_COUNT, 5))); + subscriptionRecycler.setAdapter(subscriptionAdapter); + setupEmptyView(); + subscriptionAddButton = root.findViewById(R.id.subscriptions_add); - progressBar = root.findViewById(R.id.progLoading); + subscriptionAddButton.setOnClickListener(view -> { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).loadChildFragment(new AddFeedFragment()); + } + }); feedsFilteredMsg = root.findViewById(R.id.feeds_filtered_message); feedsFilteredMsg.setOnClickListener((l) -> SubscriptionsFilterDialog.showDialog(requireContext())); @@ -254,26 +269,6 @@ public class SubscriptionFragment extends Fragment } @Override - public void onViewCreated(@NonNull View v, Bundle savedInstanceState) { - super.onViewCreated(v, savedInstanceState); - subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()) { - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuItemUtils.setOnClickListeners(menu, SubscriptionFragment.this::onContextItemSelected); - } - }; - subscriptionAdapter.setOnSelectModeListener(this); - subscriptionRecycler.setAdapter(subscriptionAdapter); - setupEmptyView(); - subscriptionAddButton.setOnClickListener(view -> { - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadChildFragment(new AddFeedFragment()); - } - }); - } - - @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); @@ -287,6 +282,7 @@ public class SubscriptionFragment extends Fragment if (disposable != null) { disposable.dispose(); } + prefs.edit().putInt(PREF_PREVIOUS_EPISODE_COUNT, subscriptionAdapter.getItemCount()).apply(); if (subscriptionAdapter != null) { subscriptionAdapter.endSelectMode(); @@ -319,13 +315,12 @@ public class SubscriptionFragment extends Fragment subscriptionAdapter.endSelectMode(); } listItems = result; + subscriptionAdapter.setDummyViews(0); subscriptionAdapter.setItems(result); subscriptionAdapter.notifyDataSetChanged(); emptyView.updateVisibility(); - progressBar.setVisibility(View.GONE); // Keep hidden to avoid flickering while refreshing }, error -> { Log.e(TAG, Log.getStackTraceString(error)); - progressBar.setVisibility(View.GONE); }); if (UserPreferences.getSubscriptionsFilter().isEnabled()) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java index 0dc416e0e..0e7c019f1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java @@ -35,6 +35,8 @@ public class EpisodeMultiSelectActionHandler { queueChecked(items); } else if (actionId == R.id.remove_from_queue_batch) { removeFromQueueChecked(items); + } else if (actionId == R.id.remove_from_inbox_batch) { + removeFromInboxChecked(items); } else if (actionId == R.id.mark_read_batch) { markedCheckedPlayed(items); } else if (actionId == R.id.mark_unread_batch) { @@ -66,6 +68,17 @@ public class EpisodeMultiSelectActionHandler { showMessage(R.plurals.removed_from_queue_batch_label, checkedIds.length); } + private void removeFromInboxChecked(List<FeedItem> items) { + LongList markUnplayed = new LongList(); + for (FeedItem episode : items) { + if (episode.isNew()) { + markUnplayed.add(episode.getId()); + } + } + DBWriter.markItemPlayed(FeedItem.UNPLAYED, markUnplayed.toArray()); + showMessage(R.plurals.removed_from_inbox_batch_label, markUnplayed.size()); + } + private void markedCheckedPlayed(List<FeedItem> items) { long[] checkedIds = getSelectedIds(items); DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java index ff974179e..ef6dabe24 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java @@ -3,20 +3,19 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Context; import android.os.Build; import android.os.Bundle; -import com.google.android.material.snackbar.Snackbar; +import android.widget.ListView; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceFragmentCompat; -import android.widget.ListView; +import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.event.PlayerStatusEvent; -import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; +import de.danoeh.antennapod.dialog.DrawerPreferencesDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; -import de.danoeh.antennapod.fragment.NavDrawerFragment; -import org.apache.commons.lang3.ArrayUtils; +import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import org.greenrobot.eventbus.EventBus; import java.util.List; @@ -55,7 +54,7 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS) .setOnPreferenceClickListener(preference -> { - showDrawerPreferencesDialog(); + DrawerPreferencesDialog.show(getContext(), null); return true; }); @@ -64,31 +63,6 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { showNotificationButtonsDialog(); return true; }); - - findPreference(UserPreferences.PREF_BACK_BUTTON_BEHAVIOR) - .setOnPreferenceChangeListener((preference, newValue) -> { - if (!newValue.equals("page")) { - return true; - } - final Context context = getActivity(); - final String[] navTitles = context.getResources().getStringArray(R.array.back_button_go_to_pages); - final String[] navTags = context.getResources().getStringArray(R.array.back_button_go_to_pages_tags); - final String[] choice = { UserPreferences.getBackButtonGoToPage() }; - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.back_button_go_to_page_title); - builder.setSingleChoiceItems(navTitles, ArrayUtils.indexOf(navTags, - UserPreferences.getBackButtonGoToPage()), (dialogInterface, i) -> { - if (i >= 0) { - choice[0] = navTags[i]; - } - }); - builder.setPositiveButton(R.string.confirm_label, (dialogInterface, i) -> UserPreferences.setBackButtonGoToPage(choice[0])); - builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - return true; - }); - findPreference(UserPreferences.PREF_FILTER_FEED) .setOnPreferenceClickListener((preference -> { SubscriptionsFilterDialog.showDialog(requireContext()); @@ -111,33 +85,6 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat { } } - private void showDrawerPreferencesDialog() { - final Context context = getActivity(); - final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); - final String[] navTitles = context.getResources().getStringArray(R.array.nav_drawer_titles); - final String[] NAV_DRAWER_TAGS = NavDrawerFragment.NAV_DRAWER_TAGS; - boolean[] checked = new boolean[NavDrawerFragment.NAV_DRAWER_TAGS.length]; - for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) { - String tag = NAV_DRAWER_TAGS[i]; - if (!hiddenDrawerItems.contains(tag)) { - checked[i] = true; - } - } - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navTitles, checked, (dialog, which, isChecked) -> { - if (isChecked) { - hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); - } else { - hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); - } - }); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> - UserPreferences.setHiddenDrawerItems(hiddenDrawerItems)); - builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - } - private void showNotificationButtonsDialog() { final Context context = getActivity(); final List<Integer> preferredButtons = UserPreferences.getCompactNotificationButtons(); diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index a2c5ca3ff..d2796b5e2 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -109,5 +109,13 @@ public class PreferenceUpgrader { if (oldVersion < 2050000) { prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply(); } + if (oldVersion < 2080000) { + // Migrate drawer feed counter setting to reflect removal of + // "unplayed and in inbox" (0), by changing it to "unplayed" (2) + String feedCounterSetting = prefs.getString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "1"); + if (feedCounterSetting.equals("0")) { + prefs.edit().putString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2").apply(); + } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java index bc29740b0..b4a01ed5a 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -220,6 +220,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { .withPlaceholderView(placeholder) .withCoverView(cover) .load(); + hideSeparatorIfNecessary(); } private void updateDuration(PlaybackPositionEvent event) { diff --git a/app/src/main/res/layout/cover_fragment.xml b/app/src/main/res/layout/cover_fragment.xml index 548e55774..9ba6de2ab 100644 --- a/app/src/main/res/layout/cover_fragment.xml +++ b/app/src/main/res/layout/cover_fragment.xml @@ -7,28 +7,33 @@ android:id="@+id/cover_fragment" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="horizontal" - android:padding="8dp" - android:gravity="center"> + android:orientation="vertical" + android:padding="8dp"> - <Space - android:id="@+id/counterweight" + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/coverHolder" android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_weight="1" /> - - <de.danoeh.antennapod.ui.common.SquareImageView - android:id="@+id/imgvCover" - android:layout_width="0dp" - android:layout_height="200dp" - android:layout_gravity="center" - android:layout_marginHorizontal="16dp" - android:layout_weight="0" - android:foreground="?attr/selectableItemBackgroundBorderless" - android:importantForAccessibility="no" - android:scaleType="fitCenter" - squareImageView:direction="height" - tools:src="@android:drawable/sym_def_app_icon" /> + android:layout_height="0dp" + android:layout_weight="1"> + + <ImageView + android:id="@+id/imgvCover" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_gravity="center" + android:layout_marginHorizontal="16dp" + android:foreground="?attr/selectableItemBackgroundBorderless" + android:importantForAccessibility="no" + android:scaleType="fitCenter" + squareImageView:direction="minimum" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + tools:src="@android:drawable/sym_def_app_icon" /> + + </androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout android:id="@+id/cover_fragment_text_container" @@ -65,29 +70,18 @@ android:textColor="?android:attr/textColorPrimary" android:textIsSelectable="false" android:textSize="@dimen/text_size_small" + android:layout_marginBottom="32dp" tools:text="Episode" /> - <Space - android:id="@+id/vertical_divider" - android:layout_width="match_parent" - android:layout_height="8dp" - android:visibility="gone" /> - </LinearLayout> - <Space - android:id="@+id/details_spacer" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_weight="1" /> - <LinearLayout android:id="@+id/episode_details" android:layout_width="wrap_content" android:layout_height="36dp" - android:layout_weight="0" android:baselineAligned="false" android:orientation="horizontal" + android:layout_gravity="center_horizontal" android:paddingLeft="8dp" android:paddingRight="8dp"> @@ -139,7 +133,8 @@ android:gravity="center" android:minWidth="150dp" android:orientation="horizontal" - android:visibility="gone"> + android:visibility="gone" + tools:visibility="visible"> <ImageButton android:id="@+id/butPrevChapter" diff --git a/app/src/main/res/layout/episodes_list_fragment.xml b/app/src/main/res/layout/episodes_list_fragment.xml index 629b7ab0e..343d530fd 100644 --- a/app/src/main/res/layout/episodes_list_fragment.xml +++ b/app/src/main/res/layout/episodes_list_fragment.xml @@ -32,8 +32,7 @@ android:id="@+id/swipeRefresh" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_below="@+id/txtvInformation" - android:layout_above="@id/loadingMore"> + android:layout_below="@+id/txtvInformation"> <de.danoeh.antennapod.view.EpisodeItemListRecyclerView android:id="@android:id/list" @@ -49,45 +48,6 @@ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> - <ProgressBar - android:id="@+id/progLoading" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:indeterminateOnly="true" - android:visibility="gone" - android:layout_centerInParent="true" - tools:background="@android:color/holo_red_light" /> - - <LinearLayout - android:id="@+id/loadingMore" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:orientation="horizontal" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:visibility="gone" - android:gravity="center"> - - <ProgressBar - android:layout_width="16dp" - android:layout_height="16dp" - android:gravity="center_horizontal" - android:indeterminateOnly="true" - tools:background="@android:color/holo_red_light" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginStart="4dp" - android:text="@string/loading_more" /> - - </LinearLayout> - <include layout="@layout/multi_select_speed_dial" /> diff --git a/app/src/main/res/layout/feed_item_list_fragment.xml b/app/src/main/res/layout/feed_item_list_fragment.xml index 2f175770f..2cc2b8214 100644 --- a/app/src/main/res/layout/feed_item_list_fragment.xml +++ b/app/src/main/res/layout/feed_item_list_fragment.xml @@ -61,14 +61,6 @@ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> - <ProgressBar - android:id="@+id/progLoading" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:indeterminateOnly="true" - android:visibility="gone" /> - <include android:id="@+id/more_content" android:layout_width="match_parent" diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index 5672a310f..7bb652eb4 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -49,14 +49,6 @@ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> - <ProgressBar - android:id="@+id/progLoading" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerInParent="true" - android:indeterminateOnly="true" - android:visibility="visible" /> - <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/subscriptions_add" android:layout_width="56dp" diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml index 7c8bdefbf..db7c55c85 100644 --- a/app/src/main/res/layout/queue_fragment.xml +++ b/app/src/main/res/layout/queue_fragment.xml @@ -49,14 +49,6 @@ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> - <ProgressBar - android:id="@+id/progLoading" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerInParent="true" - android:indeterminateOnly="true" - android:visibility="gone" /> - <include layout="@layout/multi_select_speed_dial" /> diff --git a/app/src/main/res/layout/quick_feed_discovery.xml b/app/src/main/res/layout/quick_feed_discovery.xml index 9ef3db180..0fb5fdb8c 100644 --- a/app/src/main/res/layout/quick_feed_discovery.xml +++ b/app/src/main/res/layout/quick_feed_discovery.xml @@ -1,98 +1,92 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical"> + android:orientation="horizontal"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> <TextView - android:layout_width="0dip" - android:layout_height="wrap_content" - android:text="@string/discover" - android:textSize="18sp" - android:layout_marginBottom="8dp" - android:layout_weight="1" - android:textColor="?android:attr/textColorPrimary"/> + android:layout_width="0dip" + android:layout_height="wrap_content" + android:text="@string/discover" + android:textSize="18sp" + android:layout_marginBottom="8dp" + android:layout_weight="1" + android:textColor="?android:attr/textColorPrimary" /> <Button - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:minWidth="0dp" - android:text="@string/discover_more" - style="@style/Widget.MaterialComponents.Button.TextButton" - android:id="@+id/discover_more"/> + android:id="@+id/discover_more" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:minWidth="0dp" + android:text="@string/discover_more" + style="@style/Widget.MaterialComponents.Button.TextButton" /> + </LinearLayout> <RelativeLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_width="match_parent" + android:layout_height="wrap_content"> <de.danoeh.antennapod.ui.common.WrappingGridView - android:id="@+id/discover_grid" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:numColumns="4" - app:layout_columnWeight="1" - app:layout_rowWeight="1" - android:scrollbars="none" - android:layout_marginTop="8dp" - android:layout_centerInParent="true" - android:layout_gravity="center_horizontal"/> - - <ProgressBar - android:id="@+id/discover_progress_bar" - style="?android:attr/progressBarStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_centerInParent="true" - android:layout_marginTop="30dp"/> + android:id="@+id/discover_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:numColumns="4" + android:scrollbars="none" + android:layout_marginTop="8dp" + android:layout_centerInParent="true" + android:layout_gravity="center_horizontal" + app:layout_columnWeight="1" + app:layout_rowWeight="1" /> <LinearLayout - android:id="@+id/discover_error" + android:id="@+id/discover_error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:gravity="center" + android:orientation="vertical"> + + <TextView + android:id="@+id/discover_error_txtV" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_centerInParent="true" android:gravity="center" - android:orientation="vertical"> - - <TextView - android:id="@+id/discover_error_txtV" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:layout_margin="16dp" - android:textSize="@dimen/text_size_small" - tools:text="Error message" - tools:background="@android:color/holo_red_light" /> + android:layout_margin="16dp" + android:textSize="@dimen/text_size_small" + tools:text="Error message" + tools:background="@android:color/holo_red_light" /> <Button - android:id="@+id/discover_error_retry_btn" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="16dp" - android:text="@string/retry_label" - tools:background="@android:color/holo_red_light" /> + android:id="@+id/discover_error_retry_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/retry_label" + tools:background="@android:color/holo_red_light" /> + </LinearLayout> </RelativeLayout> <TextView - android:id="@+id/discover_powered_by_itunes" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textColor="?android:attr/textColorTertiary" - android:text="@string/discover_powered_by_itunes" - android:textSize="12sp" - android:layout_gravity="right|end" - android:paddingHorizontal="4dp" - android:textAlignment="textEnd"/> + android:id="@+id/discover_powered_by_itunes" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorTertiary" + android:text="@string/discover_powered_by_itunes" + android:textSize="12sp" + android:layout_gravity="right|end" + android:paddingHorizontal="4dp" + android:textAlignment="textEnd" /> </LinearLayout> diff --git a/app/src/main/res/menu/episodes_apply_action_speeddial.xml b/app/src/main/res/menu/episodes_apply_action_speeddial.xml index 0ebcb238c..944d2e7c9 100644 --- a/app/src/main/res/menu/episodes_apply_action_speeddial.xml +++ b/app/src/main/res/menu/episodes_apply_action_speeddial.xml @@ -1,34 +1,45 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> +<menu + xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- the order is opposite of the typical menu: catered to FAB speed dial, which somehow shows the item in reverse. E.g., item @id/delete_batch is the first in the xml, visually it will be shown at the bottom of the list of actions. --> - <item android:id="@+id/delete_batch" + <item + android:id="@+id/delete_batch" android:icon="@drawable/ic_delete" - android:title="@string/delete_episode_label" - /> - <item android:id="@+id/download_batch" + android:title="@string/delete_episode_label" /> + + <item + android:id="@+id/download_batch" android:icon="@drawable/ic_download" - android:title="@string/download_label" - /> - <item android:id="@+id/mark_unread_batch" + android:title="@string/download_label" /> + + <item + android:id="@+id/mark_unread_batch" android:icon="@drawable/ic_mark_unplayed" - android:title="@string/mark_unread_label" - /> + android:title="@string/mark_unread_label" /> + <item android:id="@+id/mark_read_batch" android:icon="@drawable/ic_mark_played" - android:title="@string/mark_read_label" - /> - <item android:id="@+id/remove_from_queue_batch" + android:title="@string/mark_read_label" /> + + <item + android:id="@+id/remove_from_queue_batch" android:icon="@drawable/ic_playlist_remove" - android:title="@string/remove_from_queue_label" - /> + android:title="@string/remove_from_queue_label" /> + <item android:id="@+id/add_to_queue_batch" android:icon="@drawable/ic_playlist_play" - android:title="@string/add_to_queue_label" - /> + android:title="@string/add_to_queue_label" /> + + <item + android:id="@+id/remove_from_inbox_batch" + android:icon="@drawable/ic_check" + android:title="@string/remove_inbox_label" /> + </menu> diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml index 59e7092a1..dd3810efc 100644 --- a/app/src/main/res/xml/preferences_user_interface.xml +++ b/app/src/main/res/xml/preferences_user_interface.xml @@ -77,12 +77,12 @@ </PreferenceCategory> <PreferenceCategory android:title="@string/behavior"> <ListPreference - android:entryValues="@array/back_button_behavior_values" - android:entries="@array/back_button_behavior_options" - android:key="prefBackButtonBehavior" - android:title="@string/pref_back_button_behavior_title" - android:summary="@string/pref_back_button_behavior_sum" - android:defaultValue="default"/> + android:entryValues="@array/default_page_values" + android:entries="@array/default_page_titles" + android:key="prefDefaultPage" + android:title="@string/pref_default_page" + android:summary="@string/pref_default_page_sum" + android:defaultValue="HomeFragment"/> <Preference android:key="prefSwipe" android:summary="@string/swipeactions_summary" diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index b1bc38ebc..f0227ae7b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -52,7 +52,7 @@ public class UserPreferences { public static final String PREF_THEME = "prefTheme"; public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems"; public static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder"; - private static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator"; + public static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator"; public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; public static final String PREF_USE_EPISODE_COVER = "prefEpisodeCover"; public static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; @@ -61,8 +61,7 @@ public class UserPreferences { public static final String PREF_LOCKSCREEN_BACKGROUND = "prefLockscreenBackground"; private static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport"; private static final String PREF_SHOW_AUTO_DOWNLOAD_REPORT = "prefShowAutoDownloadReport"; - public static final String PREF_BACK_BUTTON_BEHAVIOR = "prefBackButtonBehavior"; - private static final String PREF_BACK_BUTTON_GO_TO_PAGE = "prefBackButtonGoToPage"; + public static final String PREF_DEFAULT_PAGE = "prefDefaultPage"; public static final String PREF_FILTER_FEED = "prefSubscriptionsFilter"; public static final String PREF_SUBSCRIPTION_TITLE = "prefSubscriptionTitle"; @@ -137,6 +136,7 @@ public class UserPreferences { public static final int FEED_ORDER_COUNTER = 0; public static final int FEED_ORDER_ALPHABETICAL = 1; public static final int FEED_ORDER_MOST_PLAYED = 3; + public static final String DEFAULT_PAGE_REMEMBER = "remember"; private static Context context; private static SharedPreferences prefs; @@ -901,29 +901,12 @@ public class UserPreferences { return getUpdateTimeOfDay().length == 2; } - public enum BackButtonBehavior { - DEFAULT, OPEN_DRAWER, DOUBLE_TAP, SHOW_PROMPT, GO_TO_PAGE + public static String getDefaultPage() { + return prefs.getString(PREF_DEFAULT_PAGE, "HomeFragment"); } - public static BackButtonBehavior getBackButtonBehavior() { - switch (prefs.getString(PREF_BACK_BUTTON_BEHAVIOR, "default")) { - case "drawer": return BackButtonBehavior.OPEN_DRAWER; - case "doubletap": return BackButtonBehavior.DOUBLE_TAP; - case "prompt": return BackButtonBehavior.SHOW_PROMPT; - case "page": return BackButtonBehavior.GO_TO_PAGE; - case "default": // Deliberate fall-through - default: return BackButtonBehavior.DEFAULT; - } - } - - public static String getBackButtonGoToPage() { - return prefs.getString(PREF_BACK_BUTTON_GO_TO_PAGE, "QueueFragment"); - } - - public static void setBackButtonGoToPage(String tag) { - prefs.edit() - .putString(PREF_BACK_BUTTON_GO_TO_PAGE, tag) - .apply(); + public static void setDefaultPage(String defaultPage) { + prefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply(); } public static boolean timeRespectsSpeed() { diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 4a32eb760..0ae8cf96e 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -175,14 +175,12 @@ </string-array> <string-array name="nav_drawer_feed_counter_options"> - <item>@string/drawer_feed_counter_inbox_unplayed</item> <item>@string/drawer_feed_counter_inbox</item> <item>@string/drawer_feed_counter_unplayed</item> <item>@string/drawer_feed_counter_downloaded</item> <item>@string/drawer_feed_counter_none</item> </string-array> <string-array name="nav_drawer_feed_counter_values"> - <item>0</item> <item>1</item> <item>2</item> <item>4</item> @@ -265,35 +263,21 @@ <item>@string/skip_episode_label</item> </string-array> - <string-array name="back_button_behavior_options"> - <item>@string/back_button_default</item> - <item>@string/back_button_go_to_page</item> - <item>@string/back_button_open_drawer</item> - <item>@string/back_button_double_tap</item> - <item>@string/back_button_show_prompt</item> - </string-array> - - <string-array name="back_button_behavior_values"> - <item>default</item> - <item>page</item> - <item>drawer</item> - <item>doubletap</item> - <item>prompt</item> + <string-array name="default_page_values"> + <item>HomeFragment</item> + <item>QueueFragment</item> + <item>InboxFragment</item> + <item>EpisodesFragment</item> + <item>SubscriptionFragment</item> + <item>remember</item> </string-array> - <string-array name="back_button_go_to_pages"> + <string-array name="default_page_titles"> <item>@string/home_label</item> <item>@string/queue_label</item> <item>@string/inbox_label</item> <item>@string/episodes_label</item> <item>@string/subscriptions_label</item> - </string-array> - - <string-array name="back_button_go_to_pages_tags"> - <item>HomeFragment</item> - <item>QueueFragment</item> - <item>InboxFragment</item> - <item>EpisodesFragment</item> - <item>SubscriptionFragment</item> + <item>@string/remember_last_page</item> </string-array> </resources> diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedCounter.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedCounter.java index eef1cc1ef..0cf6e0858 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedCounter.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedCounter.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.model.feed; public enum FeedCounter { - SHOW_NEW_UNPLAYED_SUM(0), SHOW_NEW(1), SHOW_UNPLAYED(2), SHOW_NONE(3), diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java index bd0024042..3cf7e37dc 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java @@ -1271,15 +1271,12 @@ public class PodDBAdapter { public final Map<Long, Integer> getFeedCounters(FeedCounter setting, long... feedIds) { String whereRead; switch (setting) { - case SHOW_NEW_UNPLAYED_SUM: - whereRead = "(" + KEY_READ + "=" + FeedItem.NEW + - " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")"; - break; case SHOW_NEW: whereRead = KEY_READ + "=" + FeedItem.NEW; break; case SHOW_UNPLAYED: - whereRead = KEY_READ + "=" + FeedItem.UNPLAYED; + whereRead = "(" + KEY_READ + "=" + FeedItem.NEW + + " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")"; break; case SHOW_DOWNLOADED: whereRead = KEY_DOWNLOADED + "=1"; diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index a19a60892..d6b80f6fd 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -73,7 +73,6 @@ <string name="drawer_feed_order_alphabetical">Sort alphabetically</string> <string name="drawer_feed_order_last_update">Sort by publication date</string> <string name="drawer_feed_order_most_played">Sort by number of played episodes</string> - <string name="drawer_feed_counter_inbox_unplayed">Number of unplayed episodes and episodes in the inbox</string> <string name="drawer_feed_counter_inbox">Number of episodes in the inbox</string> <string name="drawer_feed_counter_unplayed">Number of unplayed episodes</string> <string name="drawer_feed_counter_downloaded">Number of downloaded episodes</string> @@ -146,7 +145,6 @@ <item quantity="one">%d episode</item> <item quantity="other">%d episodes</item> </plurals> - <string name="loading_more">Loading more…</string> <string name="episode_notification">Episode Notifications</string> <string name="episode_notification_summary">Show a notification when a new episode is released.</string> <plurals name="new_episode_notification_message"> @@ -239,6 +237,10 @@ <item quantity="one">%d episode removed from queue.</item> <item quantity="other">%d episodes removed from queue.</item> </plurals> + <plurals name="removed_from_inbox_batch_label"> + <item quantity="one">%d episode removed from inbox.</item> + <item quantity="other">%d episodes removed from inbox.</item> + </plurals> <string name="add_to_favorite_label">Add to Favorites</string> <string name="remove_from_favorite_label">Remove from Favorites</string> <string name="visit_website_label">Visit Website</string> @@ -517,16 +519,9 @@ <string name="media_player_switched_to_exoplayer">Switched to ExoPlayer.</string> <string name="pref_skip_silence_title">Skip Silence in Audio</string> <string name="behavior">Behavior</string> - <string name="pref_back_button_behavior_title">Back Button Behavior</string> - <string name="pref_back_button_behavior_sum">Change behavior of the back button.</string> - <string name="back_button_default">Default</string> - <string name="back_button_open_drawer">Open navigation drawer</string> - <string name="back_button_double_tap">Double tap to exit</string> - <string name="back_button_show_prompt">Confirm to exit</string> - <string name="close_prompt">Are you sure you want to close AntennaPod?</string> - <string name="double_tap_toast">Tap back button again to exit</string> - <string name="back_button_go_to_page">Go to page…</string> - <string name="back_button_go_to_page_title">Select page</string> + <string name="pref_default_page">Default Page</string> + <string name="pref_default_page_sum">Screen that is opened when starting AntennaPod.</string> + <string name="remember_last_page">Remember last page</string> <string name="pref_delete_removes_from_queue_title">Delete removes from Queue</string> <string name="pref_delete_removes_from_queue_sum">Automatically remove an episode from the queue when it is deleted.</string> <string name="pref_filter_feed_title">Subscription Filter</string> |