Add Notifications for episodes (#4646)

Co-authored-by: ByteHamster <info@bytehamster.com>
This commit is contained in:
Connectety 2021-01-22 17:27:46 +01:00 committed by GitHub
parent 7bd20ae406
commit 03c71ee6c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 263 additions and 54 deletions

View File

@ -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;

View File

@ -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"

View File

@ -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;
}
} }

View File

@ -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());

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}
} }

View File

@ -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());

View File

@ -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;");
}
} }
} }

View File

@ -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());

View File

@ -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

View File

@ -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>

View File

@ -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>