From da9bd1d420ebc55c9697ba996caf79d793317a2f Mon Sep 17 00:00:00 2001 From: Vasiliy Date: Wed, 8 May 2019 20:17:54 +0300 Subject: [PATCH 01/46] Notifications about new streams --- app/build.gradle | 3 + app/src/debug/res/xml/main_settings.xml | 6 + app/src/main/java/org/schabi/newpipe/App.java | 18 ++- .../java/org/schabi/newpipe/MainActivity.java | 3 +- .../org/schabi/newpipe/NewPipeDatabase.java | 13 +- .../schabi/newpipe/database/AppDatabase.java | 6 +- .../schabi/newpipe/database/Migrations.java | 12 +- .../newpipe/database/stream/dao/StreamDAO.kt | 3 + .../subscription/NotificationMode.java | 14 ++ .../subscription/SubscriptionEntity.java | 13 ++ .../list/channel/ChannelFragment.java | 77 ++++++++-- .../local/subscription/SubscriptionManager.kt | 23 +++ .../newpipe/notifications/ChannelUpdates.kt | 46 ++++++ .../notifications/NotificationHelper.java | 137 ++++++++++++++++++ .../notifications/NotificationIcon.java | 60 ++++++++ .../notifications/NotificationWorker.kt | 82 +++++++++++ .../newpipe/notifications/ScheduleOptions.kt | 33 +++++ .../notifications/SubscriptionUpdates.kt | 53 +++++++ .../settings/NotificationsSettingsFragment.kt | 112 ++++++++++++++ .../NotificationsChannelsConfigFragment.java | 84 +++++++++++ .../NotificationsConfigAdapter.kt | 114 +++++++++++++++ .../schabi/newpipe/util/NavigationHelper.java | 6 + .../drawable-anydpi-v24/ic_stat_newpipe.xml | 14 ++ .../res/drawable-hdpi/ic_stat_newpipe.png | Bin 0 -> 413 bytes .../res/drawable-mdpi/ic_stat_newpipe.png | Bin 0 -> 294 bytes .../res/drawable-night/ic_notifications.xml | 9 ++ .../res/drawable-xhdpi/ic_stat_newpipe.png | Bin 0 -> 522 bytes .../res/drawable-xxhdpi/ic_stat_newpipe.png | Bin 0 -> 731 bytes .../main/res/drawable/ic_notifications.xml | 9 ++ .../fragment_channels_notifications.xml | 14 ++ .../res/layout/item_notification_config.xml | 16 ++ app/src/main/res/menu/menu_channel.xml | 7 + app/src/main/res/values-ru/strings.xml | 21 +++ app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rHK/strings.xml | 1 + app/src/main/res/values/settings_keys.xml | 30 ++++ app/src/main/res/values/strings.xml | 27 +++- .../main/res/xml/notifications_settings.xml | 41 ++++++ app/src/release/res/xml/main_settings.xml | 9 +- assets/db.dia | Bin 3129 -> 3167 bytes 40 files changed, 1090 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt create mode 100644 app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_newpipe.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_newpipe.png create mode 100644 app/src/main/res/drawable-night/ic_notifications.xml create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_newpipe.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_newpipe.png create mode 100644 app/src/main/res/drawable/ic_notifications.xml create mode 100644 app/src/main/res/layout/fragment_channels_notifications.xml create mode 100644 app/src/main/res/layout/item_notification_config.xml create mode 100644 app/src/main/res/xml/notifications_settings.xml diff --git a/app/build.gradle b/app/build.gradle index 1219aeb330e..61a0cdc2b11 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 d482d033c03..4e812bb1c43 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 00000000000..d817032eebd --- /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 1cf38dbca6a..0e4bda49076 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 void setDescription(final String description) { 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 548ae7b2cdd..754036dfde7 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.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.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.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 void onCreateOptionsMenu(@NonNull final Menu menu, + "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 boolean onOptionsItemSelected(final MenuItem item) { 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 @@ private void monitorSubscription(final ChannelInfo info) { .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 mapOnSubscribe(final SubscriptionEntity subscription, @@ -326,6 +344,7 @@ private Consumer> getSubscribeUpdateMonitor(final Chann info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount()); + updateNotifyButton(null); subscribeButtonMonitor = monitorSubscribeButton( headerBinding.channelSubscribeButton, mapOnSubscribe(channel, info)); } else { @@ -333,6 +352,7 @@ private Consumer> getSubscribeUpdateMonitor(final Chann Log.d(TAG, "Found subscription to this channel!"); } final SubscriptionEntity subscription = subscriptionEntities.get(0); + updateNotifyButton(subscription); subscribeButtonMonitor = monitorSubscribeButton( headerBinding.channelSubscribeButton, mapOnUnsubscribe(subscription)); } @@ -375,6 +395,41 @@ private void updateSubscribeButton(final boolean isSubscribed) { AnimationType.LIGHT_SCALE_AND_ALPHA); } + private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) { + if (menuNotifyButton == null) { + return; + } + if (subscription == null) { + menuNotifyButton.setVisible(false); + } else { + menuNotifyButton.setEnabled( + NotificationHelper.isNewStreamsNotificationsEnabled(requireContext()) + ); + menuNotifyButton.setChecked( + subscription.getNotificationMode() != NotificationMode.DISABLED + ); + menuNotifyButton.setVisible(true); + } + } + + private void setNotify(final boolean isEnabled) { + final int mode = isEnabled ? NotificationMode.ENABLED_DEFAULT : NotificationMode.DISABLED; + disposables.add( + subscriptionManager.updateNotificationMode(currentInfo.getServiceId(), + currentInfo.getUrl(), mode) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe() + ); + } + + private void showNotifySnackbar() { + Snackbar.make(itemsList, R.string.you_successfully_subscribed, Snackbar.LENGTH_LONG) + .setAction(R.string.get_notified, v -> setNotify(true)) + .setActionTextColor(Color.YELLOW) + .show(); + } + /*////////////////////////////////////////////////////////////////////////// // Load and handle //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index fb9cffa9855..442a867b39a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -7,6 +7,8 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.database.feed.model.FeedGroupEntity +import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionDAO import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.extractor.ListInfo @@ -14,6 +16,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo import org.schabi.newpipe.extractor.feed.FeedInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.FeedDatabaseManager +import org.schabi.newpipe.util.ExtractorHelper class SubscriptionManager(context: Context) { private val database = NewPipeDatabase.getInstance(context) @@ -66,6 +69,16 @@ class SubscriptionManager(context: Context) { } } + fun updateNotificationMode(serviceId: Int, url: String?, @NotificationMode mode: Int): Completable { + return subscriptionTable().getSubscription(serviceId, url!!) + .flatMapCompletable { entity: SubscriptionEntity -> + Completable.fromAction { + entity.notificationMode = mode + subscriptionTable().update(entity) + }.andThen(rememberLastStream(entity)) + } + } + fun updateFromInfo(subscriptionId: Long, info: ListInfo) { val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId) @@ -94,4 +107,14 @@ class SubscriptionManager(context: Context) { fun deleteSubscription(subscriptionEntity: SubscriptionEntity) { subscriptionTable.delete(subscriptionEntity) } + + private fun rememberLastStream(subscription: SubscriptionEntity): Completable { + return ExtractorHelper.getChannelInfo(subscription.serviceId, subscription.url, false) + .map { channel -> channel.relatedItems.map { stream -> StreamEntity(stream) } } + .flatMapCompletable { entities -> + Completable.fromAction { + database.streamDAO().upsertAll(entities) + } + }.onErrorComplete() + } } diff --git a/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt b/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt new file mode 100644 index 00000000000..9a3b2cbf33f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt @@ -0,0 +1,46 @@ +package org.schabi.newpipe.notifications + +import android.content.Context +import android.content.Intent +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.channel.ChannelInfo +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.util.NavigationHelper + +data class ChannelUpdates( + val serviceId: Int, + val url: String, + val avatarUrl: String, + val name: String, + val streams: List +) { + + val id = url.hashCode() + + val isNotEmpty: Boolean + get() = streams.isNotEmpty() + + val size = streams.size + + fun getText(context: Context): String { + val separator = context.resources.getString(R.string.enumeration_comma) + " " + return streams.joinToString(separator) { it.name } + } + + fun createOpenChannelIntent(context: Context?): Intent { + return NavigationHelper.getChannelIntent(context, serviceId, url) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + companion object { + fun from(channel: ChannelInfo, streams: List): ChannelUpdates { + return ChannelUpdates( + channel.serviceId, + channel.url, + channel.avatarUrl, + channel.name, + streams + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java b/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java new file mode 100644 index 00000000000..6207cd613ef --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java @@ -0,0 +1,137 @@ +package org.schabi.newpipe.notifications; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; + +import org.schabi.newpipe.BuildConfig; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public final class NotificationHelper { + + private final Context context; + private final NotificationManager manager; + private final CompositeDisposable disposable; + + public NotificationHelper(final Context context) { + this.context = context; + this.disposable = new CompositeDisposable(); + this.manager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE + ); + } + + public Context getContext() { + return context; + } + + /** + * Check whether notifications are not disabled by user via system settings. + * + * @param context Context + * @return true if notifications are allowed, false otherwise + */ + public static boolean isNotificationsEnabledNative(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final String channelId = context.getString(R.string.streams_notification_channel_id); + final NotificationManager manager = (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) { + final NotificationChannel channel = manager.getNotificationChannel(channelId); + return channel != null + && channel.getImportance() != NotificationManager.IMPORTANCE_NONE; + } else { + return false; + } + } else { + return NotificationManagerCompat.from(context).areNotificationsEnabled(); + } + } + + public static boolean isNewStreamsNotificationsEnabled(@NonNull final Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.enable_streams_notifications), false) + && isNotificationsEnabledNative(context); + } + + public static void openNativeSettingsScreen(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final String channelId = context.getString(R.string.streams_notification_channel_id); + final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()) + .putExtra(Settings.EXTRA_CHANNEL_ID, channelId); + context.startActivity(intent); + } else { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + context.getPackageName())); + context.startActivity(intent); + } + } + + public void notify(final ChannelUpdates data) { + final String summary = context.getResources().getQuantityString( + R.plurals.new_streams, data.getSize(), data.getSize() + ); + final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, + context.getString(R.string.streams_notification_channel_id)) + .setContentTitle( + context.getString(R.string.notification_title_pattern, + data.getName(), + summary) + ) + .setContentText(data.getText(context)) + .setNumber(data.getSize()) + .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSmallIcon(R.drawable.ic_stat_newpipe) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_newpipe_triangle_white)) + .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) + .setColorized(true) + .setAutoCancel(true) + .setCategory(NotificationCompat.CATEGORY_SOCIAL); + final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + for (final StreamInfoItem stream : data.getStreams()) { + style.addLine(stream.getName()); + } + style.setSummaryText(summary); + style.setBigContentTitle(data.getName()); + builder.setStyle(style); + builder.setContentIntent(PendingIntent.getActivity( + context, + data.getId(), + data.createOpenChannelIntent(context), + 0 + )); + + disposable.add( + Single.create(new NotificationIcon(context, data.getAvatarUrl())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doAfterTerminate(() -> manager.notify(data.getId(), builder.build())) + .subscribe(builder::setLargeIcon, throwable -> { + if (BuildConfig.DEBUG) { + throwable.printStackTrace(); + } + }) + ); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java b/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java new file mode 100644 index 00000000000..fc59b55f05f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java @@ -0,0 +1,60 @@ +package org.schabi.newpipe.notifications; + +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; + +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.assist.ImageSize; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.SingleEmitter; +import io.reactivex.rxjava3.core.SingleOnSubscribe; + +final class NotificationIcon implements SingleOnSubscribe { + + private final String url; + private final int size; + + NotificationIcon(final Context context, final String url) { + this.url = url; + this.size = getIconSize(context); + } + + @Override + public void subscribe(@NonNull final SingleEmitter emitter) throws Throwable { + ImageLoader.getInstance().loadImage( + url, + new ImageSize(size, size), + new SimpleImageLoadingListener() { + + @Override + public void onLoadingFailed(final String imageUri, + final View view, + final FailReason failReason) { + emitter.onError(failReason.getCause()); + } + + @Override + public void onLoadingComplete(final String imageUri, + final View view, + final Bitmap loadedImage) { + emitter.onSuccess(loadedImage); + } + } + ); + } + + private static int getIconSize(final Context context) { + final ActivityManager activityManager = (ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE + ); + final int size2 = activityManager != null ? activityManager.getLauncherLargeIconSize() : 0; + final int size1 = context.getResources() + .getDimensionPixelSize(android.R.dimen.app_icon_size); + return Math.max(size2, size1); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt new file mode 100644 index 00000000000..24dbc82e078 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt @@ -0,0 +1,82 @@ +package org.schabi.newpipe.notifications + +import android.content.Context +import androidx.preference.PreferenceManager +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequest +import androidx.work.RxWorker +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.Single +import org.schabi.newpipe.R +import java.util.concurrent.TimeUnit + +class NotificationWorker( + appContext: Context, + workerParams: WorkerParameters +) : RxWorker(appContext, workerParams) { + + private val notificationHelper by lazy { + NotificationHelper(appContext) + } + + override fun createWork() = if (isEnabled(applicationContext)) { + Flowable.create( + SubscriptionUpdates(applicationContext), + BackpressureStrategy.BUFFER + ).doOnNext { notificationHelper.notify(it) } + .toList() + .map { Result.success() } + .onErrorReturnItem(Result.failure()) + } else Single.just(Result.success()) + + companion object { + + private const val TAG = "notifications" + + private fun isEnabled(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean( + context.getString(R.string.enable_streams_notifications), + false + ) && NotificationHelper.isNotificationsEnabledNative(context) + } + + fun schedule(context: Context, options: ScheduleOptions, force: Boolean = false) { + val constraints = Constraints.Builder() + .setRequiredNetworkType( + if (options.isRequireNonMeteredNetwork) { + NetworkType.UNMETERED + } else { + NetworkType.CONNECTED + } + ).build() + val request = PeriodicWorkRequest.Builder( + NotificationWorker::class.java, + options.interval, + TimeUnit.MILLISECONDS + ).setConstraints(constraints) + .addTag(TAG) + .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) + .build() + WorkManager.getInstance(context) + .enqueueUniquePeriodicWork( + TAG, + if (force) { + ExistingPeriodicWorkPolicy.REPLACE + } else { + ExistingPeriodicWorkPolicy.KEEP + }, + request + ) + } + + @JvmStatic + fun schedule(context: Context) = schedule(context, ScheduleOptions.from(context)) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt b/app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt new file mode 100644 index 00000000000..b0617b30360 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt @@ -0,0 +1,33 @@ +package org.schabi.newpipe.notifications + +import android.content.Context +import androidx.preference.PreferenceManager +import org.schabi.newpipe.R +import java.util.concurrent.TimeUnit + +data class ScheduleOptions( + val interval: Long, + val isRequireNonMeteredNetwork: Boolean +) { + + companion object { + + fun from(context: Context): ScheduleOptions { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + return ScheduleOptions( + interval = TimeUnit.HOURS.toMillis( + preferences.getString( + context.getString(R.string.streams_notifications_interval_key), + context.getString(R.string.streams_notifications_interval_default) + )?.toLongOrNull() ?: context.getString( + R.string.streams_notifications_interval_default + ).toLong() + ), + isRequireNonMeteredNetwork = preferences.getString( + context.getString(R.string.streams_notifications_network_key), + context.getString(R.string.streams_notifications_network_default) + ) == context.getString(R.string.streams_notifications_network_wifi) + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt b/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt new file mode 100644 index 00000000000..6f7c3881b5a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt @@ -0,0 +1,53 @@ +package org.schabi.newpipe.notifications + +import android.content.Context +import io.reactivex.FlowableEmitter +import io.reactivex.FlowableOnSubscribe +import org.schabi.newpipe.NewPipeDatabase +import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.local.subscription.SubscriptionManager +import org.schabi.newpipe.util.ExtractorHelper + +class SubscriptionUpdates(context: Context) : FlowableOnSubscribe { + + private val subscriptionManager = SubscriptionManager(context) + private val streamTable = NewPipeDatabase.getInstance(context).streamDAO() + + override fun subscribe(emitter: FlowableEmitter) { + try { + val subscriptions = subscriptionManager.subscriptions().blockingFirst() + for (subscription in subscriptions) { + if (subscription.notificationMode != NotificationMode.DISABLED) { + val channel = ExtractorHelper.getChannelInfo( + subscription.serviceId, + subscription.url, true + ).blockingGet() + val updates = ChannelUpdates.from(channel, filterStreams(channel.relatedItems)) + if (updates.isNotEmpty) { + emitter.onNext(updates) + // prevent duplicated notifications + streamTable.upsertAll(updates.streams.map { StreamEntity(it) }) + } + } + } + emitter.onComplete() + } catch (e: Exception) { + emitter.onError(e) + } + } + + private fun filterStreams(list: List<*>): List { + val streams = ArrayList(list.size) + for (o in list) { + if (o is StreamInfoItem) { + if (streamTable.exists(o.serviceId.toLong(), o.url)) { + break + } + streams.add(o) + } + } + return streams + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt new file mode 100644 index 00000000000..62a819e64b7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -0,0 +1,112 @@ +package org.schabi.newpipe.settings + +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.graphics.Color +import android.os.Bundle +import androidx.preference.Preference +import com.google.android.material.snackbar.Snackbar +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.Disposable +import org.schabi.newpipe.R +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.database.subscription.SubscriptionEntity +import org.schabi.newpipe.error.ErrorActivity +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.UserAction +import org.schabi.newpipe.local.subscription.SubscriptionManager +import org.schabi.newpipe.notifications.NotificationHelper +import org.schabi.newpipe.notifications.NotificationWorker +import org.schabi.newpipe.notifications.ScheduleOptions + +class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferenceChangeListener { + + private var notificationWarningSnackbar: Snackbar? = null + private var loader: Disposable? = null + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.notifications_settings) + } + + override fun onStart() { + super.onStart() + defaultPreferences.registerOnSharedPreferenceChangeListener(this) + } + + override fun onStop() { + defaultPreferences.unregisterOnSharedPreferenceChangeListener(this) + super.onStop() + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + val context = context ?: return + if (key == getString(R.string.streams_notifications_interval_key) || key == getString(R.string.streams_notifications_network_key)) { + NotificationWorker.schedule(context, ScheduleOptions.from(context), true) + } + } + + override fun onResume() { + super.onResume() + val enabled = NotificationHelper.isNotificationsEnabledNative(context) + preferenceScreen.isEnabled = enabled + if (!enabled) { + if (notificationWarningSnackbar == null) { + notificationWarningSnackbar = Snackbar.make( + listView, + R.string.notifications_disabled, + Snackbar.LENGTH_INDEFINITE + ).apply { + setAction(R.string.settings) { v -> + NotificationHelper.openNativeSettingsScreen(v.context) + } + setActionTextColor(Color.YELLOW) + addCallback(object : Snackbar.Callback() { + override fun onDismissed(transientBottomBar: Snackbar, event: Int) { + super.onDismissed(transientBottomBar, event) + notificationWarningSnackbar = null + } + }) + show() + } + } + } else { + notificationWarningSnackbar?.dismiss() + notificationWarningSnackbar = null + } + loader?.dispose() + loader = SubscriptionManager(requireContext()) + .subscriptions() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::updateSubscriptions, this::onError) + } + + override fun onPause() { + loader?.dispose() + loader = null + super.onPause() + } + + private fun updateSubscriptions(subscriptions: List) { + var notified = 0 + for (subscription in subscriptions) { + if (subscription.notificationMode != NotificationMode.DISABLED) { + notified++ + } + } + val preference = findPreference(getString(R.string.streams_notifications_channels_key)) + if (preference != null) { + preference.summary = preference.context.getString( + R.string.streams_notifications_channels_summary, + notified, + subscriptions.size + ) + } + } + + private fun onError(e: Throwable) { + ErrorActivity.reportErrorInSnackbar( + this, + ErrorInfo(e, UserAction.SUBSCRIPTION_GET, "Get subscriptions list") + ) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java new file mode 100644 index 00000000000..7aa0826e559 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java @@ -0,0 +1,84 @@ +package org.schabi.newpipe.settings.notifications; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.subscription.NotificationMode; +import org.schabi.newpipe.local.subscription.SubscriptionManager; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public final class NotificationsChannelsConfigFragment extends Fragment + implements NotificationsConfigAdapter.ModeToggleListener { + + private NotificationsConfigAdapter adapter; + @Nullable + private Disposable loader = null; + private CompositeDisposable updaters; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + adapter = new NotificationsConfigAdapter(this); + updaters = new CompositeDisposable(); + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_channels_notifications, container, false); + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final RecyclerView recyclerView = view.findViewById(R.id.recycler_view); + recyclerView.setAdapter(adapter); + } + + @Override + public void onActivityCreated(@Nullable final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (loader != null) { + loader.dispose(); + } + loader = new SubscriptionManager(requireContext()) + .subscriptions() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(adapter::update); + } + + @Override + public void onDestroy() { + if (loader != null) { + loader.dispose(); + } + updaters.dispose(); + super.onDestroy(); + } + + @Override + public void onModeToggle(final int position, @NotificationMode final int mode) { + final NotificationsConfigAdapter.SubscriptionItem subscription = adapter.getItem(position); + updaters.add( + new SubscriptionManager(requireContext()) + .updateNotificationMode(subscription.getServiceId(), + subscription.getUrl(), mode) + .subscribeOn(Schedulers.io()) + .subscribe() + ); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt new file mode 100644 index 00000000000..44d2256afa5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt @@ -0,0 +1,114 @@ +package org.schabi.newpipe.settings.notifications + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckedTextView +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import org.schabi.newpipe.R +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.database.subscription.SubscriptionEntity +import org.schabi.newpipe.settings.notifications.NotificationsConfigAdapter.SubscriptionHolder + +class NotificationsConfigAdapter( + private val listener: ModeToggleListener +) : RecyclerView.Adapter() { + + private val differ = AsyncListDiffer(this, DiffCallback()) + + init { + setHasStableIds(true) + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): SubscriptionHolder { + val view = LayoutInflater.from(viewGroup.context) + .inflate(R.layout.item_notification_config, viewGroup, false) + return SubscriptionHolder(view, listener) + } + + override fun onBindViewHolder(subscriptionHolder: SubscriptionHolder, i: Int) { + subscriptionHolder.bind(differ.currentList[i]) + } + + fun getItem(position: Int): SubscriptionItem = differ.currentList[position] + + override fun getItemCount() = differ.currentList.size + + override fun getItemId(position: Int): Long { + return differ.currentList[position].id + } + + fun update(newData: List) { + differ.submitList( + newData.map { + SubscriptionItem( + id = it.uid, + title = it.name, + notificationMode = it.notificationMode, + serviceId = it.serviceId, + url = it.url + ) + } + ) + } + + data class SubscriptionItem( + val id: Long, + val title: String, + @NotificationMode + val notificationMode: Int, + val serviceId: Int, + val url: String + ) + + class SubscriptionHolder( + itemView: View, + private val listener: ModeToggleListener + ) : RecyclerView.ViewHolder(itemView), View.OnClickListener { + + private val checkedTextView = itemView as CheckedTextView + + init { + itemView.setOnClickListener(this) + } + + fun bind(data: SubscriptionItem) { + checkedTextView.text = data.title + checkedTextView.isChecked = data.notificationMode != NotificationMode.DISABLED + } + + override fun onClick(v: View) { + val mode = if (checkedTextView.isChecked) { + NotificationMode.DISABLED + } else { + NotificationMode.ENABLED_DEFAULT + } + listener.onModeToggle(adapterPosition, mode) + } + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: SubscriptionItem, newItem: SubscriptionItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: SubscriptionItem, newItem: SubscriptionItem): Boolean { + return oldItem == newItem + } + + override fun getChangePayload(oldItem: SubscriptionItem, newItem: SubscriptionItem): Any? { + if (oldItem.notificationMode != newItem.notificationMode) { + return newItem.notificationMode + } else { + return super.getChangePayload(oldItem, newItem) + } + } + } + + interface ModeToggleListener { + fun onModeToggle(position: Int, @NotificationMode mode: Int) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index eba24020f1a..859bfa31d70 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -571,6 +571,12 @@ public static Intent getIntentByLink(final Context context, return getOpenIntent(context, url, service.getServiceId(), linkType); } + public static Intent getChannelIntent(final Context context, + final int serviceId, + final String url) { + return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL); + } + /** * Start an activity to install Kore. * diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml b/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml new file mode 100644 index 00000000000..e95f5b4aca1 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_stat_newpipe.png b/app/src/main/res/drawable-hdpi/ic_stat_newpipe.png new file mode 100644 index 0000000000000000000000000000000000000000..dc08d67ff89167b42ab133658c4a42d127aca7a5 GIT binary patch literal 413 zcmV;O0b>4%P)QuB#Tw7U`%%oFC2aV032bBx#yy1-$Tux)hO3M%RC0Ua^Em zD?$3XW-RMBuQXQ}vl0~Ar%RtEX?D?LB`CD-u>N3Inm4QExyFkl00000NkvXX Hu0mjfv&*yy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_stat_newpipe.png b/app/src/main/res/drawable-mdpi/ic_stat_newpipe.png new file mode 100644 index 0000000000000000000000000000000000000000..4af6c74df80b88fb6208df08da68e02ffbc98f94 GIT binary patch literal 294 zcmV+>0oneEP) + + diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_newpipe.png b/app/src/main/res/drawable-xhdpi/ic_stat_newpipe.png new file mode 100644 index 0000000000000000000000000000000000000000..5c5750ce576d073072a26b280b0a95e62305405e GIT binary patch literal 522 zcmV+l0`>igP)*Znnf&%5_L%L)@FOvc!@U8Y?i0hU39R)ID9 zFVA2CM70VyxddIB1)OYyTFnBKH!uqdwF)@71B03coa}=}%>tB9umDQ53OIQHBOphs zfRkg;B2i!-95E%n!4fE!BoH&-4}*KA$P*X?d13_2m?AI--kBm7phJv+Yiu3Z@vuHN zK(!bF*EK!hiYbuO8;{I0dcJPGlT`M=%OGD7>CE&{)kbnXhI+bCDfttAm%|ZsEbepMu+H96xPL(E?&$!iI=(v9wbBXQjrG_ z)*%tXgHhnYiXbWv5eWumNKl&F@56WC1Gm}3J3BV7zXzV%?hHIIGv9pgZV(Vc2qA>5 zkjv#R!way4HS!0#kz3(8tY?vb2Xg#@18^71C?g|n z?NjY7JZ|b9s0@6p@@Op+xj&V zKSYl?(6a13+xaU)jDcRjBY`zy2;^QWFRuAZ<$oIJ4BQ!5X00~u8Hj-xDC7y;$3Uz3 z08a($ojifuCVQ9rK~z9i1zNxVEG!Txxk+UT1JxutsU(CDLI^1ie*xq30c~$9$P@qo N002ovPDHLkV1g)7OiKU& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_notifications.xml b/app/src/main/res/drawable/ic_notifications.xml new file mode 100644 index 00000000000..0243818167e --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_channels_notifications.xml b/app/src/main/res/layout/fragment_channels_notifications.xml new file mode 100644 index 00000000000..d1ae01bfe96 --- /dev/null +++ b/app/src/main/res/layout/fragment_channels_notifications.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_notification_config.xml b/app/src/main/res/layout/item_notification_config.xml new file mode 100644 index 00000000000..b68692dd737 --- /dev/null +++ b/app/src/main/res/layout/item_notification_config.xml @@ -0,0 +1,16 @@ + + diff --git a/app/src/main/res/menu/menu_channel.xml b/app/src/main/res/menu/menu_channel.xml index af902062659..d6c54b6804c 100644 --- a/app/src/main/res/menu/menu_channel.xml +++ b/app/src/main/res/menu/menu_channel.xml @@ -17,6 +17,13 @@ android:title="@string/share" app:showAsAction="ifRoom" /> + + %s видео %s видео + + %s новое видео + %s новых видео + %s новых видео + Удалить этот элемент из истории поиска? Главная страница Пустая страница @@ -683,4 +688,20 @@ Удалять элементы смахиванием Не начинать просмотр видео в мини-плеере, но сразу переключиться в полноэкранный режим, если автовращение экрана заблокировано. Вы можете переключиться на мини-плеер, выйдя из полноэкранного режима. Начинать просмотр в полноэкранном режиме + Уведомления + Новые видео + Уведомления о новых видео в подписках + Частота проверки + Уведомлять о новых видео + Получать уведомления о новых видео из каналов, на которые Вы подписаны + Каждый час + Каждые 2 часа + Каждые 3 часа + Дважды в день + Каждый день + Тип подключения + Любая сеть + Уведомления отключены + Уведомлять + Вы подписались на канал \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e5180c51e5c..91166c75451 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -443,4 +443,5 @@ 快进 / 快退的单位时间 清除下载历史记录 删除下载了的文件 + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 63ca8f8270e..1592b8b599c 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -132,4 +132,5 @@ 使用粗略快查 添加到 選擇標籤 + \ No newline at end of file diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 9261dfae166..42d2233c870 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1260,4 +1260,34 @@ recaptcha_cookies_key + enable_streams_notifications + streams_notifications_interval + 3 + + 1 + 2 + 3 + 12 + 24 + + + @string/every_hour + @string/every_two_hours + @string/every_three_hours + @string/twice_per_day + @string/every_day + + streams_notifications_network + any + wifi + @string/streams_notifications_network_wifi + + @string/streams_notifications_network_any + @string/streams_notifications_network_wifi + + + @string/any_network + @string/wifi_only + + streams_notifications_channels diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e5600b5e683..f84cc83b1f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + @@ -180,6 +180,7 @@ Always Just Once File + Notifications newpipe NewPipe Notification Notifications for NewPipe background and popup players @@ -189,6 +190,9 @@ newpipeHash Video Hash Notification Notifications for video hashing progress + newpipeNewStreams + New streams + Notifications about new streams for subscriptions [Unknown] Switch to Background Switch to Popup @@ -309,6 +313,10 @@ No comments Comments are disabled + + %s new stream + %s new streams + Start Pause @@ -513,6 +521,17 @@ 240p 144p + + New streams notifications + Notify about new streams from subscriptions + Checking frequency + Every hour + Every 2 hours + Every 3 hours + Twice per day + Every day + Required network connection + Any network Updates Show a notification to prompt app update when a new version is available @@ -703,4 +722,10 @@ Error at Show Channel Details Loading Channel Details… + Notifications are disabled + Get notified + You now subscribed to this channel + , + %s • %s + %d/%d \ No newline at end of file diff --git a/app/src/main/res/xml/notifications_settings.xml b/app/src/main/res/xml/notifications_settings.xml new file mode 100644 index 00000000000..4390dc48c99 --- /dev/null +++ b/app/src/main/res/xml/notifications_settings.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/app/src/release/res/xml/main_settings.xml b/app/src/release/res/xml/main_settings.xml index 1d524110232..e999aa8c488 100644 --- a/app/src/release/res/xml/main_settings.xml +++ b/app/src/release/res/xml/main_settings.xml @@ -1,5 +1,6 @@ - @@ -40,6 +41,12 @@ android:title="@string/settings_category_notification_title" app:iconSpaceReserved="false" /> + + 8zj+yD0=v_z5)T{~FC~dkh zc!Zo;-psd;u$_KCFapX@CnKsdZOgiFIf!$QuC9Ll?fWe5y)m9eY4Xx1n%?iRWSoXk zGI{C$=j%V8y#8-rKK&X-^cVSW!s)Chzma75tC#*%i1{zi&+qT=TD;7tNVyhA3oT>M z|D$nCpJk)x{+Ca^-hKxm6*PZu^DY&FN27&cy@bx#OMgVi-zGd=Bw>HuYSV6<#wqW; z(fFnR`E~Wvf8KQSe9+I8zUOqpMx4=am%G;GkMEc1dd~Q9x3hVgMY4%l&QF?L=qLYw zug#`aCL1M_FQ5NreO@2Z=8aun=MMCu5iz5D5+$d0q;KN2BMi+m3`07@_B~&BEth=w zX`LgM-S_j ziI^@K-*o?nymX9Dc>bL3ZLaWU$m!9sh~7S4?KveK{l&&&bGEOqVT2vr+(`q$)WJXVe@p0Dsw>GV+WiLAD8+bjgGtK^6+UgiP^6EA<;F@ zvIoT1N}8H6LHjo_9!6tPu6Gu*Sujp{a!Nfaan!2+)$nxctdF6|l^(MEy&q4()|5q) zskm&+&|LlC$0%n^u1vHZ>GEXV{E>?}>b$B?`OXDktyY$^Bu@ld>A=Nb{Z3&nIIx#^^CGx90G z_zDPf9!WmD48F1DRbz+b^d4KIG!FO8WObcXl1geH2e0X=-}3i*uXGm0OF4k2Nt(^+ zSU$osvCJQX@&UH);ve$9ix@AD_1o4nnq*tU-q!LTEPi7m8q=r0OMjI+fDLHwJ%-am z_;VCT|6QnGhMR-Gd=?*#GnS(73z&5L!yiZ8+s#?szRqrO5RtRAVB7wb%-@tU&X3no zctO*+5(r!x?c{-8vlyqzq%vctLu#?7L>XJ>N7Z6s|NYjIMI5gv`7Ki?5&HeM7RhR4 zsM6BeK&Lra&v`oGEXx8B%^09vCA2HRTQ{+NUWjFi!I8lwl?SWy|7=`EE4-H-O z&4FpQIS4QBry@Z(v89J&Jn&QCHxdA?8NRjhGuTH)8I`jky1%e27v(B?tw>g|r#Zx~AU(8Dim+MsY9J@LZEJDoF_l zPB3zUo4|AaImA;;d|_G_=mxqUR#h^qQk96;fT~2iovLKDy40JZ*B(w)a*ipu36bgN z5ScierGf>}nxHj7Yl7AUtqEEav?gdxU0;75A|8j@1PK+O2B=X}0hq?L0Hy_iZY>%aJ+*`Q_ib{NHV?eD3FiQ zy6(}sPLau06`9-tAvMZNdG|wN=c(@l`O9ptu2kMp54SkL(#*rq!iB)}lk5i1C?8LQ zsf<-pzJ#;{X$jI2q$Nm8k1H)9_C}1-rO4|un>)7VNFl$tj z_N)w_^87O09GI=JAlIlexloxNLS@pAWhSGB+`Gjn8}n$M&wgi+ znIJPkW`fKFnF(ciAuwSzC(@G;gF{|I2~%Jim`1H>fV!I#TyIoRj-tGPcK1@G*W%uY zaZGao-kpPY$hOiohHhZHTU0w0(Q7nbuS%SyA%i*sc(*%4!Bfsqa5T#>?3xo4wnm1A zM*RFQyUm*jt2*uB>I4~@Tgj|Ak`-E)tnkWMh#UpvC?H1xISR;8z)~1wDj-v#aW!?N zHv!wgcH67;`synN+;swfo}KOzr`BZyho**4tBm;k4NJ31+kTAo166pjXmN@I3gx4m zFT(VC!t`Lm)5RRYI)Zfs>j>5ntYbj|LK%eVNK(aoInqJO6kH!St2rSF1fZ^8P))fA zQZ8c_;2OAYcXAWaE2+Oi9A`lCj2)fSU4&)N)bPpIbju4z!CqU^*XVydq> zuCZT{#X`;##MC;))ZkzOGT$u}@+#z2$g7Z7A+J8Jyt+4jLuiH2ihL+w8`%D#A^gAC zE8{E~pAF-Qau~lUYHFC8W!D+Qf7mf38?fx*Zk3e|C5zdH)tnr}?lW@bGhN}B=t88Dg9uhrxY7+_q0!BMR>oG*#63{iD+yed zfaelgz-bsQvMZ-qQq4-jfO4L`Up^;8wd){e$>jPavQ@OKP}%; zk(@zzKzKlSKzKlSwuFaVBrSIn9;Z>lW2zD!NAq*#fcRRSyD;xr8G4%UDO7cN1kQ8T zX&0LrL^H3lnGcT?ZD-aU{6Y*o5r7@F&w?!a(rp4HMdf0NNNtia|Yu$~L2ai$^BJcsyqG`u;z zweu#jdBAovfA_U#N!A}_Dke)ex*N*k(s5ZOH-Kp;RMKp;RMK$b!>v+*O7nO*g|ET-mps+Fb-)@3;~HO${I{rnA; zawZS4uIr)~ueG%`%d-9I+bUJq!HgJ45l9hIo{{nlTI8$Di5=w+8IAHHuL@q|c&=CT zcAVR1_PR9PiV_iD!R({3p%dR-YeqEP(Mch4b5C2PdVe57AVMHQAVMHQAVSBB-3=8Y z$3{HSFBX8>r_!iuu!R9 zThl8kS%^}-kH}-LR_cwCD4Vh{m_=DW&SWUhi}JiE&x`WBD9`)2<$32+OtD)OHX!Lz zVcrFg0ck+GOOPHd<-;Ke)AuA!X~Fmp={{vxiwFP!000021MOYglA|~heebVuxL;iq;?8)gCw6yYW8NlWxA&QljmpI| z2CtB-+AsUb( zXdKgL+330d-$6(P&5x~*QXzOWnhVxT=#;(mM|Avc!qa&Y_E)Xe?Z#=G^4>d* zU;3Y4m#_Zwx|`>Ney;RAqZ2mbjDEY^wJu-2U#9CBy^T{Qtc+ z>sFa;luW*S{+soAbxP|4yS~ny=tUu7O8F#8PUT45#4AM@nr9e>REF()zV2Es+34hc zaieC%#m$Q2&5Db%V3u;hX(UcXj?y${G+9X%e9p?t$;LF6CU)(z6t3bm5+c2E{;xF7 zid?|z@b+oD_sN~`D7^mQ9<;d7%SRN7x8QqC>RR=_sHyk&D2qlhJ9qX`A}Z^CQBC*9 zcV{_z?|!am4XfTRP7k5SY-Zm-Jxu0N$g&$|cG%`ZPjBmH&$qOG+^(o%R)>xz>!pa8 zE*M{T|A)MFj8AwzPxsb$cs=Cw=vYMWn@@X6Nk@ONu~=X2@3Q9+%~Y_jfQv zf{-YD>Hn!8>g)IlWp{EYJW<#z-CC77AnvgOPKS?6eUC=RU2}Q*G?~O~*Zq*_nrGPq z;%g;EO_`wm8#W$BV^OYm7PDzEPI+<)Jt|Suvj64qbgHb)(Bw)DS^nP3Q;_w>qRE@M zEX>ec{orMk6DHVq5#*2loS)vEL%gW#G7nP(E+Q-3bdaAelSnriiqj(_)@H9!Y86C?h zEE9`-8k7^*s*8Wf=PqKrysh7sp3x-R3ih^=|6uVu6VaGH{axy-+zG6W=H7ESJ%v9< zarEE0`fa#5_{+KYXq>SWbzeZF;~%~pb!|5%b^AHHg+WA)(t>UKQ!sy%$~Zq>N8trd zp9oertRW{EPg&X20a-2VHeCG$95Lh@UpPCWGcWzCc2$WW!E zvyM)4WNSAm^FIgdG|v@*L-te znr(K%%j>C#6HaWYp&I>eq8~Zl1b+7e_}%)k<89RCcxUjr;d8_1hR+S3`*D5lIlOIn z+f`oF4|cXQ#^0kcYm!8gcpZ*5NTiy_X&yIb(Enckg-yez>$PbSiI~BvVbz_o>U9(= z@4ZRemNLIqDH8S#`#yWu!#SrSN)xz^FzYS>>{*=Bka6G<7QZltT6;KF$vKALCP=2A zgJj}tnhF+xYXa8&7Bo z7&Z*MWko}a_QsgYjFyB`uePkkTAKW2M*rSgh;xWxgMW*)j0E(oTdWH)$5`S>k( zlfFvI7oe6vErD7BwFGMEaj7Nv-tbYnrUEr?lmIc z6lUG}q&!EJFD1r+xHS@{%l?PN*DPmX=xt8KQ_xhg96ZZG)*VPubyD<$3UH#}M8S!I z69p&Q22K=dNuWkM=w{RLm}MD=Q5bf|44bnVqXG*%!69{mLvmF)q$lEOu49`6!>o}> z+LJPT@-*Mi-}-H?(yOeuBD|8+kW&RdMw#CBl1}txiBiZ>r+V;;@&V0Z@B39J)X zC$LUnolvD0d=nOTB3lVAIoKvtG=)vWrcrttM%~SL*Em$0JazxkBEW@yC4(M)$3=NI=`8&JS;ao+fJzQNCLvt$$8zP$9 zx@hhzV529nLzObvWzD zxP~hOHyv4gh?gT5q)dV8akHEg))2s`>nBuGE`pRxp9OXeyKZ-K6VWTFS3w+SK=O!T2fYe< z74$0T)yJk+_r`A!tsq*F4+YzXZU0ag{$K2sah8nFy75HWjo%c3G&Rev(}&;4F(ewW z?BQ;mPieXJI`AnMZ^2SgCDf1C&If>nOq$~i>DYSsoFq&sqF0&+?m4pH1JpH(QO@?aM zLCliL^;=}CinwV6B_(8nadFCu#~^lZNAz0o9GUzbRi6|8%^Z(sV($+LXsw`BcW@9?)L)^>72Rbn7R6$9C| zqo2*v>^1mlJ?tp}PfTgNj!|X9jS_!ayrV)n1MmRw0Pq0t0Pt)954i|h?gl(gqkzX$ z1w4-C=ga{qz41pO(6c9H=xM&EP}SuTFwa?~U2J9$&Af_cHX13~&a4sg6ofU`eg}#N zh^MuP#{lBFi9{L#Jit4(;T`=Dv-tPmog=J78pS)7D&8Ts=9&Ye^fOWj?(BpNi39{%~U>{%~V4qUh$5IGwksle_ za+I-;FQ;3t0qnCcXJ`_6d%gCfpYzH)le431u4z`Z zADPVTs@G*PHP2J6G+nSR%b}=Ye!%qe11jZA9%5bBMJ--yYipKe`_;Eqs<4BI7(fv~ z5mKI!@(ftytHi{P@`sE@agkRUE~D&4{KuI;p&^QoTO_ zApju&Apju&ApoJ{#qNd*5b~=ALKlnn`sxe6%sji+^fb?OYF+xZCnrybCPaR;OwBi2 zy!BZH+ACJ7*LA9>B3P(YudV5olq^K4-bchSS1a{KNtC^@FqlSJKF(w)&x`WBD9?-X zyeQB6xaE0gRJ>ugC~QE|rNX>(9>b(z(p@s?(LzoRL709daY{pmV$-nhF71RSqAAM+ zoz7s_u50yP~YCq@U?Zle&6@%EE9JmY50_vL^HjCVM~9#tmKE- z+^-5176}-5nnUag_I1PFe(enyG`6>2OfwP8XCW0A+!{m-5HUc+01*R33?3q4fNjsP z?O9i8)|*(Q4#Sr27lYRhuf3if{S5UT{qjBKohG*Q+c@g^t~RDItU~Z(tadnm9p^lJ z>3G@1y|bm|x!2pS^?KYC#lW!I0jnLb+5xK_u-ZXowZmQIzzkir9@y%0Oc#uQ`Ly~V T|4lfZe);r&fdl#HS9t*dv-=o4 From e0c674bc9e93a20848f4b3fd796d44d80341299f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 24 May 2021 19:32:56 +0300 Subject: [PATCH 02/46] Move player notification settings into appearance section --- app/src/debug/res/xml/main_settings.xml | 6 ----- .../local/subscription/SubscriptionManager.kt | 24 +++++++++---------- .../settings/AppearanceSettingsFragment.java | 2 +- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/appearance_settings.xml | 7 ++++++ 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/app/src/debug/res/xml/main_settings.xml b/app/src/debug/res/xml/main_settings.xml index 4e812bb1c43..1b1c17e855d 100644 --- a/app/src/debug/res/xml/main_settings.xml +++ b/app/src/debug/res/xml/main_settings.xml @@ -34,12 +34,6 @@ android:title="@string/content" app:iconSpaceReserved="false" /> - - - Completable.fromAction { - entity.notificationMode = mode - subscriptionTable().update(entity) - }.andThen(rememberLastStream(entity)) - } + .flatMapCompletable { entity: SubscriptionEntity -> + Completable.fromAction { + entity.notificationMode = mode + subscriptionTable().update(entity) + }.andThen(rememberLastStream(entity)) + } } fun updateFromInfo(subscriptionId: Long, info: ListInfo) { @@ -110,11 +110,11 @@ class SubscriptionManager(context: Context) { private fun rememberLastStream(subscription: SubscriptionEntity): Completable { return ExtractorHelper.getChannelInfo(subscription.serviceId, subscription.url, false) - .map { channel -> channel.relatedItems.map { stream -> StreamEntity(stream) } } - .flatMapCompletable { entities -> - Completable.fromAction { - database.streamDAO().upsertAll(entities) - } - }.onErrorComplete() + .map { channel -> channel.relatedItems.map { stream -> StreamEntity(stream) } } + .flatMapCompletable { entities -> + Completable.fromAction { + database.streamDAO().upsertAll(entities) + } + }.onErrorComplete() } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java index 1e1b03b4f81..e363469c84d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java @@ -60,7 +60,7 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro @Override public boolean onPreferenceTreeClick(final Preference preference) { - if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) { + if (captionSettingsKey.equals(preference.getKey()) && CAPTIONING_SETTINGS_ACCESSIBLE) { try { startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); } catch (final ActivityNotFoundException e) { diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f9280663b5d..30821be5190 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -569,6 +569,7 @@ Подтверждать очистку очереди Переход от одного плеера к другому может заменить вашу очередь Уведомление + Настроить уведомление о воспроизводимом сейчас потоке Ничего Буферизация Перемешать diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0795d378ce6..7dc69920226 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -582,6 +582,7 @@ Повʼязані елементи Коментарі Сповіщення + Налаштувати повідомлення про відтворюваний наразі потік Не розпізнано URL. Відкрити через іншу програму\? Самододавання в чергу Показувати метадані diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f84cc83b1f1..40dcd17c905 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -147,6 +147,7 @@ Debug Updates Notification + Configure current playing stream notification Playing in background Playing in popup mode Content diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index f0c6f2aa15d..6bc9f338169 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -57,4 +57,11 @@ android:title="@string/tablet_mode_title" app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + + From c95aec9da68525707bbe3d5e96d4336ef2e91bd2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 21 Jun 2021 16:51:23 +0300 Subject: [PATCH 03/46] Fix database test --- .../newpipe/database/AppDatabaseTest.kt | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt new file mode 100644 index 00000000000..b80120074dd --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt @@ -0,0 +1,144 @@ +package org.schabi.newpipe.database + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.schabi.newpipe.extractor.stream.StreamType + +@RunWith(AndroidJUnit4::class) +class AppDatabaseTest { + companion object { + private const val DEFAULT_SERVICE_ID = 0 + private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4" + private const val DEFAULT_TITLE = "Test Title" + private val DEFAULT_TYPE = StreamType.VIDEO_STREAM + private const val DEFAULT_DURATION = 480L + private const val DEFAULT_UPLOADER_NAME = "Uploader Test" + private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg" + + private const val DEFAULT_SECOND_SERVICE_ID = 0 + private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc" + } + + @get:Rule + val testHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory() + ) + + @Test + fun migrateDatabaseFrom2to3() { + val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2) + + databaseInV2.run { + insert( + "streams", SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + // put("uid", null) + put("service_id", DEFAULT_SERVICE_ID) + put("url", DEFAULT_URL) + put("title", DEFAULT_TITLE) + put("stream_type", DEFAULT_TYPE.name) + put("duration", DEFAULT_DURATION) + put("uploader", DEFAULT_UPLOADER_NAME) + put("thumbnail_url", DEFAULT_THUMBNAIL) + } + ) + insert( + "streams", SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + // put("uid", null) + put("service_id", DEFAULT_SECOND_SERVICE_ID) + put("url", DEFAULT_SECOND_URL) + // put("title", null) + // put("stream_type", null) + // put("duration", null) + // put("uploader", null) + // put("thumbnail_url", null) + } + ) + insert( + "streams", SQLiteDatabase.CONFLICT_FAIL, + ContentValues().apply { + // put("uid", null) + put("service_id", DEFAULT_SERVICE_ID) + // put("url", null) + // put("title", null) + // put("stream_type", null) + // put("duration", null) + // put("uploader", null) + // put("thumbnail_url", null) + } + ) + close() + } + + testHelper.runMigrationsAndValidate( + AppDatabase.DATABASE_NAME, Migrations.DB_VER_3, + true, Migrations.MIGRATION_2_3 + ) + + testHelper.runMigrationsAndValidate( + AppDatabase.DATABASE_NAME, Migrations.DB_VER_4, + true, Migrations.MIGRATION_3_4 + ) + + testHelper.runMigrationsAndValidate( + AppDatabase.DATABASE_NAME, Migrations.DB_VER_5, + true, Migrations.MIGRATION_4_5 + ) + + val migratedDatabaseV3 = getMigratedDatabase() + val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst() + + // Only expect 2, the one with the null url will be ignored + assertEquals(2, listFromDB.size) + + val streamFromMigratedDatabase = listFromDB[0] + assertEquals(DEFAULT_SERVICE_ID, streamFromMigratedDatabase.serviceId) + assertEquals(DEFAULT_URL, streamFromMigratedDatabase.url) + assertEquals(DEFAULT_TITLE, streamFromMigratedDatabase.title) + assertEquals(DEFAULT_TYPE, streamFromMigratedDatabase.streamType) + assertEquals(DEFAULT_DURATION, streamFromMigratedDatabase.duration) + assertEquals(DEFAULT_UPLOADER_NAME, streamFromMigratedDatabase.uploader) + assertEquals(DEFAULT_THUMBNAIL, streamFromMigratedDatabase.thumbnailUrl) + assertNull(streamFromMigratedDatabase.viewCount) + assertNull(streamFromMigratedDatabase.textualUploadDate) + assertNull(streamFromMigratedDatabase.uploadDate) + assertNull(streamFromMigratedDatabase.isUploadDateApproximation) + + val secondStreamFromMigratedDatabase = listFromDB[1] + assertEquals(DEFAULT_SECOND_SERVICE_ID, secondStreamFromMigratedDatabase.serviceId) + assertEquals(DEFAULT_SECOND_URL, secondStreamFromMigratedDatabase.url) + assertEquals("", secondStreamFromMigratedDatabase.title) + // Should fallback to VIDEO_STREAM + assertEquals(StreamType.VIDEO_STREAM, secondStreamFromMigratedDatabase.streamType) + assertEquals(0, secondStreamFromMigratedDatabase.duration) + assertEquals("", secondStreamFromMigratedDatabase.uploader) + assertEquals("", secondStreamFromMigratedDatabase.thumbnailUrl) + assertNull(secondStreamFromMigratedDatabase.viewCount) + assertNull(secondStreamFromMigratedDatabase.textualUploadDate) + assertNull(secondStreamFromMigratedDatabase.uploadDate) + assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation) + } + + private fun getMigratedDatabase(): AppDatabase { + val database: AppDatabase = Room.databaseBuilder( + ApplicationProvider.getApplicationContext(), + AppDatabase::class.java, AppDatabase.DATABASE_NAME + ) + .build() + testHelper.closeWhenFinished(database) + return database + } +} From a5b9fe4c352bf7345e52d646a8b24795c1a02ba5 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 20 Jul 2021 13:20:51 +0300 Subject: [PATCH 04/46] Refactor FeedLoadService to use it within the notification worker --- app/build.gradle | 4 +- .../settings/DebugSettingsFragment.java | 9 + app/src/main/java/org/schabi/newpipe/App.java | 19 +- .../java/org/schabi/newpipe/MainActivity.java | 2 +- .../newpipe/database/stream/dao/StreamDAO.kt | 2 +- .../list/channel/ChannelFragment.java | 8 +- .../newpipe/local/feed/FeedDatabaseManager.kt | 4 + .../feed/notifications/NotificationHelper.kt | 135 +++++++ .../feed/notifications/NotificationIcon.kt | 48 +++ .../feed}/notifications/NotificationWorker.kt | 43 ++- .../feed}/notifications/ScheduleOptions.kt | 2 +- .../local/feed/service/FeedLoadManager.kt | 217 ++++++++++++ .../local/feed/service/FeedLoadService.kt | 329 +++--------------- .../local/feed/service/FeedLoadState.kt | 7 + .../local/feed/service/FeedResultsHolder.kt | 19 + .../local/feed/service/FeedUpdateInfo.kt | 34 ++ .../newpipe/notifications/ChannelUpdates.kt | 46 --- .../notifications/NotificationHelper.java | 137 -------- .../notifications/NotificationIcon.java | 60 ---- .../notifications/SubscriptionUpdates.kt | 53 --- .../settings/NotificationsSettingsFragment.kt | 8 +- .../drawable-anydpi-v24/ic_stat_newpipe.xml | 14 - .../res/drawable-hdpi/ic_stat_newpipe.png | Bin 413 -> 0 bytes .../res/drawable-mdpi/ic_stat_newpipe.png | Bin 294 -> 0 bytes .../res/drawable-xhdpi/ic_stat_newpipe.png | Bin 522 -> 0 bytes .../res/drawable-xxhdpi/ic_stat_newpipe.png | Bin 731 -> 0 bytes app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/debug_settings.xml | 5 + 29 files changed, 576 insertions(+), 631 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt rename app/src/main/java/org/schabi/newpipe/{ => local/feed}/notifications/NotificationWorker.kt (64%) rename app/src/main/java/org/schabi/newpipe/{ => local/feed}/notifications/ScheduleOptions.kt (96%) create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/service/FeedResultsHolder.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java delete mode 100644 app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java delete mode 100644 app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt delete mode 100644 app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml delete mode 100644 app/src/main/res/drawable-hdpi/ic_stat_newpipe.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_stat_newpipe.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_newpipe.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_newpipe.png diff --git a/app/build.gradle b/app/build.gradle index 61a0cdc2b11..a9a4e700129 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,8 +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}" + implementation "androidx.work:work-runtime-ktx:${workVersion}" + implementation "androidx.work:work-rxjava3:${workVersion}" /** Third-party libraries **/ // Instance state boilerplate elimination diff --git a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index 55b2c770860..31c8dd40c63 100644 --- a/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -6,6 +6,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.local.feed.notifications.NotificationWorker; import leakcanary.LeakCanary; @@ -20,10 +21,13 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro = findPreference(getString(R.string.show_image_indicators_key)); final Preference crashTheAppPreference = findPreference(getString(R.string.crash_the_app_key)); + final Preference checkNewStreamsPreference + = findPreference(getString(R.string.check_new_streams_key)); assert showMemoryLeaksPreference != null; assert showImageIndicatorsPreference != null; assert crashTheAppPreference != null; + assert checkNewStreamsPreference != null; showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> { startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent()); @@ -38,5 +42,10 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro crashTheAppPreference.setOnPreferenceClickListener(preference -> { throw new RuntimeException(); }); + + checkNewStreamsPreference.setOnPreferenceClickListener(preference -> { + NotificationWorker.runNow(preference.getContext()); + return true; + }); } } diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 67b7b2527df..3c2866c941a 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -1,7 +1,5 @@ package org.schabi.newpipe; -import android.app.NotificationChannel; -import android.app.NotificationManager; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -250,18 +248,15 @@ private void initNotificationChannels() { .setDescription(getString(R.string.hash_channel_description)) .build(); - final NotificationChannel newStreamsChannel = new NotificationChannel( - getString(R.string.streams_notification_channel_id), - getString(R.string.streams_notification_channel_name), - NotificationManager.IMPORTANCE_DEFAULT - ); - newStreamsChannel.setDescription( - getString(R.string.streams_notification_channel_description) - ); - newStreamsChannel.enableVibration(false); + final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat + .Builder(getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) + .setName(getString(R.string.streams_notification_channel_name)) + .setDescription(getString(R.string.streams_notification_channel_description)) + .build(); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.createNotificationChannels( + notificationManager.createNotificationChannelsCompat( Arrays.asList(mainChannel, appUpdateChannel, hashChannel, newStreamsChannel) ); } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 7770d25dcc1..89964acbcee 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -69,7 +69,7 @@ import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; -import org.schabi.newpipe.notifications.NotificationWorker; +import org.schabi.newpipe.local.feed.notifications.NotificationWorker; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.helper.PlayerHolder; diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt index fbb46134dc5..a22fd2bb98d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt @@ -40,7 +40,7 @@ abstract class StreamDAO : BasicDAO { 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 + internal abstract fun exists(serviceId: Int, url: String): Boolean @Query( """ 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 754036dfde7..941035b0768 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 @@ -44,7 +44,7 @@ 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.local.feed.notifications.NotificationHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; @@ -252,13 +252,13 @@ private void monitorSubscription(final ChannelInfo info) { .map(List::isEmpty) .distinctUntilChanged() .observeOn(AndroidSchedulers.mainThread()) - .subscribe((Boolean isEmpty) -> updateSubscribeButton(!isEmpty), onError)); + .subscribe(isEmpty -> updateSubscribeButton(!isEmpty), onError)); disposables.add(observable .map(List::isEmpty) - .filter(x -> NotificationHelper.isNewStreamsNotificationsEnabled(requireContext())) .distinctUntilChanged() - .skip(1) + .skip(1) // channel has just been opened + .filter(x -> NotificationHelper.isNewStreamsNotificationsEnabled(requireContext())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(isEmpty -> { if (!isEmpty) { diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index ff7c2848e6a..c4a9f6af940 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -72,6 +72,10 @@ class FeedDatabaseManager(context: Context) { fun markAsOutdated(subscriptionId: Long) = feedTable .setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null)) + fun isStreamExist(stream: StreamInfoItem): Boolean { + return streamTable.exists(stream.serviceId, stream.url) + } + fun upsertAll( subscriptionId: Long, items: List, diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt new file mode 100644 index 00000000000..ec5cb790f2a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -0,0 +1,135 @@ +package org.schabi.newpipe.local.feed.notifications + +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import androidx.preference.PreferenceManager +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.local.feed.service.FeedUpdateInfo +import org.schabi.newpipe.util.NavigationHelper + +class NotificationHelper(val context: Context) { + + private val manager = context.getSystemService( + Context.NOTIFICATION_SERVICE + ) as NotificationManager + + fun notify(data: FeedUpdateInfo): Completable { + val newStreams: List = data.newStreams + val summary = context.resources.getQuantityString( + R.plurals.new_streams, newStreams.size, newStreams.size + ) + val builder = NotificationCompat.Builder( + context, + context.getString(R.string.streams_notification_channel_id) + ) + .setContentTitle( + context.getString( + R.string.notification_title_pattern, + data.name, + summary + ) + ) + .setContentText( + data.listInfo.relatedItems.joinToString( + context.getString(R.string.enumeration_comma) + ) { x -> x.name } + ) + .setNumber(newStreams.size) + .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSmallIcon(R.drawable.ic_newpipe_triangle_white) + .setLargeIcon( + BitmapFactory.decodeResource( + context.resources, + R.drawable.ic_newpipe_triangle_white + ) + ) + .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) + .setColorized(true) + .setAutoCancel(true) + .setCategory(NotificationCompat.CATEGORY_SOCIAL) + val style = NotificationCompat.InboxStyle() + for (stream in newStreams) { + style.addLine(stream.name) + } + style.setSummaryText(summary) + style.setBigContentTitle(data.name) + builder.setStyle(style) + builder.setContentIntent( + PendingIntent.getActivity( + context, + data.pseudoId, + NavigationHelper.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + 0 + ) + ) + return Single.create(NotificationIcon(context, data.avatarUrl)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess { icon -> + builder.setLargeIcon(icon) + } + .ignoreElement() + .onErrorComplete() + .doOnComplete { manager.notify(data.pseudoId, builder.build()) } + } + + companion object { + /** + * Check whether notifications are not disabled by user via system settings. + * + * @param context Context + * @return true if notifications are allowed, false otherwise + */ + fun isNotificationsEnabledNative(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channelId = context.getString(R.string.streams_notification_channel_id) + val manager = context.getSystemService( + Context.NOTIFICATION_SERVICE + ) as NotificationManager + val channel = manager.getNotificationChannel(channelId) + channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE + } else { + NotificationManagerCompat.from(context).areNotificationsEnabled() + } + } + + @JvmStatic + fun isNewStreamsNotificationsEnabled(context: Context): Boolean { + return ( + PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.enable_streams_notifications), false) && + isNotificationsEnabledNative(context) + ) + } + + fun openNativeSettingsScreen(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channelId = context.getString(R.string.streams_notification_channel_id) + val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) + context.startActivity(intent) + } else { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.parse("package:" + context.packageName) + context.startActivity(intent) + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt new file mode 100644 index 00000000000..eea39dfd3a3 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt @@ -0,0 +1,48 @@ +package org.schabi.newpipe.local.feed.notifications + +import android.app.ActivityManager +import android.content.Context +import android.graphics.Bitmap +import android.view.View +import com.nostra13.universalimageloader.core.ImageLoader +import com.nostra13.universalimageloader.core.assist.FailReason +import com.nostra13.universalimageloader.core.assist.ImageSize +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener +import io.reactivex.rxjava3.core.SingleEmitter +import io.reactivex.rxjava3.core.SingleOnSubscribe + +internal class NotificationIcon( + context: Context, + private val url: String +) : SingleOnSubscribe { + + private val size = getIconSize(context) + + override fun subscribe(emitter: SingleEmitter) { + ImageLoader.getInstance().loadImage( + url, + ImageSize(size, size), + object : SimpleImageLoadingListener() { + override fun onLoadingFailed(imageUri: String?, view: View?, failReason: FailReason) { + emitter.onError(failReason.cause) + } + + override fun onLoadingComplete(imageUri: String?, view: View?, loadedImage: Bitmap) { + emitter.onSuccess(loadedImage) + } + } + ) + } + + private companion object { + + fun getIconSize(context: Context): Int { + val activityManager = context.getSystemService( + Context.ACTIVITY_SERVICE + ) as ActivityManager? + val size1 = activityManager?.launcherLargeIconSize ?: 0 + val size2 = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size) + return maxOf(size2, size1) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt similarity index 64% rename from app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt rename to app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 24dbc82e078..896735983db 100644 --- a/app/src/main/java/org/schabi/newpipe/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.notifications +package org.schabi.newpipe.local.feed.notifications import android.content.Context import androidx.preference.PreferenceManager @@ -6,14 +6,16 @@ import androidx.work.BackoffPolicy import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequest -import androidx.work.RxWorker import androidx.work.WorkManager import androidx.work.WorkerParameters -import io.reactivex.BackpressureStrategy -import io.reactivex.Flowable -import io.reactivex.Single +import androidx.work.rxjava3.RxWorker +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.R +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.local.feed.service.FeedLoadManager import java.util.concurrent.TimeUnit class NotificationWorker( @@ -24,20 +26,27 @@ class NotificationWorker( private val notificationHelper by lazy { NotificationHelper(appContext) } + private val feedLoadManager = FeedLoadManager(appContext) - override fun createWork() = if (isEnabled(applicationContext)) { - Flowable.create( - SubscriptionUpdates(applicationContext), - BackpressureStrategy.BUFFER - ).doOnNext { notificationHelper.notify(it) } - .toList() - .map { Result.success() } + override fun createWork(): Single = if (isEnabled(applicationContext)) { + feedLoadManager.startLoading() + .map { feed -> + feed.mapNotNull { x -> + x.value?.takeIf { + it.notificationMode == NotificationMode.ENABLED_DEFAULT && + it.newStreamsCount > 0 + } + } + } + .flatMapObservable { Observable.fromIterable(it) } + .flatMapCompletable { x -> notificationHelper.notify(x) } + .toSingleDefault(Result.success()) .onErrorReturnItem(Result.failure()) } else Single.just(Result.success()) companion object { - private const val TAG = "notifications" + private const val TAG = "streams_notifications" private fun isEnabled(context: Context): Boolean { return PreferenceManager.getDefaultSharedPreferences(context) @@ -78,5 +87,13 @@ class NotificationWorker( @JvmStatic fun schedule(context: Context) = schedule(context, ScheduleOptions.from(context)) + + @JvmStatic + fun runNow(context: Context) { + val request = OneTimeWorkRequestBuilder() + .addTag(TAG) + .build() + WorkManager.getInstance(context).enqueue(request) + } } } diff --git a/app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt similarity index 96% rename from app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt rename to app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt index b0617b30360..30e8d551570 100644 --- a/app/src/main/java/org/schabi/newpipe/notifications/ScheduleOptions.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.notifications +package org.schabi.newpipe.local.feed.notifications import android.content.Context import androidx.preference.PreferenceManager diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt new file mode 100644 index 00000000000..79c4b747b48 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -0,0 +1,217 @@ +package org.schabi.newpipe.local.feed.service + +import android.content.Context +import androidx.preference.PreferenceManager +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Notification +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.functions.Consumer +import io.reactivex.rxjava3.processors.PublishProcessor +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.R +import org.schabi.newpipe.database.feed.model.FeedGroupEntity +import org.schabi.newpipe.extractor.ListInfo +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.local.feed.FeedDatabaseManager +import org.schabi.newpipe.local.subscription.SubscriptionManager +import org.schabi.newpipe.util.ExtractorHelper +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class FeedLoadManager(private val context: Context) { + + private val subscriptionManager = SubscriptionManager(context) + private val feedDatabaseManager = FeedDatabaseManager(context) + + private val notificationUpdater = PublishProcessor.create() + private val currentProgress = AtomicInteger(-1) + private val maxProgress = AtomicInteger(-1) + private val cancelSignal = AtomicBoolean() + private val feedResultsHolder = FeedResultsHolder() + + val notification: Flowable = notificationUpdater.map { description -> + FeedLoadState(description, maxProgress.get(), currentProgress.get()) + } + + fun startLoading( + groupId: Long = FeedGroupEntity.GROUP_ALL_ID + ): Single>> { + val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val useFeedExtractor = defaultSharedPreferences.getBoolean( + context.getString(R.string.feed_use_dedicated_fetch_method_key), + false + ) + val thresholdOutdatedSeconds = defaultSharedPreferences.getString( + context.getString(R.string.feed_update_threshold_key), + context.getString(R.string.feed_update_threshold_default_value) + )!!.toInt() + + val outdatedThreshold = OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong()) + + val subscriptions = when (groupId) { + FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold) + else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) + } + + return subscriptions + .take(1) + + .doOnNext { + currentProgress.set(0) + maxProgress.set(it.size) + } + .filter { it.isNotEmpty() } + + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { + notificationUpdater.onNext("") + broadcastProgress() + } + + .observeOn(Schedulers.io()) + .flatMap { Flowable.fromIterable(it) } + .takeWhile { !cancelSignal.get() } + + .parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2) + .runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2) + .filter { !cancelSignal.get() } + + .map { subscriptionEntity -> + var error: Throwable? = null + try { + val listInfo = if (useFeedExtractor) { + ExtractorHelper + .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url) + .onErrorReturn { + error = it // store error, otherwise wrapped into RuntimeException + throw it + } + .blockingGet() + } else { + ExtractorHelper + .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true) + .onErrorReturn { + error = it // store error, otherwise wrapped into RuntimeException + throw it + } + .blockingGet() + } as ListInfo + + return@map Notification.createOnNext(FeedUpdateInfo(subscriptionEntity, listInfo)) + } catch (e: Throwable) { + if (error == null) { + // do this to prevent blockingGet() from wrapping into RuntimeException + error = e + } + + val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}" + val wrapper = FeedLoadService.RequestException(subscriptionEntity.uid, request, error!!) + return@map Notification.createOnError(wrapper) + } + } + .sequential() + + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext(NotificationConsumer()) + + .observeOn(Schedulers.io()) + .buffer(BUFFER_COUNT_BEFORE_INSERT) + .doOnNext(DatabaseConsumer()) + + .subscribeOn(Schedulers.io()) + .toList() + .flatMap { x -> postProcessFeed().toSingleDefault(x.flatten()) } + } + + fun cancel() { + cancelSignal.set(true) + } + + private fun broadcastProgress() { + FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(currentProgress.get(), maxProgress.get())) + } + + private fun postProcessFeed() = Completable.fromRunnable { + FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message)) + feedDatabaseManager.removeOrphansOrOlderStreams() + + FeedEventManager.postEvent(FeedEventManager.Event.SuccessResultEvent(feedResultsHolder.itemsErrors)) + }.doOnSubscribe { + currentProgress.set(-1) + maxProgress.set(-1) + + notificationUpdater.onNext(context.getString(R.string.feed_processing_message)) + FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message)) + }.subscribeOn(Schedulers.io()) + + private inner class NotificationConsumer : Consumer> { + override fun accept(item: Notification) { + currentProgress.incrementAndGet() + notificationUpdater.onNext(item.value?.name.orEmpty()) + + broadcastProgress() + } + } + + private inner class DatabaseConsumer : Consumer>> { + + override fun accept(list: List>) { + feedDatabaseManager.database().runInTransaction { + for (notification in list) { + when { + notification.isOnNext -> { + val subscriptionId = notification.value.uid + val info = notification.value.listInfo + + notification.value.newStreamsCount = countNewStreams(info.relatedItems) + feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems) + subscriptionManager.updateFromInfo(subscriptionId, info) + + if (info.errors.isNotEmpty()) { + feedResultsHolder.addErrors(FeedLoadService.RequestException.wrapList(subscriptionId, info)) + feedDatabaseManager.markAsOutdated(subscriptionId) + } + } + notification.isOnError -> { + val error = notification.error + feedResultsHolder.addError(error) + + if (error is FeedLoadService.RequestException) { + feedDatabaseManager.markAsOutdated(error.subscriptionId) + } + } + } + } + } + } + + private fun countNewStreams(list: List): Int { + var count = 0 + for (item in list) { + if (feedDatabaseManager.isStreamExist(item)) { + return count + } else { + count++ + } + } + return 0 + } + } + + private companion object { + + /** + * How many extractions will be running in parallel. + */ + const val PARALLEL_EXTRACTIONS = 6 + + /** + * Number of items to buffer to mass-insert in the database. + */ + const val BUFFER_COUNT_BEFORE_INSERT = 20 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index 98ff5914d4e..ea181d3d9b3 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -31,36 +31,19 @@ import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.ServiceCompat -import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Notification -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.functions.Consumer +import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.functions.Function -import io.reactivex.rxjava3.processors.PublishProcessor -import io.reactivex.rxjava3.schedulers.Schedulers -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription import org.schabi.newpipe.App import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem -import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent -import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent -import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent -import org.schabi.newpipe.local.subscription.SubscriptionManager -import org.schabi.newpipe.util.ExtractorHelper -import java.time.OffsetDateTime -import java.time.ZoneOffset import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicInteger class FeedLoadService : Service() { companion object { @@ -73,27 +56,13 @@ class FeedLoadService : Service() { */ private const val NOTIFICATION_SAMPLING_PERIOD = 1500 - /** - * How many extractions will be running in parallel. - */ - private const val PARALLEL_EXTRACTIONS = 6 - - /** - * Number of items to buffer to mass-insert in the database. - */ - private const val BUFFER_COUNT_BEFORE_INSERT = 20 - const val EXTRA_GROUP_ID: String = "FeedLoadService.EXTRA_GROUP_ID" } - private var loadingSubscription: Subscription? = null - private lateinit var subscriptionManager: SubscriptionManager - - private lateinit var feedDatabaseManager: FeedDatabaseManager - private lateinit var feedResultsHolder: ResultsHolder + private var loadingDisposable: Disposable? = null + private var notificationDisposable: Disposable? = null - private var disposables = CompositeDisposable() - private var notificationUpdater = PublishProcessor.create() + private lateinit var feedLoadManager: FeedLoadManager // ///////////////////////////////////////////////////////////////////////// // Lifecycle @@ -101,8 +70,7 @@ class FeedLoadService : Service() { override fun onCreate() { super.onCreate() - subscriptionManager = SubscriptionManager(this) - feedDatabaseManager = FeedDatabaseManager(this) + feedLoadManager = FeedLoadManager(this) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -114,40 +82,45 @@ class FeedLoadService : Service() { ) } - if (intent == null || loadingSubscription != null) { + if (intent == null || loadingDisposable != null) { return START_NOT_STICKY } setupNotification() setupBroadcastReceiver() - val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID) - val useFeedExtractor = defaultSharedPreferences - .getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false) - - val thresholdOutdatedSecondsString = defaultSharedPreferences - .getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value)) - val thresholdOutdatedSeconds = thresholdOutdatedSecondsString!!.toInt() - - startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds) - + loadingDisposable = feedLoadManager.startLoading(groupId) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { + startForeground(NOTIFICATION_ID, notificationBuilder.build()) + } + .subscribe { _, error -> + // There seems to be a bug in the kotlin plugin as it tells you when + // building that this can't be null: + // "Condition 'error != null' is always 'true'" + // However it can indeed be null + // The suppression may be removed in further versions + @Suppress("SENSELESS_COMPARISON") + if (error != null) { + Log.e(TAG, "Error while storing result", error) + handleError(error) + return@subscribe + } + stopService() + } return START_NOT_STICKY } private fun disposeAll() { unregisterReceiver(broadcastReceiver) - - loadingSubscription?.cancel() - loadingSubscription = null - - disposables.dispose() + loadingDisposable?.dispose() + notificationDisposable?.dispose() } private fun stopService() { disposeAll() ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) - notificationManager.cancel(NOTIFICATION_ID) stopSelf() } @@ -171,190 +144,6 @@ class FeedLoadService : Service() { } } - private fun startLoading(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, useFeedExtractor: Boolean, thresholdOutdatedSeconds: Int) { - feedResultsHolder = ResultsHolder() - - val outdatedThreshold = OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong()) - - val subscriptions = when (groupId) { - FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold) - else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) - } - - subscriptions - .take(1) - - .doOnNext { - currentProgress.set(0) - maxProgress.set(it.size) - } - .filter { it.isNotEmpty() } - - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { - startForeground(NOTIFICATION_ID, notificationBuilder.build()) - updateNotificationProgress(null) - broadcastProgress() - } - - .observeOn(Schedulers.io()) - .flatMap { Flowable.fromIterable(it) } - .takeWhile { !cancelSignal.get() } - - .parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2) - .runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2) - .filter { !cancelSignal.get() } - - .map { subscriptionEntity -> - var error: Throwable? = null - try { - val listInfo = if (useFeedExtractor) { - ExtractorHelper - .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url) - .onErrorReturn { - error = it // store error, otherwise wrapped into RuntimeException - throw it - } - .blockingGet() - } else { - ExtractorHelper - .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true) - .onErrorReturn { - error = it // store error, otherwise wrapped into RuntimeException - throw it - } - .blockingGet() - } as ListInfo - - return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo)) - } catch (e: Throwable) { - if (error == null) { - // do this to prevent blockingGet() from wrapping into RuntimeException - error = e - } - - val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}" - val wrapper = RequestException(subscriptionEntity.uid, request, error!!) - return@map Notification.createOnError>>(wrapper) - } - } - .sequential() - - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(notificationsConsumer) - - .observeOn(Schedulers.io()) - .buffer(BUFFER_COUNT_BEFORE_INSERT) - .doOnNext(databaseConsumer) - - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(resultSubscriber) - } - - private fun broadcastProgress() { - postEvent(ProgressEvent(currentProgress.get(), maxProgress.get())) - } - - private val resultSubscriber - get() = object : Subscriber>>>> { - - override fun onSubscribe(s: Subscription) { - loadingSubscription = s - s.request(java.lang.Long.MAX_VALUE) - } - - override fun onNext(notification: List>>>) { - if (DEBUG) Log.v(TAG, "onNext() → $notification") - } - - override fun onError(error: Throwable) { - handleError(error) - } - - override fun onComplete() { - if (maxProgress.get() == 0) { - postEvent(FeedEventManager.Event.IdleEvent) - stopService() - - return - } - - currentProgress.set(-1) - maxProgress.set(-1) - - notificationUpdater.onNext(getString(R.string.feed_processing_message)) - postEvent(ProgressEvent(R.string.feed_processing_message)) - - disposables.add( - Single - .fromCallable { - feedResultsHolder.ready() - - postEvent(ProgressEvent(R.string.feed_processing_message)) - feedDatabaseManager.removeOrphansOrOlderStreams() - - postEvent(SuccessResultEvent(feedResultsHolder.itemsErrors)) - true - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { _, throwable -> - // There seems to be a bug in the kotlin plugin as it tells you when - // building that this can't be null: - // "Condition 'throwable != null' is always 'true'" - // However it can indeed be null - // The suppression may be removed in further versions - @Suppress("SENSELESS_COMPARISON") - if (throwable != null) { - Log.e(TAG, "Error while storing result", throwable) - handleError(throwable) - return@subscribe - } - stopService() - } - ) - } - } - - private val databaseConsumer: Consumer>>>> - get() = Consumer { - feedDatabaseManager.database().runInTransaction { - for (notification in it) { - - if (notification.isOnNext) { - val subscriptionId = notification.value!!.first - val info = notification.value!!.second - - feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems) - subscriptionManager.updateFromInfo(subscriptionId, info) - - if (info.errors.isNotEmpty()) { - feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info)) - feedDatabaseManager.markAsOutdated(subscriptionId) - } - } else if (notification.isOnError) { - val error = notification.error!! - feedResultsHolder.addError(error) - - if (error is RequestException) { - feedDatabaseManager.markAsOutdated(error.subscriptionId) - } - } - } - } - } - - private val notificationsConsumer: Consumer>>> - get() = Consumer { onItemCompleted(it.value?.second?.name) } - - private fun onItemCompleted(updateDescription: String?) { - currentProgress.incrementAndGet() - notificationUpdater.onNext(updateDescription ?: "") - - broadcastProgress() - } - // ///////////////////////////////////////////////////////////////////////// // Notification // ///////////////////////////////////////////////////////////////////////// @@ -362,9 +151,6 @@ class FeedLoadService : Service() { private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationBuilder: NotificationCompat.Builder - private var currentProgress = AtomicInteger(-1) - private var maxProgress = AtomicInteger(-1) - private fun createNotification(): NotificationCompat.Builder { val cancelActionIntent = PendingIntent.getBroadcast( this, @@ -384,33 +170,36 @@ class FeedLoadService : Service() { notificationManager = NotificationManagerCompat.from(this) notificationBuilder = createNotification() - val throttleAfterFirstEmission = Function { flow: Flowable -> + val throttleAfterFirstEmission = Function { flow: Flowable -> flow.take(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS)) } - disposables.add( - notificationUpdater - .publish(throttleAfterFirstEmission) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::updateNotificationProgress) - ) + notificationDisposable = feedLoadManager.notification + .publish(throttleAfterFirstEmission) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate { notificationManager.cancel(NOTIFICATION_ID) } + .subscribe(this::updateNotificationProgress) } - private fun updateNotificationProgress(updateDescription: String?) { - notificationBuilder.setProgress(maxProgress.get(), currentProgress.get(), maxProgress.get() == -1) + private fun updateNotificationProgress(state: FeedLoadState) { + notificationBuilder.setProgress(state.maxProgress, state.currentProgress, state.maxProgress == -1) - if (maxProgress.get() == -1) { + if (state.maxProgress == -1) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) notificationBuilder.setContentInfo(null) - if (!updateDescription.isNullOrEmpty()) notificationBuilder.setContentText(updateDescription) - notificationBuilder.setContentText(updateDescription) + if (state.updateDescription.isNotEmpty()) notificationBuilder.setContentText(state.updateDescription) + notificationBuilder.setContentText(state.updateDescription) } else { - val progressText = this.currentProgress.toString() + "/" + maxProgress + val progressText = state.currentProgress.toString() + "/" + state.maxProgress if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!updateDescription.isNullOrEmpty()) notificationBuilder.setContentText("$updateDescription ($progressText)") + if (state.updateDescription.isNotEmpty()) { + notificationBuilder.setContentText("${state.updateDescription} ($progressText)") + } } else { notificationBuilder.setContentInfo(progressText) - if (!updateDescription.isNullOrEmpty()) notificationBuilder.setContentText(updateDescription) + if (state.updateDescription.isNotEmpty()) { + notificationBuilder.setContentText(state.updateDescription) + } } } @@ -422,13 +211,12 @@ class FeedLoadService : Service() { // ///////////////////////////////////////////////////////////////////////// private lateinit var broadcastReceiver: BroadcastReceiver - private val cancelSignal = AtomicBoolean() private fun setupBroadcastReceiver() { broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == ACTION_CANCEL) { - cancelSignal.set(true) + feedLoadManager.cancel() } } } @@ -443,29 +231,4 @@ class FeedLoadService : Service() { postEvent(ErrorResultEvent(error)) stopService() } - - // ///////////////////////////////////////////////////////////////////////// - // Results Holder - // ///////////////////////////////////////////////////////////////////////// - - class ResultsHolder { - /** - * List of errors that may have happen during loading. - */ - internal lateinit var itemsErrors: List - - private val itemsErrorsHolder: MutableList = ArrayList() - - fun addError(error: Throwable) { - itemsErrorsHolder.add(error) - } - - fun addErrors(errors: List) { - itemsErrorsHolder.addAll(errors) - } - - fun ready() { - itemsErrors = itemsErrorsHolder.toList() - } - } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt new file mode 100644 index 00000000000..703f593adee --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt @@ -0,0 +1,7 @@ +package org.schabi.newpipe.local.feed.service + +data class FeedLoadState( + val updateDescription: String, + val maxProgress: Int, + val currentProgress: Int, +) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedResultsHolder.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedResultsHolder.kt new file mode 100644 index 00000000000..729f2c009c2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedResultsHolder.kt @@ -0,0 +1,19 @@ +package org.schabi.newpipe.local.feed.service + +class FeedResultsHolder { + /** + * List of errors that may have happen during loading. + */ + val itemsErrors: List + get() = itemsErrorsHolder + + private val itemsErrorsHolder: MutableList = ArrayList() + + fun addError(error: Throwable) { + itemsErrorsHolder.add(error) + } + + fun addErrors(errors: List) { + itemsErrorsHolder.addAll(errors) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt new file mode 100644 index 00000000000..a86578e1524 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt @@ -0,0 +1,34 @@ +package org.schabi.newpipe.local.feed.service + +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.database.subscription.SubscriptionEntity +import org.schabi.newpipe.extractor.ListInfo +import org.schabi.newpipe.extractor.stream.StreamInfoItem + +data class FeedUpdateInfo( + val uid: Long, + @NotificationMode + val notificationMode: Int, + val name: String, + val avatarUrl: String, + val listInfo: ListInfo +) { + constructor(subscription: SubscriptionEntity, listInfo: ListInfo) : this( + uid = subscription.uid, + notificationMode = subscription.notificationMode, + name = subscription.name, + avatarUrl = subscription.avatarUrl, + listInfo = listInfo + ) + + /** + * Integer id, can be used as notification id, etc. + */ + val pseudoId: Int + get() = listInfo.url.hashCode() + + var newStreamsCount: Int = 0 + + val newStreams: List + get() = listInfo.relatedItems.take(newStreamsCount) +} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt b/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt deleted file mode 100644 index 9a3b2cbf33f..00000000000 --- a/app/src/main/java/org/schabi/newpipe/notifications/ChannelUpdates.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.schabi.newpipe.notifications - -import android.content.Context -import android.content.Intent -import org.schabi.newpipe.R -import org.schabi.newpipe.extractor.channel.ChannelInfo -import org.schabi.newpipe.extractor.stream.StreamInfoItem -import org.schabi.newpipe.util.NavigationHelper - -data class ChannelUpdates( - val serviceId: Int, - val url: String, - val avatarUrl: String, - val name: String, - val streams: List -) { - - val id = url.hashCode() - - val isNotEmpty: Boolean - get() = streams.isNotEmpty() - - val size = streams.size - - fun getText(context: Context): String { - val separator = context.resources.getString(R.string.enumeration_comma) + " " - return streams.joinToString(separator) { it.name } - } - - fun createOpenChannelIntent(context: Context?): Intent { - return NavigationHelper.getChannelIntent(context, serviceId, url) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - companion object { - fun from(channel: ChannelInfo, streams: List): ChannelUpdates { - return ChannelUpdates( - channel.serviceId, - channel.url, - channel.avatarUrl, - channel.name, - streams - ) - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java b/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java deleted file mode 100644 index 6207cd613ef..00000000000 --- a/app/src/main/java/org/schabi/newpipe/notifications/NotificationHelper.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.schabi.newpipe.notifications; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Build; -import android.provider.Settings; - -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; - -import org.schabi.newpipe.BuildConfig; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.disposables.CompositeDisposable; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public final class NotificationHelper { - - private final Context context; - private final NotificationManager manager; - private final CompositeDisposable disposable; - - public NotificationHelper(final Context context) { - this.context = context; - this.disposable = new CompositeDisposable(); - this.manager = (NotificationManager) context.getSystemService( - Context.NOTIFICATION_SERVICE - ); - } - - public Context getContext() { - return context; - } - - /** - * Check whether notifications are not disabled by user via system settings. - * - * @param context Context - * @return true if notifications are allowed, false otherwise - */ - public static boolean isNotificationsEnabledNative(final Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final String channelId = context.getString(R.string.streams_notification_channel_id); - final NotificationManager manager = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); - if (manager != null) { - final NotificationChannel channel = manager.getNotificationChannel(channelId); - return channel != null - && channel.getImportance() != NotificationManager.IMPORTANCE_NONE; - } else { - return false; - } - } else { - return NotificationManagerCompat.from(context).areNotificationsEnabled(); - } - } - - public static boolean isNewStreamsNotificationsEnabled(@NonNull final Context context) { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.enable_streams_notifications), false) - && isNotificationsEnabledNative(context); - } - - public static void openNativeSettingsScreen(final Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final String channelId = context.getString(R.string.streams_notification_channel_id); - final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()) - .putExtra(Settings.EXTRA_CHANNEL_ID, channelId); - context.startActivity(intent); - } else { - final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.parse("package:" + context.getPackageName())); - context.startActivity(intent); - } - } - - public void notify(final ChannelUpdates data) { - final String summary = context.getResources().getQuantityString( - R.plurals.new_streams, data.getSize(), data.getSize() - ); - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, - context.getString(R.string.streams_notification_channel_id)) - .setContentTitle( - context.getString(R.string.notification_title_pattern, - data.getName(), - summary) - ) - .setContentText(data.getText(context)) - .setNumber(data.getSize()) - .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(R.drawable.ic_stat_newpipe) - .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_newpipe_triangle_white)) - .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) - .setColorized(true) - .setAutoCancel(true) - .setCategory(NotificationCompat.CATEGORY_SOCIAL); - final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - for (final StreamInfoItem stream : data.getStreams()) { - style.addLine(stream.getName()); - } - style.setSummaryText(summary); - style.setBigContentTitle(data.getName()); - builder.setStyle(style); - builder.setContentIntent(PendingIntent.getActivity( - context, - data.getId(), - data.createOpenChannelIntent(context), - 0 - )); - - disposable.add( - Single.create(new NotificationIcon(context, data.getAvatarUrl())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doAfterTerminate(() -> manager.notify(data.getId(), builder.build())) - .subscribe(builder::setLargeIcon, throwable -> { - if (BuildConfig.DEBUG) { - throwable.printStackTrace(); - } - }) - ); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java b/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java deleted file mode 100644 index fc59b55f05f..00000000000 --- a/app/src/main/java/org/schabi/newpipe/notifications/NotificationIcon.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.schabi.newpipe.notifications; - -import android.app.ActivityManager; -import android.content.Context; -import android.graphics.Bitmap; -import android.view.View; - -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.assist.ImageSize; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; - -import io.reactivex.rxjava3.annotations.NonNull; -import io.reactivex.rxjava3.core.SingleEmitter; -import io.reactivex.rxjava3.core.SingleOnSubscribe; - -final class NotificationIcon implements SingleOnSubscribe { - - private final String url; - private final int size; - - NotificationIcon(final Context context, final String url) { - this.url = url; - this.size = getIconSize(context); - } - - @Override - public void subscribe(@NonNull final SingleEmitter emitter) throws Throwable { - ImageLoader.getInstance().loadImage( - url, - new ImageSize(size, size), - new SimpleImageLoadingListener() { - - @Override - public void onLoadingFailed(final String imageUri, - final View view, - final FailReason failReason) { - emitter.onError(failReason.getCause()); - } - - @Override - public void onLoadingComplete(final String imageUri, - final View view, - final Bitmap loadedImage) { - emitter.onSuccess(loadedImage); - } - } - ); - } - - private static int getIconSize(final Context context) { - final ActivityManager activityManager = (ActivityManager) context.getSystemService( - Context.ACTIVITY_SERVICE - ); - final int size2 = activityManager != null ? activityManager.getLauncherLargeIconSize() : 0; - final int size1 = context.getResources() - .getDimensionPixelSize(android.R.dimen.app_icon_size); - return Math.max(size2, size1); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt b/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt deleted file mode 100644 index 6f7c3881b5a..00000000000 --- a/app/src/main/java/org/schabi/newpipe/notifications/SubscriptionUpdates.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.schabi.newpipe.notifications - -import android.content.Context -import io.reactivex.FlowableEmitter -import io.reactivex.FlowableOnSubscribe -import org.schabi.newpipe.NewPipeDatabase -import org.schabi.newpipe.database.stream.model.StreamEntity -import org.schabi.newpipe.database.subscription.NotificationMode -import org.schabi.newpipe.extractor.stream.StreamInfoItem -import org.schabi.newpipe.local.subscription.SubscriptionManager -import org.schabi.newpipe.util.ExtractorHelper - -class SubscriptionUpdates(context: Context) : FlowableOnSubscribe { - - private val subscriptionManager = SubscriptionManager(context) - private val streamTable = NewPipeDatabase.getInstance(context).streamDAO() - - override fun subscribe(emitter: FlowableEmitter) { - try { - val subscriptions = subscriptionManager.subscriptions().blockingFirst() - for (subscription in subscriptions) { - if (subscription.notificationMode != NotificationMode.DISABLED) { - val channel = ExtractorHelper.getChannelInfo( - subscription.serviceId, - subscription.url, true - ).blockingGet() - val updates = ChannelUpdates.from(channel, filterStreams(channel.relatedItems)) - if (updates.isNotEmpty) { - emitter.onNext(updates) - // prevent duplicated notifications - streamTable.upsertAll(updates.streams.map { StreamEntity(it) }) - } - } - } - emitter.onComplete() - } catch (e: Exception) { - emitter.onError(e) - } - } - - private fun filterStreams(list: List<*>): List { - val streams = ArrayList(list.size) - for (o in list) { - if (o is StreamInfoItem) { - if (streamTable.exists(o.serviceId.toLong(), o.url)) { - break - } - streams.add(o) - } - } - return streams - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 62a819e64b7..01a3ca6ebd0 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -14,10 +14,10 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.error.ErrorActivity import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.UserAction +import org.schabi.newpipe.local.feed.notifications.NotificationHelper +import org.schabi.newpipe.local.feed.notifications.NotificationWorker +import org.schabi.newpipe.local.feed.notifications.ScheduleOptions import org.schabi.newpipe.local.subscription.SubscriptionManager -import org.schabi.newpipe.notifications.NotificationHelper -import org.schabi.newpipe.notifications.NotificationWorker -import org.schabi.newpipe.notifications.ScheduleOptions class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferenceChangeListener { @@ -47,7 +47,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onResume() { super.onResume() - val enabled = NotificationHelper.isNotificationsEnabledNative(context) + val enabled = NotificationHelper.isNotificationsEnabledNative(requireContext()) preferenceScreen.isEnabled = enabled if (!enabled) { if (notificationWarningSnackbar == null) { diff --git a/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml b/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml deleted file mode 100644 index e95f5b4aca1..00000000000 --- a/app/src/main/res/drawable-anydpi-v24/ic_stat_newpipe.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable-hdpi/ic_stat_newpipe.png b/app/src/main/res/drawable-hdpi/ic_stat_newpipe.png deleted file mode 100644 index dc08d67ff89167b42ab133658c4a42d127aca7a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 413 zcmV;O0b>4%P)QuB#Tw7U`%%oFC2aV032bBx#yy1-$Tux)hO3M%RC0Ua^Em zD?$3XW-RMBuQXQ}vl0~Ar%RtEX?D?LB`CD-u>N3Inm4QExyFkl00000NkvXX Hu0mjfv&*yy diff --git a/app/src/main/res/drawable-mdpi/ic_stat_newpipe.png b/app/src/main/res/drawable-mdpi/ic_stat_newpipe.png deleted file mode 100644 index 4af6c74df80b88fb6208df08da68e02ffbc98f94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmV+>0oneEP)igP)*Znnf&%5_L%L)@FOvc!@U8Y?i0hU39R)ID9 zFVA2CM70VyxddIB1)OYyTFnBKH!uqdwF)@71B03coa}=}%>tB9umDQ53OIQHBOphs zfRkg;B2i!-95E%n!4fE!BoH&-4}*KA$P*X?d13_2m?AI--kBm7phJv+Yiu3Z@vuHN zK(!bF*EK!hiYbuO8;{I0dcJPGlT`M=%OGD7>CE&{)kbnXhI+bCDfttAm%|ZsEbepMu+H96xPL(E?&$!iI=(v9wbBXQjrG_ z)*%tXgHhnYiXbWv5eWumNKl&F@56WC1Gm}3J3BV7zXzV%?hHIIGv9pgZV(Vc2qA>5 zkjv#R!way4HS!0#kz3(8tY?vb2Xg#@18^71C?g|n z?NjY7JZ|b9s0@6p@@Op+xj&V zKSYl?(6a13+xaU)jDcRjBY`zy2;^QWFRuAZ<$oIJ4BQ!5X00~u8Hj-xDC7y;$3Uz3 z08a($ojifuCVQ9rK~z9i1zNxVEG!Txxk+UT1JxutsU(CDLI^1ie*xq30c~$9$P@qo N002ovPDHLkV1g)7OiKU& diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 42d2233c870..efb91cbc5e9 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -188,6 +188,7 @@ disable_media_tunneling_key crash_the_app_key show_image_indicators_key + check_new_streams theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 40dcd17c905..d8906200f2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -481,6 +481,7 @@ Show image indicators Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory Crash the app + Run check for new streams Import Import from diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 22abebcae94..206401c2368 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -49,6 +49,11 @@ android:title="@string/show_image_indicators_title" app:iconSpaceReserved="false" /> + + Date: Sat, 4 Sep 2021 13:26:50 +0300 Subject: [PATCH 05/46] Migrate NotificationIcon to Picasso --- .../5.json | 719 ++++++++++++++++++ .../feed/notifications/NotificationIcon.kt | 44 +- .../schabi/newpipe/util/PicassoHelper.java | 9 +- 3 files changed, 752 insertions(+), 20 deletions(-) create mode 100644 app/schemas/org.schabi.newpipe.database.AppDatabase/5.json diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/5.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/5.json new file mode 100644 index 00000000000..9a1c62995a9 --- /dev/null +++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/5.json @@ -0,0 +1,719 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "096731b513bb71dd44517639f4a2c1e3", + "entities": [ + { + "tableName": "subscriptions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatar_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subscriberCount", + "columnName": "subscriber_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "notificationMode", + "columnName": "notification_mode", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_subscriptions_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "creationDate", + "columnName": "creation_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "search", + "columnName": "search", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_search_history_search", + "unique": false, + "columnNames": [ + "search" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "streams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "streamType", + "columnName": "stream_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploader_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "viewCount", + "columnName": "view_count", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "textualUploadDate", + "columnName": "textual_upload_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "upload_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isUploadDateApproximation", + "columnName": "is_upload_date_approximation", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_streams_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "stream_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accessDate", + "columnName": "access_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "repeatCount", + "columnName": "repeat_count", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "stream_id", + "access_date" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_stream_history_stream_id", + "unique": false, + "columnNames": [ + "stream_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)" + } + ], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "stream_state", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "progressMillis", + "columnName": "progress_time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "stream_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_playlists_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "playlist_stream_join", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "playlistUid", + "columnName": "playlist_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "streamUid", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "join_index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "playlist_id", + "join_index" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_playlist_stream_join_playlist_id_join_index", + "unique": true, + "columnNames": [ + "playlist_id", + "join_index" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)" + }, + { + "name": "index_playlist_stream_join_stream_id", + "unique": false, + "columnNames": [ + "stream_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)" + } + ], + "foreignKeys": [ + { + "table": "playlists", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "playlist_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "remote_playlists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "service_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "streamCount", + "columnName": "stream_count", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_remote_playlists_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_remote_playlists_service_id_url", + "unique": true, + "columnNames": [ + "service_id", + "url" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "feed", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "streamId", + "columnName": "stream_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "stream_id", + "subscription_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_feed_subscription_id", + "unique": false, + "columnNames": [ + "subscription_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)" + } + ], + "foreignKeys": [ + { + "table": "streams", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "stream_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "feed_group", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortOrder", + "columnName": "sort_order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_feed_group_sort_order", + "unique": false, + "columnNames": [ + "sort_order" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "feed_group_subscription_join", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "feedGroupId", + "columnName": "group_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "group_id", + "subscription_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_feed_group_subscription_join_subscription_id", + "unique": false, + "columnNames": [ + "subscription_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)" + } + ], + "foreignKeys": [ + { + "table": "feed_group", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "group_id" + ], + "referencedColumns": [ + "uid" + ] + }, + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "feed_last_updated", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "subscriptionId", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "last_updated", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "subscription_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "subscriptions", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "subscription_id" + ], + "referencedColumns": [ + "uid" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '096731b513bb71dd44517639f4a2c1e3')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt index eea39dfd3a3..1073945d474 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt @@ -3,35 +3,43 @@ package org.schabi.newpipe.local.feed.notifications import android.app.ActivityManager import android.content.Context import android.graphics.Bitmap -import android.view.View -import com.nostra13.universalimageloader.core.ImageLoader -import com.nostra13.universalimageloader.core.assist.FailReason -import com.nostra13.universalimageloader.core.assist.ImageSize -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener +import android.graphics.drawable.Drawable +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.core.SingleOnSubscribe +import org.schabi.newpipe.util.PicassoHelper internal class NotificationIcon( context: Context, - private val url: String + private val url: String, ) : SingleOnSubscribe { private val size = getIconSize(context) override fun subscribe(emitter: SingleEmitter) { - ImageLoader.getInstance().loadImage( - url, - ImageSize(size, size), - object : SimpleImageLoadingListener() { - override fun onLoadingFailed(imageUri: String?, view: View?, failReason: FailReason) { - emitter.onError(failReason.cause) - } - - override fun onLoadingComplete(imageUri: String?, view: View?, loadedImage: Bitmap) { - emitter.onSuccess(loadedImage) - } + val target = SingleEmitterTarget(emitter) + PicassoHelper.loadThumbnail(url) + .resize(size, size) + .centerCrop() + .into(target) + emitter.setCancellable { + PicassoHelper.cancelRequest(target) + } + } + + private class SingleEmitterTarget(private val emitter: SingleEmitter) : Target { + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom?) { + if (!emitter.isDisposed) { + emitter.onSuccess(bitmap) } - ) + } + + override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) { + emitter.tryOnError(e) + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable?) = Unit } private companion object { diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index e15ecd277f0..efacd1fc225 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; @@ -9,6 +11,7 @@ import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; +import com.squareup.picasso.Target; import com.squareup.picasso.Transformation; import org.schabi.newpipe.R; @@ -19,8 +22,6 @@ import okhttp3.OkHttpClient; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; - public final class PicassoHelper { public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY @@ -78,6 +79,10 @@ public static void cancelTag(final Object tag) { picassoInstance.cancelTag(tag); } + public static void cancelRequest(final Target target) { + picassoInstance.cancelRequest(target); + } + public static void setIndicatorsEnabled(final boolean enabled) { picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging } From 111dc4963dab4de87ae08587b1dd44b6b986ab74 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 4 Sep 2021 14:53:11 +0300 Subject: [PATCH 06/46] Ignore feed update threshold when run from NotificationWorker --- .../feed/notifications/NotificationWorker.kt | 22 +++++++++++++++++-- .../local/feed/service/FeedLoadManager.kt | 19 +++++++++++----- .../local/feed/service/FeedLoadService.kt | 2 +- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 896735983db..ee16a403d93 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -1,10 +1,12 @@ package org.schabi.newpipe.local.feed.notifications import android.content.Context +import androidx.core.app.NotificationCompat import androidx.preference.PreferenceManager import androidx.work.BackoffPolicy import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ForegroundInfo import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequest @@ -16,11 +18,12 @@ import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.R import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.local.feed.service.FeedLoadManager +import org.schabi.newpipe.local.feed.service.FeedLoadService import java.util.concurrent.TimeUnit class NotificationWorker( appContext: Context, - workerParams: WorkerParameters + workerParams: WorkerParameters, ) : RxWorker(appContext, workerParams) { private val notificationHelper by lazy { @@ -29,7 +32,7 @@ class NotificationWorker( private val feedLoadManager = FeedLoadManager(appContext) override fun createWork(): Single = if (isEnabled(applicationContext)) { - feedLoadManager.startLoading() + feedLoadManager.startLoading(ignoreOutdatedThreshold = true) .map { feed -> feed.mapNotNull { x -> x.value?.takeIf { @@ -38,12 +41,27 @@ class NotificationWorker( } } } + .doOnSubscribe { setForegroundAsync(createForegroundInfo()) } .flatMapObservable { Observable.fromIterable(it) } .flatMapCompletable { x -> notificationHelper.notify(x) } .toSingleDefault(Result.success()) .onErrorReturnItem(Result.failure()) } else Single.just(Result.success()) + private fun createForegroundInfo(): ForegroundInfo { + val notification = NotificationCompat.Builder( + applicationContext, + applicationContext.getString(R.string.notification_channel_id) + ).setOngoing(true) + .setProgress(-1, -1, true) + .setSmallIcon(R.drawable.ic_newpipe_triangle_white) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setContentTitle(applicationContext.getString(R.string.feed_notification_loading)) + .build() + return ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification) + } + companion object { private const val TAG = "streams_notifications" diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 79c4b747b48..bea699999ea 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -38,19 +38,26 @@ class FeedLoadManager(private val context: Context) { } fun startLoading( - groupId: Long = FeedGroupEntity.GROUP_ALL_ID + groupId: Long = FeedGroupEntity.GROUP_ALL_ID, + ignoreOutdatedThreshold: Boolean = false, ): Single>> { val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val useFeedExtractor = defaultSharedPreferences.getBoolean( context.getString(R.string.feed_use_dedicated_fetch_method_key), false ) - val thresholdOutdatedSeconds = defaultSharedPreferences.getString( - context.getString(R.string.feed_update_threshold_key), - context.getString(R.string.feed_update_threshold_default_value) - )!!.toInt() - val outdatedThreshold = OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong()) + val outdatedThreshold = if (ignoreOutdatedThreshold) { + OffsetDateTime.now(ZoneOffset.UTC) + } else { + val thresholdOutdatedSeconds = ( + defaultSharedPreferences.getString( + context.getString(R.string.feed_update_threshold_key), + context.getString(R.string.feed_update_threshold_default_value) + ) ?: context.getString(R.string.feed_update_threshold_default_value) + ).toInt() + OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong()) + } val subscriptions = when (groupId) { FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index ea181d3d9b3..0d5d904e837 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -48,7 +48,7 @@ import java.util.concurrent.TimeUnit class FeedLoadService : Service() { companion object { private val TAG = FeedLoadService::class.java.simpleName - private const val NOTIFICATION_ID = 7293450 + const val NOTIFICATION_ID = 7293450 private const val ACTION_CANCEL = App.PACKAGE_NAME + ".local.feed.service.FeedLoadService.CANCEL" /** From 9d249904bdcd944bdae006f9e7d3a7ab6dec1285 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 4 Sep 2021 15:28:11 +0300 Subject: [PATCH 07/46] Toggle all subscriptions notification mode --- .../settings/NotificationsSettingsFragment.kt | 7 +- .../NotificationsChannelsConfigFragment.java | 84 ------------- .../NotificationsChannelsConfigFragment.kt | 112 ++++++++++++++++++ .../NotificationsConfigAdapter.kt | 2 + app/src/main/res/drawable/ic_list_check.xml | 10 ++ .../res/menu/menu_notifications_channels.xml | 10 ++ app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 137 insertions(+), 90 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt create mode 100644 app/src/main/res/drawable/ic_list_check.xml create mode 100644 app/src/main/res/menu/menu_notifications_channels.xml diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 01a3ca6ebd0..50fb954507a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -87,12 +87,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen } private fun updateSubscriptions(subscriptions: List) { - var notified = 0 - for (subscription in subscriptions) { - if (subscription.notificationMode != NotificationMode.DISABLED) { - notified++ - } - } + val notified = subscriptions.count { it.notificationMode != NotificationMode.DISABLED } val preference = findPreference(getString(R.string.streams_notifications_channels_key)) if (preference != null) { preference.summary = preference.context.getString( diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java deleted file mode 100644 index 7aa0826e559..00000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.schabi.newpipe.settings.notifications; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.database.subscription.NotificationMode; -import org.schabi.newpipe.local.subscription.SubscriptionManager; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.CompositeDisposable; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public final class NotificationsChannelsConfigFragment extends Fragment - implements NotificationsConfigAdapter.ModeToggleListener { - - private NotificationsConfigAdapter adapter; - @Nullable - private Disposable loader = null; - private CompositeDisposable updaters; - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - adapter = new NotificationsConfigAdapter(this); - updaters = new CompositeDisposable(); - } - - @Nullable - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_channels_notifications, container, false); - } - - @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final RecyclerView recyclerView = view.findViewById(R.id.recycler_view); - recyclerView.setAdapter(adapter); - } - - @Override - public void onActivityCreated(@Nullable final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (loader != null) { - loader.dispose(); - } - loader = new SubscriptionManager(requireContext()) - .subscriptions() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(adapter::update); - } - - @Override - public void onDestroy() { - if (loader != null) { - loader.dispose(); - } - updaters.dispose(); - super.onDestroy(); - } - - @Override - public void onModeToggle(final int position, @NotificationMode final int mode) { - final NotificationsConfigAdapter.SubscriptionItem subscription = adapter.getItem(position); - updaters.add( - new SubscriptionManager(requireContext()) - .updateNotificationMode(subscription.getServiceId(), - subscription.getUrl(), mode) - .subscribeOn(Schedulers.io()) - .subscribe() - ); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt new file mode 100644 index 00000000000..eb94fb84311 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt @@ -0,0 +1,112 @@ +package org.schabi.newpipe.settings.notifications + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.R +import org.schabi.newpipe.database.subscription.NotificationMode +import org.schabi.newpipe.local.subscription.SubscriptionManager +import org.schabi.newpipe.settings.notifications.NotificationsConfigAdapter.ModeToggleListener + +class NotificationsChannelsConfigFragment : Fragment(), ModeToggleListener { + + private lateinit var updaters: CompositeDisposable + private var loader: Disposable? = null + private var adapter: NotificationsConfigAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + updaters = CompositeDisposable() + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = inflater.inflate(R.layout.fragment_channels_notifications, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val recyclerView: RecyclerView = view.findViewById(R.id.recycler_view) + adapter = NotificationsConfigAdapter(this) + recyclerView.adapter = adapter + loader?.dispose() + loader = SubscriptionManager(requireContext()) + .subscriptions() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { newData -> adapter?.update(newData) } + } + + override fun onDestroyView() { + loader?.dispose() + loader = null + super.onDestroyView() + } + + override fun onDestroy() { + updaters.dispose() + super.onDestroy() + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_notifications_channels, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_toggle_all -> { + toggleAll() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onModeToggle(position: Int, @NotificationMode mode: Int) { + val subscription = adapter?.getItem(position) ?: return + updaters.add( + SubscriptionManager(requireContext()) + .updateNotificationMode( + subscription.serviceId, + subscription.url, + mode + ) + .subscribeOn(Schedulers.io()) + .subscribe() + ) + } + + private fun toggleAll() { + val subscriptions = adapter?.getCurrentList() ?: return + val mode = subscriptions.firstOrNull()?.notificationMode ?: return + val newMode = when (mode) { + NotificationMode.DISABLED -> NotificationMode.ENABLED_DEFAULT + else -> NotificationMode.DISABLED + } + val subscriptionManager = SubscriptionManager(requireContext()) + updaters.add( + CompositeDisposable( + subscriptions.map { item -> + subscriptionManager.updateNotificationMode( + serviceId = item.serviceId, + url = item.url, + mode = newMode + ).subscribeOn(Schedulers.io()) + .subscribe() + } + ) + ) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt index 44d2256afa5..1689747e257 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt @@ -40,6 +40,8 @@ class NotificationsConfigAdapter( return differ.currentList[position].id } + fun getCurrentList(): List = differ.currentList + fun update(newData: List) { differ.submitList( newData.map { diff --git a/app/src/main/res/drawable/ic_list_check.xml b/app/src/main/res/drawable/ic_list_check.xml new file mode 100644 index 00000000000..37d806044ef --- /dev/null +++ b/app/src/main/res/drawable/ic_list_check.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_notifications_channels.xml b/app/src/main/res/menu/menu_notifications_channels.xml new file mode 100644 index 00000000000..79b9cd7c10b --- /dev/null +++ b/app/src/main/res/menu/menu_notifications_channels.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 30821be5190..0e29edcc484 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -705,4 +705,5 @@ Уведомления отключены Уведомлять Вы подписались на канал + Переключить все \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8906200f2e..4da2ac60cee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -730,4 +730,5 @@ , %s • %s %d/%d + Toggle all \ No newline at end of file From 55c51ad49d46e77b49382cd59cea81936d932f18 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Mon, 11 Oct 2021 23:20:52 +0200 Subject: [PATCH 08/46] Rename isStreamExist -> doesStreamExist --- .../java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt | 2 +- .../org/schabi/newpipe/local/feed/service/FeedLoadManager.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index c4a9f6af940..99629322523 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -72,7 +72,7 @@ class FeedDatabaseManager(context: Context) { fun markAsOutdated(subscriptionId: Long) = feedTable .setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null)) - fun isStreamExist(stream: StreamInfoItem): Boolean { + fun doesStreamExist(stream: StreamInfoItem): Boolean { return streamTable.exists(stream.serviceId, stream.url) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index bea699999ea..6a44935c519 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -199,7 +199,7 @@ class FeedLoadManager(private val context: Context) { private fun countNewStreams(list: List): Int { var count = 0 for (item in list) { - if (feedDatabaseManager.isStreamExist(item)) { + if (feedDatabaseManager.doesStreamExist(item)) { return count } else { count++ From 7c6140b33100cb0cf877b83bc018fdab8018b863 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 15 Oct 2021 19:57:31 +0200 Subject: [PATCH 09/46] Remove unused code --- .../org/schabi/newpipe/database/AppDatabaseTest.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt index b80120074dd..158994cddf3 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt @@ -44,7 +44,6 @@ class AppDatabaseTest { insert( "streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { - // put("uid", null) put("service_id", DEFAULT_SERVICE_ID) put("url", DEFAULT_URL) put("title", DEFAULT_TITLE) @@ -57,27 +56,14 @@ class AppDatabaseTest { insert( "streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { - // put("uid", null) put("service_id", DEFAULT_SECOND_SERVICE_ID) put("url", DEFAULT_SECOND_URL) - // put("title", null) - // put("stream_type", null) - // put("duration", null) - // put("uploader", null) - // put("thumbnail_url", null) } ) insert( "streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { - // put("uid", null) put("service_id", DEFAULT_SERVICE_ID) - // put("url", null) - // put("title", null) - // put("stream_type", null) - // put("duration", null) - // put("uploader", null) - // put("thumbnail_url", null) } ) close() From 64a7978c7f44fc41647ad97e16a66043d72479a4 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 15 Oct 2021 19:59:06 +0200 Subject: [PATCH 10/46] Rename NotificationMode.ENABLED_DEFAULT to NotificationMode.ENABLED --- .../newpipe/database/subscription/NotificationMode.java | 4 ++-- .../newpipe/fragments/list/channel/ChannelFragment.java | 2 +- .../newpipe/local/feed/notifications/NotificationWorker.kt | 2 +- .../notifications/NotificationsChannelsConfigFragment.kt | 2 +- .../settings/notifications/NotificationsConfigAdapter.kt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) 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 index d817032eebd..07e0eb7d358 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java @@ -4,11 +4,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -@IntDef({NotificationMode.DISABLED, NotificationMode.ENABLED_DEFAULT}) +@IntDef({NotificationMode.DISABLED, NotificationMode.ENABLED}) @Retention(RetentionPolicy.SOURCE) public @interface NotificationMode { int DISABLED = 0; - int ENABLED_DEFAULT = 1; + int ENABLED = 1; //other values reserved for the future } 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 dcda6db0b71..8ed6b5149d6 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 @@ -414,7 +414,7 @@ private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) } private void setNotify(final boolean isEnabled) { - final int mode = isEnabled ? NotificationMode.ENABLED_DEFAULT : NotificationMode.DISABLED; + final int mode = isEnabled ? NotificationMode.ENABLED : NotificationMode.DISABLED; disposables.add( subscriptionManager.updateNotificationMode(currentInfo.getServiceId(), currentInfo.getUrl(), mode) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index ee16a403d93..82e923d9476 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -36,7 +36,7 @@ class NotificationWorker( .map { feed -> feed.mapNotNull { x -> x.value?.takeIf { - it.notificationMode == NotificationMode.ENABLED_DEFAULT && + it.notificationMode == NotificationMode.ENABLED && it.newStreamsCount > 0 } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt index eb94fb84311..9c47972370b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt @@ -92,7 +92,7 @@ class NotificationsChannelsConfigFragment : Fragment(), ModeToggleListener { val subscriptions = adapter?.getCurrentList() ?: return val mode = subscriptions.firstOrNull()?.notificationMode ?: return val newMode = when (mode) { - NotificationMode.DISABLED -> NotificationMode.ENABLED_DEFAULT + NotificationMode.DISABLED -> NotificationMode.ENABLED else -> NotificationMode.DISABLED } val subscriptionManager = SubscriptionManager(requireContext()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt index 1689747e257..2fa91616696 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt @@ -85,7 +85,7 @@ class NotificationsConfigAdapter( val mode = if (checkedTextView.isChecked) { NotificationMode.DISABLED } else { - NotificationMode.ENABLED_DEFAULT + NotificationMode.ENABLED } listener.onModeToggle(adapterPosition, mode) } From 4f7cdcce5568dc8973a42d837e49d9b1d966f2b0 Mon Sep 17 00:00:00 2001 From: Tobi Date: Fri, 15 Oct 2021 20:22:12 +0200 Subject: [PATCH 11/46] Update app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java Co-authored-by: litetex <40789489+litetex@users.noreply.github.com> --- .../newpipe/fragments/list/channel/ChannelFragment.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 8ed6b5149d6..752b05d5a9d 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 @@ -400,17 +400,16 @@ private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) if (menuNotifyButton == null) { return; } - if (subscription == null) { - menuNotifyButton.setVisible(false); - } else { + if (subscription != null) { menuNotifyButton.setEnabled( NotificationHelper.isNewStreamsNotificationsEnabled(requireContext()) ); menuNotifyButton.setChecked( - subscription.getNotificationMode() != NotificationMode.DISABLED + subscription.getNotificationMode() == NotificationMode.ENABLED ); - menuNotifyButton.setVisible(true); } + + menuNotifyButton.setVisible(subscription != null); } private void setNotify(final boolean isEnabled) { From 793ff1a72847e6ecc313d912eb19368dd1f57f2f Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 15 Oct 2021 20:57:49 +0200 Subject: [PATCH 12/46] Add a few comments and rename a few methods --- .../fragments/list/channel/ChannelFragment.java | 9 ++++++--- .../feed/notifications/NotificationHelper.kt | 16 +++++++++++++--- .../feed/notifications/NotificationWorker.kt | 6 +++++- .../local/feed/notifications/ScheduleOptions.kt | 4 ++++ 4 files changed, 28 insertions(+), 7 deletions(-) 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 752b05d5a9d..5e2e24fab2a 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 @@ -259,7 +259,7 @@ private void monitorSubscription(final ChannelInfo info) { .map(List::isEmpty) .distinctUntilChanged() .skip(1) // channel has just been opened - .filter(x -> NotificationHelper.isNewStreamsNotificationsEnabled(requireContext())) + .filter(x -> NotificationHelper.areNewStreamsNotificationsEnabled(requireContext())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(isEmpty -> { if (!isEmpty) { @@ -402,13 +402,13 @@ private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) } if (subscription != null) { menuNotifyButton.setEnabled( - NotificationHelper.isNewStreamsNotificationsEnabled(requireContext()) + NotificationHelper.areNewStreamsNotificationsEnabled(requireContext()) ); menuNotifyButton.setChecked( subscription.getNotificationMode() == NotificationMode.ENABLED ); } - + menuNotifyButton.setVisible(subscription != null); } @@ -423,6 +423,9 @@ private void setNotify(final boolean isEnabled) { ); } + /** + * Show a snackbar with the option to enable notifications on new streams for this channel. + */ private void showNotifySnackbar() { Snackbar.make(itemsList, R.string.you_successfully_subscribed, Snackbar.LENGTH_LONG) .setAction(R.string.get_notified, v -> setNotify(true)) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index ec5cb790f2a..fac3b8f72ca 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -21,13 +21,20 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo import org.schabi.newpipe.util.NavigationHelper +/** + * Helper for everything related to show notifications about new streams to the user. + */ class NotificationHelper(val context: Context) { private val manager = context.getSystemService( Context.NOTIFICATION_SERVICE ) as NotificationManager - fun notify(data: FeedUpdateInfo): Completable { + /** + * Show a notification about new streams from a single channel. + * Opening the notification will open the corresponding channel page. + */ + fun displayNewStreamsNotification(data: FeedUpdateInfo): Completable { val newStreams: List = data.newStreams val summary = context.resources.getQuantityString( R.plurals.new_streams, newStreams.size, newStreams.size @@ -69,11 +76,14 @@ class NotificationHelper(val context: Context) { style.setSummaryText(summary) style.setBigContentTitle(data.name) builder.setStyle(style) + // open the channel page when clicking on the notification builder.setContentIntent( PendingIntent.getActivity( context, data.pseudoId, - NavigationHelper.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) + NavigationHelper.getChannelIntent( + context, data.listInfo.serviceId, data.listInfo.url + ) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0 ) @@ -110,7 +120,7 @@ class NotificationHelper(val context: Context) { } @JvmStatic - fun isNewStreamsNotificationsEnabled(context: Context): Boolean { + fun areNewStreamsNotificationsEnabled(context: Context): Boolean { return ( PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.enable_streams_notifications), false) && diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 82e923d9476..daae52fdd0f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -21,6 +21,10 @@ import org.schabi.newpipe.local.feed.service.FeedLoadManager import org.schabi.newpipe.local.feed.service.FeedLoadService import java.util.concurrent.TimeUnit +/* + * Worker which checks for new streams of subscribed channels + * in intervals which can be set by the user in the settings. + */ class NotificationWorker( appContext: Context, workerParams: WorkerParameters, @@ -43,7 +47,7 @@ class NotificationWorker( } .doOnSubscribe { setForegroundAsync(createForegroundInfo()) } .flatMapObservable { Observable.fromIterable(it) } - .flatMapCompletable { x -> notificationHelper.notify(x) } + .flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) } .toSingleDefault(Result.success()) .onErrorReturnItem(Result.failure()) } else Single.just(Result.success()) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt index 30e8d551570..0dbc1539559 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt @@ -5,6 +5,10 @@ import androidx.preference.PreferenceManager import org.schabi.newpipe.R import java.util.concurrent.TimeUnit +/** + * Information for the Scheduler which checks for new streams. + * See [NotificationWorker] + */ data class ScheduleOptions( val interval: Long, val isRequireNonMeteredNetwork: Boolean From 7d4c7718aa64dd00c2a64b9d72df764aad9977fd Mon Sep 17 00:00:00 2001 From: TobiGr Date: Mon, 18 Oct 2021 13:11:44 +0200 Subject: [PATCH 13/46] comments & rename --- .../feed/notifications/NotificationIcon.kt | 4 ++++ ...ter.kt => NotificationModeConfigAdapter.kt} | 18 +++++++++++++----- ...nt.kt => NotificationModeConfigFragment.kt} | 17 ++++++++++++----- .../main/res/xml/notifications_settings.xml | 2 +- 4 files changed, 30 insertions(+), 11 deletions(-) rename app/src/main/java/org/schabi/newpipe/settings/notifications/{NotificationsConfigAdapter.kt => NotificationModeConfigAdapter.kt} (85%) rename app/src/main/java/org/schabi/newpipe/settings/notifications/{NotificationsChannelsConfigFragment.kt => NotificationModeConfigFragment.kt} (84%) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt index 1073945d474..0fb6877a662 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt @@ -10,6 +10,10 @@ import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.core.SingleOnSubscribe import org.schabi.newpipe.util.PicassoHelper +/** + * Helper class to handle loading and resizing of icons + * which are used going to be used in notifications. + */ internal class NotificationIcon( context: Context, private val url: String, diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt similarity index 85% rename from app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt rename to app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt index 2fa91616696..156877b4e93 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsConfigAdapter.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt @@ -10,9 +10,14 @@ import androidx.recyclerview.widget.RecyclerView import org.schabi.newpipe.R import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionEntity -import org.schabi.newpipe.settings.notifications.NotificationsConfigAdapter.SubscriptionHolder - -class NotificationsConfigAdapter( +import org.schabi.newpipe.settings.notifications.NotificationModeConfigAdapter.SubscriptionHolder + +/** + * This [RecyclerView.Adapter] is used in the [NotificationModeConfigFragment]. + * The adapter holds all subscribed channels and their [NotificationMode]s + * and provides the needed data structures and methods for this task. + */ +class NotificationModeConfigAdapter( private val listener: ModeToggleListener ) : RecyclerView.Adapter() { @@ -87,7 +92,7 @@ class NotificationsConfigAdapter( } else { NotificationMode.ENABLED } - listener.onModeToggle(adapterPosition, mode) + listener.onModeChange(adapterPosition, mode) } } @@ -111,6 +116,9 @@ class NotificationsConfigAdapter( } interface ModeToggleListener { - fun onModeToggle(position: Int, @NotificationMode mode: Int) + /** + * Triggered when the UI representation of a notification mode is changed. + */ + fun onModeChange(position: Int, @NotificationMode mode: Int) } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt similarity index 84% rename from app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt rename to app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt index 9c47972370b..9021fd68c3f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationsChannelsConfigFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt @@ -16,13 +16,18 @@ import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.R import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.local.subscription.SubscriptionManager -import org.schabi.newpipe.settings.notifications.NotificationsConfigAdapter.ModeToggleListener +import org.schabi.newpipe.settings.notifications.NotificationModeConfigAdapter.ModeToggleListener -class NotificationsChannelsConfigFragment : Fragment(), ModeToggleListener { +/** + * [NotificationModeConfigFragment] is a settings fragment + * which allows changing the [NotificationMode] of all subscribed channels. + * The [NotificationMode] can either be changed one by one or toggled for all channels. + */ +class NotificationModeConfigFragment : Fragment(), ModeToggleListener { private lateinit var updaters: CompositeDisposable private var loader: Disposable? = null - private var adapter: NotificationsConfigAdapter? = null + private var adapter: NotificationModeConfigAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,7 +44,7 @@ class NotificationsChannelsConfigFragment : Fragment(), ModeToggleListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val recyclerView: RecyclerView = view.findViewById(R.id.recycler_view) - adapter = NotificationsConfigAdapter(this) + adapter = NotificationModeConfigAdapter(this) recyclerView.adapter = adapter loader?.dispose() loader = SubscriptionManager(requireContext()) @@ -74,7 +79,9 @@ class NotificationsChannelsConfigFragment : Fragment(), ModeToggleListener { } } - override fun onModeToggle(position: Int, @NotificationMode mode: Int) { + override fun onModeChange(position: Int, @NotificationMode mode: Int) { + // Notification mode has been changed via the UI. + // Now change it in the database. val subscription = adapter?.getItem(position) ?: return updaters.add( SubscriptionManager(requireContext()) diff --git a/app/src/main/res/xml/notifications_settings.xml b/app/src/main/res/xml/notifications_settings.xml index 4390dc48c99..60d0428f7f6 100644 --- a/app/src/main/res/xml/notifications_settings.xml +++ b/app/src/main/res/xml/notifications_settings.xml @@ -32,7 +32,7 @@ app:iconSpaceReserved="false" /> Date: Fri, 22 Oct 2021 21:24:22 +0200 Subject: [PATCH 14/46] Fix check wether the app's notifications are disabled via system settings Add comments Rename a few methods --- .../feed/notifications/NotificationHelper.kt | 30 +++++++++++++++---- .../feed/notifications/NotificationWorker.kt | 2 +- .../settings/NotificationsSettingsFragment.kt | 6 ++-- .../schabi/newpipe/util/NavigationHelper.java | 8 ++--- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index fac3b8f72ca..fa26be37aa6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -101,39 +101,57 @@ class NotificationHelper(val context: Context) { companion object { /** - * Check whether notifications are not disabled by user via system settings. + * Check whether notifications are enabled on the device. + * Users can disable them via the system settings for a single app. + * If this is the case, the app cannot create any notifications + * and display them to the user. + *
+ * On Android 26 and above, notification channels are used by NewPipe. + * These can be configured by the user, too. + * The notification channel for new streams is also checked by this method. * * @param context Context - * @return true if notifications are allowed, false otherwise + * @return true if notifications are allowed and can be displayed; + * false otherwise */ - fun isNotificationsEnabledNative(context: Context): Boolean { + fun areNotificationsEnabledOnDevice(context: Context): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = context.getString(R.string.streams_notification_channel_id) val manager = context.getSystemService( Context.NOTIFICATION_SERVICE ) as NotificationManager + val enabled = manager.areNotificationsEnabled() val channel = manager.getNotificationChannel(channelId) - channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE + val importance = channel?.importance + enabled && channel != null && importance != NotificationManager.IMPORTANCE_NONE } else { NotificationManagerCompat.from(context).areNotificationsEnabled() } } @JvmStatic + /** + * Whether the user enabled the notifications for new streams in the app settings. + */ fun areNewStreamsNotificationsEnabled(context: Context): Boolean { return ( PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.enable_streams_notifications), false) && - isNotificationsEnabledNative(context) + areNotificationsEnabledOnDevice(context) ) } - fun openNativeSettingsScreen(context: Context) { + /** + * Open the system's notification settings for NewPipe on Android O (API 26) and later. + * Open the system's app settings for NewPipe on previous Android versions. + */ + fun openNewPipeSystemNotificationSettings(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = context.getString(R.string.streams_notification_channel_id) val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } else { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index daae52fdd0f..afdeee7f4d0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -75,7 +75,7 @@ class NotificationWorker( .getBoolean( context.getString(R.string.enable_streams_notifications), false - ) && NotificationHelper.isNotificationsEnabledNative(context) + ) && NotificationHelper.areNotificationsEnabledOnDevice(context) } fun schedule(context: Context, options: ScheduleOptions, force: Boolean = false) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 50fb954507a..938b7ff8dd4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -47,7 +47,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onResume() { super.onResume() - val enabled = NotificationHelper.isNotificationsEnabledNative(requireContext()) + val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) preferenceScreen.isEnabled = enabled if (!enabled) { if (notificationWarningSnackbar == null) { @@ -56,8 +56,8 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen R.string.notifications_disabled, Snackbar.LENGTH_INDEFINITE ).apply { - setAction(R.string.settings) { v -> - NotificationHelper.openNativeSettingsScreen(v.context) + setAction(R.string.settings) { + activity?.let { NotificationHelper.openNewPipeSystemNotificationSettings(it) } } setActionTextColor(Color.YELLOW) addCallback(object : Snackbar.Callback() { diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 80267a9dd6b..f70002409e9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp; + import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; @@ -18,6 +20,8 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import com.jakewharton.processphoenix.ProcessPhoenix; + import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; @@ -57,10 +61,6 @@ import java.util.ArrayList; -import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp; - -import com.jakewharton.processphoenix.ProcessPhoenix; - public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; From 77aaa15082f82b6f4d2d102b9ee873b9cb7ddb97 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Mon, 25 Oct 2021 13:59:49 +0200 Subject: [PATCH 15/46] Fix toggling the system's settings for app notification Do not open the setting for a specific notification channel (Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS), but the settings for all notifications by the app (Settings.ACTION_APP_NOTIFICATION_SETTINGS) --- .../newpipe/local/feed/notifications/NotificationHelper.kt | 6 ++---- .../newpipe/settings/NotificationsSettingsFragment.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index fa26be37aa6..87101abcd8d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -142,15 +142,13 @@ class NotificationHelper(val context: Context) { } /** - * Open the system's notification settings for NewPipe on Android O (API 26) and later. + * Open the system's notification settings for NewPipe on Android Oreo (API 26) and later. * Open the system's app settings for NewPipe on previous Android versions. */ fun openNewPipeSystemNotificationSettings(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelId = context.getString(R.string.streams_notification_channel_id) - val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) - .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } else { diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 938b7ff8dd4..e1f7ed2c21e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -57,7 +57,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen Snackbar.LENGTH_INDEFINITE ).apply { setAction(R.string.settings) { - activity?.let { NotificationHelper.openNewPipeSystemNotificationSettings(it) } + NotificationHelper.openNewPipeSystemNotificationSettings(it.context) } setActionTextColor(Color.YELLOW) addCallback(object : Snackbar.Callback() { From 2d2b96420f867b6481908247e5c6499cf1b4762f Mon Sep 17 00:00:00 2001 From: TobiGr Date: Mon, 25 Oct 2021 15:06:15 +0200 Subject: [PATCH 16/46] Add comments and improve code formatting --- .../list/channel/ChannelFragment.java | 8 +-- .../feed/notifications/NotificationHelper.kt | 7 ++- .../local/feed/service/FeedLoadManager.kt | 53 ++++++++++++++++--- 3 files changed, 54 insertions(+), 14 deletions(-) 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 5e2e24fab2a..fe0122ea577 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 @@ -413,10 +413,12 @@ private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) } private void setNotify(final boolean isEnabled) { - final int mode = isEnabled ? NotificationMode.ENABLED : NotificationMode.DISABLED; disposables.add( - subscriptionManager.updateNotificationMode(currentInfo.getServiceId(), - currentInfo.getUrl(), mode) + subscriptionManager + .updateNotificationMode( + currentInfo.getServiceId(), + currentInfo.getUrl(), + isEnabled ? NotificationMode.ENABLED : NotificationMode.DISABLED) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe() diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 87101abcd8d..a9cdd852d1b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -81,9 +81,8 @@ class NotificationHelper(val context: Context) { PendingIntent.getActivity( context, data.pseudoId, - NavigationHelper.getChannelIntent( - context, data.listInfo.serviceId, data.listInfo.url - ) + NavigationHelper + .getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0 ) @@ -129,10 +128,10 @@ class NotificationHelper(val context: Context) { } } - @JvmStatic /** * Whether the user enabled the notifications for new streams in the app settings. */ + @JvmStatic fun areNewStreamsNotificationsEnabled(context: Context): Boolean { return ( PreferenceManager.getDefaultSharedPreferences(context) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 6a44935c519..528bcc5d299 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -37,6 +37,15 @@ class FeedLoadManager(private val context: Context) { FeedLoadState(description, maxProgress.get(), currentProgress.get()) } + /** + * Start checking for new streams of a subscription group. + * @param groupId The ID of the subscription group to load. + * When using [FeedGroupEntity.GROUP_ALL_ID], all subscriptions are loaded. + * @param ignoreOutdatedThreshold When `false`, only subscriptions which have not been updated + * within the `feed_update_threshold` are checked for updates. + * This threshold can be set by the user in the app settings. + * When `true`, all subscriptions are checked for new streams. + */ fun startLoading( groupId: Long = FeedGroupEntity.GROUP_ALL_ID, ignoreOutdatedThreshold: Boolean = false, @@ -59,12 +68,15 @@ class FeedLoadManager(private val context: Context) { OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong()) } - val subscriptions = when (groupId) { + /** + * subscriptions which have not been updated within the feed updated threshold + */ + val outdatedSubscriptions = when (groupId) { FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold) else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) } - return subscriptions + return outdatedSubscriptions .take(1) .doOnNext { @@ -90,9 +102,14 @@ class FeedLoadManager(private val context: Context) { .map { subscriptionEntity -> var error: Throwable? = null try { + // check for and load new streams + // either by using the dedicated feed method or by getting the channel info val listInfo = if (useFeedExtractor) { ExtractorHelper - .getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url) + .getFeedInfoFallbackToChannelInfo( + subscriptionEntity.serviceId, + subscriptionEntity.url + ) .onErrorReturn { error = it // store error, otherwise wrapped into RuntimeException throw it @@ -100,7 +117,11 @@ class FeedLoadManager(private val context: Context) { .blockingGet() } else { ExtractorHelper - .getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true) + .getChannelInfo( + subscriptionEntity.serviceId, + subscriptionEntity.url, + true + ) .onErrorReturn { error = it // store error, otherwise wrapped into RuntimeException throw it @@ -108,7 +129,12 @@ class FeedLoadManager(private val context: Context) { .blockingGet() } as ListInfo - return@map Notification.createOnNext(FeedUpdateInfo(subscriptionEntity, listInfo)) + return@map Notification.createOnNext( + FeedUpdateInfo( + subscriptionEntity, + listInfo + ) + ) } catch (e: Throwable) { if (error == null) { // do this to prevent blockingGet() from wrapping into RuntimeException @@ -116,7 +142,8 @@ class FeedLoadManager(private val context: Context) { } val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}" - val wrapper = FeedLoadService.RequestException(subscriptionEntity.uid, request, error!!) + val wrapper = + FeedLoadService.RequestException(subscriptionEntity.uid, request, error!!) return@map Notification.createOnError(wrapper) } } @@ -142,6 +169,13 @@ class FeedLoadManager(private val context: Context) { FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(currentProgress.get(), maxProgress.get())) } + /** + * Keep the feed and the stream tables small + * to reduce loading times when trying to display the feed. + *
+ * Remove streams from the feed which are older than [FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE]. + * Remove streams from the database which are not linked / used by any table. + */ private fun postProcessFeed() = Completable.fromRunnable { FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message)) feedDatabaseManager.removeOrphansOrOlderStreams() @@ -179,7 +213,12 @@ class FeedLoadManager(private val context: Context) { subscriptionManager.updateFromInfo(subscriptionId, info) if (info.errors.isNotEmpty()) { - feedResultsHolder.addErrors(FeedLoadService.RequestException.wrapList(subscriptionId, info)) + feedResultsHolder.addErrors( + FeedLoadService.RequestException.wrapList( + subscriptionId, + info + ) + ) feedDatabaseManager.markAsOutdated(subscriptionId) } } From 707f2835a8f9cd2ffb387c81d87e50399309eb1a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 22:26:05 +0100 Subject: [PATCH 17/46] Restructured build.gradle/androidxWorkVersion --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 488bfa3baf1..b8daca97ee9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -103,6 +103,7 @@ ext { androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.3.0' + androidxWorkVersion = '2.5.0' icepickVersion = '3.2.0' exoPlayerVersion = '2.12.3' @@ -113,7 +114,6 @@ ext { leakCanaryVersion = '2.5' stethoVersion = '1.6.0' mockitoVersion = '3.6.0' - workVersion = '2.5.0' } configurations { @@ -222,8 +222,8 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.webkit:webkit:1.4.0' implementation 'com.google.android.material:material:1.2.1' - implementation "androidx.work:work-runtime-ktx:${workVersion}" - implementation "androidx.work:work-rxjava3:${workVersion}" + implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" + implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" /** Third-party libraries **/ // Instance state boilerplate elimination From 4f8552835ea94e21e1e7d65d41b253379920c2d1 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 22:43:23 +0100 Subject: [PATCH 18/46] Better naming for a test class that does database migrations --- .../database/{AppDatabaseTest.kt => DatabaseMigrationTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/src/androidTest/java/org/schabi/newpipe/database/{AppDatabaseTest.kt => DatabaseMigrationTest.kt} (99%) diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt similarity index 99% rename from app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt rename to app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt index 158994cddf3..28dea13e9f0 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/database/AppDatabaseTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt @@ -16,7 +16,7 @@ import org.junit.runner.RunWith import org.schabi.newpipe.extractor.stream.StreamType @RunWith(AndroidJUnit4::class) -class AppDatabaseTest { +class DatabaseMigrationTest { companion object { private const val DEFAULT_SERVICE_ID = 0 private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4" From e4cd52060cde3dfb551a6a5efdd83a325659c220 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 22:48:49 +0100 Subject: [PATCH 19/46] Reformatted code so that it's better readable --- app/src/main/java/org/schabi/newpipe/App.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 3601e5c61e6..0cfa4ee622d 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -226,29 +226,33 @@ private void initNotificationChannels() { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels final NotificationChannelCompat mainChannel = new NotificationChannelCompat - .Builder(getString(R.string.notification_channel_id), - NotificationManagerCompat.IMPORTANCE_LOW) + .Builder( + getString(R.string.notification_channel_id), + NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.notification_channel_name)) .setDescription(getString(R.string.notification_channel_description)) .build(); final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat - .Builder(getString(R.string.app_update_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_LOW) + .Builder( + getString(R.string.app_update_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.app_update_notification_channel_name)) .setDescription(getString(R.string.app_update_notification_channel_description)) .build(); final NotificationChannelCompat hashChannel = new NotificationChannelCompat - .Builder(getString(R.string.hash_channel_id), - NotificationManagerCompat.IMPORTANCE_HIGH) + .Builder( + getString(R.string.hash_channel_id), + NotificationManagerCompat.IMPORTANCE_HIGH) .setName(getString(R.string.hash_channel_name)) .setDescription(getString(R.string.hash_channel_description)) .build(); final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat - .Builder(getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT) + .Builder( + getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) .setName(getString(R.string.streams_notification_channel_name)) .setDescription(getString(R.string.streams_notification_channel_description)) .build(); From 58418bcf460390d9a195e15254cfaa17a9685b51 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 22:57:31 +0100 Subject: [PATCH 20/46] Improved code readability --- .../newpipe/local/feed/notifications/NotificationWorker.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index afdeee7f4d0..6886c1e039d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -87,6 +87,7 @@ class NotificationWorker( NetworkType.CONNECTED } ).build() + val request = PeriodicWorkRequest.Builder( NotificationWorker::class.java, options.interval, @@ -95,6 +96,7 @@ class NotificationWorker( .addTag(TAG) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .build() + WorkManager.getInstance(context) .enqueueUniquePeriodicWork( TAG, From 0f4b6d7d9f1af7a12b16aadedfc2c8833b11c906 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 23:22:52 +0100 Subject: [PATCH 21/46] Improved code readablity --- .../schabi/newpipe/settings/NotificationsSettingsFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index e1f7ed2c21e..04f5a9b56c3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -47,6 +47,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onResume() { super.onResume() + val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) preferenceScreen.isEnabled = enabled if (!enabled) { @@ -73,6 +74,8 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen notificationWarningSnackbar?.dismiss() notificationWarningSnackbar = null } + + // (Re-)Create loader loader?.dispose() loader = SubscriptionManager(requireContext()) .subscriptions() @@ -83,6 +86,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onPause() { loader?.dispose() loader = null + super.onPause() } From 94219b78e71be292e215b7cd41dd3976122cb330 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 23:22:59 +0100 Subject: [PATCH 22/46] Fixed typos --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index dda0dd0e44a..2e3eec6c1a8 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -165,7 +165,7 @@ protected void onCreate(final Bundle savedInstanceState) { } openMiniPlayerUponPlayerStarted(); - // shedule worker for checking for new streans and creating corresponding notifications + // schedule worker for checking for new streams and creating corresponding notifications NotificationWorker.schedule(this); } From f0112a2de2348c9532755dc132c2d4e505463d86 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 23:36:46 +0100 Subject: [PATCH 23/46] Added some lines to improve code-readability --- .../newpipe/local/feed/notifications/NotificationHelper.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index a9cdd852d1b..2196da0d7a4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -69,6 +69,8 @@ class NotificationHelper(val context: Context) { .setColorized(true) .setAutoCancel(true) .setCategory(NotificationCompat.CATEGORY_SOCIAL) + + // Build style val style = NotificationCompat.InboxStyle() for (stream in newStreams) { style.addLine(stream.name) @@ -76,6 +78,7 @@ class NotificationHelper(val context: Context) { style.setSummaryText(summary) style.setBigContentTitle(data.name) builder.setStyle(style) + // open the channel page when clicking on the notification builder.setContentIntent( PendingIntent.getActivity( @@ -87,6 +90,7 @@ class NotificationHelper(val context: Context) { 0 ) ) + return Single.create(NotificationIcon(context, data.avatarUrl)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) From bc68836c8d167dc4a57f79018cd110391b3b43bc Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 2 Nov 2021 23:59:48 +0100 Subject: [PATCH 24/46] Reworked menu_channel.xml --- app/src/main/res/menu/menu_channel.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/menu/menu_channel.xml b/app/src/main/res/menu/menu_channel.xml index d6c54b6804c..5ea8f8c959b 100644 --- a/app/src/main/res/menu/menu_channel.xml +++ b/app/src/main/res/menu/menu_channel.xml @@ -20,19 +20,21 @@ + android:visible="false" + app:showAsAction="never" + tools:visible="true" /> From 5ae72d1ed216026bb21b001456dcde72ad68a352 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 3 Nov 2021 00:11:44 +0100 Subject: [PATCH 25/46] Removed unknown/unused file --- assets/db.dia | Bin 3167 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/db.dia diff --git a/assets/db.dia b/assets/db.dia deleted file mode 100644 index 1194f488299483bd0fb0fad7ce650a09b4b5bbc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3167 zcmV-l450HLiwFP!000021MOYQlA|~l-Pcz*Tw5oJc+>8zj+yD0=v_z5)T{~FC~dkh zc!Zo;-psd;u$_KCFapX@CnKsdZOgiFIf!$QuC9Ll?fWe5y)m9eY4Xx1n%?iRWSoXk zGI{C$=j%V8y#8-rKK&X-^cVSW!s)Chzma75tC#*%i1{zi&+qT=TD;7tNVyhA3oT>M z|D$nCpJk)x{+Ca^-hKxm6*PZu^DY&FN27&cy@bx#OMgVi-zGd=Bw>HuYSV6<#wqW; z(fFnR`E~Wvf8KQSe9+I8zUOqpMx4=am%G;GkMEc1dd~Q9x3hVgMY4%l&QF?L=qLYw zug#`aCL1M_FQ5NreO@2Z=8aun=MMCu5iz5D5+$d0q;KN2BMi+m3`07@_B~&BEth=w zX`LgM-S_j ziI^@K-*o?nymX9Dc>bL3ZLaWU$m!9sh~7S4?KveK{l&&&bGEOqVT2vr+(`q$)WJXVe@p0Dsw>GV+WiLAD8+bjgGtK^6+UgiP^6EA<;F@ zvIoT1N}8H6LHjo_9!6tPu6Gu*Sujp{a!Nfaan!2+)$nxctdF6|l^(MEy&q4()|5q) zskm&+&|LlC$0%n^u1vHZ>GEXV{E>?}>b$B?`OXDktyY$^Bu@ld>A=Nb{Z3&nIIx#^^CGx90G z_zDPf9!WmD48F1DRbz+b^d4KIG!FO8WObcXl1geH2e0X=-}3i*uXGm0OF4k2Nt(^+ zSU$osvCJQX@&UH);ve$9ix@AD_1o4nnq*tU-q!LTEPi7m8q=r0OMjI+fDLHwJ%-am z_;VCT|6QnGhMR-Gd=?*#GnS(73z&5L!yiZ8+s#?szRqrO5RtRAVB7wb%-@tU&X3no zctO*+5(r!x?c{-8vlyqzq%vctLu#?7L>XJ>N7Z6s|NYjIMI5gv`7Ki?5&HeM7RhR4 zsM6BeK&Lra&v`oGEXx8B%^09vCA2HRTQ{+NUWjFi!I8lwl?SWy|7=`EE4-H-O z&4FpQIS4QBry@Z(v89J&Jn&QCHxdA?8NRjhGuTH)8I`jky1%e27v(B?tw>g|r#Zx~AU(8Dim+MsY9J@LZEJDoF_l zPB3zUo4|AaImA;;d|_G_=mxqUR#h^qQk96;fT~2iovLKDy40JZ*B(w)a*ipu36bgN z5ScierGf>}nxHj7Yl7AUtqEEav?gdxU0;75A|8j@1PK+O2B=X}0hq?L0Hy_iZY>%aJ+*`Q_ib{NHV?eD3FiQ zy6(}sPLau06`9-tAvMZNdG|wN=c(@l`O9ptu2kMp54SkL(#*rq!iB)}lk5i1C?8LQ zsf<-pzJ#;{X$jI2q$Nm8k1H)9_C}1-rO4|un>)7VNFl$tj z_N)w_^87O09GI=JAlIlexloxNLS@pAWhSGB+`Gjn8}n$M&wgi+ znIJPkW`fKFnF(ciAuwSzC(@G;gF{|I2~%Jim`1H>fV!I#TyIoRj-tGPcK1@G*W%uY zaZGao-kpPY$hOiohHhZHTU0w0(Q7nbuS%SyA%i*sc(*%4!Bfsqa5T#>?3xo4wnm1A zM*RFQyUm*jt2*uB>I4~@Tgj|Ak`-E)tnkWMh#UpvC?H1xISR;8z)~1wDj-v#aW!?N zHv!wgcH67;`synN+;swfo}KOzr`BZyho**4tBm;k4NJ31+kTAo166pjXmN@I3gx4m zFT(VC!t`Lm)5RRYI)Zfs>j>5ntYbj|LK%eVNK(aoInqJO6kH!St2rSF1fZ^8P))fA zQZ8c_;2OAYcXAWaE2+Oi9A`lCj2)fSU4&)N)bPpIbju4z!CqU^*XVydq> zuCZT{#X`;##MC;))ZkzOGT$u}@+#z2$g7Z7A+J8Jyt+4jLuiH2ihL+w8`%D#A^gAC zE8{E~pAF-Qau~lUYHFC8W!D+Qf7mf38?fx*Zk3e|C5zdH)tnr}?lW@bGhN}B=t88Dg9uhrxY7+_q0!BMR>oG*#63{iD+yed zfaelgz-bsQvMZ-qQq4-jfO4L`Up^;8wd){e$>jPavQ@OKP}%; zk(@zzKzKlSKzKlSwuFaVBrSIn9;Z>lW2zD!NAq*#fcRRSyD;xr8G4%UDO7cN1kQ8T zX&0LrL^H3lnGcT?ZD-aU{6Y*o5r7@F&w?!a(rp4HMdf0NNNtia|Yu$~L2ai$^BJcsyqG`u;z zweu#jdBAovfA_U#N!A}_Dke)ex*N*k(s5ZOH-Kp;RMKp;RMK$b!>v+*O7nO*g|ET-mps+Fb-)@3;~HO${I{rnA; zawZS4uIr)~ueG%`%d-9I+bUJq!HgJ45l9hIo{{nlTI8$Di5=w+8IAHHuL@q|c&=CT zcAVR1_PR9PiV_iD!R({3p%dR-YeqEP(Mch4b5C2PdVe57AVMHQAVMHQAVSBB-3=8Y z$3{HSFBX8>r_!iuu!R9 zThl8kS%^}-kH}-LR_cwCD4Vh{m_=DW&SWUhi}JiE&x`WBD9`)2<$32+OtD)OHX!Lz zVcrFg0ck+GOOPHd<-;Ke)AuA!X~Fmp={{v Date: Fri, 5 Nov 2021 13:17:33 +0100 Subject: [PATCH 26/46] Code cleanup --- .../schabi/newpipe/local/subscription/SubscriptionManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index 2911e8d1922..bcd64791e77 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -69,8 +69,8 @@ class SubscriptionManager(context: Context) { } } - fun updateNotificationMode(serviceId: Int, url: String?, @NotificationMode mode: Int): Completable { - return subscriptionTable().getSubscription(serviceId, url!!) + fun updateNotificationMode(serviceId: Int, url: String, @NotificationMode mode: Int): Completable { + return subscriptionTable().getSubscription(serviceId, url) .flatMapCompletable { entity: SubscriptionEntity -> Completable.fromAction { entity.notificationMode = mode From 7b4e5dd107ec44ebde73127a8db690aa913aee62 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 5 Nov 2021 14:10:53 +0100 Subject: [PATCH 27/46] Reworked player-notfication * Fixed ``release`` ``main_settings.xml`` * Renamed "Notification" to "Player-Notification" (also reset all translations) --- .../newpipe/settings/NewPipeSettings.java | 2 +- ... => PlayerNotificationSettingsFragment.kt} | 4 +-- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-az/strings.xml | 1 - app/src/main/res/values-b+ast/strings.xml | 1 - app/src/main/res/values-b+uz+Latn/strings.xml | 1 - .../main/res/values-b+zh+HANS+CN/strings.xml | 1 - app/src/main/res/values-be/strings.xml | 1 - app/src/main/res/values-bn-rIN/strings.xml | 1 - app/src/main/res/values-bn/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-ckb/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-eo/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fa/strings.xml | 1 - app/src/main/res/values-fi/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-gl/strings.xml | 1 - app/src/main/res/values-he/strings.xml | 1 - app/src/main/res/values-hi/strings.xml | 1 - app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-hy/strings.xml | 1 - app/src/main/res/values-in/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-kmr/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-ku/strings.xml | 1 - app/src/main/res/values-lt/strings.xml | 1 - app/src/main/res/values-lv/strings.xml | 1 - app/src/main/res/values-ml/strings.xml | 1 - app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-nb-rNO/strings.xml | 1 - app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pa/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt-rPT/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 3 +- app/src/main/res/values-sc/strings.xml | 1 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-sl/strings.xml | 1 - app/src/main/res/values-so/strings.xml | 1 - app/src/main/res/values-sq/strings.xml | 1 - app/src/main/res/values-sr/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-tzm/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 3 +- app/src/main/res/values-ur/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 4 +-- app/src/main/res/xml/appearance_settings.xml | 32 +++++++++---------- ...s.xml => player_notification_settings.xml} | 2 +- app/src/main/res/xml/video_audio_settings.xml | 1 + app/src/release/res/xml/main_settings.xml | 6 ---- 66 files changed, 27 insertions(+), 89 deletions(-) rename app/src/main/java/org/schabi/newpipe/settings/{NotificationSettingsFragment.kt => PlayerNotificationSettingsFragment.kt} (79%) rename app/src/main/res/xml/{notification_settings.xml => player_notification_settings.xml} (95%) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index aa21c442270..1e1d08856df 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -70,7 +70,7 @@ public static void initSettings(final Context context) { PreferenceManager.setDefaultValues(context, R.xml.appearance_settings, true); PreferenceManager.setDefaultValues(context, R.xml.history_settings, true); PreferenceManager.setDefaultValues(context, R.xml.content_settings, true); - PreferenceManager.setDefaultValues(context, R.xml.notification_settings, true); + PreferenceManager.setDefaultValues(context, R.xml.player_notification_settings, true); PreferenceManager.setDefaultValues(context, R.xml.update_settings, true); PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true); diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt similarity index 79% rename from app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt rename to app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt index e03aa407437..1789606d1a3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt @@ -5,9 +5,9 @@ import android.os.Bundle import androidx.preference.Preference import org.schabi.newpipe.R -class NotificationSettingsFragment : BasePreferenceFragment() { +class PlayerNotificationSettingsFragment : BasePreferenceFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.notification_settings) + addPreferencesFromResource(R.xml.player_notification_settings) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key)) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 55f41563c34..0bcb5afbb87 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -586,7 +586,6 @@ فقط على شبكة Wi-Fi بدء التشغيل تلقائياً — %s تشغيل قائمة الانتظار - الإشعار تعذر التعرف على الرابط. فتح باستخدام تطبيق آخر؟ قائمة انتظار تلقائيّة سيتم استبدال قائمة انتظار للمشغل النشط diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index a5775792a36..b7cf7481eba 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -104,7 +104,6 @@ Məzmun Ani pəncərədə oxudulur Fonda oxudulur - Bildiriş Yeniləmələr Sazlama Görünüş diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index 19ed896f0a6..513dd517044 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -517,7 +517,6 @@ YouTube forne\'l «Mou torgáu» qu\'anubre conteníu\'l que seya potencialmente p\'adultos Activar el «Mou torgáu» de YouTube Amuesa\'l conteníu que quiciabes nun seya afayadizu pa guaḥes porque tien una llende d\'edá (como +18) - Avisu permanente Depuración Namás se sofiten URLs HTTPS Introduz la URL d\'una instancia diff --git a/app/src/main/res/values-b+uz+Latn/strings.xml b/app/src/main/res/values-b+uz+Latn/strings.xml index 4d4602daa16..59530ee8603 100644 --- a/app/src/main/res/values-b+uz+Latn/strings.xml +++ b/app/src/main/res/values-b+uz+Latn/strings.xml @@ -137,7 +137,6 @@ Tarkib Pop-up rejimda ijro etish Ijro etish foni - Bildirishnoma Yangilanishlar Nosozliklarni tuzatish Tashqi ko\'rinish diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 20b01f60518..74885573109 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -554,7 +554,6 @@ 第一操作按钮 将通知中视频缩略图的长宽比从 16:9 强制缩放到 1:1(可能会导致失真) 强制缩放缩略图至 1:1 比例 - 通知 显示内存泄漏 已加入播放队列 加入播放队列 diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 4b185b30149..5dd5daca1c5 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -483,7 +483,6 @@ Ператасаваць Паўтор Кнопка пятага дзеяння - Паведамленні Афарбоўваць апавяшчэнне асноўным колерам мініяцюры. Падтрымваецца не ўсімі прыладамі У кампактным апавяшчэнні дасяжна не больш за тры дзеянні! Дзеянні можна змяніць, націснуўшы на іх. Адзначце не больш за трох для адлюстравання ў кампактным апавяшчэнні diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 8313640139c..94fa9ee9e7a 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -291,7 +291,6 @@ বিবরণ মন্তব্য - নোটিফিকেশন মেটা ইনফো দেখান বিবরণ দেখান রাত্রি থিম diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 11225e7063e..ea587e6af5a 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -332,7 +332,6 @@ %s সদস্যতাগণ ব্যবহারকারীরা - বিজ্ঞপ্তি বাধার পর প্লে চালিয়ে যাও (উদাহরণস্বরূপ ফোনকল) সদস্যতা রপ্তানি করা যায়নি সদস্যতা আমদানি করা যায়নি diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 5e1d1b2015e..d191d6dccb8 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -558,7 +558,6 @@ Notificació de comprovació del vídeo YouTube proporciona un \"mode restringit\" que amaga contingut potencialment inadequat per a infants Mostra contingut que podria ser inadequat pels infants - Notificació No s\'ha pogut reconèixer l\'adreça URL. Obrir-la amb una altra aplicació\? Cua automàtica Desactiveu-ho per deixar de mostrar les metadades, que contenen informació addicional sobre el creador del directe, el contingut o una sol·licitud de cerca diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index 65af5ffff71..cc5d7d0fc99 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -543,7 +543,6 @@ تكایه‌ پشكنینێك بكه‌ كه‌ ئاخۆ كێشه‌یه‌ك هه‌یه‌ باسی كڕاشه‌كه‌ت بكات. له‌كاتی سازدانی پلیتی لێكچوو ، كات له‌ ئێمه‌ ده‌گریت كه‌ ئێمه‌ سه‌رقاڵی چاره‌سه‌ركردنی هه‌مان كێشه‌ ده‌كه‌یت. سكاڵا لەسەر GitHub له‌به‌رگرتنه‌وه‌ی سكاڵای جۆركراو - ئاگانامە ناتوانرێت به‌سته‌ره‌كه‌ بناسرێتەوە. بە بەرنامەیەکی دیكه‌ بکرێتەوە؟ خستنه‌ نۆبه‌تی-خۆكاری نۆبه‌ته‌كه‌ لە لێدەری چالاکەوە جێگۆڕکێی دەکرێت diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 5a4091db513..fb6dcafcbe1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -561,7 +561,6 @@ Fronta aktivního přehrávače bude smazána Při přechodu z jednoho přehrávače do druhého může dojít k smazání fronty Žádat potvrzení před vyklizením fronty - Oznámení Nic Bufferovat Promíchat diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 105646a167e..efa38337d91 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -344,6 +344,8 @@ Gestensteuerung für Helligkeit Gesten verwenden, um die Helligkeit einzustellen Aktualisierungen + Wiedergabebenachrichtigung + Konfiguriert die Benachrichtigung zum aktuell abgespielten Stream Datei gelöscht Aktualisierungsbenachrichtigung Benachrichtigung bei neuer NewPipe-Version @@ -552,7 +554,6 @@ Nie Du kannst maximal drei Aktionen auswählen, die in der Kompaktbenachrichtigung angezeigt werden sollen! Bearbeite jede Benachrichtigungsaktion unten, indem du darauf tippst. Wähle mithilfe der Kontrollkästchen rechts bis zu drei aus, die in der Kompaktbenachrichtigung angezeigt werden sollen - Benachrichtigung Konnte die angegebene URL nicht erkennen. Mit einer anderen Anwendung öffnen\? Fünfte Aktionstaste Vierte Aktionstaste diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 1775e1ca830..d4750c115ab 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -485,7 +485,6 @@ \n \nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.
Λειτουργία περιορισμένης πρόσβασης του YouTube - Ειδοποίηση Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή; Αυτόματη ουρά Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 76927234574..91df86e5c8f 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -514,7 +514,6 @@ Tiu ĉi filmeto havas aĝlimon. \n \nŜalti \"%1$s\" en la agordoj, se vi volas vidi ĝin. - Sciigo Malhela etoso farbi sciigon Alŝuto diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0c8ba0a9e70..73cb3a85849 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -551,7 +551,6 @@ Solo en Wi-Fi Comenzar reproducción automáticamente — %s Reproducir cola - Notificación No se pudo reconocer la URL. ¿Abrir con otra aplicación\? Poner en cola Cambiar de un reproductor a otro puede reemplazar la cola de reproducción diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 107c0f624f4..08de77661cc 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -486,7 +486,6 @@ \nKui sa soovid seda näha, siis lülita seadistustest „%1$s“ sisse.
YouTube\'is leiduv „Piiratud režiim“ peidab võimaliku täiskasvanutele mõeldud sisu Näita sisu, mis vanusepiirangu tõttu ilmselt ei sobi lastele (näiteks 18+) - Teavitus Sa saad kasutada vaid HTTPS-urle Öine teema Ei iialgi diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 5b8161bf241..88f27b920ff 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -549,7 +549,6 @@ Adinez mugatuta dagoen eta haurrentzako desegokia izan daitezkeen edukia erakutsi (+18 adibidez) YouTube-ren \"Modu Murriztua\" helduentzako edukia izan daitekeen edukia ezkutatzen du Piztu YouTube-ren \"Modu Murriztua\" - Jakinarazpena Ezin izan da URL-a ezagutu. Beste aplikazio batekin ireki\? Auto-ilara Erreprodukzio ilara aktiboa ordezkatuko da diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 58f6cef814c..0b6712a6981 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -544,7 +544,6 @@ فقط روی وای‌فای شروع خودکار پخش — %s پخش صف - اعلان نشانی قابل تشخیص نبود. با برنامه دیگری باز شود؟ صف‌گذاری خودکار صف پخش‌کنندهٔ فعال جایگزین می‌شود diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index cd6777b5e19..92be8637339 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -545,7 +545,6 @@ Vain Wi-Fi-verkossa Aloita toisto automaattisesti — %s Toistojono - Ilmoitus Ei tunnistettu URL:ää. Avataanko toisessa sovelluksessa\? Automaattinen jonoon lisääminen Aktiivisen soittimen jono korvataan diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8d27ddff986..92541f40fbd 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -554,7 +554,6 @@ Ajouter automatiquement à la liste de lecture La liste de lecture du lecteur actif sera remplacée Confirmer av. de suppr. la liste de lecture - Notification Rien Chargement Lire aléatoirement diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index f22ecff8603..1cfa6c47c29 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -595,7 +595,6 @@ Este vídeo ten restrición de idade. \nDebido ás novas políticas de Youtube cos vídeos con restrición de idade, NewPipe non pode acceder ás transmisións do vídeo, polo que non pode reproducilo. Youtube ten un \"Modo Restrinxido\" que oculta contido potencialmente só para adultos - Notificación URL non recoñecido. Abrir con outra aplicación\? Mostrar metainformación Desactíveo para ocultar a descrición do vídeo e a información adicional diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 1d054155a23..de469455d89 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -571,7 +571,6 @@ התור מהנגן הפעיל יוחלף מעבר מנגן אחד למשנהו עלול להחליף את התור שלך לבקש אישור לפני מחיקת התור - התראה כלום איסוף ערבוב diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 5f295bda8eb..3439b5e8cb4 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -465,7 +465,6 @@ यूट्यूब एक \"प्रतिबंधित मोड\" प्रदान करता है जो संभावित रूप से परिपक्व सामग्री को छुपाता है यूट्यूब का \"प्रतिबंधित मोड\" चालू करें बच्चों के लिए अनुपयुक्त सामग्री दिखाएं क्योंकि इसकी आयु सीमा है (जैसे 18) - अधिसूचना केवल HTTPS यूआरएल ही समर्थित हैं URL की पहचान नहीं हो सकी। दूसरे ऐप से खोलें\? ऑटोमैटिकली कतार करे diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 1f8ef42a001..1db40095907 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -448,7 +448,6 @@ Albumi Pjesme Napravio %s - Obavijest Nikada Ograniči popis preuzimanja Koristi birač mapa sustava (SAF) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e19fade838f..93f38692e47 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -485,7 +485,6 @@ \n \nEngedélyezd a(z) \"%1$s\" beállítást ha meg szeretnéd tekinteni.
Gyermekek számára potenciálisan nem megfelelő, korhatáros tartalom mutatása (pl. 18+) - Értesítés Csak HTTPS URL-ek támogatottak Metaadatok mutatása A jelenleg aktív lejátszási sor le lesz cserélve diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index d7da33600f3..7d7a8b55ab3 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -58,7 +58,6 @@ Մասին Ալիքներ Ամենը - Ծանուցում Տեսք Թարմացումներ Դիտման պատմություն diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index dddf0dd6a18..6e264bf13a6 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -540,7 +540,6 @@ Antrean dari pemutar yang aktif akan digantikan Beralih ke pemutar yang lain mungkin akan mengganti antrean Anda Konfirmasi sebelum mengosongkan antrean - Notifikasi Tidak ada Bufer Aduk diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2ee99d45288..f4467cfcb85 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -560,7 +560,6 @@ Buffer in corso Nella notifica compatta è possibile visualizzare al massimo 3 azioni! Casuale - Notifica Niente Ripeti Ridimensiona copertina alla proporzione 1:1 diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f2540f20505..6643895140a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -554,7 +554,6 @@ 繰り返し シャッフル バッファリング - 通知 YouTube は、成人向けの可能性があるコンテンツを除外する「制限付きモード」を提供しています 年齢制限 (18+ など) の理由で、子供には不適切な可能性のあるコンテンツを表示する キューに追加 diff --git a/app/src/main/res/values-kmr/strings.xml b/app/src/main/res/values-kmr/strings.xml index d7ac85dd126..2ed1163f3ef 100644 --- a/app/src/main/res/values-kmr/strings.xml +++ b/app/src/main/res/values-kmr/strings.xml @@ -195,7 +195,6 @@ Dilşad Di moda popupê de dilîzin Di paşayê de dilîzin - Agahdayin Nûvekirin Xeletkirin Xuyabûnî diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 880c45aee24..7befbb800ad 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -499,7 +499,6 @@ 시청 기록을 지우겠습니까\? 시청 기록 지우기 재생목록 실행 - 알림 URL을 인식할 수 없습니다. 다른 앱으로 여시겠습니까\? 대기열을 비우기 전 확인하도록 합니다. 안드로이드에서 썸네일의 색상에 따라 알림 색상을 조절합니다. (지원되지 않는 기기가 있을 수 있습니다.) diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml index be2a8086f78..883347e6262 100644 --- a/app/src/main/res/values-ku/strings.xml +++ b/app/src/main/res/values-ku/strings.xml @@ -540,7 +540,6 @@ دەسپێکردنی کارپێکەر بەخۆکاری — %s لێدانی ڕیز هیچ لیستەلێدانێک نیشانە نەکراوە - پەیام بەستەرەکە نەناسرایەوە. لە ئەپێکیتردا بکرێتەوە؟ ڕیزبوونی خۆکار ڕیزی لێدەری چالاک جێیدەگیرێتەوە diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index d0249568e18..27370272718 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -332,7 +332,6 @@ Youtube turi „apribotą režimą“ kuriame slepiamas galimai suaugusiems skirtas turinys Įjungti YouTube „apribotą režimą“ Rodyti turinį kuris gali būti netinkamas vaikams (18+) - Pranešimai Atnaujinimai Kopija jau yra Palaikomi tik HTTPS adresai diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 58d40b61d37..e7914fe29b1 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -262,7 +262,6 @@ Saturs Atskaņo popup režīmā Atskaņo fonā - Notifikācija Atjauninājumi Atkļūdošana Izskats diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index eb78ca44ce4..1713e72913b 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -589,7 +589,6 @@ \nപ്രായ-നിയന്ത്രിത വീഡിയോകളുള്ള പുതിയ യൂട്യൂബ് നയങ്ങൾ കാരണം, ന്യൂപൈപ്പിന് അതിന്റെ വീഡിയോ സ്ട്രീമുകളിലൊന്നും ആക്സസ് ചെയ്യാൻ കഴിയില്ല, അതിനാൽ ഇത് പ്ലേ ചെയ്യാൻ കഴിയില്ല. പക്വതയുള്ള ഉള്ളടക്കം മറയ്ക്കുന്ന \"നിയന്ത്രിത മോഡ്\" യൂട്യൂബ് നൽകുന്നു കുട്ടികൾക്ക് അനുയോജ്യമല്ലാത്ത ഉള്ളടക്കം കാണിക്കുക കാരണം അതിന് പ്രായപരിധി ഉണ്ട് (18+ പോലെ) - അറിയിപ്പ് URL തിരിച്ചറിയാൻ കഴിഞ്ഞില്ല. മറ്റൊരു അപ്ലിക്കേഷൻ ഉപയോഗിച്ച് തുറക്കണോ\? യാന്ത്രിക-ക്യൂ സ്ട്രീം സ്രഷ്ടാവ്, സ്ട്രീം ഉള്ളടക്കം അല്ലെങ്കിൽ ഒരു തിരയൽ അഭ്യർത്ഥന എന്നിവയെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾ ഉൾക്കൊള്ളുന്ന മെറ്റാ വിവര ബോക്സുകൾ മറയ്ക്കുന്നതിന് ഓഫാക്കുക diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 76aef646ff3..6af6ec25d68 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -384,7 +384,6 @@ %d hari Bantuan - Pemberitahuan Buka dengan %s pendengar diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9cce73360a6..37142af1b9a 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -554,7 +554,6 @@ Spill kø Ingen spillelistebokmerker enda Kopier formatert rapport - Merknad Gjenta Femte handlingstast Fjerde handlingstast diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index be762707993..0584e12e683 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -538,7 +538,6 @@ YouTube biedt een \"beperkte modes\" aan, dit verbergt mogelijk materiaal voor volwassenen YouTube \"beperkte modus\" aanzetten Toon inhoud die mogelijk niet geschikt is voor kinderen omwille van een leeftijdslimiet (zoals 18+) - Melding Kanaal bestaat al Alleen HTTPS URL\'s worden ondersteund Kon kanaal niet valideren diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b835c68dcc2..6570e104473 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -546,7 +546,6 @@ Enkel via Wi-Fi Start automatisch met afspelen — %s Speel wachtrij af - Notificatie Kon de URL niet herkennen. In een andere app openen\? De actieve spelerswachtrij wordt vervangen Veranderen van één speler naar een andere kan jouw wachtrij vervangen diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 8fad329f38b..f1aa81b6b9b 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -586,7 +586,6 @@ ਯੂਟਿਊਬ \"ਪਾਬੰਦੀਸ਼ੁਦਾ ਮੋਡ\" ਉਪਲਬਧ ਕਰਾਉਂਦਾ ਹੈ ਜੋ ਬਾਲਗਾਂ ਵਾਲ਼ੀ ਸਮੱਗਰੀ ਲੁਕਾਉਂਦਾ ਹੈ ਯੂਟਿਊਬ ਦਾ ਪਾਬੰਦੀਸ਼ੁਦਾ ਮੋਡ ਚਾਲੂ ਕਰੋ ਉਹ ਸਮੱਗਰੀ ਵੀ ਵਿਖਾਓ ਜੋ ਉਮਰ-ਸੀਮਾ ਕਰਕੇ ਬੱਚਿਆਂ ਲਈ ਸ਼ਾਇਦ ਸਹੀ ਨਾ ਹੋਵੇ (ਜਿਵੇਂ 18+) - ਇਤਲਾਹਾਂ ਸਥਿਤੀ ਪਹਿਲਾਂ ਨੂੰ ਮੌਜੂਦ ਹੈ ਸਿਰਫ਼ HTTP URLs ਹੀ ਮਾਣਨਯੋਗ ਹਨ ਸਥਿਤੀ ਦੀ ਜਾਇਜ਼ਗੀ ਤਸਦੀਕ ਨਹੀਂ ਹੋ ਸਕੀ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b4de246eb7d..812347ceb3c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -566,7 +566,6 @@ Kolejka aktywnego odtwarzacza zostanie zastąpiona Przejście z jednego odtwarzacza na inny może zastąpić kolejkę Poproś o potwierdzenie przed wyczyszczeniem kolejki - Powiadomienie Nic Buforowanie Losuj diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 7c85328d0e6..693a33910bf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -553,7 +553,6 @@ Solicitar confirmação antes de limpar uma fila Aleatório Carregando - Notificação Nada Repetir Selecione no máximo três botões para a notificação compacta! diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 4fa8e4e18b6..8b15b674e44 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -545,7 +545,6 @@ Apenas em Wi-Fi Iniciar reprodução automaticamente — %s Reproduzir fila - Notificação URL não reconhecido. Abrir com outra aplicação\? Colocar na fila automaticamente A fila do reprodutor ativo será substituída diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c1d95a797bf..ddce62f4628 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -556,7 +556,6 @@ URL não reconhecido. Abrir com outra aplicação\? Colocar na fila automaticamente Baralhar - Notificação Apenas em Wi-Fi Nada Mudar de um reprodutor para outro pode substituir a sua fila diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 19bd15385df..89540e819c5 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -365,7 +365,6 @@ YouTube oferă un \"Mod restricționat\" care ascunde conținutul potențial matur Activați \"Modul restricționat\" de pe YouTube Afișați conținut posibil nepotrivit pentru copii, deoarece are o limită de vârstă (cum ar fi 18+) - Notificare Adresa URL nu a putut fi recunoscută. Deschideți cu o altă aplicație\? Afișează informațiile meta Faceți ca Android să personalizeze culoarea notificării în funcție de culoarea principală din miniatură (rețineți că aceasta nu este disponibilă pe toate dispozitivele) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7e16d5858ef..d351b25f26e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -568,8 +568,7 @@ Очередь активного плеера будет заменена Подтверждать очистку очереди Переход от одного плеера к другому может заменить вашу очередь - Уведомление - Настроить уведомление о воспроизводимом сейчас потоке + Настроить уведомление о воспроизводимом сейчас потоке Ничего Буферизация Перемешать diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 1afb7990f90..c35d299dfee 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -550,7 +550,6 @@ Sa lista dae su riproduidore ativu at a èssere remplasada Colende dae unu riproduidore a s\'àteru dias pòdere remplasare sa lista tua Pedi una cunfirma in antis de iscantzellare una lista - Notìfica Òrdine casuale Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra Iscala sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 (diat pòdere causare istorchimentos) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3df4ff0af60..ae2e94f4251 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -556,7 +556,6 @@ Zatiaľ bez záložiek zoznamu Vyberte zoznam skladieb Skontrolujte prosím, či rovnaká chyba už nie je nahlásená. Vytváranie duplicitných hlásení komplikuje prácu vývojárov. - Oznámenia Nemožno rozpoznať URL. Otvoriť pomocou inej aplikácie\? Automatický rad Zoznam aktuálneho prehrávača bude prepísaný diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index dbfef543d50..e9afcee7004 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -427,7 +427,6 @@ Youtube ponuja \"omejeni način\", ki skrije potencialno vsebino za odrasle Vklop YouTubovega \"omejenega načina\" Prikaz vsebin, ki so morda neprimerne za otroke zaradi omejitve starosti (kot na primer 18+) - Obvestilo Instanca že obstaja Validacija instance ni bila mogoča Vnesite URL instance diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index bf9e7a768e5..a113094dac0 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -362,7 +362,6 @@ Luuqada & Fadhiga Kale Ku daaraya daaqada Ka daaraya xaga dambe - Ogaysiisyada Cusboonaysiinta Cilad bixinta Nashqada diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 10ab40e69bf..d8bcb6fd184 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -546,7 +546,6 @@ Kurrë Nise luajtjen automatikisht — %s Lista e luajtjes - Njoftim Nuk u njoh URL. Të hapet me një aplikacion tjetër\? Listë automatike luajtjeje Lista aktive e luajtjes do të zëvendësohet diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index e27d9e8a849..79020f28a1b 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -486,7 +486,6 @@ Јутјуб омогућава „Ограничени режим“ који скрива потенцијални садржај за одрасле Укључити Јутјубов „Ограничени режим“ Приказ садржаја који можда није прикладан за децу јер има старосну границу (попут 18+) - Обавештење Ажурирања Инстанца већ постоји Подржане су само HTTPS УРЛ адресе diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4fc9778bebc..30fb72a2aa2 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -531,7 +531,6 @@ Skala videominiatyrbilden som visas i aviseringen från 16:9- till 1:1-förhållande (kan orsaka bildförvrängning) Starta uppspelning automatiskt — %s Uppspelningskö - Aviseringar Kunde inte känna igen URL:en. Vill du öppna med annan app\? Köa automatiskt Den aktiva spellistan kommer att ersättas diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f318de74484..d8e8c7bcc71 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -551,7 +551,6 @@ Etkin oynatıcının kuyruğu değiştirilecek Bir oynatıcıdan diğerine geçmek kuyruğunuzu değiştirebilir Bir kuyruğu temizlemeden önce onay iste - Bildirim Hiçbir şey Ara belleğe alınıyor Karıştır diff --git a/app/src/main/res/values-tzm/strings.xml b/app/src/main/res/values-tzm/strings.xml index d53dcec8b9b..2dba77eb93d 100644 --- a/app/src/main/res/values-tzm/strings.xml +++ b/app/src/main/res/values-tzm/strings.xml @@ -134,7 +134,6 @@ Tagamin Tagamin Usrid - Tineɣmisin Tisdɣiwin Ameɣri Agem diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ecfbb07ffad..acea873f213 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -581,8 +581,7 @@ Опис Повʼязані елементи Коментарі - Сповіщення - Налаштувати повідомлення про відтворюваний наразі потік + Налаштувати повідомлення про відтворюваний наразі потік Не розпізнано URL. Відкрити через іншу програму\? Самододавання в чергу Показувати метадані diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 1f427504b9e..ba43cdd6189 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -474,7 +474,6 @@ یوٹیوب ایک \"پابندی والا وضع\" فراہم کرتا ہے جو امکانی طور پر نازیبا مواد کو چھپاتا ہے یوٹیوب کا \"پابندی والا وضع\" چالو کریں وہ مواد دکھائیں جو بچوں کے لیے ممکنہ طور پر نا مناسب ہیں کیوں کہ اس میں عمر کی حد ہے (جیسے 18+) - اطلاع URL کو نہیں پہچان سکے۔ کسی اور ایپ کے ساتھ کھولیں؟ ازخود قطار اسٹریم کے موجد، اسٹریم مواد یا تلاش کی درخواست کے بارے میں اضافی معلومات والے میٹا انفارمیشن بکسوں کو چھپانے کیلئے بند کریں۔ diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 3635aa4b409..803e22f6acf 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -534,7 +534,6 @@ Vui lòng kiểm tra xem vấn đề bạn đang gặp đã có báo cáo trước đó chưa. Nếu bạn tạo nhiều báo cáo trùng lặp, bạn sẽ làm tốn thời gian để chúng tôi đọc thay vì thực sự sửa lỗi. Báo cáo trên GitHub Sao chép bản báo cáo đã được định dạng - Thông báo Không thể đọc URL này. Mở với app khác\? Tự động thêm vào hàng đợi Hàng đợi của trình phát hiện tại sẽ bị thay thế diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e5c499119ce..28840775d5a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -541,7 +541,6 @@ 作用中播放器的佇列可能會被取代 從一個播放器切換到另一個可能會取代您的佇列 清除佇列前要求確認 - 通知 沒有東西 正在緩衝 隨機播放 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e67016142a6..e8ac4f1b811 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,8 +144,8 @@ Appearance Debug Updates - Notification - Configure current playing stream notification + Player-Notification + Configure current playing stream notification Playing in background Playing in popup mode Content diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index 6bc9f338169..cbe1d4c249b 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -23,6 +23,12 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + + + - - - - diff --git a/app/src/main/res/xml/notification_settings.xml b/app/src/main/res/xml/player_notification_settings.xml similarity index 95% rename from app/src/main/res/xml/notification_settings.xml rename to app/src/main/res/xml/player_notification_settings.xml index 13edfcb5645..c272fc7661f 100644 --- a/app/src/main/res/xml/notification_settings.xml +++ b/app/src/main/res/xml/player_notification_settings.xml @@ -1,7 +1,7 @@ + android:title="@string/settings_category_player_notification_title"> + - - Date: Sat, 6 Nov 2021 21:51:33 +0100 Subject: [PATCH 28/46] Update app/src/main/res/values/strings.xml Removed "-" Co-authored-by: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8ac4f1b811..935827c5405 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,7 +144,7 @@ Appearance Debug Updates - Player-Notification + Player Notification Configure current playing stream notification Playing in background Playing in popup mode From 44fa98497f34a8a7ffaa267d52a63198fbbf5cf0 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 21 Nov 2021 19:42:41 +0100 Subject: [PATCH 29/46] Update app/src/main/res/values/strings.xml Co-authored-by: Stypox --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 935827c5405..ff2f8bb71e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,7 +144,7 @@ Appearance Debug Updates - Player Notification + Player notification Configure current playing stream notification Playing in background Playing in popup mode From 8ce996e0659435ecce0bf80cc764b61831705344 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 21 Nov 2021 22:53:10 +0100 Subject: [PATCH 30/46] Only check for new streams of subscriptions with enabled notifications automatically --- .../newpipe/database/feed/dao/FeedDAO.kt | 18 ++++++++++++++++++ .../newpipe/local/feed/FeedDatabaseManager.kt | 6 ++++++ .../feed/notifications/NotificationWorker.kt | 9 +++++---- .../local/feed/service/FeedLoadManager.kt | 17 ++++++++++++++--- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index 72692a9f591..d573788a65e 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -12,6 +12,7 @@ import org.schabi.newpipe.database.feed.model.FeedEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamStateEntity +import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionEntity import java.time.OffsetDateTime @@ -252,4 +253,21 @@ abstract class FeedDAO { """ ) abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable> + + @Query( + """ + SELECT s.* FROM subscriptions s + + LEFT JOIN feed_last_updated lu + ON s.uid = lu.subscription_id + + WHERE + (lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold) + AND s.notification_mode = :notificationMode + """ + ) + abstract fun getOutdatedWithNotificationMode( + outdatedThreshold: OffsetDateTime, + @NotificationMode notificationMode: Int + ): Flowable> } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 2acf002c0d4..7a8723ceb2f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -14,6 +14,7 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.local.subscription.FeedGroupIcon @@ -57,6 +58,11 @@ class FeedDatabaseManager(context: Context) { fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold) + fun outdatedSubscriptionsWithNotificationMode( + outdatedThreshold: OffsetDateTime, + @NotificationMode notificationMode: Int + ) = feedTable.getOutdatedWithNotificationMode(outdatedThreshold, notificationMode) + fun notLoadedCount(groupId: Long = FeedGroupEntity.GROUP_ALL_ID): Flowable { return when (groupId) { FeedGroupEntity.GROUP_ALL_ID -> feedTable.notLoadedCount() diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 6886c1e039d..1c75442a115 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -16,7 +16,6 @@ import androidx.work.rxjava3.RxWorker import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.R -import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.local.feed.service.FeedLoadManager import org.schabi.newpipe.local.feed.service.FeedLoadService import java.util.concurrent.TimeUnit @@ -36,12 +35,14 @@ class NotificationWorker( private val feedLoadManager = FeedLoadManager(appContext) override fun createWork(): Single = if (isEnabled(applicationContext)) { - feedLoadManager.startLoading(ignoreOutdatedThreshold = true) + feedLoadManager.startLoading( + ignoreOutdatedThreshold = true, + groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED + ) .map { feed -> feed.mapNotNull { x -> x.value?.takeIf { - it.notificationMode == NotificationMode.ENABLED && - it.newStreamsCount > 0 + it.newStreamsCount > 0 } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 528bcc5d299..fb9f73ff5d8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -12,6 +12,7 @@ import io.reactivex.rxjava3.processors.PublishProcessor import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity +import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.extractor.ListInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.FeedDatabaseManager @@ -41,6 +42,8 @@ class FeedLoadManager(private val context: Context) { * Start checking for new streams of a subscription group. * @param groupId The ID of the subscription group to load. * When using [FeedGroupEntity.GROUP_ALL_ID], all subscriptions are loaded. + * When using [GROUP_NOTIFICATION_ENABLED], only subscriptions with enabled notifications + * for new streams are loaded. * @param ignoreOutdatedThreshold When `false`, only subscriptions which have not been updated * within the `feed_update_threshold` are checked for updates. * This threshold can be set by the user in the app settings. @@ -73,6 +76,9 @@ class FeedLoadManager(private val context: Context) { */ val outdatedSubscriptions = when (groupId) { FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold) + GROUP_NOTIFICATION_ENABLED -> feedDatabaseManager.outdatedSubscriptionsWithNotificationMode( + outdatedThreshold, NotificationMode.ENABLED + ) else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) } @@ -248,16 +254,21 @@ class FeedLoadManager(private val context: Context) { } } - private companion object { + companion object { + + /** + * + */ + const val GROUP_NOTIFICATION_ENABLED = -2L /** * How many extractions will be running in parallel. */ - const val PARALLEL_EXTRACTIONS = 6 + private const val PARALLEL_EXTRACTIONS = 6 /** * Number of items to buffer to mass-insert in the database. */ - const val BUFFER_COUNT_BEFORE_INSERT = 20 + private const val BUFFER_COUNT_BEFORE_INSERT = 20 } } From a8fe2d7e83734dd7c0a9f6655242d3cbe7907c7f Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 28 Nov 2021 17:09:20 +0100 Subject: [PATCH 31/46] Fix "unsage use" warnings --- .../schabi/newpipe/local/feed/service/FeedLoadManager.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index fb9f73ff5d8..d5bcce4199c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -211,10 +211,10 @@ class FeedLoadManager(private val context: Context) { for (notification in list) { when { notification.isOnNext -> { - val subscriptionId = notification.value.uid - val info = notification.value.listInfo + val subscriptionId = notification.value!!.uid + val info = notification.value!!.listInfo - notification.value.newStreamsCount = countNewStreams(info.relatedItems) + notification.value!!.newStreamsCount = countNewStreams(info.relatedItems) feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems) subscriptionManager.updateFromInfo(subscriptionId, info) @@ -230,7 +230,7 @@ class FeedLoadManager(private val context: Context) { } notification.isOnError -> { val error = notification.error - feedResultsHolder.addError(error) + feedResultsHolder.addError(error!!) if (error is FeedLoadService.RequestException) { feedDatabaseManager.markAsOutdated(error.subscriptionId) From fd1155928efaf14c9d4364f0471210769d8c07d1 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Tue, 30 Nov 2021 23:31:36 +0100 Subject: [PATCH 32/46] Fix deciding which streams are new --- .../feed/notifications/NotificationWorker.kt | 2 +- .../local/feed/service/FeedLoadManager.kt | 24 +++++++++++-------- .../local/feed/service/FeedUpdateInfo.kt | 14 +++++------ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 1c75442a115..df1ddd5c926 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -42,7 +42,7 @@ class NotificationWorker( .map { feed -> feed.mapNotNull { x -> x.value?.takeIf { - it.newStreamsCount > 0 + it.newStreams.isNotEmpty() } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index d5bcce4199c..aa4b40f5b28 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -214,7 +214,10 @@ class FeedLoadManager(private val context: Context) { val subscriptionId = notification.value!!.uid val info = notification.value!!.listInfo - notification.value!!.newStreamsCount = countNewStreams(info.relatedItems) + notification.value!!.newStreams = filterNewStreams( + notification.value!!.listInfo.relatedItems + ) + feedDatabaseManager.upsertAll(subscriptionId, info.relatedItems) subscriptionManager.updateFromInfo(subscriptionId, info) @@ -241,16 +244,17 @@ class FeedLoadManager(private val context: Context) { } } - private fun countNewStreams(list: List): Int { - var count = 0 - for (item in list) { - if (feedDatabaseManager.doesStreamExist(item)) { - return count - } else { - count++ - } + private fun filterNewStreams(list: List): List { + return list.filter { + !feedDatabaseManager.doesStreamExist(it) && + it.uploadDate != null && + // Streams older than this date are automatically removed from the feed. + // Therefore, streams which are not in the database, + // but older than this date, are considered old. + it.uploadDate!!.offsetDateTime().isAfter( + FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE + ) } - return 0 } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt index a86578e1524..5f72a6b842a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt @@ -11,14 +11,17 @@ data class FeedUpdateInfo( val notificationMode: Int, val name: String, val avatarUrl: String, - val listInfo: ListInfo + val listInfo: ListInfo, ) { - constructor(subscription: SubscriptionEntity, listInfo: ListInfo) : this( + constructor( + subscription: SubscriptionEntity, + listInfo: ListInfo, + ) : this( uid = subscription.uid, notificationMode = subscription.notificationMode, name = subscription.name, avatarUrl = subscription.avatarUrl, - listInfo = listInfo + listInfo = listInfo, ) /** @@ -27,8 +30,5 @@ data class FeedUpdateInfo( val pseudoId: Int get() = listInfo.url.hashCode() - var newStreamsCount: Int = 0 - - val newStreams: List - get() = listInfo.relatedItems.take(newStreamsCount) + lateinit var newStreams: List } From 779d3dce6fdf4e1b2327b110c4fa9d40f72b377c Mon Sep 17 00:00:00 2001 From: TobiGr Date: Wed, 8 Dec 2021 19:28:38 +0100 Subject: [PATCH 33/46] Add app:singleLineTitle="false" to preferences --- app/src/main/res/xml/appearance_settings.xml | 1 + app/src/main/res/xml/debug_settings.xml | 2 ++ app/src/main/res/xml/notifications_settings.xml | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index cbe1d4c249b..f9e376178e8 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -27,6 +27,7 @@ android:fragment="org.schabi.newpipe.settings.PlayerNotificationSettingsFragment" android:summary="@string/settings_category_player_notification_summary" android:title="@string/settings_category_player_notification_title" + app:singleLineTitle="false" app:iconSpaceReserved="false" /> diff --git a/app/src/main/res/xml/notifications_settings.xml b/app/src/main/res/xml/notifications_settings.xml index 60d0428f7f6..e4c46fc8672 100644 --- a/app/src/main/res/xml/notifications_settings.xml +++ b/app/src/main/res/xml/notifications_settings.xml @@ -9,6 +9,7 @@ android:key="@string/enable_streams_notifications" android:summary="@string/enable_streams_notifications_summary" android:title="@string/enable_streams_notifications_title" + app:singleLineTitle="false" app:iconSpaceReserved="false" /> From 19fd7bc37e8f09cb5644f619acf1f77bc2ae3988 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 10 Dec 2021 23:52:28 +0100 Subject: [PATCH 34/46] Reduce power consumption Only schedule the chek for new streams if the user enaled the check. Cancel the worker when the user disables the notifications. --- .../java/org/schabi/newpipe/MainActivity.java | 5 +- .../feed/notifications/NotificationWorker.kt | 49 +++++++++++++++---- .../local/feed/service/FeedLoadManager.kt | 2 +- .../settings/NotificationsSettingsFragment.kt | 17 ++++++- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 2e3eec6c1a8..bbf45e0356d 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -165,8 +165,9 @@ protected void onCreate(final Bundle savedInstanceState) { } openMiniPlayerUponPlayerStarted(); - // schedule worker for checking for new streams and creating corresponding notifications - NotificationWorker.schedule(this); + // Schedule worker for checking for new streams and creating corresponding notifications + // if this is enabled by the user. + NotificationWorker.initialize(this); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index df1ddd5c926..48525864bbe 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -2,7 +2,6 @@ package org.schabi.newpipe.local.feed.notifications import android.content.Context import androidx.core.app.NotificationCompat -import androidx.preference.PreferenceManager import androidx.work.BackoffPolicy import androidx.work.Constraints import androidx.work.ExistingPeriodicWorkPolicy @@ -15,6 +14,7 @@ import androidx.work.WorkerParameters import androidx.work.rxjava3.RxWorker import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single +import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.local.feed.service.FeedLoadManager import org.schabi.newpipe.local.feed.service.FeedLoadService @@ -51,7 +51,11 @@ class NotificationWorker( .flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) } .toSingleDefault(Result.success()) .onErrorReturnItem(Result.failure()) - } else Single.just(Result.success()) + } else { + // Can be the case when the user disables notifications for NewPipe + // in the device's app settings. + Single.just(Result.success()) + } private fun createForegroundInfo(): ForegroundInfo { val notification = NotificationCompat.Builder( @@ -69,16 +73,32 @@ class NotificationWorker( companion object { - private const val TAG = "streams_notifications" + private const val TAG = App.PACKAGE_NAME + "_streams_notifications" + + private fun isEnabled(context: Context) = + NotificationHelper.areNewStreamsNotificationsEnabled(context) && + NotificationHelper.areNotificationsEnabledOnDevice(context) - private fun isEnabled(context: Context): Boolean { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean( - context.getString(R.string.enable_streams_notifications), - false - ) && NotificationHelper.areNotificationsEnabledOnDevice(context) + /** + * Schedules a task for the [NotificationWorker] + * if the (device and in-app) notifications are enabled, + * otherwise [cancel]s all scheduled tasks. + */ + @JvmStatic + fun initialize(context: Context) { + if (isEnabled(context)) { + schedule(context) + } else { + cancel(context) + } } + /** + * @param context the context to use + * @param options configuration options for the scheduler + * @param force Force the scheduler to use the new options + * by replacing the previously used worker. + */ fun schedule(context: Context, options: ScheduleOptions, force: Boolean = false) { val constraints = Constraints.Builder() .setRequiredNetworkType( @@ -113,6 +133,9 @@ class NotificationWorker( @JvmStatic fun schedule(context: Context) = schedule(context, ScheduleOptions.from(context)) + /** + * Check for new streams immediately + */ @JvmStatic fun runNow(context: Context) { val request = OneTimeWorkRequestBuilder() @@ -120,5 +143,13 @@ class NotificationWorker( .build() WorkManager.getInstance(context).enqueue(request) } + + /** + * Cancels all current work related to the [NotificationWorker]. + */ + @JvmStatic + fun cancel(context: Context) { + WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index aa4b40f5b28..dea498675f2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -261,7 +261,7 @@ class FeedLoadManager(private val context: Context) { companion object { /** - * + * Constant used to check for updates of subscriptions with [NotificationMode.ENABLED]. */ const val GROUP_NOTIFICATION_ENABLED = -2L diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 04f5a9b56c3..2cea04dc343 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -40,14 +40,29 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { val context = context ?: return - if (key == getString(R.string.streams_notifications_interval_key) || key == getString(R.string.streams_notifications_network_key)) { + if (key == getString(R.string.streams_notifications_interval_key) || + key == getString(R.string.streams_notifications_network_key) + ) { + // apply new configuration NotificationWorker.schedule(context, ScheduleOptions.from(context), true) + } else if (key == getString(R.string.enable_streams_notifications)) { + if (NotificationHelper.areNewStreamsNotificationsEnabled(context)) { + // Start the worker, because notifications were disabled previously. + NotificationWorker.schedule(context) + } else { + // The user disabled the notifications. Cancel the worker to save energy. + // A new one will be created once the notifications are enabled again. + NotificationWorker.cancel(context) + } } } override fun onResume() { super.onResume() + // Check whether the notifications are disabled in the device's app settings. + // If they are disabled, show a snackbar informing the user about that + // while allowing them to open the device's app settings. val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) preferenceScreen.isEnabled = enabled if (!enabled) { From 01f3ed0e5e95e4f7eac347bdb3b46fd9424e2b71 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 12 Dec 2021 20:18:16 +0100 Subject: [PATCH 35/46] Fix loading icon in streams notifications --- .../feed/notifications/NotificationHelper.kt | 32 ++++------ .../feed/notifications/NotificationIcon.kt | 60 ------------------- .../feed/notifications/NotificationWorker.kt | 51 +++++++++------- .../schabi/newpipe/util/PicassoHelper.java | 26 ++++++++ 4 files changed, 67 insertions(+), 102 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 2196da0d7a4..e61db1dfa11 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -4,7 +4,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory import android.net.Uri import android.os.Build import android.provider.Settings @@ -12,14 +11,11 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo import org.schabi.newpipe.util.NavigationHelper +import org.schabi.newpipe.util.PicassoHelper /** * Helper for everything related to show notifications about new streams to the user. @@ -34,7 +30,7 @@ class NotificationHelper(val context: Context) { * Show a notification about new streams from a single channel. * Opening the notification will open the corresponding channel page. */ - fun displayNewStreamsNotification(data: FeedUpdateInfo): Completable { + fun displayNewStreamsNotification(data: FeedUpdateInfo) { val newStreams: List = data.newStreams val summary = context.resources.getQuantityString( R.plurals.new_streams, newStreams.size, newStreams.size @@ -59,12 +55,6 @@ class NotificationHelper(val context: Context) { .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) - .setLargeIcon( - BitmapFactory.decodeResource( - context.resources, - R.drawable.ic_newpipe_triangle_white - ) - ) .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) .setColorized(true) .setAutoCancel(true) @@ -87,19 +77,17 @@ class NotificationHelper(val context: Context) { NavigationHelper .getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + PendingIntent.FLAG_IMMUTABLE + else + 0 ) ) - return Single.create(NotificationIcon(context, data.avatarUrl)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnSuccess { icon -> - builder.setLargeIcon(icon) - } - .ignoreElement() - .onErrorComplete() - .doOnComplete { manager.notify(data.pseudoId, builder.build()) } + PicassoHelper.loadNotificationIcon(data.avatarUrl, context) { bitmap -> + builder.setLargeIcon(bitmap) + manager.notify(data.pseudoId, builder.build()) + } } companion object { diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt deleted file mode 100644 index 0fb6877a662..00000000000 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.schabi.newpipe.local.feed.notifications - -import android.app.ActivityManager -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import com.squareup.picasso.Picasso -import com.squareup.picasso.Target -import io.reactivex.rxjava3.core.SingleEmitter -import io.reactivex.rxjava3.core.SingleOnSubscribe -import org.schabi.newpipe.util.PicassoHelper - -/** - * Helper class to handle loading and resizing of icons - * which are used going to be used in notifications. - */ -internal class NotificationIcon( - context: Context, - private val url: String, -) : SingleOnSubscribe { - - private val size = getIconSize(context) - - override fun subscribe(emitter: SingleEmitter) { - val target = SingleEmitterTarget(emitter) - PicassoHelper.loadThumbnail(url) - .resize(size, size) - .centerCrop() - .into(target) - emitter.setCancellable { - PicassoHelper.cancelRequest(target) - } - } - - private class SingleEmitterTarget(private val emitter: SingleEmitter) : Target { - override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom?) { - if (!emitter.isDisposed) { - emitter.onSuccess(bitmap) - } - } - - override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) { - emitter.tryOnError(e) - } - - override fun onPrepareLoad(placeHolderDrawable: Drawable?) = Unit - } - - private companion object { - - fun getIconSize(context: Context): Int { - val activityManager = context.getSystemService( - Context.ACTIVITY_SERVICE - ) as ActivityManager? - val size1 = activityManager?.launcherLargeIconSize ?: 0 - val size2 = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size) - return maxOf(size2, size1) - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 48525864bbe..365ba94d393 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.local.feed.notifications import android.content.Context +import android.util.Log import androidx.core.app.NotificationCompat import androidx.work.BackoffPolicy import androidx.work.Constraints @@ -12,7 +13,7 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager import androidx.work.WorkerParameters import androidx.work.rxjava3.RxWorker -import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.App import org.schabi.newpipe.R @@ -34,30 +35,39 @@ class NotificationWorker( } private val feedLoadManager = FeedLoadManager(appContext) - override fun createWork(): Single = if (isEnabled(applicationContext)) { + override fun createWork(): Single = if (areNotificationsEnabled(applicationContext)) { feedLoadManager.startLoading( ignoreOutdatedThreshold = true, groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED ) + .doOnSubscribe { showLoadingFeedForegroundNotification() } .map { feed -> - feed.mapNotNull { x -> - x.value?.takeIf { - it.newStreams.isNotEmpty() + // filter out feedUpdateInfo items (i.e. channels) with nothing new + feed.mapNotNull { + it.value?.takeIf { feedUpdateInfo -> + feedUpdateInfo.newStreams.isNotEmpty() } } } - .doOnSubscribe { setForegroundAsync(createForegroundInfo()) } - .flatMapObservable { Observable.fromIterable(it) } - .flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) } - .toSingleDefault(Result.success()) + .observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread + .map { feedUpdateInfoList -> + // display notifications for each feedUpdateInfo (i.e. channel) + feedUpdateInfoList.forEach { feedUpdateInfo -> + notificationHelper.displayNewStreamsNotification(feedUpdateInfo) + } + return@map Result.success() + } + .doOnError { throwable -> + Log.e(TAG, "Error while displaying streams notifications", throwable) + // TODO show error notification + } .onErrorReturnItem(Result.failure()) } else { - // Can be the case when the user disables notifications for NewPipe - // in the device's app settings. + // the user can disable streams notifications in the device's app settings Single.just(Result.success()) } - private fun createForegroundInfo(): ForegroundInfo { + private fun showLoadingFeedForegroundNotification() { val notification = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.notification_channel_id) @@ -68,14 +78,15 @@ class NotificationWorker( .setPriority(NotificationCompat.PRIORITY_LOW) .setContentTitle(applicationContext.getString(R.string.feed_notification_loading)) .build() - return ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification) + setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification)) } companion object { - private const val TAG = App.PACKAGE_NAME + "_streams_notifications" + private val TAG = NotificationWorker::class.java.simpleName + private const val WORK_TAG = App.PACKAGE_NAME + "_streams_notifications" - private fun isEnabled(context: Context) = + private fun areNotificationsEnabled(context: Context) = NotificationHelper.areNewStreamsNotificationsEnabled(context) && NotificationHelper.areNotificationsEnabledOnDevice(context) @@ -86,7 +97,7 @@ class NotificationWorker( */ @JvmStatic fun initialize(context: Context) { - if (isEnabled(context)) { + if (areNotificationsEnabled(context)) { schedule(context) } else { cancel(context) @@ -114,13 +125,13 @@ class NotificationWorker( options.interval, TimeUnit.MILLISECONDS ).setConstraints(constraints) - .addTag(TAG) + .addTag(WORK_TAG) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .build() WorkManager.getInstance(context) .enqueueUniquePeriodicWork( - TAG, + WORK_TAG, if (force) { ExistingPeriodicWorkPolicy.REPLACE } else { @@ -139,7 +150,7 @@ class NotificationWorker( @JvmStatic fun runNow(context: Context) { val request = OneTimeWorkRequestBuilder() - .addTag(TAG) + .addTag(WORK_TAG) .build() WorkManager.getInstance(context).enqueue(request) } @@ -149,7 +160,7 @@ class NotificationWorker( */ @JvmStatic fun cancel(context: Context) { - WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG) } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index efacd1fc225..d5552ae65c9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,6 +5,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import com.squareup.picasso.Cache; import com.squareup.picasso.LruCache; @@ -19,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -161,6 +164,29 @@ public String key() { } + public static void loadNotificationIcon(final String url, + final Context context, + final Consumer bitmapConsumer) { + loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) + .into(new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + bitmapConsumer.accept(bitmap); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + bitmapConsumer.accept(BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_newpipe_triangle_white)); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + } + }); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { if (!shouldLoadImages || isBlank(url)) { return picassoInstance From e68d49e7df33f1ae76a6d2383bbe6a839c2bcce8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 31 Dec 2021 18:34:02 +0100 Subject: [PATCH 36/46] Do not fetch all streams when disabling notifications for a channel --- .../local/subscription/SubscriptionManager.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index bcd64791e77..e4af6e0acc1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -75,7 +75,12 @@ class SubscriptionManager(context: Context) { Completable.fromAction { entity.notificationMode = mode subscriptionTable().update(entity) - }.andThen(rememberLastStream(entity)) + }.apply { + if (mode != NotificationMode.DISABLED) { + // notifications have just been enabled, mark all streams as "old" + andThen(rememberAllStreams(entity)) + } + } } } @@ -108,7 +113,12 @@ class SubscriptionManager(context: Context) { subscriptionTable.delete(subscriptionEntity) } - private fun rememberLastStream(subscription: SubscriptionEntity): Completable { + /** + * Fetches the list of videos for the provided channel and saves them in the database, so that + * they will be considered as "old"/"already seen" streams and the user will never notified + * about any one of them. + */ + private fun rememberAllStreams(subscription: SubscriptionEntity): Completable { return ExtractorHelper.getChannelInfo(subscription.serviceId, subscription.url, false) .map { channel -> channel.relatedItems.map { stream -> StreamEntity(stream) } } .flatMapCompletable { entities -> From fcd2d63df47709a1e96c79f12f67247c02839aa1 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 31 Dec 2021 18:38:35 +0100 Subject: [PATCH 37/46] Don't show any channel notification thumbnail if it could not be loaded --- .../newpipe/local/feed/notifications/NotificationHelper.kt | 4 ++-- app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index e61db1dfa11..8ce59b14a90 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -84,8 +84,8 @@ class NotificationHelper(val context: Context) { ) ) - PicassoHelper.loadNotificationIcon(data.avatarUrl, context) { bitmap -> - builder.setLargeIcon(bitmap) + PicassoHelper.loadNotificationIcon(data.avatarUrl) { bitmap -> + bitmap?.let { builder.setLargeIcon(it) } // set only if != null manager.notify(data.pseudoId, builder.build()) } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index d5552ae65c9..9e157f45830 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,7 +5,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import com.squareup.picasso.Cache; @@ -165,7 +164,6 @@ public String key() { public static void loadNotificationIcon(final String url, - final Context context, final Consumer bitmapConsumer) { loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) .into(new Target() { @@ -176,8 +174,7 @@ public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - bitmapConsumer.accept(BitmapFactory.decodeResource(context.getResources(), - R.drawable.ic_newpipe_triangle_white)); + bitmapConsumer.accept(null); } @Override From ccbc3af9645b98e022750c668e0d0721396b15e8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 31 Dec 2021 20:04:56 +0100 Subject: [PATCH 38/46] Show error notification when new streams notifications failed --- .../main/java/org/schabi/newpipe/error/UserAction.java | 1 + .../local/feed/notifications/NotificationWorker.kt | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index e8dec9556b2..8e7a455b4d5 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -26,6 +26,7 @@ public enum UserAction { DOWNLOAD_OPEN_DIALOG("download open dialog"), DOWNLOAD_POSTPROCESSING("download post-processing"), DOWNLOAD_FAILED("download failed"), + NEW_STREAMS_NOTIFICATIONS("new streams notifications"), PREFERENCES_MIGRATION("migration of preferences"), SHARE_TO_NEWPIPE("share to newpipe"), CHECK_FOR_NEW_APP_VERSION("check for new app version"); diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 365ba94d393..0a0cbd1f3a6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -17,6 +17,9 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.App import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.ErrorUtil +import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.local.feed.service.FeedLoadManager import org.schabi.newpipe.local.feed.service.FeedLoadService import java.util.concurrent.TimeUnit @@ -59,7 +62,10 @@ class NotificationWorker( } .doOnError { throwable -> Log.e(TAG, "Error while displaying streams notifications", throwable) - // TODO show error notification + ErrorUtil.createNotification( + applicationContext, + ErrorInfo(throwable, UserAction.NEW_STREAMS_NOTIFICATIONS, "main worker") + ) } .onErrorReturnItem(Result.failure()) } else { From 6dcde96f85cc0173b81c6875282a04bfa5a60c1a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:31:55 +0100 Subject: [PATCH 39/46] Fixed some Sonarlint warnings --- .../settings/notifications/NotificationModeConfigAdapter.kt | 2 +- app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt index 156877b4e93..6ae264bb5fa 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigAdapter.kt @@ -92,7 +92,7 @@ class NotificationModeConfigAdapter( } else { NotificationMode.ENABLED } - listener.onModeChange(adapterPosition, mode) + listener.onModeChange(bindingAdapterPosition, mode) } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 9e157f45830..1690f722360 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -179,6 +179,7 @@ public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { @Override public void onPrepareLoad(final Drawable placeHolderDrawable) { + // Nothing to do } }); } From cc34734131a5e4b8d51eb3246a68ee5fe52d37e3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:48:46 +0100 Subject: [PATCH 40/46] Refactored ``initNotificationChannels`` --- app/src/main/java/org/schabi/newpipe/App.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index b0931d3c1fb..26b50855ef4 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -32,7 +32,7 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -225,45 +225,44 @@ protected void initACRA() { private void initNotificationChannels() { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels - final NotificationChannelCompat mainChannel = new NotificationChannelCompat + final List notificationChannelCompats = new ArrayList<>(); + notificationChannelCompats.add(new NotificationChannelCompat .Builder(getString(R.string.notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.notification_channel_name)) .setDescription(getString(R.string.notification_channel_description)) - .build(); + .build()); - final NotificationChannelCompat appUpdateChannel = new NotificationChannelCompat + notificationChannelCompats.add(new NotificationChannelCompat .Builder(getString(R.string.app_update_notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.app_update_notification_channel_name)) .setDescription(getString(R.string.app_update_notification_channel_description)) - .build(); + .build()); - final NotificationChannelCompat hashChannel = new NotificationChannelCompat + notificationChannelCompats.add(new NotificationChannelCompat .Builder(getString(R.string.hash_channel_id), NotificationManagerCompat.IMPORTANCE_HIGH) .setName(getString(R.string.hash_channel_name)) .setDescription(getString(R.string.hash_channel_description)) - .build(); + .build()); - final NotificationChannelCompat errorReportChannel = new NotificationChannelCompat + notificationChannelCompats.add(new NotificationChannelCompat .Builder(getString(R.string.error_report_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) .setName(getString(R.string.error_report_channel_name)) .setDescription(getString(R.string.error_report_channel_description)) - .build(); + .build()); - - final NotificationChannelCompat newStreamsChannel = new NotificationChannelCompat + notificationChannelCompats.add(new NotificationChannelCompat .Builder(getString(R.string.streams_notification_channel_id), NotificationManagerCompat.IMPORTANCE_DEFAULT) .setName(getString(R.string.streams_notification_channel_name)) .setDescription(getString(R.string.streams_notification_channel_description)) - .build(); + .build()); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.createNotificationChannelsCompat(Arrays.asList(mainChannel, - appUpdateChannel, hashChannel, errorReportChannel, newStreamsChannel)); + notificationManager.createNotificationChannelsCompat(notificationChannelCompats); } protected boolean isDisposedRxExceptionsReported() { From 0397a3120f515028fd8ea88c3bce5f0826121cc7 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:55:55 +0100 Subject: [PATCH 41/46] Removed unused string --- app/src/main/res/values-bg/strings.xml | 1 - app/src/main/res/values-te/strings.xml | 1 - app/src/main/res/values-zh-rHK/strings.xml | 1 - 3 files changed, 3 deletions(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 065a8fbced2..447f0a96442 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -513,7 +513,6 @@ Песни Изпълнители Албуми - Известие Скорошни Категория Изтеглянето започна diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 5c93dec4b98..872e1e8d14c 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -233,5 +233,4 @@ ఉదాహరణ URLని నమోదు చేయండి ఉదాహరణను ధృవీకరించడం సాధ్యపడలేదు ఉదాహరణ ఇప్పటికే ఉంది - నోటిఫికేషన్ \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index dd027c43e0a..fdedf8ece00 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -181,7 +181,6 @@ 專輯 淨係支援 HTTPS 嘅 URL 除錯 - 通知 復原 刪除咗個檔案 幾時都係 From 40ea51e622c3b9fadb0e3b1983b5d95214f30b62 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 24 Jan 2022 10:12:25 +0100 Subject: [PATCH 42/46] Add more checking frequencies, use DurationListPreference --- .../feed/notifications/ScheduleOptions.kt | 4 +-- app/src/main/res/values-ru/strings.xml | 5 --- app/src/main/res/values/settings_keys.xml | 32 +++++++++++-------- app/src/main/res/values/strings.xml | 5 --- .../main/res/xml/notifications_settings.xml | 2 +- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt index 0dbc1539559..37e8fc39ee0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt @@ -19,10 +19,10 @@ data class ScheduleOptions( fun from(context: Context): ScheduleOptions { val preferences = PreferenceManager.getDefaultSharedPreferences(context) return ScheduleOptions( - interval = TimeUnit.HOURS.toMillis( + interval = TimeUnit.SECONDS.toMillis( preferences.getString( context.getString(R.string.streams_notifications_interval_key), - context.getString(R.string.streams_notifications_interval_default) + null )?.toLongOrNull() ?: context.getString( R.string.streams_notifications_interval_default ).toLong() diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d39c7315e37..c77cbc5886b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -703,11 +703,6 @@ Частота проверки Уведомлять о новых видео Получать уведомления о новых видео из каналов, на которые Вы подписаны - Каждый час - Каждые 2 часа - Каждые 3 часа - Дважды в день - Каждый день Тип подключения Любая сеть Уведомления отключены diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index a28f96d97f8..2a7754395d6 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1271,20 +1271,26 @@ recaptcha_cookies_key enable_streams_notifications streams_notifications_interval - 3 - - 1 - 2 - 3 - 12 - 24 - + 14400 + - @string/every_hour - @string/every_two_hours - @string/every_three_hours - @string/twice_per_day - @string/every_day + 15 minutes + 30 minutes + 1 hour + 2 hours + 4 hours + 12 hours + 1 day + + + + 900 + 1800 + 3600 + 7200 + 14400 + 43200 + 86400 streams_notifications_network any diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3bd70f26ff3..03197bb2b3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -539,11 +539,6 @@ New streams notifications Notify about new streams from subscriptions Checking frequency - Every hour - Every 2 hours - Every 3 hours - Twice per day - Every day Required network connection Any network diff --git a/app/src/main/res/xml/notifications_settings.xml b/app/src/main/res/xml/notifications_settings.xml index e4c46fc8672..5ea410846b3 100644 --- a/app/src/main/res/xml/notifications_settings.xml +++ b/app/src/main/res/xml/notifications_settings.xml @@ -12,7 +12,7 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> - Date: Wed, 23 Feb 2022 18:16:07 +0100 Subject: [PATCH 43/46] Update android work library version to 2.7.1 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 02408433174..797e76997df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,7 @@ ext { androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.3.0' - androidxWorkVersion = '2.5.0' + androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' exoPlayerVersion = '2.14.2' From 5fea12d8ebb95a8f6a14777e7fa5c5d7a0a3ed0e Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 23 Feb 2022 19:45:49 +0100 Subject: [PATCH 44/46] Small code improvements Removed some non-translatable strings and just hardcoded them in the code, like it's being done for other string separators. This also deduplicates some code by using Localization. Used some Kotlin feature to reduce code. --- .../local/feed/notifications/NotificationHelper.kt | 13 +++---------- .../newpipe/local/feed/service/FeedLoadManager.kt | 14 +++++++------- .../local/subscription/SubscriptionManager.kt | 9 +++++++-- .../settings/NotificationsSettingsFragment.kt | 8 +------- .../org/schabi/newpipe/util/PicassoHelper.java | 4 ---- app/src/main/res/values/strings.xml | 2 -- 6 files changed, 18 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 8ce59b14a90..3a08b3e4aa5 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -14,6 +14,7 @@ import androidx.preference.PreferenceManager import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo +import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.PicassoHelper @@ -39,13 +40,7 @@ class NotificationHelper(val context: Context) { context, context.getString(R.string.streams_notification_channel_id) ) - .setContentTitle( - context.getString( - R.string.notification_title_pattern, - data.name, - summary - ) - ) + .setContentTitle(Localization.concatenateStrings(data.name, summary)) .setContentText( data.listInfo.relatedItems.joinToString( context.getString(R.string.enumeration_comma) @@ -62,9 +57,7 @@ class NotificationHelper(val context: Context) { // Build style val style = NotificationCompat.InboxStyle() - for (stream in newStreams) { - style.addLine(stream.name) - } + newStreams.forEach { style.addLine(it.name) } style.setSummaryText(summary) style.setBigContentTitle(data.name) builder.setStyle(style) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 8c608eb0ebe..fec50a579a7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -40,14 +40,14 @@ class FeedLoadManager(private val context: Context) { /** * Start checking for new streams of a subscription group. - * @param groupId The ID of the subscription group to load. - * When using [FeedGroupEntity.GROUP_ALL_ID], all subscriptions are loaded. - * When using [GROUP_NOTIFICATION_ENABLED], only subscriptions with enabled notifications - * for new streams are loaded. + * @param groupId The ID of the subscription group to load. When using + * [FeedGroupEntity.GROUP_ALL_ID], all subscriptions are loaded. When using + * [GROUP_NOTIFICATION_ENABLED], only subscriptions with enabled notifications for new streams + * are loaded. Using an id of a group created by the user results in that specific group to be + * loaded. * @param ignoreOutdatedThreshold When `false`, only subscriptions which have not been updated - * within the `feed_update_threshold` are checked for updates. - * This threshold can be set by the user in the app settings. - * When `true`, all subscriptions are checked for new streams. + * within the `feed_update_threshold` are checked for updates. This threshold can be set by + * the user in the app settings. When `true`, all subscriptions are checked for new streams. */ fun startLoading( groupId: Long = FeedGroupEntity.GROUP_ALL_ID, diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index e4af6e0acc1..b17f498015e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -90,7 +90,12 @@ class SubscriptionManager(context: Context) { if (info is FeedInfo) { subscriptionEntity.name = info.name } else if (info is ChannelInfo) { - subscriptionEntity.setData(info.name, info.avatarUrl, info.description, info.subscriberCount) + subscriptionEntity.setData( + info.name, + info.avatarUrl, + info.description, + info.subscriberCount + ) } subscriptionTable.update(subscriptionEntity) @@ -115,7 +120,7 @@ class SubscriptionManager(context: Context) { /** * Fetches the list of videos for the provided channel and saves them in the database, so that - * they will be considered as "old"/"already seen" streams and the user will never notified + * they will be considered as "old"/"already seen" streams and the user will never be notified * about any one of them. */ private fun rememberAllStreams(subscription: SubscriptionEntity): Completable { diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 4340e7ce83e..e823c2fcf9c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -108,13 +108,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen private fun updateSubscriptions(subscriptions: List) { val notified = subscriptions.count { it.notificationMode != NotificationMode.DISABLED } val preference = findPreference(getString(R.string.streams_notifications_channels_key)) - if (preference != null) { - preference.summary = preference.context.getString( - R.string.streams_notifications_channels_summary, - notified, - subscriptions.size - ) - } + preference?.apply { summary = "$notified/${subscriptions.size}" } } private fun onError(e: Throwable) { diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 1690f722360..da86ab1a478 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -81,10 +81,6 @@ public static void cancelTag(final Object tag) { picassoInstance.cancelTag(tag); } - public static void cancelRequest(final Target target) { - picassoInstance.cancelRequest(target); - } - public static void setIndicatorsEnabled(final boolean enabled) { picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3c9a3f705f5..a92849ed7da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -721,7 +721,5 @@ Get notified You now subscribed to this channel , - %s • %s - %d/%d Toggle all \ No newline at end of file From 3d9d25df52cfa35106090a52c15c96984116e347 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 19 Mar 2022 21:55:00 +0100 Subject: [PATCH 45/46] Remove backoff criteria: it never kicked in It never kicked in since we are never returning a retry() Result, but always either success() or failure() (see createWork() function). Also, there is already a default (exponential backoff starting from 30 seconds), so no need to override it. --- .../newpipe/local/feed/notifications/NotificationWorker.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 0a0cbd1f3a6..61e5c7d9ed8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -132,7 +132,6 @@ class NotificationWorker( TimeUnit.MILLISECONDS ).setConstraints(constraints) .addTag(WORK_TAG) - .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .build() WorkManager.getInstance(context) From 66fffce87ca03a6c86a07efb290ff5e2cec5b3b9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 19 Mar 2022 22:44:59 +0100 Subject: [PATCH 46/46] Make "Player notification" PreferenceScreen searchable --- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/xml/appearance_settings.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 6195b4e257e..d72be0ccf55 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1325,4 +1325,5 @@ @string/wifi_only streams_notifications_channels + player_notification_screen diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index f9e376178e8..2afd39800d3 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -24,6 +24,7 @@ app:iconSpaceReserved="false" />