summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle8
-rw-r--r--app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java2
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java131
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java36
-rw-r--r--app/src/main/AndroidManifest.xml11
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java25
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java58
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java87
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java27
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java174
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java32
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java5
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java9
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java466
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java188
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java69
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java78
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java14
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java75
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java146
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java22
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java71
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java399
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java88
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java130
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/SwipePreferencesFragment.java37
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java6
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/AddToQueueSwipeAction.java47
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkFavoriteSwipeAction.java43
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkPlayedSwipeAction.java46
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromInboxSwipeAction.java44
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromQueueSwipeAction.java57
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/ShowFirstSwipeDialogAction.java42
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/StartDownloadSwipeAction.java44
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeAction.java34
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java258
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java190
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java68
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java68
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java9
-rw-r--r--app/src/main/res/layout-sw720dp/main.xml39
-rw-r--r--app/src/main/res/layout/episodes_apply_action_fragment.xml59
-rw-r--r--app/src/main/res/layout/feed_item_list_fragment.xml107
-rw-r--r--app/src/main/res/layout/feeditemlist_item.xml347
-rw-r--r--app/src/main/res/layout/main.xml39
-rw-r--r--app/src/main/res/layout/multi_select_speed_dial.xml41
-rw-r--r--app/src/main/res/layout/queue_fragment.xml31
-rw-r--r--app/src/main/res/layout/simple_list_fragment.xml44
-rw-r--r--app/src/main/res/layout/swipeactions_dialog.xml24
-rw-r--r--app/src/main/res/layout/swipeactions_picker.xml10
-rw-r--r--app/src/main/res/layout/swipeactions_picker_item.xml29
-rw-r--r--app/src/main/res/layout/swipeactions_row.xml84
-rw-r--r--app/src/main/res/menu/downloads.xml7
-rw-r--r--app/src/main/res/menu/episodes_apply_action_options.xml58
-rw-r--r--app/src/main/res/menu/episodes_apply_action_speeddial.xml8
-rw-r--r--app/src/main/res/menu/feeditem_options.xml12
-rw-r--r--app/src/main/res/menu/feeditemlist_context.xml23
-rw-r--r--app/src/main/res/menu/feedlist.xml7
-rw-r--r--app/src/main/res/menu/multi_select_context_popup.xml11
-rw-r--r--app/src/main/res/menu/multi_select_options.xml8
-rw-r--r--app/src/main/res/menu/queue.xml6
-rw-r--r--app/src/main/res/xml/preferences.xml2
-rw-r--r--app/src/main/res/xml/preferences_swipe.xml11
-rw-r--r--app/src/main/res/xml/preferences_user_interface.xml4
71 files changed, 2576 insertions, 1806 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 06c15a5f6..8141107c7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,7 +1,7 @@
plugins {
id('com.android.application')
id('com.getkeepsafe.dexcount')
- id('com.github.triplet.play') version '2.7.5' apply false
+ id('com.github.triplet.play') version '3.4.0' apply false
}
apply from: "../common.gradle"
apply from: "../playFlavor.gradle"
@@ -149,6 +149,8 @@ dependencies {
implementation 'com.github.mfietz:fyydlin:v0.5.0'
implementation 'com.github.ByteHamster:SearchPreference:v2.0.0'
implementation 'com.github.skydoves:balloon:1.1.5'
+ implementation 'it.xabaras.android:recyclerview-swipedecorator:1.2.3'
+ implementation 'com.annimon:stream:1.2.2'
// Non-free dependencies:
playImplementation 'com.google.android.play:core:1.8.0'
@@ -168,8 +170,8 @@ dependencies {
if (project.hasProperty("antennaPodPlayPublisherCredentials")) {
apply plugin: 'com.github.triplet.play'
play {
- track = 'alpha'
- serviceAccountCredentials = file(antennaPodPlayPublisherCredentials)
+ track.set('alpha')
+ serviceAccountCredentials.set(file(antennaPodPlayPublisherCredentials))
}
}
diff --git a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java
index f1d71b07a..32ad5a694 100644
--- a/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/gpodnet/GPodnetServiceTest.java
@@ -20,7 +20,7 @@ import static java.util.Collections.singletonList;
/**
* Test class for GpodnetService
*/
-@Ignore
+@Ignore("Needs valid credentials to run")
@RunWith(AndroidJUnit4.class)
public class GPodnetServiceTest {
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java
deleted file mode 100644
index 09730ee4e..000000000
--- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package de.test.antennapod.ui;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.playback.PlayerStatus;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.util.playback.PlaybackController;
-import de.danoeh.antennapod.fragment.QueueFragment;
-import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
-import de.test.antennapod.EspressoTestUtils;
-import de.test.antennapod.IgnoreOnCi;
-import org.awaitility.Awaitility;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static de.test.antennapod.EspressoTestUtils.waitForView;
-
-/**
- * User interface tests for changing the playback speed.
- */
-@RunWith(AndroidJUnit4.class)
-@IgnoreOnCi
-public class SpeedChangeTest {
-
- @Rule
- public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class, false, false);
- private UITestUtils uiTestUtils;
- private String[] availableSpeeds;
- private PlaybackController controller;
-
- @Before
- public void setUp() throws Exception {
- EspressoTestUtils.clearPreferences();
- EspressoTestUtils.clearDatabase();
- EspressoTestUtils.setLastNavFragment(QueueFragment.TAG);
-
- Context context = getInstrumentation().getTargetContext();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, true).commit();
-
- uiTestUtils = new UITestUtils(context);
- uiTestUtils.setup();
- uiTestUtils.setMediaFileName("30sec.mp3");
- uiTestUtils.addLocalFeedData(true);
-
- List<FeedItem> queue = DBReader.getQueue();
- PlaybackPreferences.writeMediaPlaying(queue.get(0).getMedia(), PlayerStatus.PAUSED, false);
- availableSpeeds = new String[] {"1.00", "2.00", "3.00"};
- UserPreferences.setPlaybackSpeedArray(Arrays.asList(1.0f, 2.0f, 3.0f));
-
- EspressoTestUtils.tryKillPlaybackService();
- activityRule.launchActivity(new Intent().putExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, true));
- controller = new PlaybackController(activityRule.getActivity()) {
- @Override
- public void loadMediaInfo() {
- // Do nothing
- }
- };
- controller.init();
- controller.getMedia(); // To load media
- }
-
- @After
- public void tearDown() throws Exception {
- uiTestUtils.tearDown();
- controller.release();
- }
-
- @Test
- public void testChangeSpeedServiceNotRunning() {
- clickThroughSpeeds();
- }
-
- @Test
- public void testChangeSpeedPlaying() {
- onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000));
- controller.playPause();
- Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
- -> controller.getStatus() == PlayerStatus.PLAYING);
- clickThroughSpeeds();
- }
-
- @Test
- public void testChangeSpeedPaused() {
- onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000));
- controller.playPause();
- Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
- -> controller.getStatus() == PlayerStatus.PLAYING);
- controller.playPause();
- Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
- -> controller.getStatus() == PlayerStatus.PAUSED);
- clickThroughSpeeds();
- }
-
- private void clickThroughSpeeds() {
- onView(isRoot()).perform(waitForView(withText(availableSpeeds[0]), 1000));
- onView(withId(R.id.txtvPlaybackSpeed)).check(matches(withText(availableSpeeds[0])));
- onView(withId(R.id.butPlaybackSpeed)).perform(click());
- onView(isRoot()).perform(waitForView(withText(availableSpeeds[1]), 1000));
- onView(withId(R.id.txtvPlaybackSpeed)).check(matches(withText(availableSpeeds[1])));
- onView(withId(R.id.butPlaybackSpeed)).perform(click());
- onView(isRoot()).perform(waitForView(withText(availableSpeeds[2]), 1000));
- onView(withId(R.id.txtvPlaybackSpeed)).check(matches(withText(availableSpeeds[2])));
- onView(withId(R.id.butPlaybackSpeed)).perform(click());
- onView(isRoot()).perform(waitForView(withText(availableSpeeds[0]), 1000));
- onView(withId(R.id.txtvPlaybackSpeed)).check(matches(withText(availableSpeeds[0])));
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java
deleted file mode 100644
index 0dae22db1..000000000
--- a/app/src/androidTest/java/de/test/antennapod/ui/VideoplayerActivityTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package de.test.antennapod.ui;
-
-import android.content.Intent;
-import androidx.test.filters.MediumTest;
-import androidx.test.rule.ActivityTestRule;
-
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.VideoplayerActivity;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-
-/**
- * Test class for VideoplayerActivity
- */
-@MediumTest
-@Ignore
-public class VideoplayerActivityTest {
-
- @Rule
- public ActivityTestRule<VideoplayerActivity> activityTestRule = new ActivityTestRule<>(VideoplayerActivity.class, false, false);
-
- /**
- * Test if activity can be started.
- */
- @Test
- public void testStartActivity() throws Exception {
- activityTestRule.launchActivity(new Intent());
- onView(withId(R.id.videoPlayerContainer)).check(matches(isDisplayed()));
- }
-}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 074ea0c1c..c03ff3a0a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="de.danoeh.antennapod"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -353,4 +355,11 @@
android:resource="@xml/actions" />
</application>
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.SEND" />
+ <data android:mimeType="text/*" />
+ </intent>
+ </queries>
+
</manifest>
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
index 376074525..1c61fc15e 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
@@ -119,20 +119,19 @@ public class OpmlFeedChooserActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.select_all_item:
- selectAll.setVisible(false);
- selectAllItems(true);
- deselectAll.setVisible(true);
- return true;
- case R.id.deselect_all_item:
- deselectAll.setVisible(false);
- selectAllItems(false);
- selectAll.setVisible(true);
- return true;
- default:
- return false;
+ final int itemId = item.getItemId();
+ if (itemId == R.id.select_all_item) {
+ selectAll.setVisible(false);
+ selectAllItems(true);
+ deselectAll.setVisible(true);
+ return true;
+ } else if (itemId == R.id.deselect_all_item) {
+ deselectAll.setVisible(false);
+ selectAllItems(false);
+ selectAll.setVisible(true);
+ return true;
}
+ return false;
}
private void selectAllItems(boolean b) {
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java
index cd72e34e8..157ad246f 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java
@@ -3,15 +3,16 @@ package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.inputmethod.InputMethodManager;
+
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
-import android.provider.Settings;
-import android.view.Menu;
-import android.view.MenuItem;
-
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult;
import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener;
@@ -26,6 +27,7 @@ import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.NotificationPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.StoragePreferencesFragment;
+import de.danoeh.antennapod.fragment.preferences.SwipePreferencesFragment;
import de.danoeh.antennapod.fragment.preferences.UserInterfacePreferencesFragment;
/**
@@ -79,33 +81,35 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe
prefFragment = new PlaybackPreferencesFragment();
} else if (screen == R.xml.preferences_notifications) {
prefFragment = new NotificationPreferencesFragment();
+ } else if (screen == R.xml.preferences_swipe) {
+ prefFragment = new SwipePreferencesFragment();
}
return prefFragment;
}
public static int getTitleOfPage(int preferences) {
- switch (preferences) {
- case R.xml.preferences_network:
- return R.string.network_pref;
- case R.xml.preferences_autodownload:
- return R.string.pref_automatic_download_title;
- case R.xml.preferences_playback:
- return R.string.playback_pref;
- case R.xml.preferences_storage:
- return R.string.storage_pref;
- case R.xml.preferences_import_export:
- return R.string.import_export_pref;
- case R.xml.preferences_user_interface:
- return R.string.user_interface_label;
- case R.xml.preferences_gpodder:
- return R.string.gpodnet_main_label;
- case R.xml.preferences_notifications:
- return R.string.notification_pref_fragment;
- case R.xml.feed_settings:
- return R.string.feed_settings_label;
- default:
- return R.string.settings_label;
+ if (preferences == R.xml.preferences_network) {
+ return R.string.network_pref;
+ } else if (preferences == R.xml.preferences_autodownload) {
+ return R.string.pref_automatic_download_title;
+ } else if (preferences == R.xml.preferences_playback) {
+ return R.string.playback_pref;
+ } else if (preferences == R.xml.preferences_storage) {
+ return R.string.storage_pref;
+ } else if (preferences == R.xml.preferences_import_export) {
+ return R.string.import_export_pref;
+ } else if (preferences == R.xml.preferences_user_interface) {
+ return R.string.user_interface_label;
+ } else if (preferences == R.xml.preferences_gpodder) {
+ return R.string.gpodnet_main_label;
+ } else if (preferences == R.xml.preferences_notifications) {
+ return R.string.notification_pref_fragment;
+ } else if (preferences == R.xml.feed_settings) {
+ return R.string.feed_settings_label;
+ } else if (preferences == R.xml.preferences_swipe) {
+ return R.string.swipeactions_label;
}
+ return R.string.settings_label;
}
public PreferenceFragmentCompat openScreen(int screen) {
@@ -136,6 +140,10 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finish();
} else {
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ if (imm.isActive()) {
+ imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_NOT_ALWAYS);
+ }
getSupportFragmentManager().popBackStack();
}
return true;
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
index 774b97454..674b5f86e 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/EpisodeItemListAdapter.java
@@ -3,11 +3,20 @@ package de.danoeh.antennapod.adapter;
import android.app.Activity;
import android.view.ContextMenu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.model.feed.FeedItem;
@@ -15,24 +24,20 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.fragment.ItemPagerFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
-import org.apache.commons.lang3.ArrayUtils;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
/**
* List adapter for the list of new episodes.
*/
-public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder>
+public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder>
implements View.OnCreateContextMenuListener {
private final WeakReference<MainActivity> mainActivityRef;
private List<FeedItem> episodes = new ArrayList<>();
- private FeedItem selectedItem;
+ private FeedItem longPressedItem;
+ int longPressedPosition = 0; // used to init actionMode
public EpisodeItemListAdapter(MainActivity mainActivity) {
- super();
+ super(mainActivity);
this.mainActivityRef = new WeakReference<>(mainActivity);
setHasStableIds(true);
}
@@ -63,19 +68,35 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
FeedItem item = episodes.get(pos);
holder.bind(item);
- holder.itemView.setOnLongClickListener(v -> {
- selectedItem = item;
- return false;
- });
+
holder.itemView.setOnClickListener(v -> {
MainActivity activity = mainActivityRef.get();
- if (activity != null) {
+ if (activity != null && !inActionMode()) {
long[] ids = FeedItemUtil.getIds(episodes);
int position = ArrayUtils.indexOf(ids, item.getId());
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position));
+ } else {
+ toggleSelection(pos);
}
});
holder.itemView.setOnCreateContextMenuListener(this);
+ holder.itemView.setOnLongClickListener(v -> {
+ longPressedItem = item;
+ longPressedPosition = pos;
+ return false;
+ });
+
+ if (inActionMode()) {
+ holder.secondaryActionButton.setVisibility(View.GONE);
+ holder.selectCheckBox.setOnClickListener(v -> {
+ toggleSelection(pos);
+ });
+ holder.selectCheckBox.setChecked(isSelected(pos));
+ holder.selectCheckBox.setVisibility(View.VISIBLE);
+ } else {
+ holder.selectCheckBox.setVisibility(View.GONE);
+ }
+
afterBindViewHolder(holder, pos);
holder.hideSeparatorIfNecessary();
}
@@ -106,6 +127,7 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
* Instead, we tell the adapter to use partial binding by calling {@link #notifyItemChanged(int, Object)}.
* We actually ignore the payload and always do a full bind but calling the partial bind method ensures
* that ViewHolders are always re-used.
+ *
* @param position Position of the item that has changed
*/
public void notifyItemChangedCompat(int position) {
@@ -113,8 +135,8 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
}
@Nullable
- public FeedItem getSelectedItem() {
- return selectedItem;
+ public FeedItem getLongPressedItem() {
+ return longPressedItem;
}
@Override
@@ -139,8 +161,37 @@ public class EpisodeItemListAdapter extends RecyclerView.Adapter<EpisodeItemView
@Override
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
- inflater.inflate(R.menu.feeditemlist_context, menu);
- menu.setHeaderTitle(selectedItem.getTitle());
- FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item);
+ if (inActionMode()) {
+ inflater.inflate(R.menu.multi_select_context_popup, menu);
+ } else {
+ inflater.inflate(R.menu.feeditemlist_context, menu);
+ menu.setHeaderTitle(longPressedItem.getTitle());
+ FeedItemMenuHandler.onPrepareMenu(menu, longPressedItem, R.id.skip_episode_item);
+ }
+ }
+
+ public boolean onContextItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.multi_select) {
+ startSelectMode(longPressedPosition);
+ return true;
+ } else if (item.getItemId() == R.id.select_all_above) {
+ setSelected(0, longPressedPosition, true);
+ return true;
+ } else if (item.getItemId() == R.id.select_all_below) {
+ setSelected(longPressedPosition + 1, getItemCount(), true);
+ return true;
+ }
+ return false;
}
+
+ public List<FeedItem> getSelectedItems() {
+ List<FeedItem> items = new ArrayList<>();
+ for (int i = 0; i < getItemCount(); i++) {
+ if (isSelected(i)) {
+ items.add(getItem(i));
+ }
+ }
+ return items;
+ }
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
index 01712ea29..6055582a3 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
@@ -6,10 +6,11 @@ import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.View;
-import androidx.recyclerview.widget.ItemTouchHelper;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
/**
@@ -18,13 +19,13 @@ import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
private static final String TAG = "QueueRecyclerAdapter";
- private final ItemTouchHelper itemTouchHelper;
+ private final SwipeActions swipeActions;
private boolean dragDropEnabled;
- public QueueRecyclerAdapter(MainActivity mainActivity, ItemTouchHelper itemTouchHelper) {
+ public QueueRecyclerAdapter(MainActivity mainActivity, SwipeActions swipeActions) {
super(mainActivity);
- this.itemTouchHelper = itemTouchHelper;
+ this.swipeActions = swipeActions;
dragDropEnabled = ! (UserPreferences.isQueueKeepSorted() || UserPreferences.isQueueLocked());
}
@@ -39,12 +40,12 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
View.OnTouchListener startDragTouchListener = (v1, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "startDrag()");
- itemTouchHelper.startDrag(holder);
+ swipeActions.startDrag(holder);
}
return false;
};
- if (!dragDropEnabled) {
+ if (!dragDropEnabled || inActionMode()) {
holder.dragHandle.setVisibility(View.GONE);
holder.dragHandle.setOnTouchListener(null);
holder.coverHolder.setOnTouchListener(null);
@@ -63,11 +64,17 @@ public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
inflater.inflate(R.menu.queue_context, menu);
super.onCreateContextMenu(menu, v, menuInfo);
- final boolean keepSorted = UserPreferences.isQueueKeepSorted();
- if (getItem(0).getId() == getSelectedItem().getId() || keepSorted) {
+ if (!inActionMode()) {
+ menu.findItem(R.id.multi_select).setVisible(true);
+ final boolean keepSorted = UserPreferences.isQueueKeepSorted();
+ if (getItem(0).getId() == getLongPressedItem().getId() || keepSorted) {
+ menu.findItem(R.id.move_to_top_item).setVisible(false);
+ }
+ if (getItem(getItemCount() - 1).getId() == getLongPressedItem().getId() || keepSorted) {
+ menu.findItem(R.id.move_to_bottom_item).setVisible(false);
+ }
+ } else {
menu.findItem(R.id.move_to_top_item).setVisible(false);
- }
- if (getItem(getItemCount() - 1).getId() == getSelectedItem().getId() || keepSorted) {
menu.findItem(R.id.move_to_bottom_item).setVisible(false);
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
new file mode 100644
index 000000000..43f749ff3
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/SelectableAdapter.java
@@ -0,0 +1,174 @@
+package de.danoeh.antennapod.adapter;
+
+import android.app.Activity;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import de.danoeh.antennapod.R;
+
+import java.util.HashSet;
+
+/**
+ * Used by Recyclerviews that need to provide ability to select items.
+ */
+abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
+ private ActionMode actionMode;
+ private final HashSet<Long> selectedIds = new HashSet<>();
+ private final Activity activity;
+ private OnSelectModeListener onSelectModeListener;
+
+ public SelectableAdapter(Activity activity) {
+ this.activity = activity;
+ }
+
+ public void startSelectMode(int pos) {
+ if (inActionMode()) {
+ endSelectMode();
+ }
+
+ if (onSelectModeListener != null) {
+ onSelectModeListener.onStartSelectMode();
+ }
+
+ selectedIds.clear();
+ selectedIds.add(getItemId(pos));
+ notifyDataSetChanged();
+
+ actionMode = activity.startActionMode(new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.multi_select_options, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateTitle();
+ toggleSelectAllIcon(menu.findItem(R.id.select_toggle), false);
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ if (item.getItemId() == R.id.select_toggle) {
+ boolean allSelected = selectedIds.size() == getItemCount();
+ setSelected(0, getItemCount(), !allSelected);
+ toggleSelectAllIcon(item, !allSelected);
+ updateTitle();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ callOnEndSelectMode();
+ actionMode = null;
+ selectedIds.clear();
+ notifyDataSetChanged();
+ }
+ });
+ updateTitle();
+ }
+
+ /**
+ * End action mode if currently in select mode, otherwise do nothing
+ */
+ public void endSelectMode() {
+ if (inActionMode()) {
+ callOnEndSelectMode();
+ actionMode.finish();
+ }
+ }
+
+ public boolean isSelected(int pos) {
+ return selectedIds.contains(getItemId(pos));
+ }
+
+ /**
+ * Set the selected state of item at given position
+ *
+ * @param pos the position to select
+ * @param selected true for selected state and false for unselected
+ */
+ public void setSelected(int pos, boolean selected) {
+ if (selected) {
+ selectedIds.add(getItemId(pos));
+ } else {
+ selectedIds.remove(getItemId(pos));
+ }
+ updateTitle();
+ }
+
+ /**
+ * Set the selected state of item for a given range
+ *
+ * @param startPos start position of range, inclusive
+ * @param endPos end position of range, inclusive
+ * @param selected indicates the selection state
+ * @throws IllegalArgumentException if start and end positions are not valid
+ */
+ public void setSelected(int startPos, int endPos, boolean selected) throws IllegalArgumentException {
+ for (int i = startPos; i < endPos && i < getItemCount(); i++) {
+ setSelected(i, selected);
+ }
+ notifyItemRangeChanged(startPos, (endPos - startPos));
+ }
+
+ protected void toggleSelection(int pos) {
+ setSelected(pos, !isSelected(pos));
+ notifyItemChanged(pos);
+
+ if (selectedIds.size() == 0) {
+ endSelectMode();
+ }
+ }
+
+ public boolean inActionMode() {
+ return actionMode != null;
+ }
+
+ public int getSelectedCount() {
+ return selectedIds.size();
+ }
+
+ private void toggleSelectAllIcon(MenuItem selectAllItem, boolean allSelected) {
+ if (allSelected) {
+ selectAllItem.setIcon(R.drawable.ic_select_none);
+ selectAllItem.setTitle(R.string.deselect_all_label);
+ } else {
+ selectAllItem.setIcon(R.drawable.ic_select_all);
+ selectAllItem.setTitle(R.string.select_all_label);
+ }
+ }
+
+ private void updateTitle() {
+ if (actionMode == null) {
+ return;
+ }
+ actionMode.setTitle(activity.getResources()
+ .getQuantityString(R.plurals.num_selected_label, selectedIds.size(),
+ selectedIds.size(), getItemCount()));
+ }
+
+ public void setOnSelectModeListener(OnSelectModeListener onSelectModeListener) {
+ this.onSelectModeListener = onSelectModeListener;
+ }
+
+ private void callOnEndSelectMode() {
+ if (onSelectModeListener != null) {
+ onSelectModeListener.onEndSelectMode();
+ }
+ }
+
+ public interface OnSelectModeListener {
+ void onStartSelectMode();
+
+ void onEndSelectMode();
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java
deleted file mode 100644
index b362a5a1d..000000000
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/AddToQueueActionButton.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.danoeh.antennapod.adapter.actionbutton;
-
-import android.content.Context;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.StringRes;
-
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.model.feed.FeedItem;
-
-class AddToQueueActionButton extends ItemActionButton {
-
- AddToQueueActionButton(FeedItem item) {
- super(item);
- }
-
- @Override
- @StringRes
- public int getLabel() {
- return R.string.add_to_queue_label;
- }
-
- @Override
- @DrawableRes
- public int getDrawable() {
- return R.drawable.ic_add;
- }
-
- @Override
- public void onClick(Context context) {
- MobileDownloadHelper.confirmMobileDownload(context, item);
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java
index afa86c9d7..dedf8e5e6 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java
@@ -3,7 +3,6 @@ package de.danoeh.antennapod.adapter.actionbutton;
import android.content.Context;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
-import android.widget.Toast;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.model.feed.FeedItem;
@@ -35,8 +34,8 @@ public class CancelDownloadActionButton extends ItemActionButton {
FeedMedia media = item.getMedia();
DownloadRequester.getInstance().cancelDownload(context, media);
if (UserPreferences.isEnableAutodownload()) {
- DBWriter.setFeedItemAutoDownload(media.getItem(), false);
- Toast.makeText(context, R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_LONG).show();
+ item.setAutoDownload(false);
+ DBWriter.setFeedItem(item);
}
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java
index c3e979dd8..7b922154e 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/DownloadActionButton.java
@@ -21,9 +21,9 @@ import de.danoeh.antennapod.core.util.NetworkUtils;
public class DownloadActionButton extends ItemActionButton {
private boolean isInQueue;
- public DownloadActionButton(FeedItem item, boolean isInQueue) {
+ public DownloadActionButton(FeedItem item) {
super(item);
- this.isInQueue = isInQueue;
+ this.isInQueue = item.isTagged(FeedItem.TAG_QUEUE);;
}
@Override
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java
index 12150293f..ad4f35786 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java
@@ -33,7 +33,7 @@ public abstract class ItemActionButton {
}
@NonNull
- public static ItemActionButton forItem(@NonNull FeedItem item, boolean isInQueue, boolean allowStream) {
+ public static ItemActionButton forItem(@NonNull FeedItem item) {
final FeedMedia media = item.getMedia();
if (media == null) {
return new MarkAsPlayedActionButton(item);
@@ -48,13 +48,10 @@ public abstract class ItemActionButton {
return new PlayActionButton(item);
} else if (isDownloadingMedia) {
return new CancelDownloadActionButton(item);
- } else if (UserPreferences.isStreamOverDownload() && allowStream) {
+ } else if (UserPreferences.isStreamOverDownload()) {
return new StreamActionButton(item);
- } else if (MobileDownloadHelper.userAllowedMobileDownloads()
- || !MobileDownloadHelper.userChoseAddToQueue() || isInQueue) {
- return new DownloadActionButton(item, isInQueue);
} else {
- return new AddToQueueActionButton(item);
+ return new DownloadActionButton(item);
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java
deleted file mode 100644
index 508ce74f4..000000000
--- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java
+++ /dev/null
@@ -1,466 +0,0 @@
-package de.danoeh.antennapod.dialog;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.PluralsRes;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.widget.Toolbar;
-import androidx.collection.ArrayMap;
-import androidx.fragment.app.Fragment;
-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.core.dialog.DownloadRequestErrorDialogCreator;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.DownloadRequestException;
-import de.danoeh.antennapod.core.storage.DownloadRequester;
-import de.danoeh.antennapod.core.util.FeedItemPermutors;
-import de.danoeh.antennapod.core.util.LongList;
-import de.danoeh.antennapod.model.feed.SortOrder;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
-
- public static final String TAG = "EpisodeActionFragment";
-
- public static final int ACTION_ADD_TO_QUEUE = 1;
- public static final int ACTION_REMOVE_FROM_QUEUE = 2;
- private static final int ACTION_MARK_PLAYED = 4;
- private static final int ACTION_MARK_UNPLAYED = 8;
- public static final int ACTION_DOWNLOAD = 16;
- public static final int ACTION_DELETE = 32;
- public static final int ACTION_ALL = ACTION_ADD_TO_QUEUE | ACTION_REMOVE_FROM_QUEUE
- | ACTION_MARK_PLAYED | ACTION_MARK_UNPLAYED | ACTION_DOWNLOAD | ACTION_DELETE;
-
- /**
- * Specify an action (defined by #flag) 's UI bindings.
- *
- * Includes: the menu / action item and the actual logic
- */
- private static class ActionBinding {
- int flag;
- @IdRes
- final int actionItemId;
- @NonNull
- final Runnable action;
-
- ActionBinding(int flag, @IdRes int actionItemId, @NonNull Runnable action) {
- this.flag = flag;
- this.actionItemId = actionItemId;
- this.action = action;
- }
- }
-
- private final List<? extends ActionBinding> actionBindings;
- private final Map<Long, FeedItem> idMap = new ArrayMap<>();
- private final List<FeedItem> episodes = new ArrayList<>();
- private int actions;
- private final List<String> titles = new ArrayList<>();
- private final LongList checkedIds = new LongList();
-
- private ListView mListView;
- private ArrayAdapter<String> mAdapter;
- private SpeedDialView mSpeedDialView;
- private Toolbar toolbar;
-
- public EpisodesApplyActionFragment() {
- actionBindings = Arrays.asList(
- new ActionBinding(ACTION_ADD_TO_QUEUE,
- R.id.add_to_queue_batch, this::queueChecked),
- new ActionBinding(ACTION_REMOVE_FROM_QUEUE,
- R.id.remove_from_queue_batch, this::removeFromQueueChecked),
- new ActionBinding(ACTION_MARK_PLAYED,
- R.id.mark_read_batch, this::markedCheckedPlayed),
- new ActionBinding(ACTION_MARK_UNPLAYED,
- R.id.mark_unread_batch, this::markedCheckedUnplayed),
- new ActionBinding(ACTION_DOWNLOAD,
- R.id.download_batch, this::downloadChecked),
- new ActionBinding(ACTION_DELETE,
- R.id.delete_batch, this::deleteChecked)
- );
- }
-
- public static EpisodesApplyActionFragment newInstance(List<FeedItem> items, int actions) {
- EpisodesApplyActionFragment f = new EpisodesApplyActionFragment();
- f.episodes.addAll(items);
- for (FeedItem episode : items) {
- f.idMap.put(episode.getId(), episode);
- }
- f.actions = actions;
- return f;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setRetainInstance(true);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.episodes_apply_action_fragment, container, false);
-
- toolbar = view.findViewById(R.id.toolbar);
- toolbar.inflateMenu(R.menu.episodes_apply_action_options);
- toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
- toolbar.setOnMenuItemClickListener(this);
- refreshToolbarState();
-
- mListView = view.findViewById(android.R.id.list);
- mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- mListView.setOnItemClickListener((listView, view1, position, rowId) -> {
- long id = episodes.get(position).getId();
- if (checkedIds.contains(id)) {
- checkedIds.remove(id);
- } else {
- checkedIds.add(id);
- }
- refreshCheckboxes();
- });
- mListView.setOnItemLongClickListener((adapterView, view12, position, id) -> {
- new AlertDialog.Builder(getActivity())
- .setItems(R.array.batch_long_press_options, (dialogInterface, item) -> {
- int direction;
- if (item == 0) {
- direction = -1;
- } else {
- direction = 1;
- }
-
- int currentPosition = position + direction;
- while (currentPosition >= 0 && currentPosition < episodes.size()) {
- long id1 = episodes.get(currentPosition).getId();
- if (!checkedIds.contains(id1)) {
- checkedIds.add(id1);
- }
- currentPosition += direction;
- }
- refreshCheckboxes();
- }).show();
- return true;
- });
-
- titles.clear();
- for (FeedItem episode : episodes) {
- titles.add(episode.getTitle());
- }
-
- mAdapter = new ArrayAdapter<>(getActivity(),
- R.layout.simple_list_item_multiple_choice_on_start, titles);
- mListView.setAdapter(mAdapter);
-
- // Init action UI (via a FAB Speed Dial)
- mSpeedDialView = view.findViewById(R.id.fabSD);
- mSpeedDialView.inflate(R.menu.episodes_apply_action_speeddial);
-
- // show only specified actions, and bind speed dial UIs to the actual logic
- for (ActionBinding binding : actionBindings) {
- if ((actions & binding.flag) == 0) {
- mSpeedDialView.removeActionItemById(binding.actionItemId);
- }
- }
-
- mSpeedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
- @Override
- public boolean onMainActionSelected() {
- return false;
- }
-
- @Override
- public void onToggleChanged(boolean open) {
- if (open && checkedIds.size() == 0) {
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
- Snackbar.LENGTH_SHORT);
- mSpeedDialView.close();
- }
- }
- });
- mSpeedDialView.setOnActionSelectedListener(actionItem -> {
- ActionBinding selectedBinding = null;
- for (ActionBinding binding : actionBindings) {
- if (actionItem.getId() == binding.actionItemId) {
- selectedBinding = binding;
- break;
- }
- }
- if (selectedBinding != null) {
- selectedBinding.action.run();
- } else {
- Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + actionItem.getId());
- }
- return true;
- });
- refreshCheckboxes();
- return view;
- }
-
- public void refreshToolbarState() {
- MenuItem selectAllItem = toolbar.getMenu().findItem(R.id.select_toggle);
- if (checkedIds.size() == episodes.size()) {
- selectAllItem.setIcon(R.drawable.ic_select_none);
- selectAllItem.setTitle(R.string.deselect_all_label);
- } else {
- selectAllItem.setIcon(R.drawable.ic_select_all);
- selectAllItem.setTitle(R.string.select_all_label);
- }
- }
-
- private static final Map<Integer, SortOrder> menuItemIdToSortOrder;
- static {
- Map<Integer, SortOrder> map = new ArrayMap<>();
- map.put(R.id.sort_title_a_z, SortOrder.EPISODE_TITLE_A_Z);
- map.put(R.id.sort_title_z_a, SortOrder.EPISODE_TITLE_Z_A);
- map.put(R.id.sort_date_new_old, SortOrder.DATE_NEW_OLD);
- map.put(R.id.sort_date_old_new, SortOrder.DATE_OLD_NEW);
- map.put(R.id.sort_duration_long_short, SortOrder.DURATION_LONG_SHORT);
- map.put(R.id.sort_duration_short_long, SortOrder.DURATION_SHORT_LONG);
- menuItemIdToSortOrder = Collections.unmodifiableMap(map);
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- @StringRes int resId = 0;
- switch (item.getItemId()) {
- case R.id.select_options:
- return true;
- case R.id.select_toggle:
- if (checkedIds.size() == episodes.size()) {
- checkNone();
- } else {
- checkAll();
- }
- return true;
- case R.id.check_all:
- checkAll();
- resId = R.string.selected_all_label;
- break;
- case R.id.check_none:
- checkNone();
- resId = R.string.deselected_all_label;
- break;
- case R.id.check_played:
- checkPlayed(true);
- resId = R.string.selected_played_label;
- break;
- case R.id.check_unplayed:
- checkPlayed(false);
- resId = R.string.selected_unplayed_label;
- break;
- case R.id.check_downloaded:
- checkDownloaded(true);
- resId = R.string.selected_downloaded_label;
- break;
- case R.id.check_not_downloaded:
- checkDownloaded(false);
- resId = R.string.selected_not_downloaded_label;
- break;
- case R.id.check_queued:
- checkQueued(true);
- resId = R.string.selected_queued_label;
- break;
- case R.id.check_not_queued:
- checkQueued(false);
- resId = R.string.selected_not_queued_label;
- break;
- case R.id.check_has_media:
- checkWithMedia();
- resId = R.string.selected_has_media_label;
- break;
- default: // handle various sort options
- SortOrder sortOrder = menuItemIdToSortOrder.get(item.getItemId());
- if (sortOrder != null) {
- sort(sortOrder);
- return true;
- }
- }
- if (resId != 0) {
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(resId, Snackbar.LENGTH_SHORT);
- return true;
- } else {
- return false;
- }
- }
-
- private void sort(@NonNull SortOrder sortOrder) {
- FeedItemPermutors.getPermutor(sortOrder)
- .reorder(episodes);
- refreshTitles();
- refreshCheckboxes();
- }
-
- private void checkAll() {
- for (FeedItem episode : episodes) {
- if (!checkedIds.contains(episode.getId())) {
- checkedIds.add(episode.getId());
- }
- }
- refreshCheckboxes();
- }
-
- private void checkNone() {
- checkedIds.clear();
- refreshCheckboxes();
- }
-
- private void checkPlayed(boolean isPlayed) {
- for (FeedItem episode : episodes) {
- if (episode.isPlayed() == isPlayed) {
- if (!checkedIds.contains(episode.getId())) {
- checkedIds.add(episode.getId());
- }
- } else {
- if (checkedIds.contains(episode.getId())) {
- checkedIds.remove(episode.getId());
- }
- }
- }
- refreshCheckboxes();
- }
-
- private void checkDownloaded(boolean isDownloaded) {
- for (FeedItem episode : episodes) {
- if (episode.hasMedia() && episode.getMedia().isDownloaded() == isDownloaded) {
- if (!checkedIds.contains(episode.getId())) {
- checkedIds.add(episode.getId());
- }
- } else {
- if (checkedIds.contains(episode.getId())) {
- checkedIds.remove(episode.getId());
- }
- }
- }
- refreshCheckboxes();
- }
-
- private void checkQueued(boolean isQueued) {
- for (FeedItem episode : episodes) {
- if (episode.isTagged(FeedItem.TAG_QUEUE) == isQueued) {
- checkedIds.add(episode.getId());
- } else {
- checkedIds.remove(episode.getId());
- }
- }
- refreshCheckboxes();
- }
-
- private void checkWithMedia() {
- for (FeedItem episode : episodes) {
- if (episode.hasMedia()) {
- checkedIds.add(episode.getId());
- } else {
- checkedIds.remove(episode.getId());
- }
- }
- refreshCheckboxes();
- }
-
- private void refreshTitles() {
- titles.clear();
- for (FeedItem episode : episodes) {
- titles.add(episode.getTitle());
- }
- mAdapter.notifyDataSetChanged();
- }
-
- private void refreshCheckboxes() {
- for (int i = 0; i < episodes.size(); i++) {
- FeedItem episode = episodes.get(i);
- boolean checked = checkedIds.contains(episode.getId());
- mListView.setItemChecked(i, checked);
- }
- refreshToolbarState();
- toolbar.setTitle(getResources().getQuantityString(R.plurals.num_selected_label,
- checkedIds.size(), checkedIds.size()));
- }
-
- private void queueChecked() {
- // Check if an episode actually contains any media files before adding it to queue
- LongList toQueue = new LongList(checkedIds.size());
- for (FeedItem episode : episodes) {
- if (checkedIds.contains(episode.getId()) && episode.hasMedia()) {
- toQueue.add(episode.getId());
- }
- }
- DBWriter.addQueueItem(getActivity(), true, toQueue.toArray());
- close(R.plurals.added_to_queue_batch_label, toQueue.size());
- }
-
- private void removeFromQueueChecked() {
- DBWriter.removeQueueItem(getActivity(), true, checkedIds.toArray());
- close(R.plurals.removed_from_queue_batch_label, checkedIds.size());
- }
-
- private void markedCheckedPlayed() {
- DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds.toArray());
- close(R.plurals.marked_read_batch_label, checkedIds.size());
- }
-
- private void markedCheckedUnplayed() {
- DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds.toArray());
- close(R.plurals.marked_unread_batch_label, checkedIds.size());
- }
-
- private void downloadChecked() {
- // download the check episodes in the same order as they are currently displayed
- List<FeedItem> toDownload = new ArrayList<>(checkedIds.size());
- for (FeedItem episode : episodes) {
- if (checkedIds.contains(episode.getId()) && episode.hasMedia() && !episode.getFeed().isLocalFeed()) {
- toDownload.add(episode);
- }
- }
- try {
- DownloadRequester.getInstance().downloadMedia(getActivity(), true, toDownload.toArray(new FeedItem[0]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage());
- }
- close(R.plurals.downloading_batch_label, toDownload.size());
- }
-
- private void deleteChecked() {
- int countHasMedia = 0;
- int countNoMedia = 0;
- for (long id : checkedIds.toArray()) {
- FeedItem episode = idMap.get(id);
- if (episode.hasMedia() && episode.getMedia().isDownloaded()) {
- countHasMedia++;
- DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId());
- } else {
- countNoMedia++;
- }
- }
- closeMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
- }
-
- private void close(@PluralsRes int msgId, int numItems) {
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- getResources().getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG);
- getActivity().getSupportFragmentManager().popBackStack();
- }
-
- private void closeMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- getResources().getQuantityString(msgId,
- (countHasMedia + countNoMedia),
- (countHasMedia + countNoMedia), countHasMedia),
- Snackbar.LENGTH_LONG);
- getActivity().getSupportFragmentManager().popBackStack();
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java
new file mode 100644
index 000000000..53c2697ce
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/SwipeActionsDialog.java
@@ -0,0 +1,188 @@
+package de.danoeh.antennapod.dialog;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.gridlayout.widget.GridLayout;
+
+import com.annimon.stream.Stream;
+
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.databinding.FeeditemlistItemBinding;
+import de.danoeh.antennapod.databinding.SwipeactionsDialogBinding;
+import de.danoeh.antennapod.databinding.SwipeactionsPickerBinding;
+import de.danoeh.antennapod.databinding.SwipeactionsPickerItemBinding;
+import de.danoeh.antennapod.databinding.SwipeactionsRowBinding;
+import de.danoeh.antennapod.fragment.EpisodesFragment;
+import de.danoeh.antennapod.fragment.FeedItemlistFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeAction;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
+
+public class SwipeActionsDialog {
+ private static final int LEFT = 1;
+ private static final int RIGHT = 0;
+
+ private final Context context;
+ private final String tag;
+
+ private SwipeAction rightAction;
+ private SwipeAction leftAction;
+ private List<SwipeAction> keys;
+
+ public SwipeActionsDialog(Context context, String tag) {
+ this.context = context;
+ this.tag = tag;
+ }
+
+ public void show(Callback prefsChanged) {
+ SwipeActions.Actions actions = SwipeActions.getPrefsWithDefaults(context, tag);
+ leftAction = actions.left;
+ rightAction = actions.right;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+
+ keys = SwipeActions.swipeActions;
+
+ String forFragment = "";
+ switch (tag) {
+ /*case InboxFragment.TAG:
+ forFragment = context.getString(R.string.inbox_label);
+ break;*/
+ case EpisodesFragment.TAG:
+ forFragment = context.getString(R.string.episodes_label);
+ break;
+ case FeedItemlistFragment.TAG:
+ forFragment = context.getString(R.string.feeds_label);
+ break;
+ case QueueFragment.TAG:
+ forFragment = context.getString(R.string.queue_label);
+ keys = Stream.of(keys).filter(a -> !a.getId().equals(SwipeAction.ADD_TO_QUEUE)
+ && !a.getId().equals(SwipeAction.REMOVE_FROM_INBOX)).toList();
+ break;
+ default: break;
+ }
+
+ if (!tag.equals(QueueFragment.TAG)) {
+ keys = Stream.of(keys).filter(a -> !a.getId().equals(SwipeAction.REMOVE_FROM_QUEUE)).toList();
+ }
+
+ builder.setTitle(context.getString(R.string.swipeactions_label) + " - " + forFragment);
+ SwipeactionsDialogBinding viewBinding = SwipeactionsDialogBinding.inflate(LayoutInflater.from(context));
+ builder.setView(viewBinding.getRoot());
+
+ viewBinding.enableSwitch.setOnCheckedChangeListener((compoundButton, b) -> {
+ viewBinding.actionLeftContainer.getRoot().setAlpha(b ? 1.0f : 0.4f);
+ viewBinding.actionRightContainer.getRoot().setAlpha(b ? 1.0f : 0.4f);
+ });
+
+ viewBinding.enableSwitch.setChecked(SwipeActions.isSwipeActionEnabled(context, tag));
+
+ setupSwipeDirectionView(viewBinding.actionLeftContainer, LEFT);
+ setupSwipeDirectionView(viewBinding.actionRightContainer, RIGHT);
+
+ builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
+ savePrefs(tag, rightAction.getId(), leftAction.getId());
+ saveActionsEnabledPrefs(viewBinding.enableSwitch.isChecked());
+ prefsChanged.onCall();
+ });
+
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.create().show();
+ }
+
+ private void setupSwipeDirectionView(SwipeactionsRowBinding view, int direction) {
+ SwipeAction action = direction == LEFT ? leftAction : rightAction;
+
+ view.swipeDirectionLabel.setText(direction == LEFT ? R.string.swipe_left : R.string.swipe_right);
+ view.swipeActionLabel.setText(action.getTitle(context));
+ populateMockEpisode(view.mockEpisode);
+ if (direction == RIGHT && view.previewContainer.getChildAt(0) != view.swipeIcon) {
+ view.previewContainer.removeView(view.swipeIcon);
+ view.previewContainer.addView(view.swipeIcon, 0);
+ }
+
+ view.swipeIcon.setImageResource(action.getActionIcon());
+ view.swipeIcon.setColorFilter(ThemeUtils.getColorFromAttr(context, action.getActionColor()));
+
+ view.changeButton.setOnClickListener(v -> showPicker(view, direction));
+ view.previewContainer.setOnClickListener(v -> showPicker(view, direction));
+ }
+
+ private void showPicker(SwipeactionsRowBinding view, int direction) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(direction == LEFT ? R.string.swipe_left : R.string.swipe_right);
+
+ SwipeactionsPickerBinding picker = SwipeactionsPickerBinding.inflate(LayoutInflater.from(context));
+ builder.setView(picker.getRoot());
+ builder.setNegativeButton(R.string.cancel_label, null);
+ AlertDialog dialog = builder.show();
+
+ for (int i = 0; i < keys.size(); i++) {
+ final int actionIndex = i;
+ SwipeAction action = keys.get(actionIndex);
+ SwipeactionsPickerItemBinding item = SwipeactionsPickerItemBinding.inflate(LayoutInflater.from(context));
+ item.swipeActionLabel.setText(action.getTitle(context));
+
+ Drawable icon = DrawableCompat.wrap(AppCompatResources.getDrawable(context, action.getActionIcon()));
+ DrawableCompat.setTintMode(icon, PorterDuff.Mode.SRC_ATOP);
+ if ((direction == LEFT && leftAction == action) || (direction == RIGHT && rightAction == action)) {
+ DrawableCompat.setTint(icon, ThemeUtils.getColorFromAttr(context, action.getActionColor()));
+ item.swipeActionLabel.setTextColor(ThemeUtils.getColorFromAttr(context, action.getActionColor()));
+ } else {
+ DrawableCompat.setTint(icon, ThemeUtils.getColorFromAttr(context, R.attr.action_icon_color));
+ }
+ item.swipeIcon.setImageDrawable(icon);
+
+ item.getRoot().setOnClickListener(v -> {
+ if (direction == LEFT) {
+ leftAction = keys.get(actionIndex);
+ } else {
+ rightAction = keys.get(actionIndex);
+ }
+ setupSwipeDirectionView(view, direction);
+ dialog.dismiss();
+ });
+ GridLayout.LayoutParams param = new GridLayout.LayoutParams(
+ GridLayout.spec(GridLayout.UNDEFINED, GridLayout.BASELINE),
+ GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f));
+ param.width = 0;
+ picker.pickerGridLayout.addView(item.getRoot(), param);
+ }
+ picker.pickerGridLayout.setColumnCount(2);
+ picker.pickerGridLayout.setRowCount((keys.size() + 1) / 2);
+ }
+
+ private void populateMockEpisode(FeeditemlistItemBinding view) {
+ view.container.setAlpha(0.3f);
+ view.secondaryActionButton.secondaryActionButton.setVisibility(View.GONE);
+ view.dragHandle.setVisibility(View.GONE);
+ view.statusUnread.setText("███");
+ view.txtvTitle.setText("███████");
+ view.txtvPosition.setText("█████");
+ }
+
+ private void savePrefs(String tag, String right, String left) {
+ SharedPreferences prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + tag, right + "," + left).apply();
+ }
+
+ private void saveActionsEnabledPrefs(Boolean enabled) {
+ SharedPreferences prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(SwipeActions.KEY_PREFIX_NO_ACTION + tag, enabled).apply();
+ }
+
+ public interface Callback {
+ void onCall();
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
index c6927c69f..6011872cf 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
@@ -131,8 +131,8 @@ public class VariableSpeedDialog extends DialogFragment {
});
holder.chip.setOnClickListener(v -> {
if (controller != null) {
+ dismiss();
controller.setPlaybackSpeed(speed);
- notifyDataSetChanged();
}
});
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
index a84c34b7e..168133c7a 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
@@ -75,7 +75,6 @@ public class AudioPlayerFragment extends Fragment implements
public static final int POS_COVER = 0;
public static final int POS_DESCRIPTION = 1;
private static final int NUM_CONTENT_FRAGMENTS = 2;
- private static final float EPSILON = 0.001f;
PlaybackSpeedIndicatorView butPlaybackSpeed;
TextView txtvPlaybackSpeed;
@@ -136,7 +135,7 @@ public class AudioPlayerFragment extends Fragment implements
setupLengthTextView();
setupControlButtons();
- setupPlaybackSpeedButton();
+ butPlaybackSpeed.setOnClickListener(v -> new VariableSpeedDialog().show(getChildFragmentManager(), null));
sbPosition.setOnSeekBarChangeListener(this);
pager = root.findViewById(R.id.pager);
@@ -244,40 +243,6 @@ public class AudioPlayerFragment extends Fragment implements
});
}
- private void setupPlaybackSpeedButton() {
- butPlaybackSpeed.setOnClickListener(v -> {
- if (controller == null) {
- return;
- }
- List<Float> availableSpeeds = UserPreferences.getPlaybackSpeedArray();
- float currentSpeed = controller.getCurrentPlaybackSpeedMultiplier();
-
- int newSpeedIndex = 0;
- while (newSpeedIndex < availableSpeeds.size()
- && availableSpeeds.get(newSpeedIndex) < currentSpeed + EPSILON) {
- newSpeedIndex++;
- }
-
- float newSpeed;
- if (availableSpeeds.size() == 0) {
- newSpeed = 1.0f;
- } else if (newSpeedIndex == availableSpeeds.size()) {
- newSpeed = availableSpeeds.get(0);
- } else {
- newSpeed = availableSpeeds.get(newSpeedIndex);
- }
-
- controller.setPlaybackSpeed(newSpeed);
- loadMediaInfo(false);
- });
- butPlaybackSpeed.setOnLongClickListener(v -> {
- new VariableSpeedDialog().show(getChildFragmentManager(), null);
- return true;
- });
- butPlaybackSpeed.setVisibility(View.VISIBLE);
- txtvPlaybackSpeed.setVisibility(View.VISIBLE);
- }
-
protected void updatePlaybackSpeedButton(Playable media) {
if (butPlaybackSpeed == null || controller == null) {
return;
@@ -286,8 +251,6 @@ public class AudioPlayerFragment extends Fragment implements
String speedStr = new DecimalFormat("0.00").format(speed);
txtvPlaybackSpeed.setText(speedStr);
butPlaybackSpeed.setSpeed(speed);
- butPlaybackSpeed.setVisibility(View.VISIBLE);
- txtvPlaybackSpeed.setVisibility(View.VISIBLE);
}
private void loadMediaInfo(boolean includingChapters) {
@@ -550,21 +513,21 @@ public class AudioPlayerFragment extends Fragment implements
if (feedItem != null && FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), feedItem)) {
return true;
}
- switch (item.getItemId()) {
- case R.id.disable_sleeptimer_item: // Fall-through
- case R.id.set_sleeptimer_item:
- new SleepTimerDialog().show(getChildFragmentManager(), "SleepTimerDialog");
- return true;
- case R.id.audio_controls:
- PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance();
- dialog.show(getChildFragmentManager(), "playback_controls");
- return true;
- case R.id.open_feed_item:
- if (feedItem != null) {
- Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId());
- startActivity(intent);
- }
- return true;
+
+ final int itemId = item.getItemId();
+ if (itemId == R.id.disable_sleeptimer_item || itemId == R.id.set_sleeptimer_item) {
+ new SleepTimerDialog().show(getChildFragmentManager(), "SleepTimerDialog");
+ return true;
+ } else if (itemId == R.id.audio_controls) {
+ PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance();
+ dialog.show(getChildFragmentManager(), "playback_controls");
+ return true;
+ } else if (itemId == R.id.open_feed_item) {
+ if (feedItem != null) {
+ Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId());
+ startActivity(intent);
+ }
+ return true;
}
return false;
}
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 66f85d84d..f02c1a6ac 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.fragment;
import android.os.Bundle;
import android.util.Log;
+import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -12,6 +13,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
+
+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;
@@ -22,13 +27,13 @@ import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
+import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
-import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
@@ -45,13 +50,11 @@ import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.List;
-import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE;
-import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
-
/**
* Displays all completed downloads and provides a button to delete them.
*/
-public class CompletedDownloadsFragment extends Fragment {
+public class CompletedDownloadsFragment extends Fragment implements
+ EpisodeItemListAdapter.OnSelectModeListener {
private static final String TAG = CompletedDownloadsFragment.class.getSimpleName();
@@ -64,6 +67,8 @@ public class CompletedDownloadsFragment extends Fragment {
private boolean isUpdatingFeeds = false;
+ private SpeedDialView speedDialView;
+
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@@ -74,9 +79,38 @@ public class CompletedDownloadsFragment extends Fragment {
recyclerView = root.findViewById(R.id.recyclerView);
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity());
+ adapter.setOnSelectModeListener(this);
recyclerView.setAdapter(adapter);
progressBar = root.findViewById(R.id.progLoading);
+ speedDialView = root.findViewById(R.id.fabSD);
+ speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
+ speedDialView.removeActionItemById(R.id.download_batch);
+ speedDialView.removeActionItemById(R.id.mark_read_batch);
+ speedDialView.removeActionItemById(R.id.mark_unread_batch);
+ speedDialView.removeActionItemById(R.id.remove_from_queue_batch);
+ speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
+ @Override
+ public boolean onMainActionSelected() {
+ return false;
+ }
+
+ @Override
+ public void onToggleChanged(boolean open) {
+ if (open && adapter.getSelectedCount() == 0) {
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
+ Snackbar.LENGTH_SHORT);
+ speedDialView.close();
+ }
+ }
+ });
+ speedDialView.setOnActionSelectedListener(actionItem -> {
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
+ .handleAction(actionItem.getId());
+ adapter.endSelectMode();
+ return true;
+ });
+
addEmptyView();
EventBus.getDefault().register(this);
return root;
@@ -85,6 +119,7 @@ public class CompletedDownloadsFragment extends Fragment {
@Override
public void onDestroyView() {
EventBus.getDefault().unregister(this);
+ adapter.endSelectMode();
super.onDestroyView();
}
@@ -105,17 +140,12 @@ public class CompletedDownloadsFragment extends Fragment {
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
menu.findItem(R.id.clear_logs_item).setVisible(false);
- menu.findItem(R.id.episode_actions).setVisible(items.size() > 0);
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.episode_actions) {
- ((MainActivity) requireActivity())
- .loadChildFragment(EpisodesApplyActionFragment.newInstance(items, ACTION_DELETE | ACTION_ADD_TO_QUEUE));
- return true;
- } else if (item.getItemId() == R.id.refresh_item) {
+ if (item.getItemId() == R.id.refresh_item) {
AutoUpdateManager.runImmediate(requireContext());
return true;
}
@@ -135,11 +165,15 @@ public class CompletedDownloadsFragment extends Fragment {
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
}
+ if (adapter.onContextItemSelected(item)) {
+ return true;
+ }
+
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@@ -151,7 +185,6 @@ public class CompletedDownloadsFragment extends Fragment {
emptyView.attachToRecyclerView(recyclerView);
}
-
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedItemEvent event) {
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
@@ -221,6 +254,17 @@ public class CompletedDownloadsFragment extends Fragment {
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
+ @Override
+ public void onStartSelectMode() {
+ speedDialView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onEndSelectMode() {
+ speedDialView.close();
+ speedDialView.setVisibility(View.GONE);
+ }
+
private static class CompletedDownloadsListAdapter extends EpisodeItemListAdapter {
public CompletedDownloadsListAdapter(MainActivity mainActivity) {
@@ -232,5 +276,13 @@ public class CompletedDownloadsFragment extends Fragment {
DeleteActionButton actionButton = new DeleteActionButton(getItem(pos));
actionButton.configure(holder.secondaryActionButton, holder.secondaryActionIcon, 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);
+ }
+ }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
index 1f6067125..85bc3facf 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
@@ -11,7 +11,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@@ -23,7 +22,6 @@ import de.danoeh.antennapod.adapter.DownloadLogAdapter;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
@@ -34,6 +32,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.view.EmptyViewHandler;
import io.reactivex.Observable;
@@ -111,13 +110,11 @@ public class DownloadLogFragment extends ListFragment {
DownloadRequest downloadRequest = ((Downloader) item).getDownloadRequest();
DownloadRequester.getInstance().cancelDownload(getActivity(), downloadRequest.getSource());
- if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
- && UserPreferences.isEnableAutodownload()) {
+ if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId());
- DBWriter.setFeedItemAutoDownload(media.getItem(), false);
-
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_SHORT);
+ FeedItem feedItem = media.getItem();
+ feedItem.setAutoDownload(false);
+ DBWriter.setFeedItem(feedItem);
}
} else if (item instanceof DownloadStatus) {
DownloadStatus status = (DownloadStatus) item;
@@ -164,7 +161,6 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
- menu.findItem(R.id.episode_actions).setVisible(false);
menu.findItem(R.id.clear_logs_item).setVisible(!downloadLog.isEmpty());
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
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 e88ef432c..59556a340 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
@@ -122,43 +122,42 @@ public abstract class EpisodesListFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (!super.onOptionsItemSelected(item)) {
- switch (item.getItemId()) {
- case R.id.refresh_item:
- AutoUpdateManager.runImmediate(requireContext());
- return true;
- case R.id.mark_all_read_item:
- ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
- R.string.mark_all_read_label,
- R.string.mark_all_read_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markAllItemsRead();
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- R.string.mark_all_read_msg, Toast.LENGTH_SHORT);
- }
- };
- markAllReadConfirmationDialog.createNewDialog().show();
- return true;
- case R.id.remove_all_new_flags_item:
- ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
- R.string.remove_all_new_flags_label,
- R.string.remove_all_new_flags_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.removeAllNewFlags();
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT);
- }
- };
- removeAllNewFlagsConfirmationDialog.createNewDialog().show();
- return true;
- default:
- return false;
+ final int itemId = item.getItemId();
+ if (itemId == R.id.refresh_item) {
+ AutoUpdateManager.runImmediate(requireContext());
+ return true;
+ } else if (itemId == R.id.mark_all_read_item) {
+ ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
+ R.string.mark_all_read_label,
+ R.string.mark_all_read_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.markAllItemsRead();
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(
+ R.string.mark_all_read_msg, Toast.LENGTH_SHORT);
+ }
+ };
+ markAllReadConfirmationDialog.createNewDialog().show();
+ return true;
+ } else if (itemId == R.id.remove_all_new_flags_item) {
+ ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
+ R.string.remove_all_new_flags_label,
+ R.string.remove_all_new_flags_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.removeAllNewFlags();
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(
+ R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT);
+ }
+ };
+ removeAllNewFlagsConfirmationDialog.createNewDialog().show();
+ return true;
}
+ return false;
} else {
return true;
}
@@ -177,11 +176,11 @@ public abstract class EpisodesListFragment extends Fragment {
return true; // avoids that the position is reset when we need it in the submenu
}
- if (listAdapter.getSelectedItem() == null) {
+ if (listAdapter.getLongPressedItem() == null) {
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
return super.onContextItemSelected(item);
}
- FeedItem selectedItem = listAdapter.getSelectedItem();
+ FeedItem selectedItem = listAdapter.getLongPressedItem();
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
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 de27ff0af..9c5f5079d 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -1,13 +1,14 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.Intent;
+import android.content.res.Configuration;
import android.graphics.LightingColorFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@@ -18,6 +19,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatDrawableManager;
@@ -30,23 +32,33 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.snackbar.Snackbar;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconTextView;
+import com.leinardi.android.speeddial.SpeedDialView;
+
+import org.apache.commons.lang3.Validate;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.util.List;
+import java.util.Set;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.DownloaderUpdate;
+import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
+import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
-import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.glide.FastBlurTransformation;
import de.danoeh.antennapod.core.service.download.DownloadService;
@@ -58,13 +70,17 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
-import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.dialog.FilterDialog;
import de.danoeh.antennapod.dialog.RemoveFeedDialog;
import de.danoeh.antennapod.dialog.RenameFeedDialog;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
+import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
+import de.danoeh.antennapod.model.feed.Feed;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
import de.danoeh.antennapod.view.ToolbarIconTintManager;
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
@@ -72,24 +88,18 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
-import org.apache.commons.lang3.Validate;
-import org.greenrobot.eventbus.EventBus;
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-
-import java.util.List;
-import java.util.Set;
/**
* Displays a list of FeedItems.
*/
public class FeedItemlistFragment extends Fragment implements AdapterView.OnItemClickListener,
- Toolbar.OnMenuItemClickListener {
- private static final String TAG = "ItemlistFragment";
+ Toolbar.OnMenuItemClickListener, EpisodeItemListAdapter.OnSelectModeListener {
+ public static final String TAG = "ItemlistFragment";
private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
private static final String KEY_UP_ARROW = "up_arrow";
private FeedItemListAdapter adapter;
+ private SwipeActions swipeActions;
private MoreContentListFooterUtil nextPageLoader;
private ProgressBar progressBar;
@@ -106,6 +116,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
private View header;
private Toolbar toolbar;
private ToolbarIconTintManager iconTintManager;
+ private SpeedDialView speedDialView;
+
private boolean displayUpArrow;
private long feedID;
@@ -158,6 +170,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
progressBar = root.findViewById(R.id.progLoading);
+ progressBar.setVisibility(View.VISIBLE);
txtvTitle = root.findViewById(R.id.txtvTitle);
txtvAuthor = root.findViewById(R.id.txtvAuthor);
imgvBackground = root.findViewById(R.id.imgvBackground);
@@ -222,6 +235,31 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
});
loadItems();
+
+ // Init action UI (via a FAB Speed Dial)
+ speedDialView = root.findViewById(R.id.fabSD);
+ speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
+ speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
+ @Override
+ public boolean onMainActionSelected() {
+ return false;
+ }
+
+ @Override
+ public void onToggleChanged(boolean open) {
+ if (open && adapter.getSelectedCount() == 0) {
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
+ Snackbar.LENGTH_SHORT);
+ speedDialView.close();
+ }
+ }
+ });
+ speedDialView.setOnActionSelectedListener(actionItem -> {
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
+ .handleAction(actionItem.getId());
+ adapter.endSelectMode();
+ return true;
+ });
return root;
}
@@ -233,6 +271,9 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (disposable != null) {
disposable.dispose();
}
+ if (adapter != null) {
+ adapter.endSelectMode();
+ }
adapter = null;
}
@@ -291,37 +332,28 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (feedMenuHandled) {
return true;
}
- switch (item.getItemId()) {
- case R.id.episode_actions:
- int actions = EpisodesApplyActionFragment.ACTION_ALL;
- if (feed.isLocalFeed()) {
- // turn off download and delete actions for local feed
- actions ^= EpisodesApplyActionFragment.ACTION_DOWNLOAD;
- actions ^= EpisodesApplyActionFragment.ACTION_DELETE;
- }
- EpisodesApplyActionFragment fragment = EpisodesApplyActionFragment
- .newInstance(feed.getItems(), actions);
- ((MainActivity) getActivity()).loadChildFragment(fragment);
- return true;
- case R.id.rename_item:
- new RenameFeedDialog(getActivity(), feed).show();
- return true;
- case R.id.remove_item:
- RemoveFeedDialog.show(getContext(), feed, () ->
- ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null));
- return true;
- default:
- return false;
+ final int itemId = item.getItemId();
+ if (itemId == R.id.rename_item) {
+ new RenameFeedDialog(getActivity(), feed).show();
+ return true;
+ } else if (itemId == R.id.remove_item) {
+ RemoveFeedDialog.show(getContext(), feed, () ->
+ ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null));
+ return true;
}
+ return false;
}
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
}
+ if (adapter.onContextItemSelected(item)) {
+ return true;
+ }
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
@@ -393,6 +425,34 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
}
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void favoritesChanged(FavoritesEvent event) {
+ updateUi();
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onQueueChanged(QueueEvent event) {
+ updateUi();
+ }
+
+ @Override
+ public void onStartSelectMode() {
+ swipeActions.detach();
+ if (feed.isLocalFeed()) {
+ speedDialView.removeActionItemById(R.id.download_batch);
+ speedDialView.removeActionItemById(R.id.delete_batch);
+ }
+ speedDialView.setVisibility(View.VISIBLE);
+ refreshToolbarState();
+ }
+
+ @Override
+ public void onEndSelectMode() {
+ speedDialView.close();
+ speedDialView.setVisibility(View.GONE);
+ swipeActions.attachTo(recyclerView);
+ }
+
private void updateUi() {
loadItems();
updateSyncProgressBarVisibility();
@@ -433,11 +493,14 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (adapter == null) {
recyclerView.setAdapter(null);
adapter = new FeedItemListAdapter((MainActivity) getActivity());
+ adapter.setOnSelectModeListener(this);
recyclerView.setAdapter(adapter);
+ swipeActions = new SwipeActions(this, TAG).attachTo(recyclerView);
}
progressBar.setVisibility(View.GONE);
if (feed != null) {
adapter.updateItems(feed.getItems());
+ swipeActions.setFilter(feed.getItemFilter());
}
refreshToolbarState();
@@ -551,7 +614,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (disposable != null) {
disposable.dispose();
}
- progressBar.setVisibility(View.VISIBLE);
disposable = Observable.fromCallable(this::loadData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -592,5 +654,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
protected void beforeBindViewHolder(EpisodeItemViewHolder holder, int pos) {
holder.coverHolder.setVisibility(View.GONE);
}
+
+ @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);
+ }
+ }
}
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java
index 9e57497bf..dbc7f2ae3 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java
@@ -1,7 +1,5 @@
package de.danoeh.antennapod.fragment;
-import android.content.Context;
-import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -16,7 +14,6 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
import androidx.recyclerview.widget.RecyclerView;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent;
import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent;
import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent;
@@ -384,8 +381,6 @@ public class FeedSettingsFragment extends Fragment {
feedPreferences.setAutoDownload(checked);
DBWriter.setFeedPreferences(feedPreferences);
updateAutoDownloadEnabled();
- ApplyToEpisodesDialog dialog = new ApplyToEpisodesDialog(getActivity(), checked);
- dialog.createNewDialog().show();
pref.setChecked(checked);
return false;
});
@@ -417,22 +412,5 @@ public class FeedSettingsFragment extends Fragment {
return false;
});
}
-
- private class ApplyToEpisodesDialog extends ConfirmationDialog {
- private final boolean autoDownload;
-
- ApplyToEpisodesDialog(Context context, boolean autoDownload) {
- super(context, R.string.auto_download_apply_to_items_title,
- R.string.auto_download_apply_to_items_message);
- this.autoDownload = autoDownload;
- setPositiveText(R.string.yes);
- setNegativeText(R.string.no);
- }
-
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- DBWriter.setFeedsItemsAutoDownload(feed, autoDownload);
- }
- }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
index c83ed4722..5a2061a5f 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
@@ -349,7 +349,7 @@ public class ItemFragment extends Fragment {
if (DownloadRequester.getInstance().isDownloadingFile(media)) {
actionButton2 = new CancelDownloadActionButton(item);
} else if (!media.isDownloaded()) {
- actionButton2 = new DownloadActionButton(item, false);
+ actionButton2 = new DownloadActionButton(item);
} else {
actionButton2 = new DeleteActionButton(item);
}
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 98ba59980..7acb94ab3 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java
@@ -143,43 +143,42 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS
}
private boolean onFeedContextMenuClicked(Feed feed, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.remove_all_new_flags_item:
- ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getContext(),
- R.string.remove_all_new_flags_label,
- R.string.remove_all_new_flags_confirmation_msg) {
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.removeFeedNewFlag(feed.getId());
- }
- };
- removeAllNewFlagsConfirmationDialog.createNewDialog().show();
- return true;
- case R.id.mark_all_read_item:
- ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getContext(),
- R.string.mark_all_read_label,
- R.string.mark_all_read_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markFeedRead(feed.getId());
- }
- };
- markAllReadConfirmationDialog.createNewDialog().show();
- return true;
- case R.id.rename_item:
- new RenameFeedDialog(getActivity(), feed).show();
- return true;
- case R.id.remove_item:
- RemoveFeedDialog.show(getContext(), feed, () -> {
- ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null);
- });
- return true;
- default:
- return super.onContextItemSelected(item);
+ final int itemId = item.getItemId();
+ if (itemId == R.id.remove_all_new_flags_item) {
+ ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getContext(),
+ R.string.remove_all_new_flags_label,
+ R.string.remove_all_new_flags_confirmation_msg) {
+ @Override
+ public void onConfirmButtonPressed(DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.removeFeedNewFlag(feed.getId());
+ }
+ };
+ removeAllNewFlagsConfirmationDialog.createNewDialog().show();
+ return true;
+ } else if (itemId == R.id.mark_all_read_item) {
+ ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getContext(),
+ R.string.mark_all_read_label,
+ R.string.mark_all_read_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.markFeedRead(feed.getId());
+ }
+ };
+ markAllReadConfirmationDialog.createNewDialog().show();
+ return true;
+ } else if (itemId == R.id.rename_item) {
+ new RenameFeedDialog(getActivity(), feed).show();
+ return true;
+ } else if (itemId == R.id.remove_item) {
+ RemoveFeedDialog.show(getContext(), feed, () -> {
+ ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null);
+ });
+ return true;
}
+ return super.onContextItemSelected(item);
}
@Subscribe(threadMode = ThreadMode.MAIN)
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
index 84b0e97c1..5e3d36c03 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
@@ -172,7 +172,7 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
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 80f666fa2..6f8b9adbb 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
@@ -24,8 +24,11 @@ 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;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.event.DownloadEvent;
@@ -35,6 +38,8 @@ import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
+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.preferences.UserPreferences;
@@ -44,9 +49,9 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequester;
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.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.view.EmptyViewHandler;
@@ -63,14 +68,11 @@ import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import java.util.Locale;
-import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
-import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DOWNLOAD;
-import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE;
-
/**
* Shows all items in the queue.
*/
-public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
+public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener,
+ EpisodeItemListAdapter.OnSelectModeListener {
public static final String TAG = "QueueFragment";
private static final String KEY_UP_ARROW = "up_arrow";
@@ -90,9 +92,11 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning";
private Disposable disposable;
- private ItemTouchHelper itemTouchHelper;
+ private SwipeActions swipeActions;
private SharedPreferences prefs;
+ private SpeedDialView speedDialView;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -230,14 +234,13 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
}
- private void resetViewState() {
- recyclerAdapter = null;
- }
-
@Override
public void onDestroyView() {
super.onDestroyView();
- resetViewState();
+ if (recyclerAdapter != null) {
+ recyclerAdapter.endSelectMode();
+ }
+ recyclerAdapter = null;
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
@@ -255,82 +258,76 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
@Override
public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.queue_lock:
- toggleQueueLock();
- return true;
- case R.id.refresh_item:
- AutoUpdateManager.runImmediate(requireContext());
- return true;
- case R.id.clear_queue:
- // make sure the user really wants to clear the queue
- ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
- R.string.clear_queue_label,
- R.string.clear_queue_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.clearQueue();
- }
- };
- conDialog.createNewDialog().show();
- return true;
- case R.id.episode_actions:
- ((MainActivity) requireActivity()).loadChildFragment(
- EpisodesApplyActionFragment.newInstance(queue,
- ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE | ACTION_DOWNLOAD));
- return true;
- case R.id.queue_sort_episode_title_asc:
- setSortOrder(SortOrder.EPISODE_TITLE_A_Z);
- return true;
- case R.id.queue_sort_episode_title_desc:
- setSortOrder(SortOrder.EPISODE_TITLE_Z_A);
- return true;
- case R.id.queue_sort_date_asc:
- setSortOrder(SortOrder.DATE_OLD_NEW);
- return true;
- case R.id.queue_sort_date_desc:
- setSortOrder(SortOrder.DATE_NEW_OLD);
- return true;
- case R.id.queue_sort_duration_asc:
- setSortOrder(SortOrder.DURATION_SHORT_LONG);
- return true;
- case R.id.queue_sort_duration_desc:
- setSortOrder(SortOrder.DURATION_LONG_SHORT);
- return true;
- case R.id.queue_sort_feed_title_asc:
- setSortOrder(SortOrder.FEED_TITLE_A_Z);
- return true;
- case R.id.queue_sort_feed_title_desc:
- setSortOrder(SortOrder.FEED_TITLE_Z_A);
- return true;
- case R.id.queue_sort_random:
- setSortOrder(SortOrder.RANDOM);
- return true;
- case R.id.queue_sort_smart_shuffle_asc:
- setSortOrder(SortOrder.SMART_SHUFFLE_OLD_NEW);
- return true;
- case R.id.queue_sort_smart_shuffle_desc:
- setSortOrder(SortOrder.SMART_SHUFFLE_NEW_OLD);
- return true;
- case R.id.queue_keep_sorted:
- boolean keepSortedOld = UserPreferences.isQueueKeepSorted();
- boolean keepSortedNew = !keepSortedOld;
- UserPreferences.setQueueKeepSorted(keepSortedNew);
- if (keepSortedNew) {
- SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder();
- DBWriter.reorderQueue(sortOrder, true);
- }
- if (recyclerAdapter != null) {
- recyclerAdapter.updateDragDropEnabled();
+ final int itemId = item.getItemId();
+ if (itemId == R.id.queue_lock) {
+ toggleQueueLock();
+ return true;
+ } else if (itemId == R.id.refresh_item) {
+ AutoUpdateManager.runImmediate(requireContext());
+ return true;
+ } else if (itemId == R.id.clear_queue) {
+ // make sure the user really wants to clear the queue
+ ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
+ R.string.clear_queue_label,
+ R.string.clear_queue_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.clearQueue();
}
- refreshToolbarState();
- return true;
- default:
- return false;
+ };
+ conDialog.createNewDialog().show();
+ return true;
+ } else if (itemId == R.id.queue_sort_episode_title_asc) {
+ setSortOrder(SortOrder.EPISODE_TITLE_A_Z);
+ return true;
+ } else if (itemId == R.id.queue_sort_episode_title_desc) {
+ setSortOrder(SortOrder.EPISODE_TITLE_Z_A);
+ return true;
+ } else if (itemId == R.id.queue_sort_date_asc) {
+ setSortOrder(SortOrder.DATE_OLD_NEW);
+ return true;
+ } else if (itemId == R.id.queue_sort_date_desc) {
+ setSortOrder(SortOrder.DATE_NEW_OLD);
+ return true;
+ } else if (itemId == R.id.queue_sort_duration_asc) {
+ setSortOrder(SortOrder.DURATION_SHORT_LONG);
+ return true;
+ } else if (itemId == R.id.queue_sort_duration_desc) {
+ setSortOrder(SortOrder.DURATION_LONG_SHORT);
+ return true;
+ } else if (itemId == R.id.queue_sort_feed_title_asc) {
+ setSortOrder(SortOrder.FEED_TITLE_A_Z);
+ return true;
+ } else if (itemId == R.id.queue_sort_feed_title_desc) {
+ setSortOrder(SortOrder.FEED_TITLE_Z_A);
+ return true;
+ } else if (itemId == R.id.queue_sort_random) {
+ setSortOrder(SortOrder.RANDOM);
+ return true;
+ } else if (itemId == R.id.queue_sort_smart_shuffle_asc) {
+ setSortOrder(SortOrder.SMART_SHUFFLE_OLD_NEW);
+ return true;
+ } else if (itemId == R.id.queue_sort_smart_shuffle_desc) {
+ setSortOrder(SortOrder.SMART_SHUFFLE_NEW_OLD);
+ return true;
+ } else if (itemId == R.id.queue_keep_sorted) {
+ boolean keepSortedOld = UserPreferences.isQueueKeepSorted();
+ boolean keepSortedNew = !keepSortedOld;
+ UserPreferences.setQueueKeepSorted(keepSortedNew);
+ if (keepSortedNew) {
+ SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder();
+ DBWriter.reorderQueue(sortOrder, true);
+ }
+ if (recyclerAdapter != null) {
+ recyclerAdapter.updateDragDropEnabled();
+ }
+ refreshToolbarState();
+ return true;
}
+ return false;
}
private void toggleQueueLock() {
@@ -391,7 +388,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
if (!isVisible() || recyclerAdapter == null) {
return false;
}
- FeedItem selectedItem = recyclerAdapter.getSelectedItem();
+ FeedItem selectedItem = recyclerAdapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item was null, ignoring selection");
return super.onContextItemSelected(item);
@@ -402,24 +399,25 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
Log.i(TAG, "Selected item no longer exist, ignoring selection");
return super.onContextItemSelected(item);
}
+ if (recyclerAdapter.onContextItemSelected(item)) {
+ return true;
+ }
- switch(item.getItemId()) {
- case R.id.move_to_top_item:
- queue.add(0, queue.remove(position));
- recyclerAdapter.notifyItemMoved(position, 0);
- DBWriter.moveQueueItemToTop(selectedItem.getId(), true);
- return true;
- case R.id.move_to_bottom_item:
- queue.add(queue.size()-1, queue.remove(position));
- recyclerAdapter.notifyItemMoved(position, queue.size()-1);
- DBWriter.moveQueueItemToBottom(selectedItem.getId(), true);
- return true;
- default:
- return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
+ final int itemId = item.getItemId();
+ if (itemId == R.id.move_to_top_item) {
+ queue.add(0, queue.remove(position));
+ recyclerAdapter.notifyItemMoved(position, 0);
+ DBWriter.moveQueueItemToTop(selectedItem.getId(), true);
+ return true;
+ } else if (itemId == R.id.move_to_bottom_item) {
+ queue.add(queue.size() - 1, queue.remove(position));
+ recyclerAdapter.notifyItemMoved(position, queue.size() - 1);
+ DBWriter.moveQueueItemToBottom(selectedItem.getId(), true);
+ return true;
}
+ return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
}
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
@@ -451,83 +449,9 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
getResources().getInteger(R.integer.swipe_to_refresh_duration_in_ms));
});
- itemTouchHelper = new ItemTouchHelper(
- new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
- ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
-
- // Position tracking whilst dragging
- int dragFrom = -1;
- int dragTo = -1;
-
- @Override
- public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
- RecyclerView.ViewHolder target) {
- int fromPosition = viewHolder.getAdapterPosition();
- int toPosition = target.getAdapterPosition();
-
- // Update tracked position
- if (dragFrom == -1) {
- dragFrom = fromPosition;
- }
- dragTo = toPosition;
-
- int from = viewHolder.getAdapterPosition();
- int to = target.getAdapterPosition();
- Log.d(TAG, "move(" + from + ", " + to + ") in memory");
- if (from >= queue.size() || to >= queue.size() || from < 0 || to < 0) {
- return false;
- }
- queue.add(to, queue.remove(from));
- recyclerAdapter.notifyItemMoved(from, to);
- return true;
- }
-
- @Override
- public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- if (disposable != null) {
- disposable.dispose();
- }
- final int position = viewHolder.getAdapterPosition();
- Log.d(TAG, "remove(" + position + ")");
- final FeedItem item = queue.get(position);
- DBWriter.removeQueueItem(getActivity(), true, item);
-
- ((MainActivity) getActivity()).showSnackbarAbovePlayer(
- getResources().getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1),
- Snackbar.LENGTH_LONG)
- .setAction(getString(R.string.undo), v ->
- DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false));
- }
-
- @Override
- public boolean isLongPressDragEnabled() {
- return false;
- }
-
- @Override
- public boolean isItemViewSwipeEnabled() {
- return !UserPreferences.isQueueLocked();
- }
-
- @Override
- public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
- super.clearView(recyclerView, viewHolder);
- // Check if drag finished
- if (dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
- reallyMoved(dragFrom, dragTo);
- }
-
- dragFrom = dragTo = -1;
- }
-
- private void reallyMoved(int from, int to) {
- // Write drag operation to database
- Log.d(TAG, "Write to database move(" + from + ", " + to + ")");
- DBWriter.moveQueueItem(from, to, true);
- }
- }
- );
- itemTouchHelper.attachToRecyclerView(recyclerView);
+ swipeActions = new QueueSwipeActions();
+ swipeActions.setFilter(new FeedItemFilter(FeedItemFilter.QUEUED));
+ swipeActions.attachTo(recyclerView);
emptyView = new EmptyViewHandler(getContext());
emptyView.attachToRecyclerView(recyclerView);
@@ -538,6 +462,32 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
progLoading = root.findViewById(R.id.progLoading);
progLoading.setVisibility(View.VISIBLE);
+ speedDialView = root.findViewById(R.id.fabSD);
+ speedDialView.inflate(R.menu.episodes_apply_action_speeddial);
+ speedDialView.removeActionItemById(R.id.mark_read_batch);
+ speedDialView.removeActionItemById(R.id.mark_unread_batch);
+ speedDialView.removeActionItemById(R.id.add_to_queue_batch);
+ speedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() {
+ @Override
+ public boolean onMainActionSelected() {
+ return false;
+ }
+
+ @Override
+ public void onToggleChanged(boolean open) {
+ if (open && recyclerAdapter.getSelectedCount() == 0) {
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
+ Snackbar.LENGTH_SHORT);
+ speedDialView.close();
+ }
+ }
+ });
+ speedDialView.setOnActionSelectedListener(actionItem -> {
+ new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), recyclerAdapter.getSelectedItems())
+ .handleAction(actionItem.getId());
+ recyclerAdapter.endSelectMode();
+ return true;
+ });
return root;
}
@@ -548,10 +498,11 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
private void onFragmentLoaded(final boolean restoreScrollPosition) {
- if (queue != null && queue.size() > 0) {
+ if (queue != null) {
if (recyclerAdapter == null) {
MainActivity activity = (MainActivity) getActivity();
- recyclerAdapter = new QueueRecyclerAdapter(activity, itemTouchHelper);
+ recyclerAdapter = new QueueRecyclerAdapter(activity, swipeActions);
+ recyclerAdapter.setOnSelectModeListener(this);
recyclerView.setAdapter(recyclerAdapter);
emptyView.updateAdapter(recyclerAdapter);
}
@@ -615,4 +566,92 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
+
+ @Override
+ public void onStartSelectMode() {
+ swipeActions.detach();
+ speedDialView.setVisibility(View.VISIBLE);
+ refreshToolbarState();
+ infoBar.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onEndSelectMode() {
+ speedDialView.close();
+ speedDialView.setVisibility(View.GONE);
+ infoBar.setVisibility(View.VISIBLE);
+ swipeActions.attachTo(recyclerView);
+ }
+
+ private class QueueSwipeActions extends SwipeActions {
+
+ // Position tracking whilst dragging
+ int dragFrom = -1;
+ int dragTo = -1;
+
+ public QueueSwipeActions() {
+ super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, QueueFragment.this, TAG);
+ }
+
+ @Override
+ public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull RecyclerView.ViewHolder target) {
+ int fromPosition = viewHolder.getBindingAdapterPosition();
+ int toPosition = target.getBindingAdapterPosition();
+
+ // Update tracked position
+ if (dragFrom == -1) {
+ dragFrom = fromPosition;
+ }
+ dragTo = toPosition;
+
+ int from = viewHolder.getBindingAdapterPosition();
+ int to = target.getBindingAdapterPosition();
+ Log.d(TAG, "move(" + from + ", " + to + ") in memory");
+ if (from >= queue.size() || to >= queue.size() || from < 0 || to < 0) {
+ return false;
+ }
+ queue.add(to, queue.remove(from));
+ recyclerAdapter.notifyItemMoved(from, to);
+ return true;
+ }
+
+ @Override
+ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+ if (disposable != null) {
+ disposable.dispose();
+ }
+
+ //SwipeActions
+ super.onSwiped(viewHolder, direction);
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isItemViewSwipeEnabled() {
+ return !UserPreferences.isQueueLocked();
+ }
+
+ @Override
+ public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+ // Check if drag finished
+ if (dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
+ reallyMoved(dragFrom, dragTo);
+ }
+
+ dragFrom = dragTo = -1;
+ }
+
+ private void reallyMoved(int from, int to) {
+ // Write drag operation to database
+ Log.d(TAG, "Write to database move(" + from + ", " + to + ")");
+ DBWriter.moveQueueItem(from, to, true);
+ }
+
+ }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
index 0394b5987..855078d03 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
@@ -191,7 +191,7 @@ public class SearchFragment extends Fragment {
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
- FeedItem selectedItem = adapter.getSelectedItem();
+ FeedItem selectedItem = adapter.getLongPressedItem();
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection");
return super.onContextItemSelected(item);
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 367eb4aaf..23341ff66 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java
@@ -172,31 +172,30 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
@Override
public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.refresh_item:
- AutoUpdateManager.runImmediate(requireContext());
- return true;
- case R.id.subscriptions_filter:
- SubscriptionsFilterDialog.showDialog(requireContext());
- return true;
- case R.id.subscriptions_sort:
- FeedSortDialog.showDialog(requireContext());
- return true;
- case R.id.subscription_num_columns_2:
- setColumnNumber(2);
- return true;
- case R.id.subscription_num_columns_3:
- setColumnNumber(3);
- return true;
- case R.id.subscription_num_columns_4:
- setColumnNumber(4);
- return true;
- case R.id.subscription_num_columns_5:
- setColumnNumber(5);
- return true;
- default:
- return false;
+ final int itemId = item.getItemId();
+ if (itemId == R.id.refresh_item) {
+ AutoUpdateManager.runImmediate(requireContext());
+ return true;
+ } else if (itemId == R.id.subscriptions_filter) {
+ SubscriptionsFilterDialog.showDialog(requireContext());
+ return true;
+ } else if (itemId == R.id.subscriptions_sort) {
+ FeedSortDialog.showDialog(requireContext());
+ return true;
+ } else if (itemId == R.id.subscription_num_columns_2) {
+ setColumnNumber(2);
+ return true;
+ } else if (itemId == R.id.subscription_num_columns_3) {
+ setColumnNumber(3);
+ return true;
+ } else if (itemId == R.id.subscription_num_columns_4) {
+ setColumnNumber(4);
+ return true;
+ } else if (itemId == R.id.subscription_num_columns_5) {
+ setColumnNumber(5);
+ return true;
}
+ return false;
}
private void setColumnNumber(int columns) {
@@ -315,28 +314,27 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
Feed feed = selectedFeed;
selectedFeed = null;
- switch (item.getItemId()) {
- case R.id.remove_all_new_flags_item:
- displayConfirmationDialog(
- R.string.remove_all_new_flags_label,
- R.string.remove_all_new_flags_confirmation_msg,
- () -> DBWriter.removeFeedNewFlag(feed.getId()));
- return true;
- case R.id.mark_all_read_item:
- displayConfirmationDialog(
- R.string.mark_all_read_label,
- R.string.mark_all_read_confirmation_msg,
- () -> DBWriter.markFeedRead(feed.getId()));
- return true;
- case R.id.rename_item:
- new RenameFeedDialog(getActivity(), feed).show();
- return true;
- case R.id.remove_item:
- RemoveFeedDialog.show(getContext(), feed, null);
- return true;
- default:
- return super.onContextItemSelected(item);
+ final int itemId = item.getItemId();
+ if (itemId == R.id.remove_all_new_flags_item) {
+ displayConfirmationDialog(
+ R.string.remove_all_new_flags_label,
+ R.string.remove_all_new_flags_confirmation_msg,
+ () -> DBWriter.removeFeedNewFlag(feed.getId()));
+ return true;
+ } else if (itemId == R.id.mark_all_read_item) {
+ displayConfirmationDialog(
+ R.string.mark_all_read_label,
+ R.string.mark_all_read_confirmation_msg,
+ () -> DBWriter.markFeedRead(feed.getId()));
+ return true;
+ } else if (itemId == R.id.rename_item) {
+ new RenameFeedDialog(getActivity(), feed).show();
+ return true;
+ } else if (itemId == R.id.remove_item) {
+ RemoveFeedDialog.show(getContext(), feed, null);
+ return true;
}
+ return super.onContextItemSelected(item);
}
private <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> task) {
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
new file mode 100644
index 000000000..028d2fff4
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/EpisodeMultiSelectActionHandler.java
@@ -0,0 +1,130 @@
+package de.danoeh.antennapod.fragment.actions;
+
+import android.util.Log;
+
+import androidx.annotation.PluralsRes;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.LongList;
+import de.danoeh.antennapod.model.feed.FeedItem;
+
+public class EpisodeMultiSelectActionHandler {
+ private static final String TAG = "EpisodeSelectHandler";
+ private final MainActivity activity;
+ private final List<FeedItem> selectedItems;
+
+ public EpisodeMultiSelectActionHandler(MainActivity activity, List<FeedItem> selectedItems) {
+ this.activity = activity;
+ this.selectedItems = selectedItems;
+ }
+
+ public void handleAction(int id) {
+ if (id == R.id.add_to_queue_batch) {
+ queueChecked();
+ } else if (id == R.id.remove_from_queue_batch) {
+ removeFromQueueChecked();
+ } else if (id == R.id.mark_read_batch) {
+ markedCheckedPlayed();
+ } else if (id == R.id.mark_unread_batch) {
+ markedCheckedUnplayed();
+ } else if (id == R.id.download_batch) {
+ downloadChecked();
+ } else if (id == R.id.delete_batch) {
+ deleteChecked();
+ } else {
+ Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + id);
+ }
+ }
+
+ private void queueChecked() {
+ // Check if an episode actually contains any media files before adding it to queue
+ LongList toQueue = new LongList(selectedItems.size());
+ for (FeedItem episode : selectedItems) {
+ if (episode.hasMedia()) {
+ toQueue.add(episode.getId());
+ }
+ }
+ DBWriter.addQueueItem(activity, true, toQueue.toArray());
+ showMessage(R.plurals.added_to_queue_batch_label, toQueue.size());
+ }
+
+ private void removeFromQueueChecked() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.removeQueueItem(activity, true, checkedIds);
+ showMessage(R.plurals.removed_from_queue_batch_label, checkedIds.length);
+ }
+
+ private void markedCheckedPlayed() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.markItemPlayed(FeedItem.PLAYED, checkedIds);
+ showMessage(R.plurals.marked_read_batch_label, checkedIds.length);
+ }
+
+ private void markedCheckedUnplayed() {
+ long[] checkedIds = getSelectedIds();
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, checkedIds);
+ showMessage(R.plurals.marked_unread_batch_label, checkedIds.length);
+ }
+
+ private void downloadChecked() {
+ // download the check episodes in the same order as they are currently displayed
+ List<FeedItem> toDownload = new ArrayList<>(selectedItems.size());
+ for (FeedItem episode : selectedItems) {
+ if (episode.hasMedia() && !episode.getFeed().isLocalFeed()) {
+ toDownload.add(episode);
+ }
+ }
+ try {
+ DownloadRequester.getInstance().downloadMedia(activity, true, toDownload.toArray(new FeedItem[0]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(activity, e.getMessage());
+ }
+ showMessage(R.plurals.downloading_batch_label, toDownload.size());
+ }
+
+ private void deleteChecked() {
+ int countHasMedia = 0;
+ int countNoMedia = 0;
+ for (FeedItem feedItem : selectedItems) {
+ if (feedItem.hasMedia() && feedItem.getMedia().isDownloaded()) {
+ countHasMedia++;
+ DBWriter.deleteFeedMediaOfItem(activity, feedItem.getMedia().getId());
+ } else {
+ countNoMedia++;
+ }
+ }
+ showMessageMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
+ }
+
+ private void showMessage(@PluralsRes int msgId, int numItems) {
+ activity.showSnackbarAbovePlayer(activity.getResources()
+ .getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG);
+ }
+
+ private void showMessageMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
+ activity.showSnackbarAbovePlayer(activity.getResources()
+ .getQuantityString(msgId,
+ (countHasMedia + countNoMedia),
+ (countHasMedia + countNoMedia), countHasMedia),
+ Snackbar.LENGTH_LONG);
+ }
+
+ private long[] getSelectedIds() {
+ long[] checkedIds = new long[selectedItems.size()];
+ for (int i = 0; i < selectedItems.size(); ++i) {
+ checkedIds[i] = selectedItems.get(i).getId();
+ }
+ return checkedIds;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java
index baf4c7c57..cc09acbca 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java
@@ -148,5 +148,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_notifications));
config.index(R.xml.feed_settings)
.addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.feed_settings));
+ config.index(R.xml.preferences_swipe)
+ .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_user_interface))
+ .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_swipe));
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/SwipePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/SwipePreferencesFragment.java
new file mode 100644
index 000000000..3d9709f74
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/SwipePreferencesFragment.java
@@ -0,0 +1,37 @@
+package de.danoeh.antennapod.fragment.preferences;
+
+import android.os.Bundle;
+import androidx.preference.PreferenceFragmentCompat;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.PreferenceActivity;
+import de.danoeh.antennapod.dialog.SwipeActionsDialog;
+import de.danoeh.antennapod.fragment.FeedItemlistFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
+
+public class SwipePreferencesFragment extends PreferenceFragmentCompat {
+ private static final String PREF_SWIPE_FEED = "prefSwipeFeed";
+ private static final String PREF_SWIPE_QUEUE = "prefSwipeQueue";
+ //private static final String PREF_SWIPE_INBOX = "prefSwipeInbox";
+ //private static final String PREF_SWIPE_EPISODES = "prefSwipeEpisodes";
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.preferences_swipe);
+
+ findPreference(PREF_SWIPE_FEED).setOnPreferenceClickListener(preference -> {
+ new SwipeActionsDialog(requireContext(), FeedItemlistFragment.TAG).show(() -> { });
+ return true;
+ });
+ findPreference(PREF_SWIPE_QUEUE).setOnPreferenceClickListener(preference -> {
+ new SwipeActionsDialog(requireContext(), QueueFragment.TAG).show(() -> { });
+ return true;
+ });
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.swipeactions_label);
+ }
+
+}
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 4d1b79965..7c79d0962 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
@@ -21,6 +21,7 @@ import org.greenrobot.eventbus.EventBus;
import java.util.List;
public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat {
+ private static final String PREF_SWIPE = "prefSwipe";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -98,6 +99,11 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat {
FeedSortDialog.showDialog(requireContext());
return true;
}));
+ findPreference(PREF_SWIPE)
+ .setOnPreferenceClickListener(preference -> {
+ ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_swipe);
+ return true;
+ });
if (Build.VERSION.SDK_INT >= 26) {
findPreference(UserPreferences.PREF_EXPANDED_NOTIFICATION).setVisible(false);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/AddToQueueSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/AddToQueueSwipeAction.java
new file mode 100644
index 000000000..514ba9764
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/AddToQueueSwipeAction.java
@@ -0,0 +1,47 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class AddToQueueSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return ADD_TO_QUEUE;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_playlist;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.colorAccent;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.add_to_queue_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ if (!item.isTagged(FeedItem.TAG_QUEUE)) {
+ DBWriter.addQueueItem(fragment.requireContext(), item);
+ } else {
+ new RemoveFromQueueSwipeAction().performAction(item, fragment, filter);
+ }
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return filter.showQueued || filter.showNew;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkFavoriteSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkFavoriteSwipeAction.java
new file mode 100644
index 000000000..2458657a0
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkFavoriteSwipeAction.java
@@ -0,0 +1,43 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class MarkFavoriteSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return MARK_FAV;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_star;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.icon_yellow;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.add_to_favorite_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ DBWriter.toggleFavoriteItem(item);
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return filter.showIsFavorite || filter.showNotFavorite;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkPlayedSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkPlayedSwipeAction.java
new file mode 100644
index 000000000..b820d8a65
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/MarkPlayedSwipeAction.java
@@ -0,0 +1,46 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class MarkPlayedSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return MARK_PLAYED;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_mark_played;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.icon_gray;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.mark_read_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ int togglePlayState =
+ item.getPlayState() != FeedItem.PLAYED ? FeedItem.PLAYED : FeedItem.UNPLAYED;
+ FeedItemMenuHandler.markReadWithUndo(fragment,
+ item, togglePlayState, willRemove(filter));
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return filter.showUnplayed || filter.showPlayed;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromInboxSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromInboxSwipeAction.java
new file mode 100644
index 000000000..9852269fb
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromInboxSwipeAction.java
@@ -0,0 +1,44 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class RemoveFromInboxSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return REMOVE_FROM_INBOX;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_check;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.icon_purple;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.remove_new_flag_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ FeedItemMenuHandler.markReadWithUndo(fragment,
+ item, FeedItem.UNPLAYED, willRemove(filter));
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return filter.showUnplayed;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromQueueSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromQueueSwipeAction.java
new file mode 100644
index 000000000..87cf97f56
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/RemoveFromQueueSwipeAction.java
@@ -0,0 +1,57 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class RemoveFromQueueSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return REMOVE_FROM_QUEUE;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_playlist_remove;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.colorAccent;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.remove_from_queue_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ int position = DBReader.getQueueIDList().indexOf(item.getId());
+
+ DBWriter.removeQueueItem(fragment.requireActivity(), true, item);
+
+ if (willRemove(filter)) {
+ ((MainActivity) fragment.requireActivity()).showSnackbarAbovePlayer(
+ fragment.getResources().getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1),
+ Snackbar.LENGTH_LONG)
+ .setAction(fragment.getString(R.string.undo), v ->
+ DBWriter.addQueueItemAt(fragment.requireActivity(), item.getId(), position, false));
+ }
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return filter.showQueued || filter.showNotQueued;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/ShowFirstSwipeDialogAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/ShowFirstSwipeDialogAction.java
new file mode 100644
index 000000000..7d626134d
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/ShowFirstSwipeDialogAction.java
@@ -0,0 +1,42 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class ShowFirstSwipeDialogAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return "SHOW_FIRST_SWIPE_DIALOG";
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_settings;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.icon_gray;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return "";
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ //handled in SwipeActions
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return false;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/StartDownloadSwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/StartDownloadSwipeAction.java
new file mode 100644
index 000000000..2c0110822
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/StartDownloadSwipeAction.java
@@ -0,0 +1,44 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+import androidx.fragment.app.Fragment;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.actionbutton.DownloadActionButton;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public class StartDownloadSwipeAction implements SwipeAction {
+
+ @Override
+ public String getId() {
+ return START_DOWNLOAD;
+ }
+
+ @Override
+ public int getActionIcon() {
+ return R.drawable.ic_download;
+ }
+
+ @Override
+ public int getActionColor() {
+ return R.attr.icon_green;
+ }
+
+ @Override
+ public String getTitle(Context context) {
+ return context.getString(R.string.download_label);
+ }
+
+ @Override
+ public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
+ if (!item.isDownloaded() && !item.getFeed().isLocalFeed()) {
+ new DownloadActionButton(item)
+ .onClick(fragment.requireContext());
+ }
+ }
+
+ @Override
+ public boolean willRemove(FeedItemFilter filter) {
+ return false;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeAction.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeAction.java
new file mode 100644
index 000000000..e6d002b2b
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeAction.java
@@ -0,0 +1,34 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.DrawableRes;
+import androidx.fragment.app.Fragment;
+
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+
+public interface SwipeAction {
+
+ String ADD_TO_QUEUE = "ADD_TO_QUEUE";
+ String REMOVE_FROM_INBOX = "REMOVE_FROM_INBOX";
+ String START_DOWNLOAD = "START_DOWNLOAD";
+ String MARK_FAV = "MARK_FAV";
+ String MARK_PLAYED = "MARK_PLAYED";
+ String REMOVE_FROM_QUEUE = "REMOVE_FROM_QUEUE";
+
+ String getId();
+
+ String getTitle(Context context);
+
+ @DrawableRes
+ int getActionIcon();
+
+ @AttrRes
+ int getActionColor();
+
+ void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter);
+
+ boolean willRemove(FeedItemFilter filter);
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java
new file mode 100644
index 000000000..50c7c1ae5
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java
@@ -0,0 +1,258 @@
+package de.danoeh.antennapod.fragment.swipeactions;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Canvas;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.ColorUtils;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.annimon.stream.Stream;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.dialog.SwipeActionsDialog;
+import de.danoeh.antennapod.fragment.EpisodesFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedItemFilter;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
+import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
+import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator;
+
+public class SwipeActions extends ItemTouchHelper.SimpleCallback implements LifecycleObserver {
+ public static final String PREF_NAME = "SwipeActionsPrefs";
+ public static final String KEY_PREFIX_SWIPEACTIONS = "PrefSwipeActions";
+ public static final String KEY_PREFIX_NO_ACTION = "PrefNoSwipeAction";
+
+ public static final List<SwipeAction> swipeActions = Collections.unmodifiableList(
+ Arrays.asList(new AddToQueueSwipeAction(), new RemoveFromInboxSwipeAction(),
+ new StartDownloadSwipeAction(), new MarkFavoriteSwipeAction(),
+ new MarkPlayedSwipeAction(), new RemoveFromQueueSwipeAction())
+ );
+
+ private final Fragment fragment;
+ private final String tag;
+ private FeedItemFilter filter = null;
+
+ Actions actions;
+ boolean swipeOutEnabled = true;
+ int swipedOutTo = 0;
+ private final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this);
+
+ public SwipeActions(int dragDirs, Fragment fragment, String tag) {
+ super(dragDirs, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT);
+ this.fragment = fragment;
+ this.tag = tag;
+ reloadPreference();
+ fragment.getLifecycle().addObserver(this);
+ }
+
+ public SwipeActions(Fragment fragment, String tag) {
+ this(0, fragment, tag);
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void reloadPreference() {
+ actions = getPrefs(fragment.requireContext(), tag);
+ }
+
+ public void setFilter(FeedItemFilter filter) {
+ this.filter = filter;
+ }
+
+ public SwipeActions attachTo(RecyclerView recyclerView) {
+ itemTouchHelper.attachToRecyclerView(recyclerView);
+ return this;
+ }
+
+ public void detach() {
+ itemTouchHelper.attachToRecyclerView(null);
+ }
+
+ private static Actions getPrefs(Context context, String tag, String defaultActions) {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ String prefsString = prefs.getString(KEY_PREFIX_SWIPEACTIONS + tag, defaultActions);
+
+ return new Actions(prefsString);
+ }
+
+ private static Actions getPrefs(Context context, String tag) {
+ return getPrefs(context, tag, "");
+ }
+
+ public static Actions getPrefsWithDefaults(Context context, String tag) {
+ String defaultActions;
+ switch (tag) {
+ /*case InboxFragment.TAG:
+ defaultActions = new int[] {ADD_TO_QUEUE, MARK_UNPLAYED};
+ break;*/
+ case QueueFragment.TAG:
+ defaultActions = SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE;
+ break;
+ default:
+ case EpisodesFragment.TAG:
+ defaultActions = SwipeAction.MARK_FAV + "," + SwipeAction.START_DOWNLOAD;
+ break;
+ }
+
+ return getPrefs(context, tag, defaultActions);
+ }
+
+ public static boolean isSwipeActionEnabled(Context context, String tag) {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_PREFIX_NO_ACTION + tag, true);
+ }
+
+ private boolean isSwipeActionEnabled() {
+ return isSwipeActionEnabled(fragment.requireContext(), tag);
+ }
+
+ @Override
+ public boolean onMove(@NonNull RecyclerView recyclerView,
+ @NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull RecyclerView.ViewHolder target) {
+ return false;
+ }
+
+ @Override
+ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int swipeDir) {
+ if (!actions.hasActions()) {
+ //open settings dialog if no prefs are set
+ new SwipeActionsDialog(fragment.requireContext(), tag).show(this::reloadPreference);
+ return;
+ }
+
+ FeedItem item = ((EpisodeItemViewHolder) viewHolder).getFeedItem();
+
+ (swipeDir == ItemTouchHelper.RIGHT ? actions.right : actions.left)
+ .performAction(item, fragment, filter);
+ }
+
+ @Override
+ public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
+ @NonNull RecyclerView.ViewHolder viewHolder,
+ float dx, float dy, int actionState, boolean isCurrentlyActive) {
+ SwipeAction right;
+ SwipeAction left;
+ if (actions.hasActions()) {
+ right = actions.right;
+ left = actions.left;
+ } else {
+ right = left = new ShowFirstSwipeDialogAction();
+ }
+
+ //check if it will be removed
+ boolean rightWillRemove = right.willRemove(filter);
+ boolean leftWillRemove = left.willRemove(filter);
+ boolean wontLeave = (dx > 0 && !rightWillRemove) || (dx < 0 && !leftWillRemove);
+
+ //Limit swipe if it's not removed
+ int maxMovement = recyclerView.getWidth() * 2 / 5;
+ float sign = dx > 0 ? 1 : -1;
+ float limitMovement = Math.min(maxMovement, sign * dx);
+ float displacementPercentage = limitMovement / maxMovement;
+
+ if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && wontLeave) {
+ swipeOutEnabled = false;
+
+ boolean swipeThresholdReached = displacementPercentage == 1;
+
+ // Move slower when getting near the maxMovement
+ dx = sign * maxMovement * (float) Math.sin((Math.PI / 2) * displacementPercentage);
+
+ if (isCurrentlyActive) {
+ int dir = dx > 0 ? ItemTouchHelper.RIGHT : ItemTouchHelper.LEFT;
+ swipedOutTo = swipeThresholdReached ? dir : 0;
+ }
+ } else {
+ swipeOutEnabled = true;
+ }
+
+ //add color and icon
+ Context context = fragment.requireContext();
+ int themeColor = ThemeUtils.getColorFromAttr(context, android.R.attr.windowBackground);
+ int actionColor = ThemeUtils.getColorFromAttr(context,
+ dx > 0 ? right.getActionColor() : left.getActionColor());
+ RecyclerViewSwipeDecorator.Builder builder = new RecyclerViewSwipeDecorator.Builder(
+ c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive)
+ .addSwipeRightActionIcon(right.getActionIcon())
+ .addSwipeLeftActionIcon(left.getActionIcon())
+ .addSwipeRightBackgroundColor(ThemeUtils.getColorFromAttr(context, R.attr.background_elevated))
+ .addSwipeLeftBackgroundColor(ThemeUtils.getColorFromAttr(context, R.attr.background_elevated))
+ .setActionIconTint(
+ ColorUtils.blendARGB(themeColor,
+ actionColor,
+ Math.max(0.5f, displacementPercentage)));
+ builder.create().decorate();
+
+
+ super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive);
+ }
+
+ @Override
+ public float getSwipeEscapeVelocity(float defaultValue) {
+ return swipeOutEnabled ? defaultValue : Float.MAX_VALUE;
+ }
+
+ @Override
+ public float getSwipeVelocityThreshold(float defaultValue) {
+ return swipeOutEnabled ? defaultValue : 0;
+ }
+
+ @Override
+ public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
+ return swipeOutEnabled ? 0.6f : 1.0f;
+ }
+
+ @Override
+ public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ if (swipedOutTo != 0) {
+ onSwiped(viewHolder, swipedOutTo);
+ swipedOutTo = 0;
+ }
+ }
+
+ @Override
+ public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
+ if (!isSwipeActionEnabled()) {
+ return makeMovementFlags(getDragDirs(recyclerView, viewHolder), 0);
+ } else {
+ return super.getMovementFlags(recyclerView, viewHolder);
+ }
+ }
+
+ public void startDrag(EpisodeItemViewHolder holder) {
+ itemTouchHelper.startDrag(holder);
+ }
+
+ public static class Actions {
+ public SwipeAction right = null;
+ public SwipeAction left = null;
+
+ public Actions(String prefs) {
+ String[] actions = prefs.split(",");
+ if (actions.length == 2) {
+ this.right = Stream.of(swipeActions)
+ .filter(a -> a.getId().equals(actions[0])).single();;
+ this.left = Stream.of(swipeActions)
+ .filter(a -> a.getId().equals(actions[1])).single();
+ }
+ }
+
+ public boolean hasActions() {
+ return right != null && left != null;
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
index d478c581d..c272af7d5 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
@@ -6,25 +6,26 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
-import com.google.android.material.snackbar.Snackbar;
-
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+
+import com.google.android.material.snackbar.Snackbar;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.model.feed.FeedItem;
-import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.sync.SyncService;
-import de.danoeh.antennapod.net.sync.model.EpisodeAction;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ShareUtils;
import de.danoeh.antennapod.dialog.ShareDialog;
+import de.danoeh.antennapod.model.feed.FeedItem;
+import de.danoeh.antennapod.model.feed.FeedMedia;
+import de.danoeh.antennapod.net.sync.model.EpisodeAction;
/**
* Handles interactions with the FeedItemMenu.
@@ -65,14 +66,6 @@ public class FeedItemMenuHandler {
setItemVisibility(menu, R.id.mark_unread_item, selectedItem.isPlayed());
setItemVisibility(menu, R.id.reset_position, hasMedia && selectedItem.getMedia().getPosition() != 0);
- if (!UserPreferences.isEnableAutodownload() || fileDownloaded || selectedItem.getFeed().isLocalFeed()) {
- setItemVisibility(menu, R.id.activate_auto_download, false);
- setItemVisibility(menu, R.id.deactivate_auto_download, false);
- } else {
- setItemVisibility(menu, R.id.activate_auto_download, !selectedItem.getAutoDownload());
- setItemVisibility(menu, R.id.deactivate_auto_download, selectedItem.getAutoDownload());
- }
-
// Display proper strings when item has no media
if (hasMedia) {
setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_label);
@@ -149,81 +142,60 @@ public class FeedItemMenuHandler {
@NonNull FeedItem selectedItem) {
@NonNull Context context = fragment.requireContext();
- switch (menuItemId) {
- case R.id.skip_episode_item:
- IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
- break;
- case R.id.remove_item:
- DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
- break;
- case R.id.remove_new_flag_item:
- removeNewFlagWithUndo(fragment, selectedItem);
- break;
- case R.id.mark_read_item:
- selectedItem.setPlayed(true);
- DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true);
- if (GpodnetPreferences.loggedIn()) {
- FeedMedia media = selectedItem.getMedia();
- // not all items have media, Gpodder only cares about those that do
- if (media != null) {
- EpisodeAction actionPlay = new EpisodeAction.Builder(selectedItem, EpisodeAction.PLAY)
- .currentTimestamp()
- .started(media.getDuration() / 1000)
- .position(media.getDuration() / 1000)
- .total(media.getDuration() / 1000)
- .build();
- SyncService.enqueueEpisodeAction(context, actionPlay);
- }
- }
- break;
- case R.id.mark_unread_item:
- selectedItem.setPlayed(false);
- DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
- if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) {
- EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
+ if (menuItemId == R.id.skip_episode_item) {
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
+ } else if (menuItemId == R.id.remove_item) {
+ DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
+ } else if (menuItemId == R.id.remove_new_flag_item) {
+ removeNewFlagWithUndo(fragment, selectedItem);
+ } else if (menuItemId == R.id.mark_read_item) {
+ selectedItem.setPlayed(true);
+ DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true);
+ if (GpodnetPreferences.loggedIn()) {
+ FeedMedia media = selectedItem.getMedia();
+ // not all items have media, Gpodder only cares about those that do
+ if (media != null) {
+ EpisodeAction actionPlay = new EpisodeAction.Builder(selectedItem, EpisodeAction.PLAY)
.currentTimestamp()
+ .started(media.getDuration() / 1000)
+ .position(media.getDuration() / 1000)
+ .total(media.getDuration() / 1000)
.build();
- SyncService.enqueueEpisodeAction(context, actionNew);
- }
- break;
- case R.id.add_to_queue_item:
- DBWriter.addQueueItem(context, selectedItem);
- break;
- case R.id.remove_from_queue_item:
- DBWriter.removeQueueItem(context, true, selectedItem);
- break;
- case R.id.add_to_favorites_item:
- DBWriter.addFavoriteItem(selectedItem);
- break;
- case R.id.remove_from_favorites_item:
- DBWriter.removeFavoriteItem(selectedItem);
- break;
- case R.id.reset_position:
- selectedItem.getMedia().setPosition(0);
- if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == selectedItem.getMedia().getId()) {
- PlaybackPreferences.writeNoMediaPlaying();
- IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
+ SyncService.enqueueEpisodeAction(context, actionPlay);
}
- DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, true);
- break;
- case R.id.activate_auto_download:
- selectedItem.setAutoDownload(true);
- DBWriter.setFeedItemAutoDownload(selectedItem, true);
- break;
- case R.id.deactivate_auto_download:
- selectedItem.setAutoDownload(false);
- DBWriter.setFeedItemAutoDownload(selectedItem, false);
- break;
- case R.id.visit_website_item:
- IntentUtils.openInBrowser(context, FeedItemUtil.getLinkWithFallback(selectedItem));
- break;
- case R.id.share_item:
- ShareDialog shareDialog = ShareDialog.newInstance(selectedItem);
- shareDialog.show((fragment.getActivity().getSupportFragmentManager()), "ShareEpisodeDialog");
- break;
- default:
- Log.d(TAG, "Unknown menuItemId: " + menuItemId);
- return false;
+ }
+ } else if (menuItemId == R.id.mark_unread_item) {
+ selectedItem.setPlayed(false);
+ DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
+ if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) {
+ EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
+ .currentTimestamp()
+ .build();
+ SyncService.enqueueEpisodeAction(context, actionNew);
+ }
+ } else if (menuItemId == R.id.add_to_queue_item) {
+ DBWriter.addQueueItem(context, selectedItem);
+ } else if (menuItemId == R.id.remove_from_queue_item) {
+ DBWriter.removeQueueItem(context, true, selectedItem);
+ } else if (menuItemId == R.id.add_to_favorites_item) {
+ DBWriter.addFavoriteItem(selectedItem);
+ } else if (menuItemId == R.id.remove_from_favorites_item) {
+ DBWriter.removeFavoriteItem(selectedItem);
+ } else if (menuItemId == R.id.reset_position) {
+ selectedItem.getMedia().setPosition(0);
+ if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == selectedItem.getMedia().getId()) {
+ PlaybackPreferences.writeNoMediaPlaying();
+ IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
+ }
+ DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, true);
+ } else if (menuItemId == R.id.visit_website_item) {
+ IntentUtils.openInBrowser(context, FeedItemUtil.getLinkWithFallback(selectedItem));
+ } else if (menuItemId == R.id.share_item) {
+ ShareDialog shareDialog = ShareDialog.newInstance(selectedItem);
+ shareDialog.show((fragment.getActivity().getSupportFragmentManager()), "ShareEpisodeDialog");
+ } else {
+ Log.d(TAG, "Unknown menuItemId: " + menuItemId);
+ return false;
}
// Refresh menu state
@@ -236,15 +208,16 @@ public class FeedItemMenuHandler {
* Undo is useful for Remove new flag, given there is no UI to undo it otherwise
* ,i.e., there is (context) menu item for add new flag
*/
- public static void removeNewFlagWithUndo(@NonNull Fragment fragment, FeedItem item) {
+ public static void markReadWithUndo(@NonNull Fragment fragment, FeedItem item,
+ int playState, boolean showSnackbar) {
if (item == null) {
return;
}
- Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")");
+ Log.d(TAG, "markReadWithUndo(" + item.getId() + ")");
// we're marking it as unplayed since the user didn't actually play it
// but they don't want it considered 'NEW' anymore
- DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
+ DBWriter.markItemPlayed(playState, item.getId());
final Handler h = new Handler(fragment.requireContext().getMainLooper());
final Runnable r = () -> {
@@ -254,15 +227,40 @@ public class FeedItemMenuHandler {
}
};
+ int playStateStringRes;
+ switch (playState) {
+ default:
+ case FeedItem.UNPLAYED:
+ if (item.getPlayState() == FeedItem.NEW) {
+ //was new
+ playStateStringRes = R.string.removed_new_flag_label;
+ } else {
+ //was played
+ playStateStringRes = R.string.marked_as_unplayed_label;
+ }
+ break;
+ case FeedItem.PLAYED:
+ playStateStringRes = R.string.marked_as_played_label;
+ break;
+ }
+
+ int duration = Snackbar.LENGTH_LONG;
+
+ if (showSnackbar) {
+ ((MainActivity) fragment.getActivity()).showSnackbarAbovePlayer(
+ playStateStringRes, duration)
+ .setAction(fragment.getString(R.string.undo), v -> {
+ DBWriter.markItemPlayed(item.getPlayState(), item.getId());
+ // don't forget to cancel the thing that's going to remove the media
+ h.removeCallbacks(r);
+ });
+ }
+
+ h.postDelayed(r, (int) Math.ceil(duration * 1.05f));
+ }
- Snackbar snackbar = ((MainActivity) fragment.getActivity()).showSnackbarAbovePlayer(
- R.string.removed_new_flag_label, Snackbar.LENGTH_LONG)
- .setAction(fragment.getString(R.string.undo), v -> {
- DBWriter.markItemPlayed(FeedItem.NEW, item.getId());
- // don't forget to cancel the thing that's going to remove the media
- h.removeCallbacks(r);
- });
- h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f));
+ public static void removeNewFlagWithUndo(@NonNull Fragment fragment, FeedItem item) {
+ markReadWithUndo(fragment, item, FeedItem.UNPLAYED, false);
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
index ed0cac05d..fded5fb34 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
@@ -60,44 +60,36 @@ public class FeedMenuHandler {
*/
public static boolean onOptionsItemClicked(final Context context, final MenuItem item,
final Feed selectedFeed) throws DownloadRequestException {
- switch (item.getItemId()) {
- case R.id.refresh_item:
- DBTasks.forceRefreshFeed(context, selectedFeed, true);
- break;
- case R.id.refresh_complete_item:
- DBTasks.forceRefreshCompleteFeed(context, selectedFeed);
- break;
- case R.id.sort_items:
- showSortDialog(context, selectedFeed);
- break;
- case R.id.filter_items:
- showFilterDialog(context, selectedFeed);
- break;
- case R.id.mark_all_read_item:
- ConfirmationDialog conDialog = new ConfirmationDialog(context,
- R.string.mark_all_read_label,
- R.string.mark_all_read_feed_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markFeedRead(selectedFeed.getId());
- }
- };
- conDialog.createNewDialog().show();
- break;
- case R.id.visit_website_item:
- IntentUtils.openInBrowser(context, selectedFeed.getLink());
- break;
- case R.id.share_link_item:
- ShareUtils.shareFeedlink(context, selectedFeed);
- break;
- case R.id.share_download_url_item:
- ShareUtils.shareFeedDownloadLink(context, selectedFeed);
- break;
- default:
- return false;
+ final int itemId = item.getItemId();
+ if (itemId == R.id.refresh_item) {
+ DBTasks.forceRefreshFeed(context, selectedFeed, true);
+ } else if (itemId == R.id.refresh_complete_item) {
+ DBTasks.forceRefreshCompleteFeed(context, selectedFeed);
+ } else if (itemId == R.id.sort_items) {
+ showSortDialog(context, selectedFeed);
+ } else if (itemId == R.id.filter_items) {
+ showFilterDialog(context, selectedFeed);
+ } else if (itemId == R.id.mark_all_read_item) {
+ ConfirmationDialog conDialog = new ConfirmationDialog(context,
+ R.string.mark_all_read_label,
+ R.string.mark_all_read_feed_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.markFeedRead(selectedFeed.getId());
+ }
+ };
+ conDialog.createNewDialog().show();
+ } else if (itemId == R.id.visit_website_item) {
+ IntentUtils.openInBrowser(context, selectedFeed.getLink());
+ } else if (itemId == R.id.share_link_item) {
+ ShareUtils.shareFeedlink(context, selectedFeed);
+ } else if (itemId == R.id.share_download_url_item) {
+ ShareUtils.shareFeedDownloadLink(context, selectedFeed);
+ } else {
+ return false;
}
return true;
}
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 03a8edbf0..84c738632 100644
--- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java
@@ -11,6 +11,9 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences.EnqueueLocation;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
+import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeAction;
+import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
public class PreferenceUpgrader {
private static final String PREF_CONFIGURED_VERSION = "version_code";
@@ -28,12 +31,12 @@ public class PreferenceUpgrader {
AutoUpdateManager.restartUpdateAlarm(context);
CrashReportWriter.getFile().delete();
- upgrade(oldVersion);
+ upgrade(oldVersion, context);
upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
}
}
- private static void upgrade(int oldVersion) {
+ private static void upgrade(int oldVersion, Context context) {
if (oldVersion == -1) {
//New installation
if (UserPreferences.getUsageCountingDateMillis() < 0) {
@@ -104,5 +107,10 @@ public class PreferenceUpgrader {
String.valueOf(KeyEvent.KEYCODE_MEDIA_PREVIOUS)).apply();
}
}
+ if (oldVersion < 2040000) {
+ SharedPreferences prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG,
+ SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply();
+ }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java
index 9355c0c15..ae6e88c45 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.view;
import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@@ -15,10 +16,15 @@ import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+
+import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.core.view.ViewCompat;
+
import com.google.android.material.snackbar.Snackbar;
+
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -94,6 +100,19 @@ public class ShownotesWebView extends WebView implements View.OnLongClickListene
selectedUrl = r.getExtra();
showContextMenu();
return true;
+ } else if (r != null && r.getType() == HitTestResult.EMAIL_TYPE) {
+ Log.d(TAG, "E-Mail of webview was long-pressed. Extra: " + r.getExtra());
+ ClipboardManager clipboardManager = ContextCompat.getSystemService(this.getContext(),
+ ClipboardManager.class);
+ if (clipboardManager != null) {
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("AntennaPod", r.getExtra()));
+ }
+ if (this.getContext() instanceof MainActivity) {
+ ((MainActivity) this.getContext()).showSnackbarAbovePlayer(
+ getResources().getString(R.string.copied_to_clipboard),
+ Snackbar.LENGTH_SHORT);
+ }
+ return true;
}
selectedUrl = null;
return false;
@@ -104,33 +123,28 @@ public class ShownotesWebView extends WebView implements View.OnLongClickListene
return false;
}
- switch (item.getItemId()) {
- case R.id.open_in_browser_item:
- IntentUtils.openInBrowser(getContext(), selectedUrl);
- break;
- case R.id.share_url_item:
- ShareUtils.shareLink(getContext(), selectedUrl);
- break;
- case R.id.copy_url_item:
- ClipData clipData = ClipData.newPlainText(selectedUrl, selectedUrl);
- android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setPrimaryClip(clipData);
- Snackbar s = Snackbar.make(this, R.string.copied_url_msg, Snackbar.LENGTH_LONG);
- ViewCompat.setElevation(s.getView(), 100);
- s.show();
- break;
- case R.id.go_to_position_item:
- if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
- timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl));
- } else {
- Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl);
- }
- break;
- default:
- selectedUrl = null;
- return false;
-
+ final int itemId = item.getItemId();
+ if (itemId == R.id.open_in_browser_item) {
+ IntentUtils.openInBrowser(getContext(), selectedUrl);
+ } else if (itemId == R.id.share_url_item) {
+ ShareUtils.shareLink(getContext(), selectedUrl);
+ } else if (itemId == R.id.copy_url_item) {
+ ClipData clipData = ClipData.newPlainText(selectedUrl, selectedUrl);
+ ClipboardManager cm = (ClipboardManager) getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(clipData);
+ Snackbar s = Snackbar.make(this, R.string.copied_url_msg, Snackbar.LENGTH_LONG);
+ ViewCompat.setElevation(s.getView(), 100);
+ s.show();
+ } else if (itemId == R.id.go_to_position_item) {
+ if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
+ timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl));
+ } else {
+ Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl);
+ }
+ } else {
+ selectedUrl = null;
+ return false;
}
selectedUrl = null;
return true;
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 1ea9d71f9..02d45b2a0 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
@@ -7,13 +7,16 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
+
import com.joanzapata.iconify.Iconify;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.CoverLoader;
@@ -31,8 +34,8 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.common.CircularProgressBar;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
/**
* Holds the view which shows FeedItems.
@@ -60,6 +63,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
private final TextView separatorIcons;
private final View leftPadding;
public final CardView coverHolder;
+ public final CheckBox selectCheckBox;
private final MainActivity activity;
private FeedItem item;
@@ -91,6 +95,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
coverHolder = itemView.findViewById(R.id.coverHolder);
leftPadding = itemView.findViewById(R.id.left_padding);
itemView.setTag(this);
+ selectCheckBox = itemView.findViewById(R.id.selectCheckBox);
}
public void bind(FeedItem item) {
@@ -105,7 +110,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
isInQueue.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE);
container.setAlpha(item.isPlayed() ? 0.5f : 1.0f);
- ItemActionButton actionButton = ItemActionButton.forItem(item, true, true);
+ ItemActionButton actionButton = ItemActionButton.forItem(item);
actionButton.configure(secondaryActionButton, secondaryActionIcon, activity);
secondaryActionButton.setFocusable(false);
diff --git a/app/src/main/res/layout-sw720dp/main.xml b/app/src/main/res/layout-sw720dp/main.xml
index 79b7213e0..fe5a86d24 100644
--- a/app/src/main/res/layout-sw720dp/main.xml
+++ b/app/src/main/res/layout-sw720dp/main.xml
@@ -1,28 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ 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="match_parent"
+ android:orientation="horizontal"
+ tools:viewBindingIgnore="true">
<FrameLayout
- android:id="@+id/navDrawerFragment"
- android:layout_width="300dp"
- android:layout_height="match_parent"
- android:layout_gravity="start"
- android:orientation="vertical" />
+ android:id="@+id/navDrawerFragment"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:orientation="vertical" />
<View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="?android:attr/listDivider" />
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="?android:attr/listDivider" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
- android:id="@+id/overview_coordinator_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:id="@+id/overview_coordinator_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<FrameLayout
android:id="@+id/main_view"
@@ -33,14 +34,14 @@
tools:background="@android:color/holo_red_dark" />
<FrameLayout
- android:elevation="8dp"
android:id="@+id/audioplayerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
+ android:elevation="8dp"
android:visibility="gone"
app:layout_behavior="de.danoeh.antennapod.view.LockableBottomSheetBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/app/src/main/res/layout/episodes_apply_action_fragment.xml b/app/src/main/res/layout/episodes_apply_action_fragment.xml
deleted file mode 100644
index 78827a12a..000000000
--- a/app/src/main/res/layout/episodes_apply_action_fragment.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<RelativeLayout 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="match_parent">
-
- <androidx.appcompat.widget.Toolbar
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?attr/actionBarSize"
- android:theme="?attr/actionBarTheme"
- android:layout_alignParentTop="true"
- app:navigationIcon="?homeAsUpIndicator"
- android:id="@+id/toolbar"/>
-
- <ListView
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/toolbar"
- android:layout_marginTop="0dp" />
-
- <com.leinardi.android.speeddial.SpeedDialOverlayLayout
- android:id="@+id/fabSDOverlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="no"
- android:layout_below="@id/toolbar" />
- <!-- The FAB SpeedDial
- 1. MUST be placed at the bottom of the layout xml to ensure it is at the front,
- clickable on Pre-Lollipop devices (that do not support elevation).
- See: https://stackoverflow.com/a/2614402
- 2. ScrollView is needed to ensure the vertical list of speed dials are
- accessible when screen height is small, eg., landscape mode on most phones.
- -->
- <ScrollView
- android:id="@+id/fabSDScrollCtr"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:elevation="@dimen/sd_open_elevation"
- tools:ignore="UnusedAttribute" >
-
- <com.leinardi.android.speeddial.SpeedDialView
- android:id="@+id/fabSD"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:sdMainFabClosedSrc="@drawable/ic_fab_edit"
- app:sdOverlayLayout="@id/fabSDOverlay"
- android:layout_marginEnd="16dp"
- android:layout_marginRight="16dp"
- android:layout_marginBottom="16dp"
- android:accessibilityTraversalBefore="@android:id/list"
- android:contentDescription="@string/apply_action" />
- </ScrollView>
-
-</RelativeLayout>
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 6dc484e2f..734ce64dd 100644
--- a/app/src/main/res/layout/feed_item_list_fragment.xml
+++ b/app/src/main/res/layout/feed_item_list_fragment.xml
@@ -1,51 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
- android:id="@+id/appBar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:id="@+id/appBar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
- android:id="@+id/collapsing_toolbar"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?android:attr/windowBackground"
- app:contentScrim="?android:attr/windowBackground"
- app:scrimAnimationDuration="200"
- app:layout_scrollFlags="scroll|exitUntilCollapsed">
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/windowBackground"
+ app:contentScrim="?android:attr/windowBackground"
+ app:scrimAnimationDuration="200"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
- android:id="@+id/imgvBackground"
- style="@style/BigBlurryBackground"
- android:background="@color/image_readability_tint"
- android:layout_width="match_parent"
- android:layout_height="232dp"
- app:layout_collapseMode="parallax"
- app:layout_collapseParallaxMultiplier="0.6"/>
+ android:id="@+id/imgvBackground"
+ android:layout_width="match_parent"
+ android:layout_height="232dp"
+ android:background="@color/image_readability_tint"
+ style="@style/BigBlurryBackground"
+ app:layout_collapseMode="parallax"
+ app:layout_collapseParallaxMultiplier="0.6" />
- <include layout="@layout/feeditemlist_header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- app:layout_collapseMode="parallax"
- app:layout_collapseParallaxMultiplier="0.6" />
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ layout="@layout/feeditemlist_header"
+ app:layout_collapseMode="parallax"
+ app:layout_collapseParallaxMultiplier="0.6" />
<androidx.appcompat.widget.Toolbar
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?attr/actionBarSize"
- android:theme="?attr/actionBarTheme"
- android:layout_alignParentTop="true"
- android:id="@+id/toolbar"
- app:navigationIcon="?homeAsUpIndicator"
- app:layout_collapseMode="pin"/>
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/actionBarSize"
+ android:theme="?attr/actionBarTheme"
+ android:layout_alignParentTop="true"
+ app:navigationIcon="?homeAsUpIndicator"
+ app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
+
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@@ -54,27 +56,30 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
- <de.danoeh.antennapod.view.EpisodeItemListRecyclerView
- android:id="@+id/recyclerView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingHorizontal="@dimen/additional_horizontal_spacing" />
+ <de.danoeh.antennapod.view.EpisodeItemListRecyclerView
+ android:id="@+id/recyclerView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="@dimen/additional_horizontal_spacing" />
</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"/>
+ 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
- layout="@layout/more_content_list_footer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:visibility="gone"/>
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:visibility="gone"
+ layout="@layout/more_content_list_footer" />
+
+ <include
+ layout="@layout/multi_select_speed_dial" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/feeditemlist_item.xml b/app/src/main/res/layout/feeditemlist_item.xml
index 37b88d1b5..b876f079d 100644
--- a/app/src/main/res/layout/feeditemlist_item.xml
+++ b/app/src/main/res/layout/feeditemlist_item.xml
@@ -1,177 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:ignore="MergeRootFrame">
<!--
This parent FrameLayout is necessary because RecyclerView's ItemAnimator changes alpha values,
which conflicts with our played state indicator.
-->
-
<LinearLayout
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:baselineAligned="false"
- android:paddingStart="12dp"
- android:paddingLeft="12dp"
- android:paddingEnd="0dp"
- android:paddingRight="0dp"
- tools:ignore="UselessParent">
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:baselineAligned="false"
+ android:paddingStart="12dp"
+ android:paddingLeft="12dp"
+ android:paddingEnd="0dp"
+ android:paddingRight="0dp"
+ tools:ignore="UselessParent">
<LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:id="@+id/left_padding"
- android:minWidth="4dp">
+ android:id="@+id/left_padding"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="4dp">
+
<ImageView
- android:id="@+id/drag_handle"
- android:layout_width="16dp"
- android:layout_height="match_parent"
- android:importantForAccessibility="no"
- android:scaleType="fitCenter"
- app:srcCompat="?attr/dragview_background"
- android:paddingStart="0dp"
- android:paddingLeft="0dp"
- android:paddingEnd="4dp"
- android:paddingRight="4dp"
- tools:src="@drawable/ic_drag_darktheme"
- tools:background="@android:color/holo_green_dark"/>
+ android:id="@+id/drag_handle"
+ android:layout_width="16dp"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter"
+ android:paddingStart="0dp"
+ android:paddingLeft="0dp"
+ android:paddingEnd="4dp"
+ android:paddingRight="4dp"
+ app:srcCompat="?attr/dragview_background"
+ tools:src="@drawable/ic_drag_darktheme"
+ tools:background="@android:color/holo_green_dark" />
+
+ <!-- Needs to have the same width as the action button. Otherwise, the screen jumps around. -->
+ <CheckBox
+ android:id="@+id/selectCheckBox"
+ android:layout_width="60dp"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
</LinearLayout>
<androidx.cardview.widget.CardView
- android:layout_width="@dimen/thumbnail_length_queue_item"
- android:layout_height="@dimen/thumbnail_length_queue_item"
- android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
- android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
- android:layout_marginRight="@dimen/listitem_threeline_textleftpadding"
- android:layout_marginEnd="@dimen/listitem_threeline_textleftpadding"
- android:id="@+id/coverHolder"
- app:cardBackgroundColor="@color/non_square_icon_background"
- app:cardCornerRadius="4dp"
- app:cardPreventCornerOverlap="false"
- app:cardElevation="0dp">
+ android:id="@+id/coverHolder"
+ android:layout_width="@dimen/thumbnail_length_queue_item"
+ android:layout_height="@dimen/thumbnail_length_queue_item"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginRight="@dimen/listitem_threeline_textleftpadding"
+ android:layout_marginEnd="@dimen/listitem_threeline_textleftpadding"
+ app:cardBackgroundColor="@color/non_square_icon_background"
+ app:cardCornerRadius="4dp"
+ app:cardPreventCornerOverlap="false"
+ app:cardElevation="0dp">
<RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<TextView
- android:id="@+id/txtvPlaceholder"
- android:layout_width="@dimen/thumbnail_length_queue_item"
- android:layout_height="@dimen/thumbnail_length_queue_item"
- android:layout_centerVertical="true"
- android:gravity="center"
- android:background="@color/light_gray"
- android:maxLines="3"
- android:padding="2dp"
- android:ellipsize="end"/>
+ android:id="@+id/txtvPlaceholder"
+ android:layout_width="@dimen/thumbnail_length_queue_item"
+ android:layout_height="@dimen/thumbnail_length_queue_item"
+ android:layout_centerVertical="true"
+ android:gravity="center"
+ android:background="@color/light_gray"
+ android:maxLines="3"
+ android:padding="2dp"
+ android:ellipsize="end" />
+
<ImageView
- android:id="@+id/imgvCover"
- android:layout_width="@dimen/thumbnail_length_queue_item"
- android:layout_height="@dimen/thumbnail_length_queue_item"
- android:layout_centerVertical="true"
- android:importantForAccessibility="no"
- tools:src="@tools:sample/avatars"/>
+ android:id="@+id/imgvCover"
+ android:layout_width="@dimen/thumbnail_length_queue_item"
+ android:layout_height="@dimen/thumbnail_length_queue_item"
+ android:layout_centerVertical="true"
+ android:importantForAccessibility="no"
+ tools:src="@tools:sample/avatars" />
</RelativeLayout>
+
</androidx.cardview.widget.CardView>
<LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
- android:layout_marginRight="@dimen/listitem_threeline_textrightpadding"
- android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding"
- android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
- android:layout_weight="1"
- tools:background="@android:color/holo_red_dark"
- android:orientation="vertical">
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
+ android:layout_marginRight="@dimen/listitem_threeline_textrightpadding"
+ android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding"
+ android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ tools:background="@android:color/holo_red_dark">
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/status"
- android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:id="@+id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
<TextView
- android:text="@string/new_label"
- style="@style/AntennaPod.TextView.UnreadIndicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/statusUnread"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- tools:text="@sample/episodes.json/data/status_label"/>
+ android:id="@+id/statusUnread"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/new_label"
+ android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
+ style="@style/AntennaPod.TextView.UnreadIndicator"
+ tools:text="@sample/episodes.json/data/status_label" />
<ImageView
- android:layout_width="14sp"
- android:layout_height="14sp"
- app:srcCompat="@drawable/ic_videocam"
- android:contentDescription="@string/media_type_video_label"
- android:id="@+id/ivIsVideo"/>
+ android:id="@+id/ivIsVideo"
+ android:layout_width="14sp"
+ android:layout_height="14sp"
+ android:contentDescription="@string/media_type_video_label"
+ app:srcCompat="@drawable/ic_videocam" />
<ImageView
- android:layout_width="14sp"
- android:layout_height="14sp"
- app:srcCompat="@drawable/ic_star"
- android:contentDescription="@string/is_favorite_label"
- android:id="@+id/isFavorite"/>
+ android:id="@+id/isFavorite"
+ android:layout_width="14sp"
+ android:layout_height="14sp"
+ android:contentDescription="@string/is_favorite_label"
+ app:srcCompat="@drawable/ic_star" />
<ImageView
- android:layout_width="14sp"
- android:layout_height="14sp"
- app:srcCompat="@drawable/ic_playlist"
- android:contentDescription="@string/in_queue_label"
- android:id="@+id/ivInPlaylist"/>
+ android:id="@+id/ivInPlaylist"
+ android:layout_width="14sp"
+ android:layout_height="14sp"
+ android:contentDescription="@string/in_queue_label"
+ app:srcCompat="@drawable/ic_playlist" />
<TextView
- android:id="@+id/separatorIcons"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="4dp"
- android:layout_marginStart="4dp"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- android:text="·"
- android:importantForAccessibility="no"
- tools:background="@android:color/holo_blue_light"/>
+ android:id="@+id/separatorIcons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
+ android:text="·"
+ android:importantForAccessibility="no"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:background="@android:color/holo_blue_light" />
<TextView
- android:id="@+id/txtvPubDate"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- tools:text="@sample/episodes.json/data/published_at"/>
+ android:id="@+id/txtvPubDate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:text="@sample/episodes.json/data/published_at" />
<TextView
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- android:text="·"
- android:importantForAccessibility="no"
- tools:background="@android:color/holo_blue_light"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
+ android:text="·"
+ android:importantForAccessibility="no"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:background="@android:color/holo_blue_light" />
<TextView
- android:id="@+id/size"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- tools:text="10 MB"/>
+ android:id="@+id/size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:text="10 MB" />
</LinearLayout>
@@ -181,55 +191,58 @@
Keep this in mind when changing the order of this layout!
-->
<TextView
- android:id="@+id/txtvTitle"
- style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- tools:text="@sample/episodes.json/data/title"
- android:importantForAccessibility="no"
- android:ellipsize="end"
- tools:background="@android:color/holo_blue_light"/>
+ android:id="@+id/txtvTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no"
+ android:ellipsize="end"
+ style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
+ tools:text="@sample/episodes.json/data/title"
+ tools:background="@android:color/holo_blue_light" />
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/progress"
- android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:id="@+id/progress"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
<TextView
- android:id="@+id/txtvPosition"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="0dp"
- tools:text="00:42:23"
- tools:background="@android:color/holo_blue_light"/>
+ android:id="@+id/txtvPosition"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="0dp"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:text="00:42:23"
+ tools:background="@android:color/holo_blue_light" />
<ProgressBar
- android:id="@+id/progressBar"
- style="?attr/progressBarTheme"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="4dp"
- android:max="100"
- android:layout_margin="4dp"
- tools:background="@android:color/holo_blue_light"/>
+ android:id="@+id/progressBar"
+ android:layout_width="0dp"
+ android:layout_height="4dp"
+ android:layout_weight="1"
+ android:max="100"
+ android:layout_margin="4dp"
+ style="?attr/progressBarTheme"
+ tools:background="@android:color/holo_blue_light" />
<TextView
- android:id="@+id/txtvDuration"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="0dp"
- tools:text="@sample/episodes.json/data/duration"
- tools:background="@android:color/holo_blue_light"/>
+ android:id="@+id/txtvDuration"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="0dp"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ tools:text="@sample/episodes.json/data/duration"
+ tools:background="@android:color/holo_blue_light" />
</LinearLayout>
</LinearLayout>
- <include layout="@layout/secondary_action"/>
+ <include
+ android:id="@+id/secondaryActionButton"
+ layout="@layout/secondary_action" />
</LinearLayout>
+
</FrameLayout>
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
index 48195a176..10b560faf 100644
--- a/app/src/main/res/layout/main.xml
+++ b/app/src/main/res/layout/main.xml
@@ -1,18 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/drawer_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:ignore="InconsistentLayout">
- <!-- InconsistentLayout: Tablet layout does not have a drawer -->
+ 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:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:ignore="InconsistentLayout"
+ tools:viewBindingIgnore="true">
+ <!-- InconsistentLayout: Tablet layout does not have a drawer -->
+ <!-- viewBindingIgnore: Configurations for main.xml must
+ agree on the root element's ID -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
- android:id="@+id/overview_coordinator_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:id="@+id/overview_coordinator_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<FrameLayout
android:id="@+id/main_view"
@@ -23,21 +26,21 @@
tools:background="@android:color/holo_red_dark" />
<FrameLayout
- android:elevation="8dp"
android:id="@+id/audioplayerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
+ android:elevation="8dp"
android:visibility="gone"
app:layout_behavior="de.danoeh.antennapod.view.LockableBottomSheetBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<FrameLayout
- android:id="@+id/navDrawerFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="start"
- android:orientation="vertical" />
+ android:id="@+id/navDrawerFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:orientation="vertical" />
-</androidx.drawerlayout.widget.DrawerLayout> \ No newline at end of file
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/multi_select_speed_dial.xml b/app/src/main/res/layout/multi_select_speed_dial.xml
new file mode 100644
index 000000000..0451471bc
--- /dev/null
+++ b/app/src/main/res/layout/multi_select_speed_dial.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.leinardi.android.speeddial.SpeedDialOverlayLayout
+ android:id="@+id/fabSDOverlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no" />
+
+ <!-- The FAB SpeedDial
+ 1. MUST be placed at the bottom of the layout xml to ensure it is at the front,
+ clickable on Pre-Lollipop devices (that do not support elevation).
+ See: https://stackoverflow.com/a/2614402
+ 2. ScrollView is needed to ensure the vertical list of speed dials are
+ accessible when screen height is small, eg., landscape mode on most phones.
+ -->
+ <ScrollView
+ android:id="@+id/fabSDScrollCtr"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:elevation="@dimen/sd_open_elevation">
+
+ <com.leinardi.android.speeddial.SpeedDialView
+ android:id="@+id/fabSD"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:accessibilityTraversalBefore="@android:id/list"
+ android:contentDescription="@string/apply_action"
+ android:visibility="gone"
+ app:sdMainFabClosedSrc="@drawable/ic_fab_edit"
+ app:sdOverlayLayout="@id/fabSDOverlay" />
+
+ </ScrollView>
+
+</merge>
diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml
index 3bcd4819f..292b1bb45 100644
--- a/app/src/main/res/layout/queue_fragment.xml
+++ b/app/src/main/res/layout/queue_fragment.xml
@@ -1,42 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools">
+<RelativeLayout
+ 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="match_parent">
<androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true"
- app:title="@string/queue_label"
- android:id="@+id/toolbar"/>
+ app:title="@string/queue_label" />
<TextView
- android:layout_below="@id/toolbar"
android:id="@+id/info_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@id/toolbar"
android:textSize="12sp"
android:layout_marginTop="-8dp"
android:layout_marginLeft="72dp"
android:layout_marginStart="72dp"
android:layout_marginBottom="4dp"
- tools:text="12 Episodes - Time remaining: 12 hours"/>
+ tools:text="12 Episodes - Time remaining: 12 hours" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/info_bar"
- android:background="?android:attr/listDivider"/>
+ android:background="?android:attr/listDivider" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- android:id="@+id/swipeRefresh"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/divider">
+ android:id="@+id/swipeRefresh"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/divider">
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
android:id="@+id/recyclerView"
@@ -54,4 +56,7 @@
android:indeterminateOnly="true"
android:visibility="gone" />
+ <include
+ layout="@layout/multi_select_speed_dial" />
+
</RelativeLayout>
diff --git a/app/src/main/res/layout/simple_list_fragment.xml b/app/src/main/res/layout/simple_list_fragment.xml
index 989566499..6ea3ab54b 100644
--- a/app/src/main/res/layout/simple_list_fragment.xml
+++ b/app/src/main/res/layout/simple_list_fragment.xml
@@ -1,29 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?attr/actionBarSize"
- android:theme="?attr/actionBarTheme"
- android:layout_alignParentTop="true"
- android:id="@+id/toolbar"/>
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/actionBarSize"
+ android:theme="?attr/actionBarTheme"
+ android:layout_alignParentTop="true" />
<de.danoeh.antennapod.view.EpisodeItemListRecyclerView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingHorizontal="@dimen/additional_horizontal_spacing"
- android:layout_below="@id/toolbar"
- android:id="@+id/recyclerView"/>
+ android:id="@+id/recyclerView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="@dimen/additional_horizontal_spacing"
+ android:layout_below="@id/toolbar" />
<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"/>
+ 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" />
</RelativeLayout>
diff --git a/app/src/main/res/layout/swipeactions_dialog.xml b/app/src/main/res/layout/swipeactions_dialog.xml
new file mode 100644
index 000000000..a1f0b7ae6
--- /dev/null
+++ b/app/src/main/res/layout/swipeactions_dialog.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/enableSwitch"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/enable_swipeactions" />
+
+ <include
+ android:id="@+id/actionLeftContainer"
+ layout="@layout/swipeactions_row" />
+
+ <include
+ android:id="@+id/actionRightContainer"
+ layout="@layout/swipeactions_row" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/swipeactions_picker.xml b/app/src/main/res/layout/swipeactions_picker.xml
new file mode 100644
index 000000000..e473888b2
--- /dev/null
+++ b/app/src/main/res/layout/swipeactions_picker.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.gridlayout.widget.GridLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:grid="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/pickerGridLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="16dp"
+ grid:columnCount="2"
+ grid:alignmentMode="alignBounds" />
diff --git a/app/src/main/res/layout/swipeactions_picker_item.xml b/app/src/main/res/layout/swipeactions_picker_item.xml
new file mode 100644
index 000000000..b497efb14
--- /dev/null
+++ b/app/src/main/res/layout/swipeactions_picker_item.xml
@@ -0,0 +1,29 @@
+<?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:grid="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:background="?attr/selectableItemBackground"
+ android:padding="8dp">
+
+ <ImageView
+ android:id="@+id/swipeIcon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ app:srcCompat="@drawable/ic_add" />
+
+ <TextView
+ android:id="@+id/swipeActionLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/add_to_queue_label"
+ android:textSize="14sp"
+ android:textAlignment="center"
+ android:textColor="?android:attr/textColorPrimary" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/swipeactions_row.xml b/app/src/main/res/layout/swipeactions_row.xml
new file mode 100644
index 000000000..df55d3f89
--- /dev/null
+++ b/app/src/main/res/layout/swipeactions_row.xml
@@ -0,0 +1,84 @@
+<?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:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="8dp">
+
+ <TextView
+ android:id="@+id/swipeDirectionLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/swipe_left"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="18sp" />
+
+ <TextView
+ android:id="@+id/swipeActionLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_below="@+id/swipeDirectionLabel"
+ android:textSize="14sp"
+ tools:text="@string/add_to_queue_label" />
+
+ <Button
+ android:id="@+id/changeButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:text="@string/change_setting"
+ style="@style/Widget.MaterialComponents.Button.TextButton" />
+
+ </RelativeLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/background_elevated" />
+
+ <LinearLayout
+ android:id="@+id/previewContainer"
+ android:layout_width="match_parent"
+ android:layout_height="76dp"
+ android:gravity="center"
+ android:foreground="?attr/selectableItemBackground"
+ android:orientation="horizontal">
+
+ <include
+ android:id="@+id/mockEpisode"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0.7"
+ layout="@layout/feeditemlist_item" />
+
+ <ImageView
+ android:id="@+id/swipeIcon"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0.3"
+ android:background="?attr/background_elevated"
+ android:padding="22dp"
+ app:srcCompat="@drawable/ic_add" />
+
+ </LinearLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/background_elevated" />
+
+</LinearLayout>
diff --git a/app/src/main/res/menu/downloads.xml b/app/src/main/res/menu/downloads.xml
index 142f251fc..54469a101 100644
--- a/app/src/main/res/menu/downloads.xml
+++ b/app/src/main/res/menu/downloads.xml
@@ -2,13 +2,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
- android:id="@+id/episode_actions"
- android:menuCategory="container"
- android:title="@string/multi_select"
- android:icon="@drawable/ic_check_multiple"
- android:visible="false"
- app:showAsAction="ifRoom" />
- <item
android:id="@+id/clear_logs_item"
android:menuCategory="container"
android:title="@string/clear_history_label"
diff --git a/app/src/main/res/menu/episodes_apply_action_options.xml b/app/src/main/res/menu/episodes_apply_action_options.xml
deleted file mode 100644
index 221ec4d59..000000000
--- a/app/src/main/res/menu/episodes_apply_action_options.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <item
- android:id="@+id/sort"
- android:icon="@drawable/ic_sort"
- android:title="@string/sort"
- app:showAsAction="always">
- <menu>
- <item android:id="@+id/sort_title_a_z"
- android:title="@string/sort_title_a_z"/>
- <item android:id="@+id/sort_title_z_a"
- android:title="@string/sort_title_z_a"/>
- <item android:id="@+id/sort_date_new_old"
- android:title="@string/sort_date_new_old"/>
- <item android:id="@+id/sort_date_old_new"
- android:title="@string/sort_date_old_new"/>
- <item android:id="@+id/sort_duration_short_long"
- android:title="@string/sort_duration_short_long"/>
- <item android:id="@+id/sort_duration_long_short"
- android:title="@string/sort_duration_long_short"/>
- </menu>
- </item>
-
- <item
- android:id="@+id/select_options"
- android:icon="@drawable/ic_filter"
- android:title="@string/filter"
- app:showAsAction="always">
-
- <menu>
- <item android:id="@+id/check_all"
- android:title="@string/all_label"/>
- <item android:id="@+id/check_none"
- android:title="@string/select_none_label"/>
- <item android:id="@+id/check_played"
- android:title="@string/played_label"/>
- <item android:id="@+id/check_unplayed"
- android:title="@string/unplayed_label"/>
- <item android:id="@+id/check_downloaded"
- android:title="@string/downloaded_label"/>
- <item android:id="@+id/check_not_downloaded"
- android:title="@string/not_downloaded_label"/>
- <item android:id="@+id/check_queued"
- android:title="@string/queued_label"/>
- <item android:id="@+id/check_not_queued"
- android:title="@string/not_queued_label"/>
- <item android:id="@+id/check_has_media"
- android:title="@string/has_media"/>
- </menu>
- </item>
-
- <item
- android:id="@+id/select_toggle"
- android:title="@string/select_all_label"
- app:showAsAction="always"/>
-</menu>
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 a2f509ec5..c9bc4b4df 100644
--- a/app/src/main/res/menu/episodes_apply_action_speeddial.xml
+++ b/app/src/main/res/menu/episodes_apply_action_speeddial.xml
@@ -14,21 +14,21 @@
android:title="@string/download_label"
/>
<item android:id="@+id/mark_unread_batch"
- android:icon="@drawable/ic_cancel"
+ android:icon="@drawable/ic_mark_unplayed"
android:title="@string/mark_unread_label"
/>
<item
android:id="@+id/mark_read_batch"
- android:icon="@drawable/ic_check"
+ android:icon="@drawable/ic_mark_played"
android:title="@string/mark_read_label"
/>
<item android:id="@+id/remove_from_queue_batch"
- android:icon="@drawable/ic_remove"
+ android:icon="@drawable/ic_playlist_remove"
android:title="@string/remove_from_queue_label"
/>
<item
android:id="@+id/add_to_queue_batch"
- android:icon="@drawable/ic_add"
+ android:icon="@drawable/ic_playlist"
android:title="@string/add_to_queue_label"
/>
</menu>
diff --git a/app/src/main/res/menu/feeditem_options.xml b/app/src/main/res/menu/feeditem_options.xml
index 5b33539e1..70400fe55 100644
--- a/app/src/main/res/menu/feeditem_options.xml
+++ b/app/src/main/res/menu/feeditem_options.xml
@@ -50,18 +50,6 @@
custom:showAsAction="collapseActionView"
android:title="@string/reset_position">
</item>
-
- <item
- android:id="@+id/activate_auto_download"
- custom:showAsAction="collapseActionView"
- android:title="@string/activate_auto_download">
- </item>
- <item
- android:id="@+id/deactivate_auto_download"
- custom:showAsAction="collapseActionView"
- android:title="@string/deactivate_auto_download">
- </item>
-
<item
android:id="@+id/visit_website_item"
android:icon="@drawable/ic_web"
diff --git a/app/src/main/res/menu/feeditemlist_context.xml b/app/src/main/res/menu/feeditemlist_context.xml
index 84e45d4c9..f6edec0b4 100644
--- a/app/src/main/res/menu/feeditemlist_context.xml
+++ b/app/src/main/res/menu/feeditemlist_context.xml
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-
<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
<item
android:id="@id/skip_episode_item"
android:menuCategory="container"
@@ -16,6 +14,7 @@
android:id="@+id/mark_read_item"
android:menuCategory="container"
android:title="@string/mark_read_label" />
+
<item
android:id="@+id/mark_unread_item"
android:menuCategory="container"
@@ -25,10 +24,12 @@
android:id="@+id/add_to_queue_item"
android:menuCategory="container"
android:title="@string/add_to_queue_label" />
+
<item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
+
<item
android:id="@+id/remove_item"
android:menuCategory="container"
@@ -38,6 +39,7 @@
android:id="@+id/add_to_favorites_item"
android:menuCategory="container"
android:title="@string/add_to_favorite_label" />
+
<item
android:id="@+id/remove_from_favorites_item"
android:menuCategory="container"
@@ -49,20 +51,13 @@
android:title="@string/reset_position" />
<item
- android:id="@+id/activate_auto_download"
- android:menuCategory="container"
- android:title="@string/activate_auto_download" />
- <item
- android:id="@+id/deactivate_auto_download"
+ android:id="@+id/share_item"
android:menuCategory="container"
- android:title="@string/deactivate_auto_download" />
+ android:title="@string/share_label" />
<item
- android:id="@+id/visit_website_item"
+ android:id="@+id/multi_select"
android:menuCategory="container"
- android:title="@string/visit_website_label" />
- <item
- android:id="@+id/share_item"
- android:menuCategory="container"
- android:title="@string/share_label" />
+ android:title="@string/multi_select"
+ android:visible="false" />
</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feedlist.xml
index 85e7a95ba..306be66d1 100644
--- a/app/src/main/res/menu/feedlist.xml
+++ b/app/src/main/res/menu/feedlist.xml
@@ -38,13 +38,6 @@
android:title="@string/search_label"/>
<item
- android:id="@+id/episode_actions"
- android:menuCategory="container"
- android:icon="@drawable/ic_check_multiple"
- android:title="@string/multi_select"
- custom:showAsAction="collapseActionView">
- </item>
- <item
android:id="@+id/visit_website_item"
android:icon="@drawable/ic_web"
android:menuCategory="container"
diff --git a/app/src/main/res/menu/multi_select_context_popup.xml b/app/src/main/res/menu/multi_select_context_popup.xml
new file mode 100644
index 000000000..730b01016
--- /dev/null
+++ b/app/src/main/res/menu/multi_select_context_popup.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/select_all_above"
+ android:title="@string/select_all_above">
+ </item>
+ <item
+ android:id="@+id/select_all_below"
+ android:title="@string/select_all_below">
+ </item>
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/multi_select_options.xml b/app/src/main/res/menu/multi_select_options.xml
new file mode 100644
index 000000000..5cb2b7602
--- /dev/null
+++ b/app/src/main/res/menu/multi_select_options.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/select_toggle"
+ android:title="@string/select_all_label"
+ app:showAsAction="always"/>
+</menu>
diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml
index adf44b8b1..34d8f32c0 100644
--- a/app/src/main/res/menu/queue.xml
+++ b/app/src/main/res/menu/queue.xml
@@ -115,10 +115,4 @@
android:title="@string/clear_queue_label"
custom:showAsAction="collapseActionView"
android:icon="@drawable/ic_check"/>
-
- <item
- android:id="@+id/episode_actions"
- custom:showAsAction="collapseActionView"
- android:title="@string/multi_select" />
-
</menu>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 523c7cd0f..d528945c7 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -31,7 +31,7 @@
android:key="prefScreenGpodder"
android:title="@string/synchronization_pref"
android:summary="@string/synchronization_sum"
- android:icon="@drawable/ic_star" />
+ android:icon="@drawable/ic_cloud" />
<Preference
android:key="prefScreenStorage"
diff --git a/app/src/main/res/xml/preferences_swipe.xml b/app/src/main/res/xml/preferences_swipe.xml
new file mode 100644
index 000000000..eb238ac14
--- /dev/null
+++ b/app/src/main/res/xml/preferences_swipe.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <Preference
+ android:key="prefSwipeFeed"
+ android:title="@string/feeds_label"/>
+ <Preference
+ android:key="prefSwipeQueue"
+ android:title="@string/queue_label"/>
+
+</PreferenceScreen>
diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml
index f8e80cdff..0b2707a18 100644
--- a/app/src/main/res/xml/preferences_user_interface.xml
+++ b/app/src/main/res/xml/preferences_user_interface.xml
@@ -78,5 +78,9 @@
android:title="@string/pref_back_button_behavior_title"
android:summary="@string/pref_back_button_behavior_sum"
android:defaultValue="default"/>
+ <Preference
+ android:key="prefSwipe"
+ android:summary="@string/swipeactions_summary"
+ android:title="@string/swipeactions_label"/>
</PreferenceCategory>
</PreferenceScreen>