From da9bd1d420ebc55c9697ba996caf79d793317a2f Mon Sep 17 00:00:00 2001 From: Vasiliy Date: Wed, 8 May 2019 20:17:54 +0300 Subject: [PATCH 001/213] 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 1219aeb33..61a0cdc2b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -108,6 +108,7 @@ ext { leakCanaryVersion = '2.5' stethoVersion = '1.6.0' mockitoVersion = '3.6.0' + workVersion = '2.5.0' } configurations { @@ -213,6 +214,8 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.webkit:webkit:1.4.0' implementation 'com.google.android.material:material:1.2.1' + implementation "androidx.work:work-runtime:${workVersion}" + implementation "androidx.work:work-rxjava2:${workVersion}" /** Third-party libraries **/ // Instance state boilerplate elimination diff --git a/app/src/debug/res/xml/main_settings.xml b/app/src/debug/res/xml/main_settings.xml index d482d033c..4e812bb1c 100644 --- a/app/src/debug/res/xml/main_settings.xml +++ b/app/src/debug/res/xml/main_settings.xml @@ -40,6 +40,12 @@ android:title="@string/settings_category_notification_title" app:iconSpaceReserved="false" /> + + { @Insert(onConflict = OnConflictStrategy.IGNORE) internal abstract fun silentInsertAllInternal(streams: List): List + @Query("SELECT COUNT(*) != 0 FROM streams WHERE url = :url AND service_id = :serviceId") + internal abstract fun exists(serviceId: Long, url: String?): Boolean + @Query( """ SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java b/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java new file mode 100644 index 000000000..d817032ee --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/NotificationMode.java @@ -0,0 +1,14 @@ +package org.schabi.newpipe.database.subscription; + +import androidx.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({NotificationMode.DISABLED, NotificationMode.ENABLED_DEFAULT}) +@Retention(RetentionPolicy.SOURCE) +public @interface NotificationMode { + + int DISABLED = 0; + int ENABLED_DEFAULT = 1; + //other values reserved for the future +} diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java index 1cf38dbca..0e4bda490 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java @@ -26,6 +26,7 @@ public class SubscriptionEntity { public static final String SUBSCRIPTION_AVATAR_URL = "avatar_url"; public static final String SUBSCRIPTION_SUBSCRIBER_COUNT = "subscriber_count"; public static final String SUBSCRIPTION_DESCRIPTION = "description"; + public static final String SUBSCRIPTION_NOTIFICATION_MODE = "notification_mode"; @PrimaryKey(autoGenerate = true) private long uid = 0; @@ -48,6 +49,9 @@ public class SubscriptionEntity { @ColumnInfo(name = SUBSCRIPTION_DESCRIPTION) private String description; + @ColumnInfo(name = SUBSCRIPTION_NOTIFICATION_MODE) + private int notificationMode; + @Ignore public static SubscriptionEntity from(@NonNull final ChannelInfo info) { final SubscriptionEntity result = new SubscriptionEntity(); @@ -114,6 +118,15 @@ public class SubscriptionEntity { this.description = description; } + @NotificationMode + public int getNotificationMode() { + return notificationMode; + } + + public void setNotificationMode(@NotificationMode final int notificationMode) { + this.notificationMode = notificationMode; + } + @Ignore public void setData(final String n, final String au, final String d, final Long sc) { this.setName(n); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 548ae7b2c..754036dfd 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -1,6 +1,11 @@ package org.schabi.newpipe.fragments.list.channel; +import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor; + import android.content.Context; +import android.graphics.Color; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -19,9 +24,11 @@ import androidx.appcompat.app.ActionBar; import androidx.core.content.ContextCompat; import androidx.viewbinding.ViewBinding; +import com.google.android.material.snackbar.Snackbar; import com.jakewharton.rxbinding4.view.RxView; import org.schabi.newpipe.R; +import org.schabi.newpipe.database.subscription.NotificationMode; import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.databinding.ChannelHeaderBinding; import org.schabi.newpipe.databinding.FragmentChannelBinding; @@ -37,6 +44,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; +import org.schabi.newpipe.notifications.NotificationHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; @@ -60,10 +68,6 @@ import io.reactivex.rxjava3.functions.Consumer; import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.schedulers.Schedulers; -import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor; - public class ChannelFragment extends BaseListInfoFragment implements View.OnClickListener { @@ -84,6 +88,7 @@ public class ChannelFragment extends BaseListInfoFragment private PlaylistControlBinding playlistControlBinding; private MenuItem menuRssButton; + private MenuItem menuNotifyButton; public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { @@ -181,6 +186,7 @@ public class ChannelFragment extends BaseListInfoFragment + "menu = [" + menu + "], inflater = [" + inflater + "]"); } menuRssButton = menu.findItem(R.id.menu_item_rss); + menuNotifyButton = menu.findItem(R.id.menu_item_notify); } } @@ -197,6 +203,11 @@ public class ChannelFragment extends BaseListInfoFragment case R.id.action_settings: NavigationHelper.openSettings(requireContext()); break; + case R.id.menu_item_notify: + final boolean value = !item.isChecked(); + item.setEnabled(false); + setNotify(value); + break; case R.id.menu_item_rss: openRssFeed(); break; @@ -238,15 +249,22 @@ public class ChannelFragment extends BaseListInfoFragment .subscribe(getSubscribeUpdateMonitor(info), onError)); disposables.add(observable - // Some updates are very rapid - // (for example when calling the updateSubscription(info)) - // so only update the UI for the latest emission - // ("sync" the subscribe button's state) - .debounce(100, TimeUnit.MILLISECONDS) + .map(List::isEmpty) + .distinctUntilChanged() .observeOn(AndroidSchedulers.mainThread()) - .subscribe((List subscriptionEntities) -> - updateSubscribeButton(!subscriptionEntities.isEmpty()), onError)); + .subscribe((Boolean isEmpty) -> updateSubscribeButton(!isEmpty), onError)); + disposables.add(observable + .map(List::isEmpty) + .filter(x -> NotificationHelper.isNewStreamsNotificationsEnabled(requireContext())) + .distinctUntilChanged() + .skip(1) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(isEmpty -> { + if (!isEmpty) { + showNotifySnackbar(); + } + }, onError)); } private Function mapOnSubscribe(final SubscriptionEntity subscription, @@ -326,6 +344,7 @@ public class ChannelFragment extends BaseListInfoFragment info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount()); + updateNotifyButton(null); subscribeButtonMonitor = monitorSubscribeButton( headerBinding.channelSubscribeButton, mapOnSubscribe(channel, info)); } else { @@ -333,6 +352,7 @@ public class ChannelFragment extends BaseListInfoFragment 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 @@ public class ChannelFragment extends BaseListInfoFragment 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 fb9cffa98..442a867b3 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 000000000..9a3b2cbf3 --- /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 000000000..6207cd613 --- /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 000000000..fc59b55f0 --- /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 000000000..24dbc82e0 --- /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 000000000..b0617b303 --- /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 000000000..6f7c3881b --- /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 000000000..62a819e64 --- /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 000000000..7aa0826e5 --- /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 000000000..44d2256af --- /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 eba24020f..859bfa31d 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 final class NavigationHelper { 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 000000000..e95f5b4ac --- /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 000000000..024381816 --- /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 000000000..d1ae01bfe --- /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 000000000..b68692dd7 --- /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 af9020626..d6c54b680 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 e5180c51e..91166c754 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 63ca8f827..1592b8b59 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 9261dfae1..42d2233c8 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 e5600b5e6..f84cc83b1 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 000000000..4390dc48c --- /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 1d5241102..e999aa8c4 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 002/213] 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 4e812bb1c..1b1c17e85 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 1e1b03b4f..e363469c8 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 class AppearanceSettingsFragment extends BasePreferenceFragment { @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 f9280663b..30821be51 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 0795d378c..7dc699202 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 f84cc83b1..40dcd17c9 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 f0c6f2aa1..6bc9f3381 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 003/213] 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 000000000..b80120074 --- /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 004/213] 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 61a0cdc2b..a9a4e7001 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 55b2c7708..31c8dd40c 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 androidx.preference.Preference; 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 class DebugSettingsFragment extends BasePreferenceFragment { = 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 class DebugSettingsFragment extends BasePreferenceFragment { 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 67b7b2527..3c2866c94 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 @@ public class App extends MultiDexApplication { .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 7770d25dc..89964acbc 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.BackPressable; 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 fbb46134d..a22fd2bb9 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 754036dfd..941035b07 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.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; -import org.schabi.newpipe.notifications.NotificationHelper; +import org.schabi.newpipe.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 @@ public class ChannelFragment extends BaseListInfoFragment .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 ff7c2848e..c4a9f6af9 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 000000000..ec5cb790f --- /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 000000000..eea39dfd3 --- /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 24dbc82e0..896735983 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 b0617b303..30e8d5515 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 000000000..79c4b747b --- /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 98ff5914d..ea181d3d9 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 var loadingDisposable: Disposable? = null + private var notificationDisposable: Disposable? = null - private lateinit var feedDatabaseManager: FeedDatabaseManager - private lateinit var feedResultsHolder: ResultsHolder - - 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 000000000..703f593ad --- /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 000000000..729f2c009 --- /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 000000000..a86578e15 --- /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 9a3b2cbf3..000000000 --- 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 6207cd613..000000000 --- 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 fc59b55f0..000000000 --- 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 6f7c3881b..000000000 --- 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 62a819e64..01a3ca6eb 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 e95f5b4ac..000000000 --- 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 42d2233c8..efb91cbc5 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 40dcd17c9..d8906200f 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 22abebcae..206401c23 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 005/213] Migrate NotificationIcon to Picasso --- .../5.json | 719 ++++++++++++++++++ .../feed/notifications/NotificationIcon.kt | 42 +- .../schabi/newpipe/util/PicassoHelper.java | 9 +- 3 files changed, 751 insertions(+), 19 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 000000000..9a1c62995 --- /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 eea39dfd3..1073945d4 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) - } + val target = SingleEmitterTarget(emitter) + PicassoHelper.loadThumbnail(url) + .resize(size, size) + .centerCrop() + .into(target) + emitter.setCancellable { + PicassoHelper.cancelRequest(target) + } + } - override fun onLoadingComplete(imageUri: String?, view: View?, loadedImage: Bitmap) { - emitter.onSuccess(loadedImage) - } + 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 e15ecd277..efacd1fc2 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.LruCache; 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 java.util.concurrent.TimeUnit; 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 final class PicassoHelper { 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 006/213] 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 896735983..ee16a403d 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 79c4b747b..bea699999 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 ea181d3d9..0d5d904e8 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 007/213] 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 01a3ca6eb..50fb95450 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 7aa0826e5..000000000 --- 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 000000000..eb94fb843 --- /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 44d2256af..1689747e2 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 000000000..37d806044 --- /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 000000000..79b9cd7c1 --- /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 30821be51..0e29edcc4 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 d8906200f..4da2ac60c 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 008/213] 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 c4a9f6af9..996293225 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 bea699999..6a44935c5 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 009/213] 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 b80120074..158994cdd 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 010/213] 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 d817032ee..07e0eb7d3 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 androidx.annotation.IntDef; 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 dcda6db0b..8ed6b5149 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 @@ public class ChannelFragment extends BaseListInfoFragment } 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 ee16a403d..82e923d94 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 eb94fb843..9c4797237 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 1689747e2..2fa916166 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 011/213] 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 8ed6b5149..752b05d5a 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 @@ public class ChannelFragment extends BaseListInfoFragment 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 012/213] 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 752b05d5a..5e2e24fab 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 @@ public class ChannelFragment extends BaseListInfoFragment .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 @@ public class ChannelFragment extends BaseListInfoFragment } 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 @@ public class ChannelFragment extends BaseListInfoFragment ); } + /** + * 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 ec5cb790f..fac3b8f72 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 82e923d94..daae52fdd 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 30e8d5515..0dbc15395 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 013/213] comments & rename --- .../feed/notifications/NotificationIcon.kt | 4 ++++ ...pter.kt => NotificationModeConfigAdapter.kt} | 16 ++++++++++++---- ...ent.kt => NotificationModeConfigFragment.kt} | 17 ++++++++++++----- app/src/main/res/xml/notifications_settings.xml | 2 +- 4 files changed, 29 insertions(+), 10 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 1073945d4..0fb6877a6 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 2fa916166..156877b4e 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 +import org.schabi.newpipe.settings.notifications.NotificationModeConfigAdapter.SubscriptionHolder -class NotificationsConfigAdapter( +/** + * 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 9c4797237..9021fd68c 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 4390dc48c..60d0428f7 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 014/213] 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 fac3b8f72..fa26be37a 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 daae52fdd..afdeee7f4 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 50fb95450..938b7ff8d 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 80267a9dd..f70002409 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.Fragment; 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 org.schabi.newpipe.util.external_communication.ShareUtils; 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 015/213] 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 fa26be37a..87101abcd 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 938b7ff8d..e1f7ed2c2 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 016/213] 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 5e2e24fab..fe0122ea5 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 @@ public class ChannelFragment extends BaseListInfoFragment } 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 87101abcd..a9cdd852d 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 6a44935c5..528bcc5d2 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 34ab93c9bd214c02882386db09392e02b59b0540 Mon Sep 17 00:00:00 2001 From: ktprograms Date: Mon, 1 Nov 2021 11:48:20 +0800 Subject: [PATCH 017/213] Fix player controls not hiding if resumed from media button --- app/src/main/java/org/schabi/newpipe/player/Player.java | 3 +++ .../org/schabi/newpipe/player/playback/PlayerMediaSession.java | 1 + 2 files changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 22e66e793..4e2a9d065 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3754,6 +3754,9 @@ public final class Player implements case KeyEvent.KEYCODE_SPACE: if (isFullscreen) { playPause(); + if (isPlaying()) { + hideControls(0, 0); + } } break; case KeyEvent.KEYCODE_BACK: diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 9dcb12344..fbe12efb1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -88,6 +88,7 @@ public class PlayerMediaSession implements MediaSessionCallback { @Override public void play() { player.play(); + player.hideControls(0, 0); } @Override 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 018/213] 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 488bfa3ba..b8daca97e 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 019/213] 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 158994cdd..28dea13e9 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 020/213] 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 3601e5c61..0cfa4ee62 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 @@ public class App extends MultiDexApplication { // 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 021/213] 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 afdeee7f4..6886c1e03 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 022/213] 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 e1f7ed2c2..04f5a9b56 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 023/213] 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 dda0dd0e4..2e3eec6c1 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 @@ public class MainActivity extends AppCompatActivity { } 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 024/213] 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 a9cdd852d..2196da0d7 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 025/213] 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 d6c54b680..5ea8f8c95 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 026/213] 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: Wed, 3 Nov 2021 08:26:13 +0800 Subject: [PATCH 027/213] Enable play/pause with space key even when not in fullscreen player --- app/src/main/java/org/schabi/newpipe/player/Player.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 4e2a9d065..2a88445bb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3752,11 +3752,9 @@ public final class Player implements default: break; case KeyEvent.KEYCODE_SPACE: - if (isFullscreen) { - playPause(); - if (isPlaying()) { - hideControls(0, 0); - } + playPause(); + if (isPlaying()) { + hideControls(0, 0); } break; case KeyEvent.KEYCODE_BACK: From 1289b1a283dbde7bbea8d325cd8873e10c73ade6 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 5 Nov 2021 13:17:33 +0100 Subject: [PATCH 028/213] 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 2911e8d19..bcd64791e 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 029/213] 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 aa21c4422..1e1d08856 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 final class NewPipeSettings { 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 e03aa4074..1789606d1 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 55f41563c..0bcb5afbb 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 a5775792a..b7cf7481e 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 19ed896f0..513dd5170 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 4d4602daa..59530ee86 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 20b01f605..748855731 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 4b185b301..5dd5daca1 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 831364013..94fa9ee9e 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 11225e706..ea587e6af 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 5e1d1b201..d191d6dcc 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 65af5ffff..cc5d7d0fc 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 5a4091db5..fb6dcafcb 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 105646a16..efa38337d 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 1775e1ca8..d4750c115 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 769272345..91df86e5c 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 0c8ba0a9e..73cb3a858 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 107c0f624..08de77661 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 5b8161bf2..88f27b920 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 58f6cef81..0b6712a69 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 cd6777b5e..92be86373 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 8d27ddff9..92541f40f 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 f22ecff86..1cfa6c47c 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 1d054155a..de469455d 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 5f295bda8..3439b5e8c 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 1f8ef42a0..1db400959 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 e19fade83..93f38692e 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 d7da33600..7d7a8b55a 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 dddf0dd6a..6e264bf13 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 2ee99d452..f4467cfcb 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 f2540f205..664389514 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 d7ac85dd1..2ed1163f3 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 880c45aee..7befbb800 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 be2a8086f..883347e62 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 d0249568e..273702727 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 58d40b61d..e7914fe29 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 eb78ca44c..1713e7291 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 76aef646f..6af6ec25d 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 9cce73360..37142af1b 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 be7627079..0584e12e6 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 b835c68dc..6570e1044 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 8fad329f3..f1aa81b6b 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 b4de246eb..812347ceb 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 7c85328d0..693a33910 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 4fa8e4e18..8b15b674e 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 c1d95a797..ddce62f46 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 19bd15385..89540e819 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 7e16d5858..d351b25f2 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 1afb7990f..c35d299df 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 3df4ff0af..ae2e94f42 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 dbfef543d..e9afcee70 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 bf9e7a768..a113094da 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 10ab40e69..d8bcb6fd1 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 e27d9e8a8..79020f28a 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 4fc9778be..30fb72a2a 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 f318de744..d8e8c7bcc 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 d53dcec8b..2dba77eb9 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 ecfbb07ff..acea873f2 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 1f427504b..ba43cdd61 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 3635aa4b4..803e22f6a 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 e5c499119..28840775d 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 e67016142..e8ac4f1b8 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 6bc9f3381..cbe1d4c24 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 13edfcb56..c272fc766 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 030/213] 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 e8ac4f1b8..935827c54 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 031/213] 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 935827c54..ff2f8bb71 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 032/213] 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 72692a9f5..d573788a6 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 2acf002c0..7a8723ceb 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 6886c1e03..1c75442a1 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 528bcc5d2..fb9f73ff5 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 033/213] 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 fb9f73ff5..d5bcce419 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 034/213] 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 1c75442a1..df1ddd5c9 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 d5bcce419..aa4b40f5b 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 a86578e15..5f72a6b84 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 035/213] 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 cbe1d4c24..f9e376178 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 60d0428f7..e4c46fc86 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 036/213] 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 2e3eec6c1..bbf45e035 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 @@ public class MainActivity extends AppCompatActivity { } 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 df1ddd5c9..48525864b 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): Boolean { - return PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean( - context.getString(R.string.enable_streams_notifications), - false - ) && NotificationHelper.areNotificationsEnabledOnDevice(context) + private fun isEnabled(context: Context) = + NotificationHelper.areNewStreamsNotificationsEnabled(context) && + 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 aa4b40f5b..dea498675 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 04f5a9b56..2cea04dc3 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 037/213] 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 2196da0d7..e61db1dfa 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 0fb6877a6..000000000 --- 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 48525864b..365ba94d3 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 efacd1fc2..d5552ae65 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 static org.schabi.newpipe.extractor.utils.Utils.isBlank; 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 org.schabi.newpipe.R; 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 final class PicassoHelper { } + 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 55146163729dcb45f5d8d4277b41c75425d096de Mon Sep 17 00:00:00 2001 From: martin Date: Tue, 21 Dec 2021 18:17:48 +0100 Subject: [PATCH 038/213] Change pitch by semitones --- .../helper/PlaybackParameterDialog.java | 243 +++++++++++++++++- .../res/layout/dialog_playback_parameter.xml | 125 ++++++++- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + 4 files changed, 360 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 5139ef9cd..222a95afb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.CheckBox; +import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; @@ -37,6 +38,7 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double DEFAULT_TEMPO = 1.00f; private static final double DEFAULT_PITCH = 1.00f; + private static final int DEFAULT_SEMITONES = 0; private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; @@ -64,9 +66,11 @@ public class PlaybackParameterDialog extends DialogFragment { private double initialTempo = DEFAULT_TEMPO; private double initialPitch = DEFAULT_PITCH; + private int initialSemitones = DEFAULT_SEMITONES; private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; private double tempo = DEFAULT_TEMPO; private double pitch = DEFAULT_PITCH; + private int semitones = DEFAULT_SEMITONES; private double stepSize = DEFAULT_STEP; @Nullable @@ -78,6 +82,8 @@ public class PlaybackParameterDialog extends DialogFragment { @Nullable private TextView tempoStepUpText; @Nullable + private RelativeLayout pitchControl; + @Nullable private SeekBar pitchSlider; @Nullable private TextView pitchCurrentText; @@ -86,9 +92,23 @@ public class PlaybackParameterDialog extends DialogFragment { @Nullable private TextView pitchStepUpText; @Nullable + private RelativeLayout semitoneControl; + @Nullable + private SeekBar semitoneSlider; + @Nullable + private TextView semitoneCurrentText; + @Nullable + private TextView semitoneStepDownText; + @Nullable + private TextView semitoneStepUpText; + @Nullable + private View separatorStepSizeSelector; + @Nullable private CheckBox unhookingCheckbox; @Nullable private CheckBox skipSilenceCheckbox; + @Nullable + private CheckBox adjustBySemitonesCheckbox; public static PlaybackParameterDialog newInstance(final double playbackTempo, final double playbackPitch, @@ -101,6 +121,7 @@ public class PlaybackParameterDialog extends DialogFragment { dialog.tempo = playbackTempo; dialog.pitch = playbackPitch; + dialog.semitones = dialog.percentToSemitones(playbackPitch); dialog.initialSkipSilence = playbackSkipSilence; return dialog; @@ -127,9 +148,11 @@ public class PlaybackParameterDialog extends DialogFragment { if (savedInstanceState != null) { initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); + initialSemitones = percentToSemitones(initialPitch); tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); + semitones = percentToSemitones(pitch); stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP); } } @@ -160,9 +183,11 @@ public class PlaybackParameterDialog extends DialogFragment { .setView(view) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> - setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence)) + setPlaybackParameters(initialTempo, initialPitch, + initialSemitones, initialSkipSilence)) .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> - setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE)) + setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, + DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE)) .setPositiveButton(R.string.ok, (dialogInterface, i) -> setCurrentPlaybackParameters()); @@ -176,12 +201,47 @@ public class PlaybackParameterDialog extends DialogFragment { private void setupControlViews(@NonNull final View rootView) { setupHookingControl(rootView); setupSkipSilenceControl(rootView); + setupAdjustBySemitonesControl(rootView); setupTempoControl(rootView); setupPitchControl(rootView); + setupSemitoneControl(rootView); + + togglePitchSliderType(rootView); setStepSize(stepSize); - setupStepSizeSelector(rootView); + } + + private void togglePitchSliderType(@NonNull final View rootView) { + + pitchControl = rootView.findViewById(R.id.pitchControl); + semitoneControl = rootView.findViewById(R.id.semitoneControl); + + separatorStepSizeSelector = rootView.findViewById(R.id.separatorStepSizeSelector); + final RelativeLayout.LayoutParams params = + (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); + if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { + if (getCurrentAdjustBySemitones()) { + // replaces pitchControl slider with semitoneControl slider + pitchControl.setVisibility(View.GONE); + semitoneControl.setVisibility(View.VISIBLE); + params.addRule(RelativeLayout.BELOW, R.id.semitoneControl); + + // forces unhook for semitones + unhookingCheckbox.setChecked(true); + unhookingCheckbox.setEnabled(false); + + setupTempoStepSizeSelector(rootView); + } else { + semitoneControl.setVisibility(View.GONE); + pitchControl.setVisibility(View.VISIBLE); + params.addRule(RelativeLayout.BELOW, R.id.pitchControl); + + // (re)enables hooking selection + unhookingCheckbox.setEnabled(true); + setupCombinedStepSizeSelector(rootView); + } + } } private void setupTempoControl(@NonNull final View rootView) { @@ -234,23 +294,40 @@ public class PlaybackParameterDialog extends DialogFragment { } } + private void setupSemitoneControl(@NonNull final View rootView) { + semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); + semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); + semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); + semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); + + if (semitoneCurrentText != null) { + semitoneCurrentText.setText(getSignedSemitonesString(semitones)); + } + + if (semitoneSlider != null) { + setSemitoneSlider(semitones); + semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); + } + + } + private void setupHookingControl(@NonNull final View rootView) { unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); if (unhookingCheckbox != null) { - // restore whether pitch and tempo are unhooked or not + // restores whether pitch and tempo are unhooked or not unhookingCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) .getBoolean(getString(R.string.playback_unhook_key), true)); unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // save whether pitch and tempo are unhooked or not + // saves whether pitch and tempo are unhooked or not PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() .putBoolean(getString(R.string.playback_unhook_key), isChecked) .apply(); if (!isChecked) { - // when unchecked, slide back to the minimum of current tempo or pitch + // when unchecked, slides back to the minimum of current tempo or pitch final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); setSliders(minimum); setCurrentPlaybackParameters(); @@ -268,6 +345,42 @@ public class PlaybackParameterDialog extends DialogFragment { } } + private void setupAdjustBySemitonesControl(@NonNull final View rootView) { + adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox); + if (adjustBySemitonesCheckbox != null) { + // restores whether semitone adjustment is used or not + adjustBySemitonesCheckbox.setChecked(PreferenceManager + .getDefaultSharedPreferences(requireContext()) + .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true)); + + // stores whether semitone adjustment is used or not + adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) + .apply(); + togglePitchSliderType(rootView); + if (isChecked) { + setPlaybackParameters( + getCurrentTempo(), + getCurrentPitch(), + percentToSemitones(getCurrentPitch()), + getCurrentSkipSilence() + ); + setSemitoneSlider(percentToSemitones(getCurrentPitch())); + } else { + setPlaybackParameters( + getCurrentTempo(), + semitonesToPercent(getCurrentSemitones()), + getCurrentSemitones(), + getCurrentSkipSilence() + ); + setPitchSlider(semitonesToPercent(getCurrentSemitones())); + } + }); + } + } + private void setupStepSizeSelector(@NonNull final View rootView) { final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); @@ -310,6 +423,22 @@ public class PlaybackParameterDialog extends DialogFragment { } } + private void setupTempoStepSizeSelector(@NonNull final View rootView) { + final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); + if (playbackStepTypeText != null) { + playbackStepTypeText.setText(R.string.playback_tempo_step); + } + setupStepSizeSelector(rootView); + } + + private void setupCombinedStepSizeSelector(@NonNull final View rootView) { + final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); + if (playbackStepTypeText != null) { + playbackStepTypeText.setText(R.string.playback_step); + } + setupStepSizeSelector(rootView); + } + private void setStepSize(final double stepSize) { this.stepSize = stepSize; @@ -344,6 +473,20 @@ public class PlaybackParameterDialog extends DialogFragment { setCurrentPlaybackParameters(); }); } + + if (semitoneStepDownText != null) { + semitoneStepDownText.setOnClickListener(view -> { + onSemitoneSliderUpdated(getCurrentSemitones() - 1); + setCurrentPlaybackParameters(); + }); + } + + if (semitoneStepUpText != null) { + semitoneStepUpText.setOnClickListener(view -> { + onSemitoneSliderUpdated(getCurrentSemitones() + 1); + setCurrentPlaybackParameters(); + }); + } } /*////////////////////////////////////////////////////////////////////////// @@ -398,6 +541,33 @@ public class PlaybackParameterDialog extends DialogFragment { }; } + private SeekBar.OnSeekBarChangeListener getOnSemitoneChangedListener() { + return new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + // semitone slider supplies values 0 to 25, subtraction by 12 is required + final int currentSemitones = progress - 12; + if (fromUser) { // this change is first in chain + onSemitoneSliderUpdated(currentSemitones); + // line below also saves semitones as pitch percentages + onPitchSliderUpdated(semitonesToPercent(currentSemitones)); + setCurrentPlaybackParameters(); + } + } + + @Override + public void onStartTrackingTouch(final SeekBar seekBar) { + // Do Nothing. + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + // Do Nothing. + } + }; + } + private void onTempoSliderUpdated(final double newTempo) { if (unhookingCheckbox == null) { return; @@ -420,6 +590,13 @@ public class PlaybackParameterDialog extends DialogFragment { } } + private void onSemitoneSliderUpdated(final int newSemitone) { + if (unhookingCheckbox == null) { + return; + } + setSemitoneSlider(newSemitone); + } + private void setSliders(final double newValue) { setTempoSlider(newValue); setPitchSlider(newValue); @@ -439,25 +616,49 @@ public class PlaybackParameterDialog extends DialogFragment { pitchSlider.setProgress(strategy.progressOf(newPitch)); } + private void setSemitoneSlider(final int newSemitone) { + if (semitoneSlider == null) { + return; + } + semitoneSlider.setProgress(newSemitone + 12); + } + /*////////////////////////////////////////////////////////////////////////// // Helper //////////////////////////////////////////////////////////////////////////*/ private void setCurrentPlaybackParameters() { - setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence()); + if (getCurrentAdjustBySemitones()) { + setPlaybackParameters( + getCurrentTempo(), + semitonesToPercent(getCurrentSemitones()), + getCurrentSemitones(), + getCurrentSkipSilence() + ); + } else { + setPlaybackParameters( + getCurrentTempo(), + getCurrentPitch(), + percentToSemitones(getCurrentPitch()), + getCurrentSkipSilence() + ); + } } private void setPlaybackParameters(final double newTempo, final double newPitch, - final boolean skipSilence) { - if (callback != null && tempoCurrentText != null && pitchCurrentText != null) { + final int newSemitones, final boolean skipSilence) { + if (callback != null && tempoCurrentText != null + && pitchCurrentText != null && semitoneCurrentText != null) { if (DEBUG) { Log.d(TAG, "Setting playback parameters to " + "tempo=[" + newTempo + "], " - + "pitch=[" + newPitch + "]"); + + "pitch=[" + newPitch + "], " + + "semitones=[" + newSemitones + "]"); } tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); + semitoneCurrentText.setText(getSignedSemitonesString(newSemitones)); callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); } } @@ -470,6 +671,11 @@ public class PlaybackParameterDialog extends DialogFragment { return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); } + private int getCurrentSemitones() { + // semitoneSlider is absolute, that's why - 12 + return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12; + } + private double getCurrentStepSize() { return stepSize; } @@ -478,6 +684,10 @@ public class PlaybackParameterDialog extends DialogFragment { return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); } + private boolean getCurrentAdjustBySemitones() { + return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked(); + } + @NonNull private static String getStepUpPercentString(final double percent) { return STEP_UP_SIGN + getPercentString(percent); @@ -493,8 +703,21 @@ public class PlaybackParameterDialog extends DialogFragment { return PlayerHelper.formatPitch(percent); } + @NonNull + private static String getSignedSemitonesString(final int semitones) { + return semitones > 0 ? "+" + semitones : "" + semitones; + } + public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); } + + public double semitonesToPercent(final int inSemitones) { + return Math.pow(2, inSemitones / 12.0); + } + + public int percentToSemitones(final double inPercent) { + return (int) Math.round(12 * Math.log(inPercent) / Math.log(2)); + } } diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 40db90675..fd63cbd79 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -261,11 +261,121 @@ tools:text="+5%" /> + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index b6f76fce2..a0f03a5b6 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -258,6 +258,7 @@ enable_playback_resume enable_playback_state_lists playback_unhook_key + playback_adjust_by_semitones_key playback_speed_key playback_pitch_key playback_skip_silence_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4d46a3e2..0f297006e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -503,7 +503,9 @@ Pitch Unhook (may cause distortion) Fast-forward during silence + Adjust pitch by musical semitones Step + Tempo step Reset In order to comply with the European General Data Protection Regulation (GDPR), we herby draw your attention to NewPipe\'s privacy policy. Please read it carefully.\nYou must accept it to send us the bug report. From e68d49e7df33f1ae76a6d2383bbe6a839c2bcce8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 31 Dec 2021 18:34:02 +0100 Subject: [PATCH 039/213] 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 bcd64791e..e4af6e0ac 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 040/213] 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 e61db1dfa..8ce59b14a 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 d5552ae65..9e157f458 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 static org.schabi.newpipe.extractor.utils.Utils.isBlank; 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 final class PicassoHelper { 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 final class PicassoHelper { @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 041/213] 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 e8dec9556..8e7a455b4 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 365ba94d3..0a0cbd1f3 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 042/213] 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 156877b4e..6ae264bb5 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 9e157f458..1690f7223 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 final class PicassoHelper { @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 043/213] 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 b0931d3c1..26b50855e 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 org.schabi.newpipe.util.StateSaver; 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 @@ public class App extends MultiDexApplication { 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 044/213] 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 065a8fbce..447f0a964 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 5c93dec4b..872e1e8d1 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 dd027c43e..fdedf8ece 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 045/213] 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 0dbc15395..37e8fc39e 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 d39c7315e..c77cbc588 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 a28f96d97..2a7754395 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 3bd70f26f..03197bb2b 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 e4c46fc86..5ea410846 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: Thu, 3 Feb 2022 13:58:17 +0100 Subject: [PATCH 046/213] app/build.grade: compileSdk 30 -> 31 Required for newer versions of some dependencies --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 4cebcb7f5..3e476441f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ plugins { } android { - compileSdk 30 + compileSdk 31 buildToolsVersion '30.0.3' defaultConfig { From 67b2503062277da11fef1450743e8807c6bf4541 Mon Sep 17 00:00:00 2001 From: Atemu Date: Thu, 3 Feb 2022 13:58:40 +0100 Subject: [PATCH 047/213] app/build.grade: androidxRoomVersion 2.3.0 -> 2.4.1 This version of Room includes a fix for building dependant apps such as NewPipe on Apple Silicon devices (aarch64-darwin) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3e476441f..bb4e547a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -101,7 +101,7 @@ ext { checkstyleVersion = '9.2.1' androidxLifecycleVersion = '2.3.1' - androidxRoomVersion = '2.3.0' + androidxRoomVersion = '2.4.1' icepickVersion = '3.2.0' exoPlayerVersion = '2.14.2' From 4049abf2c05f3e41202c863f8ea7f978bd45a458 Mon Sep 17 00:00:00 2001 From: martin Date: Fri, 4 Feb 2022 16:15:55 +0100 Subject: [PATCH 048/213] Addressed comment in PR --- .../helper/PlaybackParameterDialog.java | 35 ++++++-------- .../res/layout/dialog_playback_parameter.xml | 46 ++++++------------- 2 files changed, 28 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 222a95afb..74604eda0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -82,8 +82,6 @@ public class PlaybackParameterDialog extends DialogFragment { @Nullable private TextView tempoStepUpText; @Nullable - private RelativeLayout pitchControl; - @Nullable private SeekBar pitchSlider; @Nullable private TextView pitchCurrentText; @@ -92,8 +90,6 @@ public class PlaybackParameterDialog extends DialogFragment { @Nullable private TextView pitchStepUpText; @Nullable - private RelativeLayout semitoneControl; - @Nullable private SeekBar semitoneSlider; @Nullable private TextView semitoneCurrentText; @@ -102,8 +98,6 @@ public class PlaybackParameterDialog extends DialogFragment { @Nullable private TextView semitoneStepUpText; @Nullable - private View separatorStepSizeSelector; - @Nullable private CheckBox unhookingCheckbox; @Nullable private CheckBox skipSilenceCheckbox; @@ -213,11 +207,13 @@ public class PlaybackParameterDialog extends DialogFragment { } private void togglePitchSliderType(@NonNull final View rootView) { + @Nullable + final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); + @Nullable + final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - pitchControl = rootView.findViewById(R.id.pitchControl); - semitoneControl = rootView.findViewById(R.id.semitoneControl); - - separatorStepSizeSelector = rootView.findViewById(R.id.separatorStepSizeSelector); + @Nullable + final View separatorStepSizeSelector = rootView.findViewById(R.id.separatorStepSizeSelector); final RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { @@ -364,10 +360,14 @@ public class PlaybackParameterDialog extends DialogFragment { setPlaybackParameters( getCurrentTempo(), getCurrentPitch(), - percentToSemitones(getCurrentPitch()), + Integer.min(12, + Integer.max(-12, percentToSemitones(getCurrentPitch()) + )), getCurrentSkipSilence() ); - setSemitoneSlider(percentToSemitones(getCurrentPitch())); + setSemitoneSlider(Integer.min(12, + Integer.max(-12, percentToSemitones(getCurrentPitch())) + )); } else { setPlaybackParameters( getCurrentTempo(), @@ -546,7 +546,7 @@ public class PlaybackParameterDialog extends DialogFragment { @Override public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - // semitone slider supplies values 0 to 25, subtraction by 12 is required + // semitone slider supplies values 0 to 24, subtraction by 12 is required final int currentSemitones = progress - 12; if (fromUser) { // this change is first in chain onSemitoneSliderUpdated(currentSemitones); @@ -569,9 +569,6 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onTempoSliderUpdated(final double newTempo) { - if (unhookingCheckbox == null) { - return; - } if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { @@ -580,9 +577,6 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onPitchSliderUpdated(final double newPitch) { - if (unhookingCheckbox == null) { - return; - } if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { @@ -591,9 +585,6 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onSemitoneSliderUpdated(final int newSemitone) { - if (unhookingCheckbox == null) { - return; - } setSemitoneSlider(newSemitone); } diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index fd63cbd79..27ff0b2a3 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -48,8 +48,7 @@ android:text="--%" android:textColor="?attr/colorAccent" android:textStyle="bold" - tools:ignore="HardcodedText" - tools:text="-5%" /> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText"/> Date: Sat, 5 Feb 2022 12:31:07 +0100 Subject: [PATCH 049/213] Fixed checkstyle violation --- .../newpipe/player/helper/PlaybackParameterDialog.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 74604eda0..0fe500965 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -207,13 +207,11 @@ public class PlaybackParameterDialog extends DialogFragment { } private void togglePitchSliderType(@NonNull final View rootView) { - @Nullable final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); - @Nullable final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - @Nullable - final View separatorStepSizeSelector = rootView.findViewById(R.id.separatorStepSizeSelector); + final View separatorStepSizeSelector = + rootView.findViewById(R.id.separatorStepSizeSelector); final RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { From ed2967ec7d418c186841a1db41a8304c143160e0 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 17 Feb 2022 10:28:50 +0100 Subject: [PATCH 050/213] Addressing layout comments --- .../res/layout/dialog_playback_parameter.xml | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 27ff0b2a3..862b2ea67 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -48,7 +48,8 @@ android:text="--%" android:textColor="?attr/colorAccent" android:textStyle="bold" - tools:ignore="HardcodedText"/> + tools:ignore="HardcodedText" + tools:text="-5%" /> + tools:ignore="HardcodedText" + tools:text="1.00x" /> + tools:ignore="HardcodedText" + tools:text="100%" /> + tools:ignore="HardcodedText" + tools:text="300%" /> + tools:ignore="HardcodedText" + tools:text="+5%" /> + tools:ignore="HardcodedText" + tools:text="-5%" /> + tools:ignore="HardcodedText" + tools:text="25%" /> + tools:ignore="HardcodedText" + tools:text="100%" /> + tools:ignore="HardcodedText" + tools:text="300%" /> + tools:ignore="HardcodedText" + tools:text="+5%" /> + tools:ignore="HardcodedText" /> + tools:text="0" /> + tools:ignore="HardcodedText" /> + tools:ignore="HardcodedText" /> Date: Fri, 18 Feb 2022 14:05:34 +0100 Subject: [PATCH 051/213] Remove large-land player layout: not actually used --- app/src/main/res/layout-large-land/player.xml | 756 ------------------ 1 file changed, 756 deletions(-) delete mode 100644 app/src/main/res/layout-large-land/player.xml diff --git a/app/src/main/res/layout-large-land/player.xml b/app/src/main/res/layout-large-land/player.xml deleted file mode 100644 index 71a325cf3..000000000 --- a/app/src/main/res/layout-large-land/player.xml +++ /dev/null @@ -1,756 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -