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();
setupPlaybackSpeedPreference();
setupFeedAutoSkipPreference();
setupEpisodeNotificationPreference();
updateAutoDeleteSummary();
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 final boolean autoDownload;

View File

@ -9,6 +9,14 @@
android:title="@string/keep_updated"
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
android:key="authentication"
android:icon="?attr/ic_key"

View File

@ -16,37 +16,40 @@ public class FeedPreferences {
public static final float SPEED_USE_GLOBAL = -1;
@NonNull
private FeedFilter filter;
private long feedID;
private boolean autoDownload;
private boolean keepUpdated;
public enum AutoDeleteAction {
GLOBAL,
YES,
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 String username;
private String password;
private float feedPlaybackSpeed;
private int feedSkipIntro;
private int feedSkipEnding;
private boolean showEpisodeNotification;
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting,
username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0);
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction autoDeleteAction,
VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
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.autoDownload = autoDownload;
this.keepUpdated = keepUpdated;
this.auto_delete_action = auto_delete_action;
this.autoDeleteAction = autoDeleteAction;
this.volumeAdaptionSetting = volumeAdaptionSetting;
this.username = username;
this.password = password;
@ -54,6 +57,7 @@ public class FeedPreferences {
this.feedPlaybackSpeed = feedPlaybackSpeed;
this.feedSkipIntro = feedSkipIntro;
this.feedSkipEnding = feedSkipEnding;
this.showEpisodeNotification = showEpisodeNotification;
}
public static FeedPreferences fromCursor(Cursor cursor) {
@ -69,6 +73,7 @@ public class FeedPreferences {
int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED);
int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO);
int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING);
int indexEpisodeNotification = cursor.getColumnIndex(PodDBAdapter.KEY_EPISODE_NOTIFICATION);
long feedId = cursor.getLong(indexId);
boolean autoDownload = cursor.getInt(indexAutoDownload) > 0;
@ -84,6 +89,7 @@ public class FeedPreferences {
float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed);
int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro);
int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding);
boolean showNotification = cursor.getInt(indexEpisodeNotification) > 0;
return new FeedPreferences(feedId,
autoDownload,
autoRefresh,
@ -94,7 +100,8 @@ public class FeedPreferences {
new FeedFilter(includeFilter, excludeFilter),
feedPlaybackSpeed,
feedAutoSkipIntro,
feedAutoSkipEnding
feedAutoSkipEnding,
showNotification
);
}
@ -168,15 +175,15 @@ public class FeedPreferences {
}
public AutoDeleteAction getAutoDeleteAction() {
return auto_delete_action;
return autoDeleteAction;
}
public VolumeAdaptionSetting getVolumeAdaptionSetting() {
return volumeAdaptionSetting;
}
public void setAutoDeleteAction(AutoDeleteAction auto_delete_action) {
this.auto_delete_action = auto_delete_action;
public void setAutoDeleteAction(AutoDeleteAction autoDeleteAction) {
this.autoDeleteAction = autoDeleteAction;
}
public void setVolumeAdaptionSetting(VolumeAdaptionSetting volumeAdaptionSetting) {
@ -184,17 +191,15 @@ public class FeedPreferences {
}
public boolean getCurrentAutoDelete() {
switch (auto_delete_action) {
switch (autoDeleteAction) {
case GLOBAL:
return UserPreferences.isAutoDelete();
case YES:
return true;
case NO:
default: // fall-through
return false;
}
return false; // TODO - add exceptions here
}
public void save(Context context) {
@ -240,4 +245,16 @@ public class FeedPreferences {
public int getFeedSkipEnding() {
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 DownloadRequester requester;
private DownloadServiceNotification notificationManager;
private final NewEpisodesNotification newEpisodesNotification;
/**
* Currently running downloads.
@ -117,7 +118,7 @@ public class DownloadService extends Service {
private ScheduledFuture<?> notificationUpdaterFuture;
private ScheduledFuture<?> downloadPostFuture;
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
private final ScheduledThreadPoolExecutor schedExecutor;
private static DownloaderFactory downloaderFactory = new DefaultDownloaderFactory();
private final IBinder mBinder = new LocalBinder();
@ -133,12 +134,16 @@ public class DownloadService extends Service {
downloads = Collections.synchronizedList(new ArrayList<>());
numberOfDownloads = new AtomicInteger(0);
requester = DownloadRequester.getInstance();
newEpisodesNotification = new NewEpisodesNotification();
syncExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "SyncThread");
t.setPriority(Thread.MIN_PRIORITY);
return t;
});
// Must be the first runnable in syncExecutor
syncExecutor.execute(newEpisodesNotification::loadCountersBeforeRefresh);
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
downloadExecutor = new ExecutorCompletionService<>(
Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
@ -289,6 +294,10 @@ public class DownloadService extends Service {
if (log.size() > 0 && !log.get(0).isSuccessful()) {
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 {
DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true);
saveDownloadStatus(task.getDownloadStatus());

View File

@ -143,7 +143,7 @@ public class DownloadServiceNotification {
// We are generating an auto-download report
channelId = NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD;
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);
id = R.id.notification_auto_download_report;
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.util.Log;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
@ -15,6 +16,7 @@ public class FeedSyncTask {
private final DownloadRequest request;
private final Context context;
private DownloadStatus downloadStatus;
private Feed savedFeed;
public FeedSyncTask(Context context, DownloadRequest request) {
this.request = request;
@ -30,7 +32,7 @@ public class FeedSyncTask {
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
final boolean loadAllPages = request.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES);
final Feed feed = result.feed;
@ -48,4 +50,8 @@ public class FeedSyncTask {
public DownloadStatus getDownloadStatus() {
return downloadStatus;
}
public Feed getSavedFeed() {
return savedFeed;
}
}

View File

@ -802,15 +802,9 @@ public final class DBReader {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<Feed> feeds = getFeedList(adapter);
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);
final LongIntMap feedCounters = adapter.getFeedCounters();
SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter();
feeds = subscriptionsFilter.filter(getFeedList(adapter), feedCounters);
List<Feed> feeds = subscriptionsFilter.filter(getFeedList(adapter), feedCounters);
Comparator<Feed> comparator;
int feedOrder = UserPreferences.getFeedOrder();
@ -840,7 +834,7 @@ public final class DBReader {
}
};
} else if (feedOrder == UserPreferences.FEED_ORDER_MOST_PLAYED) {
final LongIntMap playedCounters = adapter.getPlayedEpisodesCounters(feedIds);
final LongIntMap playedCounters = adapter.getPlayedEpisodesCounters();
comparator = (lhs, rhs) -> {
long counterLhs = playedCounters.get(lhs.getId());

View File

@ -310,6 +310,10 @@ class DBUpgrader {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS +
" 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";
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.
@ -115,6 +115,7 @@ public class PodDBAdapter {
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_ENDING = "feed_skip_ending";
public static final String KEY_EPISODE_NOTIFICATION = "episode_notification";
// Table names
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_VOLUME_ADAPTION + " 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 "
+ 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_FEED_PLAYBACK_SPEED,
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_SKIP_INTRO, prefs.getFeedSkipIntro());
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())});
}
@ -1164,6 +1168,11 @@ public class PodDBAdapter {
public final LongIntMap getFeedCounters(long... feedIds) {
int setting = UserPreferences.getFeedCounterSetting();
return getFeedCounters(setting, feedIds);
}
public final LongIntMap getFeedCounters(int setting, long... feedIds) {
String whereRead;
switch (setting) {
case UserPreferences.FEED_COUNTER_SHOW_NEW_UNPLAYED_SUM:
@ -1188,24 +1197,26 @@ public class PodDBAdapter {
}
private LongIntMap conditionalFeedCounterRead(String whereRead, long... feedIds) {
// 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(',');
}
String limitFeeds = "";
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
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 "
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " WHERE " + KEY_FEED + " IN (" + builder.toString() + ") "
+ " AND " + whereRead + " GROUP BY " + KEY_FEED;
+ " WHERE " + limitFeeds + " "
+ whereRead + " GROUP BY " + KEY_FEED;
Cursor c = db.rawQuery(query, null);
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_SYNC_ERROR = "sync_error";
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_NEWS = "group_news";
@ -38,6 +39,7 @@ public class NotificationUtils {
mNotificationManager.createNotificationChannel(createChannelError(context));
mNotificationManager.createNotificationChannel(createChannelSyncError(context));
mNotificationManager.createNotificationChannel(createChannelAutoDownload(context));
mNotificationManager.createNotificationChannel(createChannelEpisodeNotification(context));
}
}
@ -110,6 +112,15 @@ public class NotificationUtils {
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)
private static NotificationChannelGroup createGroupErrors(Context c) {
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>
</plurals>
<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 -->
<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_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_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 -->
<string name="widget_settings">Widget settings</string>