Swipe actions (#5191)
This commit is contained in:
parent
c1efd51be9
commit
ca9ad0d2d3
@ -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'
|
||||
|
@ -27,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;
|
||||
|
||||
/**
|
||||
@ -80,6 +81,8 @@ 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;
|
||||
}
|
||||
@ -104,6 +107,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe
|
||||
return R.string.notification_pref_fragment;
|
||||
case R.xml.feed_settings:
|
||||
return R.string.feed_settings_label;
|
||||
case R.xml.preferences_swipe:
|
||||
return R.string.swipeactions_label;
|
||||
default:
|
||||
return R.string.settings_label;
|
||||
}
|
||||
|
@ -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,7 +40,7 @@ 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;
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
|
||||
private ActionMode actionMode;
|
||||
private final HashSet<Long> selectedIds = new HashSet<>();
|
||||
private final Activity activity;
|
||||
private OnEndSelectModeListener onEndSelectModeListener;
|
||||
private OnSelectModeListener onSelectModeListener;
|
||||
|
||||
public SelectableAdapter(Activity activity) {
|
||||
this.activity = activity;
|
||||
@ -30,6 +30,10 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
|
||||
endSelectMode();
|
||||
}
|
||||
|
||||
if (onSelectModeListener != null) {
|
||||
onSelectModeListener.onStartSelectMode();
|
||||
}
|
||||
|
||||
selectedIds.clear();
|
||||
selectedIds.add(getItemId(pos));
|
||||
notifyDataSetChanged();
|
||||
@ -152,17 +156,19 @@ abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends Recy
|
||||
selectedIds.size(), getItemCount()));
|
||||
}
|
||||
|
||||
public void setOnEndSelectModeListener(OnEndSelectModeListener onEndSelectModeListener) {
|
||||
this.onEndSelectModeListener = onEndSelectModeListener;
|
||||
public void setOnSelectModeListener(OnSelectModeListener onSelectModeListener) {
|
||||
this.onSelectModeListener = onSelectModeListener;
|
||||
}
|
||||
|
||||
private void callOnEndSelectMode() {
|
||||
if (onEndSelectModeListener != null) {
|
||||
onEndSelectModeListener.onEndSelectMode();
|
||||
if (onSelectModeListener != null) {
|
||||
onSelectModeListener.onEndSelectMode();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnEndSelectModeListener {
|
||||
public interface OnSelectModeListener {
|
||||
void onStartSelectMode();
|
||||
|
||||
void onEndSelectMode();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ import java.util.List;
|
||||
* Displays all completed downloads and provides a button to delete them.
|
||||
*/
|
||||
public class CompletedDownloadsFragment extends Fragment implements
|
||||
EpisodeItemListAdapter.OnEndSelectModeListener {
|
||||
EpisodeItemListAdapter.OnSelectModeListener {
|
||||
|
||||
private static final String TAG = CompletedDownloadsFragment.class.getSimpleName();
|
||||
|
||||
@ -79,7 +79,7 @@ public class CompletedDownloadsFragment extends Fragment implements
|
||||
recyclerView = root.findViewById(R.id.recyclerView);
|
||||
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
|
||||
adapter = new CompletedDownloadsListAdapter((MainActivity) getActivity());
|
||||
adapter.setOnEndSelectModeListener(this);
|
||||
adapter.setOnSelectModeListener(this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
progressBar = root.findViewById(R.id.progLoading);
|
||||
|
||||
@ -107,7 +107,6 @@ public class CompletedDownloadsFragment extends Fragment implements
|
||||
speedDialView.setOnActionSelectedListener(actionItem -> {
|
||||
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
|
||||
.handleAction(actionItem.getId());
|
||||
onEndSelectMode();
|
||||
adapter.endSelectMode();
|
||||
return true;
|
||||
});
|
||||
@ -171,9 +170,6 @@ public class CompletedDownloadsFragment extends Fragment implements
|
||||
Log.i(TAG, "Selected item at current position was null, ignoring selection");
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
if (item.getItemId() == R.id.multi_select) {
|
||||
speedDialView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (adapter.onContextItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
@ -258,6 +254,11 @@ public class CompletedDownloadsFragment extends Fragment implements
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartSelectMode() {
|
||||
speedDialView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndSelectMode() {
|
||||
speedDialView.close();
|
||||
|
@ -51,15 +51,14 @@ 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;
|
||||
@ -74,10 +73,14 @@ import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
|
||||
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;
|
||||
@ -90,12 +93,13 @@ import io.reactivex.schedulers.Schedulers;
|
||||
* Displays a list of FeedItems.
|
||||
*/
|
||||
public class FeedItemlistFragment extends Fragment implements AdapterView.OnItemClickListener,
|
||||
Toolbar.OnMenuItemClickListener, EpisodeItemListAdapter.OnEndSelectModeListener {
|
||||
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;
|
||||
@ -166,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);
|
||||
@ -252,7 +257,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
||||
speedDialView.setOnActionSelectedListener(actionItem -> {
|
||||
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), adapter.getSelectedItems())
|
||||
.handleAction(actionItem.getId());
|
||||
onEndSelectMode();
|
||||
adapter.endSelectMode();
|
||||
return true;
|
||||
});
|
||||
@ -348,16 +352,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
||||
Log.i(TAG, "Selected item at current position was null, ignoring selection");
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.multi_select) {
|
||||
if (feed.isLocalFeed()) {
|
||||
speedDialView.removeActionItemById(R.id.download_batch);
|
||||
speedDialView.removeActionItemById(R.id.delete_batch);
|
||||
}
|
||||
speedDialView.setVisibility(View.VISIBLE);
|
||||
refreshToolbarState();
|
||||
// Do not return: Let adapter handle its actions, too.
|
||||
}
|
||||
if (adapter.onContextItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
@ -432,10 +426,32 @@ 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() {
|
||||
@ -478,12 +494,14 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
||||
if (adapter == null) {
|
||||
recyclerView.setAdapter(null);
|
||||
adapter = new FeedItemListAdapter((MainActivity) getActivity());
|
||||
adapter.setOnEndSelectModeListener(this);
|
||||
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();
|
||||
@ -597,7 +615,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())
|
||||
|
@ -39,6 +39,7 @@ 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;
|
||||
@ -48,6 +49,7 @@ 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.menuhandler.FeedItemMenuHandler;
|
||||
@ -70,7 +72,7 @@ import java.util.Locale;
|
||||
* Shows all items in the queue.
|
||||
*/
|
||||
public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener,
|
||||
EpisodeItemListAdapter.OnEndSelectModeListener {
|
||||
EpisodeItemListAdapter.OnSelectModeListener {
|
||||
public static final String TAG = "QueueFragment";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
@ -90,7 +92,7 @@ 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;
|
||||
@ -398,12 +400,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
||||
Log.i(TAG, "Selected item no longer exist, ignoring selection");
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
if (item.getItemId() == R.id.multi_select) {
|
||||
speedDialView.setVisibility(View.VISIBLE);
|
||||
refreshToolbarState();
|
||||
infoBar.setVisibility(View.GONE);
|
||||
// Do not return: Let adapter handle its actions, too.
|
||||
}
|
||||
if (recyclerAdapter.onContextItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
@ -455,83 +451,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);
|
||||
@ -565,7 +487,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
||||
speedDialView.setOnActionSelectedListener(actionItem -> {
|
||||
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), recyclerAdapter.getSelectedItems())
|
||||
.handleAction(actionItem.getId());
|
||||
onEndSelectMode();
|
||||
recyclerAdapter.endSelectMode();
|
||||
return true;
|
||||
});
|
||||
@ -582,8 +503,8 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
||||
if (queue != null && queue.size() > 0) {
|
||||
if (recyclerAdapter == null) {
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
recyclerAdapter = new QueueRecyclerAdapter(activity, itemTouchHelper);
|
||||
recyclerAdapter.setOnEndSelectModeListener(this);
|
||||
recyclerAdapter = new QueueRecyclerAdapter(activity, swipeActions);
|
||||
recyclerAdapter.setOnSelectModeListener(this);
|
||||
recyclerView.setAdapter(recyclerAdapter);
|
||||
emptyView.updateAdapter(recyclerAdapter);
|
||||
}
|
||||
@ -648,10 +569,91 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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 = "PrefSwipeActions6543";
|
||||
public static final String KEY_PREFIX_NO_ACTION = "PrefNoSwipeAction6543";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
@ -220,15 +221,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 = () -> {
|
||||
@ -238,15 +240,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;
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
public static void removeNewFlagWithUndo(@NonNull Fragment fragment, FeedItem item) {
|
||||
markReadWithUndo(fragment, item, FeedItem.UNPLAYED, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
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">
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="MergeRootFrame">
|
||||
|
||||
<!--
|
||||
This parent FrameLayout is necessary because RecyclerView's ItemAnimator changes alpha values,
|
||||
@ -47,7 +48,8 @@
|
||||
<CheckBox
|
||||
android:id="@+id/selectCheckBox"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -238,6 +240,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/secondaryActionButton"
|
||||
layout="@layout/secondary_action" />
|
||||
|
||||
</LinearLayout>
|
||||
|
24
app/src/main/res/layout/swipeactions_dialog.xml
Normal file
24
app/src/main/res/layout/swipeactions_dialog.xml
Normal file
@ -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>
|
10
app/src/main/res/layout/swipeactions_picker.xml
Normal file
10
app/src/main/res/layout/swipeactions_picker.xml
Normal file
@ -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" />
|
29
app/src/main/res/layout/swipeactions_picker_item.xml
Normal file
29
app/src/main/res/layout/swipeactions_picker_item.xml
Normal file
@ -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>
|
84
app/src/main/res/layout/swipeactions_row.xml
Normal file
84
app/src/main/res/layout/swipeactions_row.xml
Normal file
@ -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>
|
@ -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>
|
||||
|
11
app/src/main/res/xml/preferences_swipe.xml
Normal file
11
app/src/main/res/xml/preferences_swipe.xml
Normal file
@ -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>
|
@ -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>
|
||||
|
@ -535,6 +535,14 @@ public class DBWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public static Future<?> toggleFavoriteItem(final FeedItem item) {
|
||||
if (item.isTagged(FeedItem.TAG_FAVORITE)) {
|
||||
return removeFavoriteItem(item);
|
||||
} else {
|
||||
return addFavoriteItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static Future<?> addFavoriteItem(final FeedItem item) {
|
||||
return dbExec.submit(() -> {
|
||||
final PodDBAdapter adapter = PodDBAdapter.getInstance().open();
|
||||
|
9
core/src/main/res/drawable/ic_mark_played.xml
Normal file
9
core/src/main/res/drawable/ic_mark_played.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="m19.89 12.43-1.9 1.15c-4.13-1.8-8.31 1.81-8 5.15l-4.3 2.74 0.01-18.05zm-5.45 8.68-2.75-3 1.16-1.16 1.59 1.58 3.59-3.58 1.16 1.41-4.75 4.75"/>
|
||||
</vector>
|
9
core/src/main/res/drawable/ic_mark_unplayed.xml
Normal file
9
core/src/main/res/drawable/ic_mark_unplayed.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="m19.89 12.43-1.9 1.15c-4.13-1.8-8.31 1.81-8 5.15l-4.3 2.74 0.01-18.05m7.46 11.99-0.69 0.69 1.82 1.82-1.82 1.82 0.69 0.69 1.82-1.82 1.81 1.82 0.69-0.69-1.82-1.82 1.82-1.82-0.69-0.69-1.81 1.82z"/>
|
||||
</vector>
|
8
core/src/main/res/drawable/ic_playlist_remove.xml
Normal file
8
core/src/main/res/drawable/ic_playlist_remove.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!-- drawable/playlist_remove.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="?attr/action_icon_color" android:pathData="M2,6V8H14V6H2M2,10V12H11V10H2M14.17,10.76L12.76,12.17L15.59,15L12.76,17.83L14.17,19.24L17,16.41L19.83,19.24L21.24,17.83L18.41,15L21.24,12.17L19.83,10.76L17,13.59L14.17,10.76M2,14V16H11V14H2Z" />
|
||||
</vector>
|
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color" android:pathData="M19,13H5v-2h14v2z"/>
|
||||
</vector>
|
@ -12,4 +12,9 @@
|
||||
<attr name="filter_dialog_clear" format="color"/>
|
||||
<attr name="filter_dialog_button_background" format="reference"/>
|
||||
<attr name="seek_background" format="color" />
|
||||
<attr name="icon_red" format="color" />
|
||||
<attr name="icon_yellow" format="color" />
|
||||
<attr name="icon_green" format="color" />
|
||||
<attr name="icon_purple" format="color" />
|
||||
<attr name="icon_gray" format="color" />
|
||||
</resources>
|
||||
|
@ -35,6 +35,14 @@
|
||||
<!-- Google Assistant -->
|
||||
<string name="app_action_not_found">\"%1$s\" not found</string>
|
||||
|
||||
<!-- SwipeActions -->
|
||||
<string name="swipeactions_label">Swipe Actions</string>
|
||||
<string name="swipeactions_summary">Choose what happens when swiping an episode in a list</string>
|
||||
<string name="swipe_right">Swipe Right</string>
|
||||
<string name="swipe_left">Swipe Left</string>
|
||||
<string name="enable_swipeactions">Enable Swipe Actions for this Screen</string>
|
||||
<string name="change_setting">Change</string>
|
||||
|
||||
<!-- Statistics fragment -->
|
||||
<string name="total_time_listened_to_podcasts">Total time of episodes played:</string>
|
||||
<string name="statistics_details_dialog">%1$d out of %2$d episodes started.\n\nPlayed %3$s out of %4$s.</string>
|
||||
@ -195,6 +203,8 @@
|
||||
<string name="remove_new_flag_label">Remove \"new\" flag</string>
|
||||
<string name="removed_new_flag_label">Removed \"new\" flag</string>
|
||||
<string name="mark_read_label">Mark as played</string>
|
||||
<string name="marked_as_played_label">Marked as played</string>
|
||||
<string name="marked_as_unplayed_label">Marked as unplayed</string>
|
||||
<string name="mark_read_no_media_label">Mark as read</string>
|
||||
<string name="play_this_to_seek_position">To jump to positions, you need to play the episode</string>
|
||||
<plurals name="marked_read_batch_label">
|
||||
|
@ -27,6 +27,11 @@
|
||||
<item name="scrollbar_thumb">@drawable/scrollbar_thumb_light</item>
|
||||
<item name="filter_dialog_clear">@color/filter_dialog_clear_light</item>
|
||||
<item name="filter_dialog_button_background">@drawable/filter_dialog_background_light</item>
|
||||
<item name="icon_red">#CF1800</item>
|
||||
<item name="icon_yellow">#F59F00</item>
|
||||
<item name="icon_green">#008537</item>
|
||||
<item name="icon_purple">#5F1984</item>
|
||||
<item name="icon_gray">#25365A</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.AntennaPod.Dark" parent="Theme.Base.AntennaPod.Dark">
|
||||
@ -56,6 +61,11 @@
|
||||
<item name="scrollbar_thumb">@drawable/scrollbar_thumb_dark</item>
|
||||
<item name="filter_dialog_clear">@color/filter_dialog_clear_dark</item>
|
||||
<item name="filter_dialog_button_background">@drawable/filter_dialog_background_dark</item>
|
||||
<item name="icon_red">#CF1800</item>
|
||||
<item name="icon_yellow">#F59F00</item>
|
||||
<item name="icon_green">#008537</item>
|
||||
<item name="icon_purple">#AA55D8</item>
|
||||
<item name="icon_gray">#CDD9E4</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.TrueBlack">
|
||||
|
@ -248,6 +248,9 @@ public class FeedItem extends FeedComponent implements Serializable {
|
||||
return state == NEW;
|
||||
}
|
||||
|
||||
public int getPlayState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setNew() {
|
||||
state = NEW;
|
||||
@ -377,6 +380,10 @@ public class FeedItem extends FeedComponent implements Serializable {
|
||||
return failedAttempts;
|
||||
}
|
||||
|
||||
public boolean isDownloaded() {
|
||||
return media != null && media.isDownloaded();
|
||||
}
|
||||
|
||||
public boolean isAutoDownloadable() {
|
||||
if (media == null || media.isDownloaded() || autoDownload == 0) {
|
||||
return false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user