diff --git a/app/build.gradle b/app/build.gradle
index 1219aeb33..61a0cdc2b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -108,6 +108,7 @@ ext {
leakCanaryVersion = '2.5'
stethoVersion = '1.6.0'
mockitoVersion = '3.6.0'
+ workVersion = '2.5.0'
}
configurations {
@@ -213,6 +214,8 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'com.google.android.material:material:1.2.1'
+ implementation "androidx.work:work-runtime:${workVersion}"
+ implementation "androidx.work:work-rxjava2:${workVersion}"
/** Third-party libraries **/
// Instance state boilerplate elimination
diff --git a/app/src/debug/res/xml/main_settings.xml b/app/src/debug/res/xml/main_settings.xml
index d482d033c..4e812bb1c 100644
--- a/app/src/debug/res/xml/main_settings.xml
+++ b/app/src/debug/res/xml/main_settings.xml
@@ -40,6 +40,12 @@
android:title="@string/settings_category_notification_title"
app:iconSpaceReserved="false" />
+
+
{
@Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertAllInternal(streams: List): List
+ @Query("SELECT COUNT(*) != 0 FROM streams WHERE url = :url AND service_id = :serviceId")
+ internal abstract fun exists(serviceId: Long, url: String?): Boolean
+
@Query(
"""
SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration
diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java b/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java
new file mode 100644
index 000000000..d817032ee
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java
@@ -0,0 +1,14 @@
+package org.schabi.newpipe.database.subscription;
+
+import androidx.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef({NotificationMode.DISABLED, NotificationMode.ENABLED_DEFAULT})
+@Retention(RetentionPolicy.SOURCE)
+public @interface NotificationMode {
+
+ int DISABLED = 0;
+ int ENABLED_DEFAULT = 1;
+ //other values reserved for the future
+}
diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
index 1cf38dbca..0e4bda490 100644
--- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
@@ -26,6 +26,7 @@ public class SubscriptionEntity {
public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url";
public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count";
public static final String SUBSCRIPTION_DESCRIPTION = "description";
+ public static final String SUBSCRIPTION_NOTIFICATION_MODE = "notification_mode";
@PrimaryKey(autoGenerate = true)
private long uid = 0;
@@ -48,6 +49,9 @@ public class SubscriptionEntity {
@ColumnInfo(name = SUBSCRIPTION_DESCRIPTION)
private String description;
+ @ColumnInfo(name = SUBSCRIPTION_NOTIFICATION_MODE)
+ private int notificationMode;
+
@Ignore
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
final SubscriptionEntity result = new SubscriptionEntity();
@@ -114,6 +118,15 @@ public class SubscriptionEntity {
this.description = description;
}
+ @NotificationMode
+ public int getNotificationMode() {
+ return notificationMode;
+ }
+
+ public void setNotificationMode(@NotificationMode final int notificationMode) {
+ this.notificationMode = notificationMode;
+ }
+
@Ignore
public void setData(final String n, final String au, final String d, final Long sc) {
this.setName(n);
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index 548ae7b2c..754036dfd 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -1,6 +1,11 @@
package org.schabi.newpipe.fragments.list.channel;
+import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
+import static org.schabi.newpipe.ktx.ViewUtils.animate;
+import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
+
import android.content.Context;
+import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@@ -19,9 +24,11 @@ import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.viewbinding.ViewBinding;
+import com.google.android.material.snackbar.Snackbar;
import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.database.subscription.NotificationMode;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
import org.schabi.newpipe.databinding.FragmentChannelBinding;
@@ -37,6 +44,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
+import org.schabi.newpipe.notifications.NotificationHelper;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -60,10 +68,6 @@ import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
-import static org.schabi.newpipe.ktx.ViewUtils.animate;
-import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
-
public class ChannelFragment extends BaseListInfoFragment
implements View.OnClickListener {
@@ -84,6 +88,7 @@ public class ChannelFragment extends BaseListInfoFragment
private PlaylistControlBinding playlistControlBinding;
private MenuItem menuRssButton;
+ private MenuItem menuNotifyButton;
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
@@ -181,6 +186,7 @@ public class ChannelFragment extends BaseListInfoFragment
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
menuRssButton = menu.findItem(R.id.menu_item_rss);
+ menuNotifyButton = menu.findItem(R.id.menu_item_notify);
}
}
@@ -197,6 +203,11 @@ public class ChannelFragment extends BaseListInfoFragment
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
+ case R.id.menu_item_notify:
+ final boolean value = !item.isChecked();
+ item.setEnabled(false);
+ setNotify(value);
+ break;
case R.id.menu_item_rss:
openRssFeed();
break;
@@ -238,15 +249,22 @@ public class ChannelFragment extends BaseListInfoFragment
.subscribe(getSubscribeUpdateMonitor(info), onError));
disposables.add(observable
- // Some updates are very rapid
- // (for example when calling the updateSubscription(info))
- // so only update the UI for the latest emission
- // ("sync" the subscribe button's state)
- .debounce(100, TimeUnit.MILLISECONDS)
+ .map(List::isEmpty)
+ .distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
- .subscribe((List subscriptionEntities) ->
- updateSubscribeButton(!subscriptionEntities.isEmpty()), onError));
+ .subscribe((Boolean isEmpty) -> updateSubscribeButton(!isEmpty), onError));
+ disposables.add(observable
+ .map(List::isEmpty)
+ .filter(x -> NotificationHelper.isNewStreamsNotificationsEnabled(requireContext()))
+ .distinctUntilChanged()
+ .skip(1)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(isEmpty -> {
+ if (!isEmpty) {
+ showNotifySnackbar();
+ }
+ }, onError));
}
private Function