summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
blob: 60eaf14a541f9d2cf4134758a783458eb1685607 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
package de.danoeh.antennapod.menuhandler;

import android.content.Context;
import android.os.Handler;
import androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import android.util.Log;

import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
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.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ShareUtils;

/**
 * Handles interactions with the FeedItemMenu.
 */
public class FeedItemMenuHandler {

    private static final String TAG = "FeedItemMenuHandler";

    private FeedItemMenuHandler() {
    }

    /**
     * Used by the MenuHandler to access different types of menus through one
     * interface
     */
    public interface MenuInterface {
        /**
         * Implementations of this method should call findItem(id) on their
         * menu-object and call setVisibility(visibility) on the returned
         * MenuItem object.
         */
        void setItemVisibility(int id, boolean visible);
    }

    /**
     * This method should be called in the prepare-methods of menus. It changes
     * the visibility of the menu items depending on a FeedItem's attributes.
     *
     * @param mi               An instance of MenuInterface that the method uses to change a
     *                         MenuItem's visibility
     * @param selectedItem     The FeedItem for which the menu is supposed to be prepared
     * @return Returns true if selectedItem is not null.
     */
    public static boolean onPrepareMenu(MenuInterface mi,
                                        FeedItem selectedItem) {
        if (selectedItem == null) {
            return false;
        }
        boolean hasMedia = selectedItem.getMedia() != null;
        boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING;

        if (!isPlaying) {
            mi.setItemVisibility(R.id.skip_episode_item, false);
        }

        boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE);
        if (!isInQueue) {
            mi.setItemVisibility(R.id.remove_from_queue_item, false);
        }
        if (!(!isInQueue && selectedItem.getMedia() != null)) {
            mi.setItemVisibility(R.id.add_to_queue_item, false);
        }

        if (!ShareUtils.hasLinkToShare(selectedItem)) {
            mi.setItemVisibility(R.id.visit_website_item, false);
            mi.setItemVisibility(R.id.share_link_item, false);
            mi.setItemVisibility(R.id.share_link_with_position_item, false);
        }
        if (!hasMedia || selectedItem.getMedia().getDownload_url() == null) {
            mi.setItemVisibility(R.id.share_download_url_item, false);
            mi.setItemVisibility(R.id.share_download_url_with_position_item, false);
        }
        if(!hasMedia || selectedItem.getMedia().getPosition() <= 0) {
            mi.setItemVisibility(R.id.share_link_with_position_item, false);
            mi.setItemVisibility(R.id.share_download_url_with_position_item, false);
        }

        boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
        mi.setItemVisibility(R.id.share_file, fileDownloaded);

        mi.setItemVisibility(R.id.remove_new_flag_item, selectedItem.isNew());
        if (selectedItem.isPlayed()) {
            mi.setItemVisibility(R.id.mark_read_item, false);
        } else {
            mi.setItemVisibility(R.id.mark_unread_item, false);
        }

        if(selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) {
            mi.setItemVisibility(R.id.reset_position, false);
        }

        if(!UserPreferences.isEnableAutodownload() || fileDownloaded) {
            mi.setItemVisibility(R.id.activate_auto_download, false);
            mi.setItemVisibility(R.id.deactivate_auto_download, false);
        } else if(selectedItem.getAutoDownload()) {
            mi.setItemVisibility(R.id.activate_auto_download, false);
        } else {
            mi.setItemVisibility(R.id.deactivate_auto_download, false);
        }

        boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
        mi.setItemVisibility(R.id.add_to_favorites_item, !isFavorite);
        mi.setItemVisibility(R.id.remove_from_favorites_item, isFavorite);

        mi.setItemVisibility(R.id.remove_item, fileDownloaded);

        return true;
    }

    /**
     * The same method as onPrepareMenu(MenuInterface, FeedItem, boolean, QueueAccess), but lets the
     * caller also specify a list of menu items that should not be shown.
     *
     * @param excludeIds Menu item that should be excluded
     * @return true if selectedItem is not null.
     */
    public static boolean onPrepareMenu(MenuInterface mi,
                                        FeedItem selectedItem,
                                        int... excludeIds) {
        boolean rc = onPrepareMenu(mi, selectedItem);
        if (rc && excludeIds != null) {
            for (int id : excludeIds) {
                mi.setItemVisibility(id, false);
            }
        }
        return rc;
    }

    /**
     * Default menu handling for the given FeedItem.
     *
     * A Fragment instance, (rather than the more generic Context), is needed as a parameter
     * to support some UI operations, e.g., creating a Snackbar.
     */
    public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId,
                                            @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) {
                        GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY)
                                .currentDeviceId()
                                .currentTimestamp()
                                .started(media.getDuration() / 1000)
                                .position(media.getDuration() / 1000)
                                .total(media.getDuration() / 1000)
                                .build();
                        GpodnetPreferences.enqueueEpisodeAction(actionPlay);
                    }
                }
                break;
            case R.id.mark_unread_item:
                selectedItem.setPlayed(false);
                DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false);
                if(GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) {
                    GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW)
                            .currentDeviceId()
                            .currentTimestamp()
                            .build();
                    GpodnetPreferences.enqueueEpisodeAction(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);
                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_link_item:
                ShareUtils.shareFeedItemLink(context, selectedItem);
                break;
            case R.id.share_download_url_item:
                ShareUtils.shareFeedItemDownloadLink(context, selectedItem);
                break;
            case R.id.share_link_with_position_item:
                ShareUtils.shareFeedItemLink(context, selectedItem, true);
                break;
            case R.id.share_download_url_with_position_item:
                ShareUtils.shareFeedItemDownloadLink(context, selectedItem, true);
                break;
            case R.id.share_file:
                ShareUtils.shareFeedItemFile(context, selectedItem.getMedia());
                break;
            default:
                Log.d(TAG, "Unknown menuItemId: " + menuItemId);
                return false;
        }
        // Refresh menu state

        return true;
    }

    /**
     * Remove new flag with additional UI logic to allow undo with Snackbar.
     *
     * 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) {
        if (item == null) {
            return;
        }

        Log.d(TAG, "removeNewFlagWithUndo(" + 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());

        final Handler h = new Handler(fragment.requireContext().getMainLooper());
        final Runnable r = () -> {
            FeedMedia media = item.getMedia();
            if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) {
                DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), media.getId());
            }
        };

        Snackbar snackbar = Snackbar.make(fragment.getView(), fragment.getString(R.string.removed_new_flag_label),
                Snackbar.LENGTH_LONG);
        snackbar.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);
        });
        snackbar.show();
        h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f));
    }

}