Add Notifications for episodes (#4646)
Co-authored-by: ByteHamster <info@bytehamster.com>
This commit is contained in:
parent
7bd20ae406
commit
03c71ee6c5
|
@ -159,6 +159,7 @@ public class FeedSettingsFragment extends Fragment {
|
||||||
setupEpisodeFilterPreference();
|
setupEpisodeFilterPreference();
|
||||||
setupPlaybackSpeedPreference();
|
setupPlaybackSpeedPreference();
|
||||||
setupFeedAutoSkipPreference();
|
setupFeedAutoSkipPreference();
|
||||||
|
setupEpisodeNotificationPreference();
|
||||||
|
|
||||||
updateAutoDeleteSummary();
|
updateAutoDeleteSummary();
|
||||||
updateVolumeReductionValue();
|
updateVolumeReductionValue();
|
||||||
|
@ -394,6 +395,19 @@ public class FeedSettingsFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupEpisodeNotificationPreference() {
|
||||||
|
SwitchPreferenceCompat pref = findPreference("episodeNotification");
|
||||||
|
|
||||||
|
pref.setChecked(feedPreferences.getShowEpisodeNotification());
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean checked = newValue == Boolean.TRUE;
|
||||||
|
feedPreferences.setShowEpisodeNotification(checked);
|
||||||
|
feed.savePreferences();
|
||||||
|
pref.setChecked(checked);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class ApplyToEpisodesDialog extends ConfirmationDialog {
|
private class ApplyToEpisodesDialog extends ConfirmationDialog {
|
||||||
private final boolean autoDownload;
|
private final boolean autoDownload;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,14 @@
|
||||||
android:title="@string/keep_updated"
|
android:title="@string/keep_updated"
|
||||||
android:summary="@string/keep_updated_summary"/>
|
android:summary="@string/keep_updated_summary"/>
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="episodeNotification"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="keepUpdated"
|
||||||
|
android:icon="?attr/ic_notifications"
|
||||||
|
android:title="@string/episode_notification"
|
||||||
|
android:summary="@string/episode_notification_summary"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="authentication"
|
android:key="authentication"
|
||||||
android:icon="?attr/ic_key"
|
android:icon="?attr/ic_key"
|
||||||
|
|
|
@ -16,37 +16,40 @@ public class FeedPreferences {
|
||||||
|
|
||||||
public static final float SPEED_USE_GLOBAL = -1;
|
public static final float SPEED_USE_GLOBAL = -1;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private FeedFilter filter;
|
|
||||||
private long feedID;
|
|
||||||
private boolean autoDownload;
|
|
||||||
private boolean keepUpdated;
|
|
||||||
|
|
||||||
public enum AutoDeleteAction {
|
public enum AutoDeleteAction {
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
YES,
|
YES,
|
||||||
NO
|
NO
|
||||||
}
|
}
|
||||||
private AutoDeleteAction auto_delete_action;
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private FeedFilter filter;
|
||||||
|
private long feedID;
|
||||||
|
private boolean autoDownload;
|
||||||
|
private boolean keepUpdated;
|
||||||
|
private AutoDeleteAction autoDeleteAction;
|
||||||
private VolumeAdaptionSetting volumeAdaptionSetting;
|
private VolumeAdaptionSetting volumeAdaptionSetting;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private float feedPlaybackSpeed;
|
private float feedPlaybackSpeed;
|
||||||
private int feedSkipIntro;
|
private int feedSkipIntro;
|
||||||
private int feedSkipEnding;
|
private int feedSkipEnding;
|
||||||
|
private boolean showEpisodeNotification;
|
||||||
|
|
||||||
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
|
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction autoDeleteAction,
|
||||||
this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting,
|
VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
|
||||||
username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0);
|
this(feedID, autoDownload, true, autoDeleteAction, volumeAdaptionSetting,
|
||||||
|
username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed, int feedSkipIntro, int feedSkipEnding) {
|
private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated,
|
||||||
|
AutoDeleteAction autoDeleteAction, VolumeAdaptionSetting volumeAdaptionSetting,
|
||||||
|
String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed,
|
||||||
|
int feedSkipIntro, int feedSkipEnding, boolean showEpisodeNotification) {
|
||||||
this.feedID = feedID;
|
this.feedID = feedID;
|
||||||
this.autoDownload = autoDownload;
|
this.autoDownload = autoDownload;
|
||||||
this.keepUpdated = keepUpdated;
|
this.keepUpdated = keepUpdated;
|
||||||
this.auto_delete_action = auto_delete_action;
|
this.autoDeleteAction = autoDeleteAction;
|
||||||
this.volumeAdaptionSetting = volumeAdaptionSetting;
|
this.volumeAdaptionSetting = volumeAdaptionSetting;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
@ -54,6 +57,7 @@ public class FeedPreferences {
|
||||||
this.feedPlaybackSpeed = feedPlaybackSpeed;
|
this.feedPlaybackSpeed = feedPlaybackSpeed;
|
||||||
this.feedSkipIntro = feedSkipIntro;
|
this.feedSkipIntro = feedSkipIntro;
|
||||||
this.feedSkipEnding = feedSkipEnding;
|
this.feedSkipEnding = feedSkipEnding;
|
||||||
|
this.showEpisodeNotification = showEpisodeNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FeedPreferences fromCursor(Cursor cursor) {
|
public static FeedPreferences fromCursor(Cursor cursor) {
|
||||||
|
@ -69,6 +73,7 @@ public class FeedPreferences {
|
||||||
int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED);
|
int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED);
|
||||||
int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO);
|
int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO);
|
||||||
int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING);
|
int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING);
|
||||||
|
int indexEpisodeNotification = cursor.getColumnIndex(PodDBAdapter.KEY_EPISODE_NOTIFICATION);
|
||||||
|
|
||||||
long feedId = cursor.getLong(indexId);
|
long feedId = cursor.getLong(indexId);
|
||||||
boolean autoDownload = cursor.getInt(indexAutoDownload) > 0;
|
boolean autoDownload = cursor.getInt(indexAutoDownload) > 0;
|
||||||
|
@ -84,6 +89,7 @@ public class FeedPreferences {
|
||||||
float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed);
|
float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed);
|
||||||
int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro);
|
int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro);
|
||||||
int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding);
|
int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding);
|
||||||
|
boolean showNotification = cursor.getInt(indexEpisodeNotification) > 0;
|
||||||
return new FeedPreferences(feedId,
|
return new FeedPreferences(feedId,
|
||||||
autoDownload,
|
autoDownload,
|
||||||
autoRefresh,
|
autoRefresh,
|
||||||
|
@ -94,7 +100,8 @@ public class FeedPreferences {
|
||||||
new FeedFilter(includeFilter, excludeFilter),
|
new FeedFilter(includeFilter, excludeFilter),
|
||||||
feedPlaybackSpeed,
|
feedPlaybackSpeed,
|
||||||
feedAutoSkipIntro,
|
feedAutoSkipIntro,
|
||||||
feedAutoSkipEnding
|
feedAutoSkipEnding,
|
||||||
|
showNotification
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,15 +175,15 @@ public class FeedPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoDeleteAction getAutoDeleteAction() {
|
public AutoDeleteAction getAutoDeleteAction() {
|
||||||
return auto_delete_action;
|
return autoDeleteAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VolumeAdaptionSetting getVolumeAdaptionSetting() {
|
public VolumeAdaptionSetting getVolumeAdaptionSetting() {
|
||||||
return volumeAdaptionSetting;
|
return volumeAdaptionSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAutoDeleteAction(AutoDeleteAction auto_delete_action) {
|
public void setAutoDeleteAction(AutoDeleteAction autoDeleteAction) {
|
||||||
this.auto_delete_action = auto_delete_action;
|
this.autoDeleteAction = autoDeleteAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVolumeAdaptionSetting(VolumeAdaptionSetting volumeAdaptionSetting) {
|
public void setVolumeAdaptionSetting(VolumeAdaptionSetting volumeAdaptionSetting) {
|
||||||
|
@ -184,17 +191,15 @@ public class FeedPreferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getCurrentAutoDelete() {
|
public boolean getCurrentAutoDelete() {
|
||||||
switch (auto_delete_action) {
|
switch (autoDeleteAction) {
|
||||||
case GLOBAL:
|
case GLOBAL:
|
||||||
return UserPreferences.isAutoDelete();
|
return UserPreferences.isAutoDelete();
|
||||||
|
|
||||||
case YES:
|
case YES:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case NO:
|
case NO:
|
||||||
|
default: // fall-through
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return false; // TODO - add exceptions here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(Context context) {
|
public void save(Context context) {
|
||||||
|
@ -240,4 +245,16 @@ public class FeedPreferences {
|
||||||
public int getFeedSkipEnding() {
|
public int getFeedSkipEnding() {
|
||||||
return feedSkipEnding;
|
return feedSkipEnding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter for preference if notifications should be display for new episodes.
|
||||||
|
* @return true for displaying notifications
|
||||||
|
*/
|
||||||
|
public boolean getShowEpisodeNotification() {
|
||||||
|
return showEpisodeNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowEpisodeNotification(boolean showEpisodeNotification) {
|
||||||
|
this.showEpisodeNotification = showEpisodeNotification;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ public class DownloadService extends Service {
|
||||||
private final CompletionService<Downloader> downloadExecutor;
|
private final CompletionService<Downloader> downloadExecutor;
|
||||||
private final DownloadRequester requester;
|
private final DownloadRequester requester;
|
||||||
private DownloadServiceNotification notificationManager;
|
private DownloadServiceNotification notificationManager;
|
||||||
|
private final NewEpisodesNotification newEpisodesNotification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently running downloads.
|
* Currently running downloads.
|
||||||
|
@ -117,7 +118,7 @@ public class DownloadService extends Service {
|
||||||
private ScheduledFuture<?> notificationUpdaterFuture;
|
private ScheduledFuture<?> notificationUpdaterFuture;
|
||||||
private ScheduledFuture<?> downloadPostFuture;
|
private ScheduledFuture<?> downloadPostFuture;
|
||||||
private static final int SCHED_EX_POOL_SIZE = 1;
|
private static final int SCHED_EX_POOL_SIZE = 1;
|
||||||
private ScheduledThreadPoolExecutor schedExecutor;
|
private final ScheduledThreadPoolExecutor schedExecutor;
|
||||||
private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
|
private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
|
||||||
|
|
||||||
private final IBinder mBinder = new LocalBinder();
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
@ -133,12 +134,16 @@ public class DownloadService extends Service {
|
||||||
downloads = Collections.synchronizedList(new ArrayList<>());
|
downloads = Collections.synchronizedList(new ArrayList<>());
|
||||||
numberOfDownloads = new AtomicInteger(0);
|
numberOfDownloads = new AtomicInteger(0);
|
||||||
requester = DownloadRequester.getInstance();
|
requester = DownloadRequester.getInstance();
|
||||||
|
newEpisodesNotification = new NewEpisodesNotification();
|
||||||
|
|
||||||
syncExecutor = Executors.newSingleThreadExecutor(r -> {
|
syncExecutor = Executors.newSingleThreadExecutor(r -> {
|
||||||
Thread t = new Thread(r, "SyncThread");
|
Thread t = new Thread(r, "SyncThread");
|
||||||
t.setPriority(Thread.MIN_PRIORITY);
|
t.setPriority(Thread.MIN_PRIORITY);
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
// Must be the first runnable in syncExecutor
|
||||||
|
syncExecutor.execute(newEpisodesNotification::loadCountersBeforeRefresh);
|
||||||
|
|
||||||
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
|
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
|
||||||
downloadExecutor = new ExecutorCompletionService<>(
|
downloadExecutor = new ExecutorCompletionService<>(
|
||||||
Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
|
Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
|
||||||
|
@ -289,6 +294,10 @@ public class DownloadService extends Service {
|
||||||
if (log.size() > 0 && !log.get(0).isSuccessful()) {
|
if (log.size() > 0 && !log.get(0).isSuccessful()) {
|
||||||
saveDownloadStatus(task.getDownloadStatus());
|
saveDownloadStatus(task.getDownloadStatus());
|
||||||
}
|
}
|
||||||
|
if (request.getFeedfileId() != 0 && !request.isInitiatedByUser()) {
|
||||||
|
// Was stored in the database before and not initiated manually
|
||||||
|
newEpisodesNotification.showIfNeeded(DownloadService.this, task.getSavedFeed());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
|
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
|
||||||
saveDownloadStatus(task.getDownloadStatus());
|
saveDownloadStatus(task.getDownloadStatus());
|
||||||
|
|
|
@ -143,7 +143,7 @@ public class DownloadServiceNotification {
|
||||||
// We are generating an auto-download report
|
// We are generating an auto-download report
|
||||||
channelId = NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD;
|
channelId = NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD;
|
||||||
titleId = R.string.auto_download_report_title;
|
titleId = R.string.auto_download_report_title;
|
||||||
iconId = R.drawable.ic_notification_auto_download_complete;
|
iconId = R.drawable.ic_notification_new;
|
||||||
intent = ClientConfig.downloadServiceCallbacks.getAutoDownloadReportNotificationContentIntent(context);
|
intent = ClientConfig.downloadServiceCallbacks.getAutoDownloadReportNotificationContentIntent(context);
|
||||||
id = R.id.notification_auto_download_report;
|
id = R.id.notification_auto_download_report;
|
||||||
content = createAutoDownloadNotificationContent(reportQueue);
|
content = createAutoDownloadNotificationContent(reportQueue);
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
package de.danoeh.antennapod.core.service.download;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import de.danoeh.antennapod.core.R;
|
||||||
|
import de.danoeh.antennapod.core.feed.Feed;
|
||||||
|
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||||
|
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
|
||||||
|
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||||
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
|
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||||
|
import de.danoeh.antennapod.core.util.LongIntMap;
|
||||||
|
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
|
||||||
|
|
||||||
|
public class NewEpisodesNotification {
|
||||||
|
private static final String TAG = "NewEpisodesNotification";
|
||||||
|
private static final String GROUP_KEY = "de.danoeh.antennapod.EPISODES";
|
||||||
|
|
||||||
|
private LongIntMap countersBefore;
|
||||||
|
|
||||||
|
public NewEpisodesNotification() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadCountersBeforeRefresh() {
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
countersBefore = adapter.getFeedCounters(UserPreferences.FEED_COUNTER_SHOW_NEW);
|
||||||
|
adapter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showIfNeeded(Context context, Feed feed) {
|
||||||
|
FeedPreferences prefs = feed.getPreferences();
|
||||||
|
if (!prefs.getKeepUpdated() || !prefs.getShowEpisodeNotification()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newEpisodesBefore = countersBefore.get(feed.getId());
|
||||||
|
int newEpisodesAfter = getNewEpisodeCount(feed.getId());
|
||||||
|
|
||||||
|
Log.d(TAG, "New episodes before: " + newEpisodesBefore + ", after: " + newEpisodesAfter);
|
||||||
|
if (newEpisodesAfter > newEpisodesBefore) {
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
showNotification(newEpisodesAfter, feed, context, notificationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showNotification(int newEpisodes, Feed feed, Context context,
|
||||||
|
NotificationManagerCompat notificationManager) {
|
||||||
|
Resources res = context.getResources();
|
||||||
|
String text = res.getQuantityString(
|
||||||
|
R.plurals.new_episode_notification_message, newEpisodes, newEpisodes, feed.getTitle()
|
||||||
|
);
|
||||||
|
String title = res.getQuantityString(R.plurals.new_episode_notification_title, newEpisodes);
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction("NewEpisodes" + feed.getId());
|
||||||
|
intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity"));
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
intent.putExtra("fragment_feed_id", feed.getId());
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
||||||
|
|
||||||
|
Notification notification = new NotificationCompat.Builder(
|
||||||
|
context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification_new)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setLargeIcon(loadIcon(context, feed))
|
||||||
|
.setContentText(text)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setGroup(GROUP_KEY)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS, feed.hashCode(), notification);
|
||||||
|
showGroupSummaryNotification(context, notificationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showGroupSummaryNotification(Context context, NotificationManagerCompat notificationManager) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction("NewEpisodes");
|
||||||
|
intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity"));
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
intent.putExtra("fragment_tag", "EpisodesFragment");
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
||||||
|
|
||||||
|
Notification notificationGroupSummary = new NotificationCompat.Builder(
|
||||||
|
context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification_new)
|
||||||
|
.setContentTitle(context.getString(R.string.new_episode_notification_group_text))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setGroup(GROUP_KEY)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build();
|
||||||
|
notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS, 0, notificationGroupSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap loadIcon(Context context, Feed feed) {
|
||||||
|
int iconSize = (int) (128 * context.getResources().getDisplayMetrics().density);
|
||||||
|
try {
|
||||||
|
return Glide.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(ImageResourceUtils.getImageLocation(feed))
|
||||||
|
.apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
|
||||||
|
.apply(new RequestOptions().centerCrop())
|
||||||
|
.submit(iconSize, iconSize)
|
||||||
|
.get();
|
||||||
|
} catch (Throwable tr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getNewEpisodeCount(long feedId) {
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
int episodeCount = adapter.getFeedCounters(UserPreferences.FEED_COUNTER_SHOW_NEW, feedId).get(feedId);
|
||||||
|
adapter.close();
|
||||||
|
return episodeCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.service.download.handler;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.feed.Feed;
|
import de.danoeh.antennapod.core.feed.Feed;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||||
|
@ -15,6 +16,7 @@ public class FeedSyncTask {
|
||||||
private final DownloadRequest request;
|
private final DownloadRequest request;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private DownloadStatus downloadStatus;
|
private DownloadStatus downloadStatus;
|
||||||
|
private Feed savedFeed;
|
||||||
|
|
||||||
public FeedSyncTask(Context context, DownloadRequest request) {
|
public FeedSyncTask(Context context, DownloadRequest request) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
|
@ -30,7 +32,7 @@ public class FeedSyncTask {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Feed savedFeed = DBTasks.updateFeed(context, result.feed, false);
|
savedFeed = DBTasks.updateFeed(context, result.feed, false);
|
||||||
// If loadAllPages=true, check if another page is available and queue it for download
|
// If loadAllPages=true, check if another page is available and queue it for download
|
||||||
final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
|
final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
|
||||||
final Feed feed = result.feed;
|
final Feed feed = result.feed;
|
||||||
|
@ -48,4 +50,8 @@ public class FeedSyncTask {
|
||||||
public DownloadStatus getDownloadStatus() {
|
public DownloadStatus getDownloadStatus() {
|
||||||
return downloadStatus;
|
return downloadStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Feed getSavedFeed() {
|
||||||
|
return savedFeed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -802,15 +802,9 @@ public final class DBReader {
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
adapter.open();
|
adapter.open();
|
||||||
|
|
||||||
List<Feed> feeds = getFeedList(adapter);
|
final LongIntMap feedCounters = adapter.getFeedCounters();
|
||||||
long[] feedIds = new long[feeds.size()];
|
|
||||||
for (int i = 0; i < feeds.size(); i++) {
|
|
||||||
feedIds[i] = feeds.get(i).getId();
|
|
||||||
}
|
|
||||||
final LongIntMap feedCounters = adapter.getFeedCounters(feedIds);
|
|
||||||
|
|
||||||
SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter();
|
SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter();
|
||||||
feeds = subscriptionsFilter.filter(getFeedList(adapter), feedCounters);
|
List<Feed> feeds = subscriptionsFilter.filter(getFeedList(adapter), feedCounters);
|
||||||
|
|
||||||
Comparator<Feed> comparator;
|
Comparator<Feed> comparator;
|
||||||
int feedOrder = UserPreferences.getFeedOrder();
|
int feedOrder = UserPreferences.getFeedOrder();
|
||||||
|
@ -840,7 +834,7 @@ public final class DBReader {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (feedOrder == UserPreferences.FEED_ORDER_MOST_PLAYED) {
|
} else if (feedOrder == UserPreferences.FEED_ORDER_MOST_PLAYED) {
|
||||||
final LongIntMap playedCounters = adapter.getPlayedEpisodesCounters(feedIds);
|
final LongIntMap playedCounters = adapter.getPlayedEpisodesCounters();
|
||||||
|
|
||||||
comparator = (lhs, rhs) -> {
|
comparator = (lhs, rhs) -> {
|
||||||
long counterLhs = playedCounters.get(lhs.getId());
|
long counterLhs = playedCounters.get(lhs.getId());
|
||||||
|
|
|
@ -310,6 +310,10 @@ class DBUpgrader {
|
||||||
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS +
|
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS +
|
||||||
" ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0;");
|
" ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0;");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 2020000) {
|
||||||
|
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
|
||||||
|
+ " ADD COLUMN " + PodDBAdapter.KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0;");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class PodDBAdapter {
|
||||||
|
|
||||||
private static final String TAG = "PodDBAdapter";
|
private static final String TAG = "PodDBAdapter";
|
||||||
public static final String DATABASE_NAME = "Antennapod.db";
|
public static final String DATABASE_NAME = "Antennapod.db";
|
||||||
public static final int VERSION = 1090001;
|
public static final int VERSION = 2020000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of arguments for IN-operator.
|
* Maximum number of arguments for IN-operator.
|
||||||
|
@ -115,6 +115,7 @@ public class PodDBAdapter {
|
||||||
public static final String KEY_FEED_PLAYBACK_SPEED = "feed_playback_speed";
|
public static final String KEY_FEED_PLAYBACK_SPEED = "feed_playback_speed";
|
||||||
public static final String KEY_FEED_SKIP_INTRO = "feed_skip_intro";
|
public static final String KEY_FEED_SKIP_INTRO = "feed_skip_intro";
|
||||||
public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending";
|
public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending";
|
||||||
|
public static final String KEY_EPISODE_NOTIFICATION = "episode_notification";
|
||||||
|
|
||||||
// Table names
|
// Table names
|
||||||
public static final String TABLE_NAME_FEEDS = "Feeds";
|
public static final String TABLE_NAME_FEEDS = "Feeds";
|
||||||
|
@ -152,7 +153,8 @@ public class PodDBAdapter {
|
||||||
+ KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ","
|
+ KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + ","
|
||||||
+ KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0,"
|
+ KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0,"
|
||||||
+ KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0,"
|
+ KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0,"
|
||||||
+ KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0)";
|
+ KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0,"
|
||||||
|
+ KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0)";
|
||||||
|
|
||||||
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
|
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
|
||||||
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
|
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
|
||||||
|
@ -254,7 +256,8 @@ public class PodDBAdapter {
|
||||||
TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER,
|
TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER,
|
||||||
TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED,
|
TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED,
|
||||||
TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO,
|
TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO,
|
||||||
TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING
|
TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING,
|
||||||
|
TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -446,6 +449,7 @@ public class PodDBAdapter {
|
||||||
values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed());
|
values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed());
|
||||||
values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro());
|
values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro());
|
||||||
values.put(KEY_FEED_SKIP_ENDING, prefs.getFeedSkipEnding());
|
values.put(KEY_FEED_SKIP_ENDING, prefs.getFeedSkipEnding());
|
||||||
|
values.put(KEY_EPISODE_NOTIFICATION, prefs.getShowEpisodeNotification());
|
||||||
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())});
|
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1164,6 +1168,11 @@ public class PodDBAdapter {
|
||||||
|
|
||||||
public final LongIntMap getFeedCounters(long... feedIds) {
|
public final LongIntMap getFeedCounters(long... feedIds) {
|
||||||
int setting = UserPreferences.getFeedCounterSetting();
|
int setting = UserPreferences.getFeedCounterSetting();
|
||||||
|
|
||||||
|
return getFeedCounters(setting, feedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final LongIntMap getFeedCounters(int setting, long... feedIds) {
|
||||||
String whereRead;
|
String whereRead;
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case UserPreferences.FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM:
|
case UserPreferences.FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM:
|
||||||
|
@ -1188,24 +1197,26 @@ public class PodDBAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private LongIntMap conditionalFeedCounterRead(String whereRead, long... feedIds) {
|
private LongIntMap conditionalFeedCounterRead(String whereRead, long... feedIds) {
|
||||||
// work around TextUtils.join wanting only boxed items
|
String limitFeeds = "";
|
||||||
// and StringUtils.join() causing NoSuchMethodErrors on MIUI
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
for (long id : feedIds) {
|
|
||||||
builder.append(id);
|
|
||||||
builder.append(',');
|
|
||||||
}
|
|
||||||
if (feedIds.length > 0) {
|
if (feedIds.length > 0) {
|
||||||
|
// work around TextUtils.join wanting only boxed items
|
||||||
|
// and StringUtils.join() causing NoSuchMethodErrors on MIUI
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (long id : feedIds) {
|
||||||
|
builder.append(id);
|
||||||
|
builder.append(',');
|
||||||
|
}
|
||||||
// there's an extra ',', get rid of it
|
// there's an extra ',', get rid of it
|
||||||
builder.deleteCharAt(builder.length() - 1);
|
builder.deleteCharAt(builder.length() - 1);
|
||||||
|
limitFeeds = KEY_FEED + " IN (" + builder.toString() + ") AND ";
|
||||||
}
|
}
|
||||||
|
|
||||||
final String query = "SELECT " + KEY_FEED + ", COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ") AS count "
|
final String query = "SELECT " + KEY_FEED + ", COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ") AS count "
|
||||||
+ " FROM " + TABLE_NAME_FEED_ITEMS
|
+ " FROM " + TABLE_NAME_FEED_ITEMS
|
||||||
+ " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
|
+ " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
|
||||||
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
|
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
|
||||||
+ " WHERE " + KEY_FEED + " IN (" + builder.toString() + ") "
|
+ " WHERE " + limitFeeds + " "
|
||||||
+ " AND " + whereRead + " GROUP BY " + KEY_FEED;
|
+ whereRead + " GROUP BY " + KEY_FEED;
|
||||||
|
|
||||||
Cursor c = db.rawQuery(query, null);
|
Cursor c = db.rawQuery(query, null);
|
||||||
LongIntMap result = new LongIntMap(c.getCount());
|
LongIntMap result = new LongIntMap(c.getCount());
|
||||||
|
|
|
@ -18,6 +18,7 @@ public class NotificationUtils {
|
||||||
public static final String CHANNEL_ID_DOWNLOAD_ERROR = "error";
|
public static final String CHANNEL_ID_DOWNLOAD_ERROR = "error";
|
||||||
public static final String CHANNEL_ID_SYNC_ERROR = "sync_error";
|
public static final String CHANNEL_ID_SYNC_ERROR = "sync_error";
|
||||||
public static final String CHANNEL_ID_AUTO_DOWNLOAD = "auto_download";
|
public static final String CHANNEL_ID_AUTO_DOWNLOAD = "auto_download";
|
||||||
|
public static final String CHANNEL_ID_EPISODE_NOTIFICATIONS = "episode_notifications";
|
||||||
|
|
||||||
public static final String GROUP_ID_ERRORS = "group_errors";
|
public static final String GROUP_ID_ERRORS = "group_errors";
|
||||||
public static final String GROUP_ID_NEWS = "group_news";
|
public static final String GROUP_ID_NEWS = "group_news";
|
||||||
|
@ -38,6 +39,7 @@ public class NotificationUtils {
|
||||||
mNotificationManager.createNotificationChannel(createChannelError(context));
|
mNotificationManager.createNotificationChannel(createChannelError(context));
|
||||||
mNotificationManager.createNotificationChannel(createChannelSyncError(context));
|
mNotificationManager.createNotificationChannel(createChannelSyncError(context));
|
||||||
mNotificationManager.createNotificationChannel(createChannelAutoDownload(context));
|
mNotificationManager.createNotificationChannel(createChannelAutoDownload(context));
|
||||||
|
mNotificationManager.createNotificationChannel(createChannelEpisodeNotification(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +112,15 @@ public class NotificationUtils {
|
||||||
return notificationChannel;
|
return notificationChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
private static NotificationChannel createChannelEpisodeNotification(Context c) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_EPISODE_NOTIFICATIONS,
|
||||||
|
c.getString(R.string.notification_channel_new_episode), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel.setDescription(c.getString(R.string.notification_channel_new_episode_description));
|
||||||
|
channel.setGroup(GROUP_ID_NEWS);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
private static NotificationChannelGroup createGroupErrors(Context c) {
|
private static NotificationChannelGroup createGroupErrors(Context c) {
|
||||||
return new NotificationChannelGroup(GROUP_ID_ERRORS,
|
return new NotificationChannelGroup(GROUP_ID_ERRORS,
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 688 B |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM16.59,7.58L10,14.17l-2.59,-2.58L6,13l4,4 8,-8z"
|
|
||||||
android:fillColor="#ffffff"/>
|
|
||||||
</vector>
|
|
|
@ -132,6 +132,17 @@
|
||||||
<item quantity="other">%d episodes</item>
|
<item quantity="other">%d episodes</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="loading_more">Loading more…</string>
|
<string name="loading_more">Loading more…</string>
|
||||||
|
<string name="episode_notification">Episode Notifications</string>
|
||||||
|
<string name="episode_notification_summary">Show a notification when a new episode is released.</string>
|
||||||
|
<plurals name="new_episode_notification_message">
|
||||||
|
<item quantity="one">%2$s has a new episode</item>
|
||||||
|
<item quantity="other">%2$s has %1$d new episodes</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="new_episode_notification_title">
|
||||||
|
<item quantity="one">New Episode</item>
|
||||||
|
<item quantity="other">New Episodes</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="new_episode_notification_group_text">Your subscriptions have new epsiodes.</string>
|
||||||
|
|
||||||
<!-- Actions on feeds -->
|
<!-- Actions on feeds -->
|
||||||
<string name="mark_all_read_label">Mark all as played</string>
|
<string name="mark_all_read_label">Mark all as played</string>
|
||||||
|
@ -870,6 +881,8 @@
|
||||||
<string name="notification_channel_sync_error_description">Shown when gpodder synchronization fails.</string>
|
<string name="notification_channel_sync_error_description">Shown when gpodder synchronization fails.</string>
|
||||||
<string name="notification_channel_auto_download">Automatic download completed</string>
|
<string name="notification_channel_auto_download">Automatic download completed</string>
|
||||||
<string name="notification_channel_episode_auto_download">Shown when episodes have been automatically downloaded.</string>
|
<string name="notification_channel_episode_auto_download">Shown when episodes have been automatically downloaded.</string>
|
||||||
|
<string name="notification_channel_new_episode">New Episode</string>
|
||||||
|
<string name="notification_channel_new_episode_description">Shown when a new episode of a podcast was found, where notifications are enabled</string>
|
||||||
|
|
||||||
<!-- Widget settings -->
|
<!-- Widget settings -->
|
||||||
<string name="widget_settings">Widget settings</string>
|
<string name="widget_settings">Widget settings</string>
|
||||||
|
|
Loading…
Reference in New Issue