mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Merge branch 'refactor' into About-Compose
This commit is contained in:
		| @@ -125,6 +125,8 @@ ext { | ||||
|  | ||||
|     leakCanaryVersion = '2.12' | ||||
|     stethoVersion = '1.6.0' | ||||
|  | ||||
|     coilVersion = '3.0.3' | ||||
| } | ||||
|  | ||||
| configurations { | ||||
| @@ -208,7 +210,7 @@ dependencies { | ||||
|     // This works thanks to JitPack: https://jitpack.io/ | ||||
|     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' | ||||
|     // WORKAROUND: v0.24.2 can't be resolved by jitpack -> use git commit hash instead | ||||
|     implementation 'com.github.TeamNewPipe:NewPipeExtractor:176da72cb4c3ec4679211339b0e59f6b01bf2f52' | ||||
|     implementation 'com.github.TeamNewPipe:NewPipeExtractor:d3d5f2b3f03a5f2b479b9f6fdf1c2555cbb9de0e' | ||||
|     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' | ||||
|  | ||||
| /** Checkstyle **/ | ||||
| @@ -270,7 +272,8 @@ dependencies { | ||||
|     implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}" | ||||
|  | ||||
|     // Image loading | ||||
|     implementation 'io.coil-kt:coil-compose:2.7.0' | ||||
|     implementation "io.coil-kt.coil3:coil-compose:${coilVersion}" | ||||
|     implementation "io.coil-kt.coil3:coil-network-okhttp:${coilVersion}" | ||||
|  | ||||
|     // Markdown library for Android | ||||
|     implementation "io.noties.markwon:core:${markwonVersion}" | ||||
|   | ||||
| @@ -1,279 +0,0 @@ | ||||
| package org.schabi.newpipe; | ||||
|  | ||||
| import android.app.ActivityManager; | ||||
| import android.app.Application; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.util.Log; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.core.app.NotificationChannelCompat; | ||||
| import androidx.core.app.NotificationManagerCompat; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.preference.PreferenceManager; | ||||
|  | ||||
| import com.jakewharton.processphoenix.ProcessPhoenix; | ||||
|  | ||||
| import org.acra.ACRA; | ||||
| import org.acra.config.CoreConfigurationBuilder; | ||||
| import org.schabi.newpipe.error.ReCaptchaActivity; | ||||
| import org.schabi.newpipe.extractor.NewPipe; | ||||
| import org.schabi.newpipe.extractor.downloader.Downloader; | ||||
| import org.schabi.newpipe.ktx.ExceptionUtils; | ||||
| import org.schabi.newpipe.settings.NewPipeSettings; | ||||
| import org.schabi.newpipe.util.BridgeStateSaverInitializer; | ||||
| import org.schabi.newpipe.util.Localization; | ||||
| import org.schabi.newpipe.util.ServiceHelper; | ||||
| import org.schabi.newpipe.util.StateSaver; | ||||
| import org.schabi.newpipe.util.image.ImageStrategy; | ||||
| import org.schabi.newpipe.util.image.PreferredImageQuality; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InterruptedIOException; | ||||
| import java.net.SocketException; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import coil.ImageLoader; | ||||
| import coil.ImageLoaderFactory; | ||||
| import coil.util.DebugLogger; | ||||
| import dagger.hilt.android.HiltAndroidApp; | ||||
| import io.reactivex.rxjava3.exceptions.CompositeException; | ||||
| import io.reactivex.rxjava3.exceptions.MissingBackpressureException; | ||||
| import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; | ||||
| import io.reactivex.rxjava3.exceptions.UndeliverableException; | ||||
| import io.reactivex.rxjava3.functions.Consumer; | ||||
| import io.reactivex.rxjava3.plugins.RxJavaPlugins; | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org> | ||||
|  * App.java is part of NewPipe. | ||||
|  * | ||||
|  * NewPipe is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * NewPipe is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| @HiltAndroidApp | ||||
| public class App extends Application implements ImageLoaderFactory { | ||||
|     public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; | ||||
|     private static final String TAG = App.class.toString(); | ||||
|  | ||||
|     private boolean isFirstRun = false; | ||||
|     private static App app; | ||||
|  | ||||
|     @NonNull | ||||
|     public static App getApp() { | ||||
|         return app; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void attachBaseContext(final Context base) { | ||||
|         super.attachBaseContext(base); | ||||
|         initACRA(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|  | ||||
|         app = this; | ||||
|  | ||||
|         if (ProcessPhoenix.isPhoenixProcess(this)) { | ||||
|             Log.i(TAG, "This is a phoenix process! " | ||||
|                     + "Aborting initialization of App[onCreate]"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // check if the last used preference version is set | ||||
|         // to determine whether this is the first app run | ||||
|         final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|                 .getInt(getString(R.string.last_used_preferences_version), -1); | ||||
|         isFirstRun = lastUsedPrefVersion == -1; | ||||
|  | ||||
|         // Initialize settings first because other initializations can use its values | ||||
|         NewPipeSettings.initSettings(this); | ||||
|  | ||||
|         NewPipe.init(getDownloader(), | ||||
|             Localization.getPreferredLocalization(this), | ||||
|             Localization.getPreferredContentCountry(this)); | ||||
|         Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext())); | ||||
|  | ||||
|         BridgeStateSaverInitializer.init(this); | ||||
|         StateSaver.init(this); | ||||
|         initNotificationChannels(); | ||||
|  | ||||
|         ServiceHelper.initServices(this); | ||||
|  | ||||
|         // Initialize image loader | ||||
|         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); | ||||
|         ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this, | ||||
|                 prefs.getString(getString(R.string.image_quality_key), | ||||
|                         getString(R.string.image_quality_default)))); | ||||
|  | ||||
|         configureRxJavaErrorHandler(); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public ImageLoader newImageLoader() { | ||||
|         return new ImageLoader.Builder(this) | ||||
|                 .allowRgb565(ContextCompat.getSystemService(this, ActivityManager.class) | ||||
|                         .isLowRamDevice()) | ||||
|                 .logger(BuildConfig.DEBUG ? new DebugLogger() : null) | ||||
|                 .crossfade(true) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     protected Downloader getDownloader() { | ||||
|         final DownloaderImpl downloader = DownloaderImpl.init(null); | ||||
|         setCookiesToDownloader(downloader); | ||||
|         return downloader; | ||||
|     } | ||||
|  | ||||
|     protected void setCookiesToDownloader(final DownloaderImpl downloader) { | ||||
|         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( | ||||
|                 getApplicationContext()); | ||||
|         final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key); | ||||
|         downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null)); | ||||
|         downloader.updateYoutubeRestrictedModeCookies(getApplicationContext()); | ||||
|     } | ||||
|  | ||||
|     private void configureRxJavaErrorHandler() { | ||||
|         // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | ||||
|         RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { | ||||
|             @Override | ||||
|             public void accept(@NonNull final Throwable throwable) { | ||||
|                 Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " | ||||
|                         + "throwable = [" + throwable.getClass().getName() + "]"); | ||||
|  | ||||
|                 final Throwable actualThrowable; | ||||
|                 if (throwable instanceof UndeliverableException) { | ||||
|                     // As UndeliverableException is a wrapper, | ||||
|                     // get the cause of it to get the "real" exception | ||||
|                     actualThrowable = Objects.requireNonNull(throwable.getCause()); | ||||
|                 } else { | ||||
|                     actualThrowable = throwable; | ||||
|                 } | ||||
|  | ||||
|                 final List<Throwable> errors; | ||||
|                 if (actualThrowable instanceof CompositeException) { | ||||
|                     errors = ((CompositeException) actualThrowable).getExceptions(); | ||||
|                 } else { | ||||
|                     errors = List.of(actualThrowable); | ||||
|                 } | ||||
|  | ||||
|                 for (final Throwable error : errors) { | ||||
|                     if (isThrowableIgnored(error)) { | ||||
|                         return; | ||||
|                     } | ||||
|                     if (isThrowableCritical(error)) { | ||||
|                         reportException(error); | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Out-of-lifecycle exceptions should only be reported if a debug user wishes so, | ||||
|                 // When exception is not reported, log it | ||||
|                 if (isDisposedRxExceptionsReported()) { | ||||
|                     reportException(actualThrowable); | ||||
|                 } else { | ||||
|                     Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             private boolean isThrowableIgnored(@NonNull final Throwable throwable) { | ||||
|                 // Don't crash the application over a simple network problem | ||||
|                 return ExceptionUtils.hasAssignableCause(throwable, | ||||
|                         // network api cancellation | ||||
|                         IOException.class, SocketException.class, | ||||
|                         // blocking code disposed | ||||
|                         InterruptedException.class, InterruptedIOException.class); | ||||
|             } | ||||
|  | ||||
|             private boolean isThrowableCritical(@NonNull final Throwable throwable) { | ||||
|                 // Though these exceptions cannot be ignored | ||||
|                 return ExceptionUtils.hasAssignableCause(throwable, | ||||
|                         NullPointerException.class, IllegalArgumentException.class, // bug in app | ||||
|                         OnErrorNotImplementedException.class, MissingBackpressureException.class, | ||||
|                         IllegalStateException.class); // bug in operator | ||||
|             } | ||||
|  | ||||
|             private void reportException(@NonNull final Throwable throwable) { | ||||
|                 // Throw uncaught exception that will trigger the report system | ||||
|                 Thread.currentThread().getUncaughtExceptionHandler() | ||||
|                         .uncaughtException(Thread.currentThread(), throwable); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called in {@link #attachBaseContext(Context)} after calling the {@code super} method. | ||||
|      * Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA. | ||||
|      */ | ||||
|     protected void initACRA() { | ||||
|         if (ACRA.isACRASenderServiceProcess()) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder() | ||||
|                 .withBuildConfigClass(BuildConfig.class); | ||||
|         ACRA.init(this, acraConfig); | ||||
|     } | ||||
|  | ||||
|     private void initNotificationChannels() { | ||||
|         // Keep the importance below DEFAULT to avoid making noise on every notification update for | ||||
|         // the main and update channels | ||||
|         final List<NotificationChannelCompat> notificationChannelCompats = List.of( | ||||
|                 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(), | ||||
|                 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(), | ||||
|                 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(), | ||||
|                 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(), | ||||
|                 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.createNotificationChannelsCompat(notificationChannelCompats); | ||||
|     } | ||||
|  | ||||
|     protected boolean isDisposedRxExceptionsReported() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public boolean isFirstRun() { | ||||
|         return isFirstRun; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										283
									
								
								app/src/main/java/org/schabi/newpipe/App.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								app/src/main/java/org/schabi/newpipe/App.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,283 @@ | ||||
| package org.schabi.newpipe | ||||
|  | ||||
| import android.app.ActivityManager | ||||
| import android.app.Application | ||||
| import android.content.Context | ||||
| import android.util.Log | ||||
| import androidx.core.app.NotificationChannelCompat | ||||
| import androidx.core.app.NotificationManagerCompat | ||||
| import androidx.core.content.getSystemService | ||||
| import androidx.preference.PreferenceManager | ||||
| import coil3.ImageLoader | ||||
| import coil3.SingletonImageLoader | ||||
| import coil3.request.allowRgb565 | ||||
| import coil3.request.crossfade | ||||
| import coil3.util.DebugLogger | ||||
| import com.jakewharton.processphoenix.ProcessPhoenix | ||||
| import dagger.hilt.android.HiltAndroidApp | ||||
| import io.reactivex.rxjava3.exceptions.CompositeException | ||||
| import io.reactivex.rxjava3.exceptions.MissingBackpressureException | ||||
| import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException | ||||
| import io.reactivex.rxjava3.exceptions.UndeliverableException | ||||
| import io.reactivex.rxjava3.functions.Consumer | ||||
| import io.reactivex.rxjava3.plugins.RxJavaPlugins | ||||
| import org.acra.ACRA.init | ||||
| import org.acra.ACRA.isACRASenderServiceProcess | ||||
| import org.acra.config.CoreConfigurationBuilder | ||||
| import org.schabi.newpipe.error.ReCaptchaActivity | ||||
| import org.schabi.newpipe.extractor.NewPipe | ||||
| import org.schabi.newpipe.extractor.downloader.Downloader | ||||
| import org.schabi.newpipe.ktx.hasAssignableCause | ||||
| import org.schabi.newpipe.settings.NewPipeSettings | ||||
| import org.schabi.newpipe.util.BridgeStateSaverInitializer | ||||
| import org.schabi.newpipe.util.Localization | ||||
| import org.schabi.newpipe.util.ServiceHelper | ||||
| import org.schabi.newpipe.util.StateSaver | ||||
| import org.schabi.newpipe.util.image.ImageStrategy | ||||
| import org.schabi.newpipe.util.image.PreferredImageQuality | ||||
| import java.io.IOException | ||||
| import java.io.InterruptedIOException | ||||
| import java.net.SocketException | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org> | ||||
|  * App.kt is part of NewPipe. | ||||
|  * | ||||
|  * NewPipe is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * NewPipe is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| @HiltAndroidApp | ||||
| open class App : | ||||
|     Application(), | ||||
|     SingletonImageLoader.Factory { | ||||
|     var isFirstRun = false | ||||
|         private set | ||||
|  | ||||
|     override fun attachBaseContext(base: Context?) { | ||||
|         super.attachBaseContext(base) | ||||
|         initACRA() | ||||
|     } | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
|         instance = this | ||||
|  | ||||
|         if (ProcessPhoenix.isPhoenixProcess(this)) { | ||||
|             Log.i(TAG, "This is a phoenix process! Aborting initialization of App[onCreate]") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         // check if the last used preference version is set | ||||
|         // to determine whether this is the first app run | ||||
|         val lastUsedPrefVersion = | ||||
|             PreferenceManager | ||||
|                 .getDefaultSharedPreferences(this) | ||||
|                 .getInt(getString(R.string.last_used_preferences_version), -1) | ||||
|         isFirstRun = lastUsedPrefVersion == -1 | ||||
|  | ||||
|         // Initialize settings first because other initializations can use its values | ||||
|         NewPipeSettings.initSettings(this) | ||||
|  | ||||
|         NewPipe.init( | ||||
|             getDownloader(), | ||||
|             Localization.getPreferredLocalization(this), | ||||
|             Localization.getPreferredContentCountry(this), | ||||
|         ) | ||||
|         Localization.initPrettyTime(Localization.resolvePrettyTime(this)) | ||||
|  | ||||
|         BridgeStateSaverInitializer.init(this) | ||||
|         StateSaver.init(this) | ||||
|         initNotificationChannels() | ||||
|  | ||||
|         ServiceHelper.initServices(this) | ||||
|  | ||||
|         // Initialize image loader | ||||
|         val prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|         ImageStrategy.setPreferredImageQuality( | ||||
|             PreferredImageQuality.fromPreferenceKey( | ||||
|                 this, | ||||
|                 prefs.getString( | ||||
|                     getString(R.string.image_quality_key), | ||||
|                     getString(R.string.image_quality_default), | ||||
|                 ), | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         configureRxJavaErrorHandler() | ||||
|     } | ||||
|  | ||||
|     override fun newImageLoader(context: Context): ImageLoader = | ||||
|         ImageLoader | ||||
|             .Builder(this) | ||||
|             .logger(if (BuildConfig.DEBUG) DebugLogger() else null) | ||||
|             .allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice) | ||||
|             .crossfade(true) | ||||
|             .build() | ||||
|  | ||||
|     protected open fun getDownloader(): Downloader { | ||||
|         val downloader = DownloaderImpl.init(null) | ||||
|         setCookiesToDownloader(downloader) | ||||
|         return downloader | ||||
|     } | ||||
|  | ||||
|     protected fun setCookiesToDownloader(downloader: DownloaderImpl) { | ||||
|         val prefs = PreferenceManager.getDefaultSharedPreferences(this) | ||||
|         val key = getString(R.string.recaptcha_cookies_key) | ||||
|         downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null)) | ||||
|         downloader.updateYoutubeRestrictedModeCookies(this) | ||||
|     } | ||||
|  | ||||
|     private fun configureRxJavaErrorHandler() { | ||||
|         // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | ||||
|         RxJavaPlugins.setErrorHandler( | ||||
|             object : Consumer<Throwable> { | ||||
|                 override fun accept(throwable: Throwable) { | ||||
|                     Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [${throwable.javaClass.getName()}]") | ||||
|  | ||||
|                     // As UndeliverableException is a wrapper, | ||||
|                     // get the cause of it to get the "real" exception | ||||
|                     val actualThrowable = (throwable as? UndeliverableException)?.cause ?: throwable | ||||
|  | ||||
|                     val errors = (actualThrowable as? CompositeException)?.exceptions ?: listOf(actualThrowable) | ||||
|  | ||||
|                     for (error in errors) { | ||||
|                         if (isThrowableIgnored(error)) { | ||||
|                             return | ||||
|                         } | ||||
|                         if (isThrowableCritical(error)) { | ||||
|                             reportException(error) | ||||
|                             return | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // Out-of-lifecycle exceptions should only be reported if a debug user wishes so, | ||||
|                     // When exception is not reported, log it | ||||
|                     if (isDisposedRxExceptionsReported()) { | ||||
|                         reportException(actualThrowable) | ||||
|                     } else { | ||||
|                         Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 fun isThrowableIgnored(throwable: Throwable): Boolean { | ||||
|                     // Don't crash the application over a simple network problem | ||||
|                     return throwable // network api cancellation | ||||
|                         .hasAssignableCause( | ||||
|                             IOException::class.java, | ||||
|                             SocketException::class.java, // blocking code disposed | ||||
|                             InterruptedException::class.java, | ||||
|                             InterruptedIOException::class.java, | ||||
|                         ) | ||||
|                 } | ||||
|  | ||||
|                 fun isThrowableCritical(throwable: Throwable): Boolean { | ||||
|                     // Though these exceptions cannot be ignored | ||||
|                     return throwable | ||||
|                         .hasAssignableCause( | ||||
|                             // bug in app | ||||
|                             NullPointerException::class.java, | ||||
|                             IllegalArgumentException::class.java, | ||||
|                             OnErrorNotImplementedException::class.java, | ||||
|                             MissingBackpressureException::class.java, | ||||
|                             // bug in operator | ||||
|                             IllegalStateException::class.java, | ||||
|                         ) | ||||
|                 } | ||||
|  | ||||
|                 fun reportException(throwable: Throwable) { | ||||
|                     // Throw uncaught exception that will trigger the report system | ||||
|                     Thread | ||||
|                         .currentThread() | ||||
|                         .uncaughtExceptionHandler | ||||
|                         .uncaughtException(Thread.currentThread(), throwable) | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called in [.attachBaseContext] after calling the `super` method. | ||||
|      * Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA. | ||||
|      */ | ||||
|     protected fun initACRA() { | ||||
|         if (isACRASenderServiceProcess()) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         val acraConfig = | ||||
|             CoreConfigurationBuilder() | ||||
|                 .withBuildConfigClass(BuildConfig::class.java) | ||||
|         init(this, acraConfig) | ||||
|     } | ||||
|  | ||||
|     private fun initNotificationChannels() { | ||||
|         // Keep the importance below DEFAULT to avoid making noise on every notification update for | ||||
|         // the main and update channels | ||||
|         val mainChannel = | ||||
|             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() | ||||
|         val appUpdateChannel = | ||||
|             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() | ||||
|         val hashChannel = | ||||
|             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() | ||||
|         val errorReportChannel = | ||||
|             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() | ||||
|         val newStreamChannel = | ||||
|             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() | ||||
|  | ||||
|         val channels = listOf(mainChannel, appUpdateChannel, hashChannel, errorReportChannel, newStreamChannel) | ||||
|  | ||||
|         NotificationManagerCompat.from(this).createNotificationChannelsCompat(channels) | ||||
|     } | ||||
|  | ||||
|     protected open fun isDisposedRxExceptionsReported(): Boolean = false | ||||
|  | ||||
|     companion object { | ||||
|         const val PACKAGE_NAME: String = BuildConfig.APPLICATION_ID | ||||
|         private val TAG = App::class.java.toString() | ||||
|  | ||||
|         @JvmStatic | ||||
|         lateinit var instance: App | ||||
|             private set | ||||
|     } | ||||
| } | ||||
| @@ -166,7 +166,7 @@ public class MainActivity extends AppCompatActivity { | ||||
|             NotificationWorker.initialize(this); | ||||
|         } | ||||
|         if (!UpdateSettingsFragment.wasUserAskedForConsent(this) | ||||
|                 && !App.getApp().isFirstRun() | ||||
|                 && !App.getInstance().isFirstRun() | ||||
|                 && ReleaseVersionUtil.INSTANCE.isReleaseApk()) { | ||||
|             UpdateSettingsFragment.askForConsentToUpdateChecks(this); | ||||
|         } | ||||
| @@ -176,7 +176,7 @@ public class MainActivity extends AppCompatActivity { | ||||
|     protected void onPostCreate(final Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|  | ||||
|         final App app = App.getApp(); | ||||
|         final App app = App.getInstance(); | ||||
|         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app); | ||||
|  | ||||
|         if (prefs.getBoolean(app.getString(R.string.update_app_key), false) | ||||
|   | ||||
| @@ -127,7 +127,7 @@ import java.util.Optional; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import coil.util.CoilUtils; | ||||
| import coil3.util.CoilUtils; | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.rxjava3.disposables.CompositeDisposable; | ||||
| import io.reactivex.rxjava3.disposables.Disposable; | ||||
|   | ||||
| @@ -60,7 +60,7 @@ import java.util.List; | ||||
| import java.util.Queue; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| import coil.util.CoilUtils; | ||||
| import coil3.util.CoilUtils; | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.rxjava3.core.Observable; | ||||
| import io.reactivex.rxjava3.disposables.CompositeDisposable; | ||||
|   | ||||
| @@ -62,7 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import coil.util.CoilUtils; | ||||
| import coil3.util.CoilUtils; | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.rxjava3.core.Flowable; | ||||
| import io.reactivex.rxjava3.core.Single; | ||||
|   | ||||
| @@ -346,7 +346,7 @@ public final class InfoItemDialog { | ||||
|  | ||||
|         public static void reportErrorDuringInitialization(final Throwable throwable, | ||||
|                                                            final InfoItem item) { | ||||
|             ErrorUtil.showSnackbar(App.getApp().getBaseContext(), new ErrorInfo( | ||||
|             ErrorUtil.showSnackbar(App.getInstance().getBaseContext(), new ErrorInfo( | ||||
|                     throwable, | ||||
|                     UserAction.OPEN_INFO_ITEM_DIALOG, | ||||
|                     "none", | ||||
|   | ||||
| @@ -165,7 +165,7 @@ class FeedViewModel( | ||||
|         fun getFactory(context: Context, groupId: Long) = viewModelFactory { | ||||
|             initializer { | ||||
|                 FeedViewModel( | ||||
|                     App.getApp(), | ||||
|                     App.instance, | ||||
|                     groupId, | ||||
|                     // Read initial value from preferences | ||||
|                     getShowPlayedItemsFromPreferences(context.applicationContext), | ||||
|   | ||||
| @@ -46,6 +46,7 @@ import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; | ||||
| import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; | ||||
| import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; | ||||
| import static java.util.concurrent.TimeUnit.MILLISECONDS; | ||||
| import static coil3.Image_androidKt.toBitmap; | ||||
|  | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| @@ -53,14 +54,12 @@ import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import android.content.SharedPreferences; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.media.AudioManager; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.graphics.drawable.DrawableKt; | ||||
| import androidx.core.math.MathUtils; | ||||
| import androidx.preference.PreferenceManager; | ||||
|  | ||||
| @@ -125,7 +124,7 @@ import java.util.List; | ||||
| import java.util.Optional; | ||||
| import java.util.stream.IntStream; | ||||
|  | ||||
| import coil.target.Target; | ||||
| import coil3.target.Target; | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.rxjava3.core.Observable; | ||||
| import io.reactivex.rxjava3.disposables.CompositeDisposable; | ||||
| @@ -193,7 +192,7 @@ public final class Player implements PlaybackListener, Listener { | ||||
|     @Nullable | ||||
|     private Bitmap currentThumbnail; | ||||
|     @Nullable | ||||
|     private coil.request.Disposable thumbnailDisposable; | ||||
|     private coil3.request.Disposable thumbnailDisposable; | ||||
|  | ||||
|     /*////////////////////////////////////////////////////////////////////////// | ||||
|     // Player | ||||
| @@ -789,27 +788,26 @@ public final class Player implements PlaybackListener, Listener { | ||||
|         // scale down the notification thumbnail for performance | ||||
|         final var thumbnailTarget = new Target() { | ||||
|             @Override | ||||
|             public void onError(@Nullable final Drawable error) { | ||||
|             public void onError(@Nullable final coil3.Image error) { | ||||
|                 Log.e(TAG, "Thumbnail - onError() called"); | ||||
|                 // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. | ||||
|                 onThumbnailLoaded(null); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onStart(@Nullable final Drawable placeholder) { | ||||
|             public void onStart(@Nullable final coil3.Image placeholder) { | ||||
|                 if (DEBUG) { | ||||
|                     Log.d(TAG, "Thumbnail - onStart() called"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onSuccess(@NonNull final Drawable result) { | ||||
|             public void onSuccess(@NonNull final coil3.Image result) { | ||||
|                 if (DEBUG) { | ||||
|                     Log.d(TAG, "Thumbnail - onSuccess() called with: drawable = [" + result + "]"); | ||||
|                 } | ||||
|                 // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. | ||||
|                 onThumbnailLoaded(DrawableKt.toBitmapOrNull(result, result.getIntrinsicWidth(), | ||||
|                         result.getIntrinsicHeight(), null)); | ||||
|                 onThumbnailLoaded(toBitmap(result)); | ||||
|             } | ||||
|         }; | ||||
|         thumbnailDisposable = CoilHelper.INSTANCE | ||||
|   | ||||
| @@ -16,8 +16,8 @@ import com.google.android.exoplayer2.PlaybackParameters; | ||||
| import org.schabi.newpipe.App; | ||||
| import org.schabi.newpipe.MainActivity; | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||
| import org.schabi.newpipe.player.PlayerService; | ||||
| import org.schabi.newpipe.player.Player; | ||||
| import org.schabi.newpipe.player.PlayerService; | ||||
| import org.schabi.newpipe.player.PlayerType; | ||||
| import org.schabi.newpipe.player.event.PlayerServiceEventListener; | ||||
| import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; | ||||
| @@ -116,7 +116,7 @@ public final class PlayerHolder { | ||||
|     // helper to handle context in common place as using the same | ||||
|     // context to bind/unbind a service is crucial | ||||
|     private Context getCommonContext() { | ||||
|         return App.getApp(); | ||||
|         return App.getInstance(); | ||||
|     } | ||||
|  | ||||
|     public void startService(final boolean playAfterConnect, | ||||
|   | ||||
| @@ -179,7 +179,7 @@ public class SeekbarPreviewThumbnailHolder { | ||||
|  | ||||
|             // Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient | ||||
|             // Ensure that you are not running on the main thread, otherwise this will hang | ||||
|             final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getApp(), url); | ||||
|             final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getInstance(), url); | ||||
|  | ||||
|             if (sw != null) { | ||||
|                 Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took " | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.localization.Localization; | ||||
| import org.schabi.newpipe.util.image.ImageStrategy; | ||||
| import org.schabi.newpipe.util.image.PreferredImageQuality; | ||||
|  | ||||
| import coil.Coil; | ||||
| import coil3.SingletonImageLoader; | ||||
|  | ||||
| public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|     private String youtubeRestrictedModeEnabledKey; | ||||
| @@ -41,7 +41,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { | ||||
|                 (preference, newValue) -> { | ||||
|                     ImageStrategy.setPreferredImageQuality(PreferredImageQuality | ||||
|                             .fromPreferenceKey(requireContext(), (String) newValue)); | ||||
|                     final var loader = Coil.imageLoader(preference.getContext()); | ||||
|                     final var loader = SingletonImageLoader.get(preference.getContext()); | ||||
|                     loader.getMemoryCache().clear(); | ||||
|                     loader.getDiskCache().clear(); | ||||
|                     Toast.makeText(preference.getContext(), | ||||
|   | ||||
| @@ -156,7 +156,7 @@ public final class NewPipeSettings { | ||||
|                 prefs.getInt(disabledTunnelingAutomaticallyKey, -1) == 0 | ||||
|                         && !prefs.getBoolean(disabledTunnelingKey, false); | ||||
|  | ||||
|         if (App.getApp().isFirstRun() | ||||
|         if (App.getInstance().isFirstRun() | ||||
|                 || (wasDeviceBlacklistUpdated && !wasMediaTunnelingEnabledByUser)) { | ||||
|             setMediaTunneling(context); | ||||
|         } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package org.schabi.newpipe.settings; | ||||
|  | ||||
| import static org.schabi.newpipe.MainActivity.DEBUG; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.util.Log; | ||||
| @@ -18,8 +20,6 @@ import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
|  | ||||
| import static org.schabi.newpipe.MainActivity.DEBUG; | ||||
|  | ||||
| /** | ||||
|  * In order to add a migration, follow these steps, given P is the previous version:<br> | ||||
|  * - in the class body add a new {@code MIGRATION_P_P+1 = new Migration(P, P+1) { ... }} and put in | ||||
| @@ -171,7 +171,7 @@ public final class SettingMigrations { | ||||
|         final int lastPrefVersion = sp.getInt(lastPrefVersionKey, 0); | ||||
|  | ||||
|         // no migration to run, already up to date | ||||
|         if (App.getApp().isFirstRun()) { | ||||
|         if (App.getInstance().isFirstRun()) { | ||||
|             sp.edit().putInt(lastPrefVersionKey, VERSION).apply(); | ||||
|             return; | ||||
|         } else if (lastPrefVersion == VERSION) { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import androidx.compose.ui.layout.ContentScale | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import coil.compose.AsyncImage | ||||
| import coil3.compose.AsyncImage | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem | ||||
| import org.schabi.newpipe.util.Localization | ||||
|   | ||||
| @@ -22,7 +22,7 @@ import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.lifecycle.viewmodel.compose.viewModel | ||||
| import coil.compose.AsyncImage | ||||
| import coil3.compose.AsyncImage | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem | ||||
| import org.schabi.newpipe.util.Localization | ||||
|   | ||||
| @@ -41,7 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.tooling.preview.PreviewParameter | ||||
| import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider | ||||
| import androidx.compose.ui.unit.dp | ||||
| import coil.compose.AsyncImage | ||||
| import coil3.compose.AsyncImage | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.Page | ||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem | ||||
|   | ||||
| @@ -28,7 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.tooling.preview.datasource.LoremIpsum | ||||
| import androidx.compose.ui.unit.dp | ||||
| import coil.compose.AsyncImage | ||||
| import coil3.compose.AsyncImage | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem | ||||
| import org.schabi.newpipe.extractor.stream.Description | ||||
|   | ||||
| @@ -130,7 +130,7 @@ public final class DeviceUtils { | ||||
|         } | ||||
|  | ||||
|         isFireTV = | ||||
|                 App.getApp().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV); | ||||
|                 App.getInstance().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV); | ||||
|         return isFireTV; | ||||
|     } | ||||
|  | ||||
| @@ -139,7 +139,7 @@ public final class DeviceUtils { | ||||
|             return isTV; | ||||
|         } | ||||
|  | ||||
|         final PackageManager pm = App.getApp().getPackageManager(); | ||||
|         final PackageManager pm = App.getInstance().getPackageManager(); | ||||
|  | ||||
|         // from doc: https://developer.android.com/training/tv/start/hardware.html#runtime-check | ||||
|         boolean isTv = ContextCompat.getSystemService(context, UiModeManager.class) | ||||
|   | ||||
| @@ -21,7 +21,7 @@ object ReleaseVersionUtil { | ||||
|         val certificates = mapOf( | ||||
|             RELEASE_CERT_PUBLIC_KEY_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256 | ||||
|         ) | ||||
|         val app = App.getApp() | ||||
|         val app = App.instance | ||||
|         try { | ||||
|             PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false) | ||||
|         } catch (e: PackageManager.NameNotFoundException) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package org.schabi.newpipe.util.external_communication; | ||||
|  | ||||
| import static org.schabi.newpipe.MainActivity.DEBUG; | ||||
| import static coil3.Image_androidKt.toBitmap; | ||||
|  | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.ClipData; | ||||
| @@ -31,9 +32,9 @@ import java.nio.file.Files; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import coil.Coil; | ||||
| import coil.disk.DiskCache; | ||||
| import coil.memory.MemoryCache; | ||||
| import coil3.SingletonImageLoader; | ||||
| import coil3.disk.DiskCache; | ||||
| import coil3.memory.MemoryCache; | ||||
|  | ||||
| public final class ShareUtils { | ||||
|     private static final String TAG = ShareUtils.class.getSimpleName(); | ||||
| @@ -377,13 +378,13 @@ public final class ShareUtils { | ||||
|             // Save the image in memory to the application's cache because we need a URI to the | ||||
|             // image to generate a ClipData which will show the share sheet, and so an image file | ||||
|             final Context applicationContext = context.getApplicationContext(); | ||||
|             final var loader = Coil.imageLoader(context); | ||||
|             final var loader = SingletonImageLoader.get(context); | ||||
|             final var value = loader.getMemoryCache() | ||||
|                     .get(new MemoryCache.Key(thumbnailUrl, Collections.emptyMap())); | ||||
|  | ||||
|             final Bitmap cachedBitmap; | ||||
|             if (value != null) { | ||||
|                 cachedBitmap = value.getBitmap(); | ||||
|                 cachedBitmap = toBitmap(value.getImage()); | ||||
|             } else { | ||||
|                 try (var snapshot = loader.getDiskCache().openSnapshot(thumbnailUrl)) { | ||||
|                     if (snapshot != null) { | ||||
|   | ||||
| @@ -5,14 +5,18 @@ import android.graphics.Bitmap | ||||
| import android.util.Log | ||||
| import android.widget.ImageView | ||||
| import androidx.annotation.DrawableRes | ||||
| import androidx.core.graphics.drawable.toBitmapOrNull | ||||
| import coil.executeBlocking | ||||
| import coil.imageLoader | ||||
| import coil.request.Disposable | ||||
| import coil.request.ImageRequest | ||||
| import coil.size.Size | ||||
| import coil.target.Target | ||||
| import coil.transform.Transformation | ||||
| import coil3.executeBlocking | ||||
| import coil3.imageLoader | ||||
| import coil3.request.Disposable | ||||
| import coil3.request.ImageRequest | ||||
| import coil3.request.error | ||||
| import coil3.request.placeholder | ||||
| import coil3.request.target | ||||
| import coil3.request.transformations | ||||
| import coil3.size.Size | ||||
| import coil3.target.Target | ||||
| import coil3.toBitmap | ||||
| import coil3.transform.Transformation | ||||
| import org.schabi.newpipe.MainActivity | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.Image | ||||
| @@ -26,84 +30,119 @@ object CoilHelper { | ||||
|     fun loadBitmapBlocking( | ||||
|         context: Context, | ||||
|         url: String?, | ||||
|         @DrawableRes placeholderResId: Int = 0 | ||||
|     ): Bitmap? { | ||||
|         val request = getImageRequest(context, url, placeholderResId).build() | ||||
|         return context.imageLoader.executeBlocking(request).drawable?.toBitmapOrNull() | ||||
|     } | ||||
|         @DrawableRes placeholderResId: Int = 0, | ||||
|     ): Bitmap? = | ||||
|         context.imageLoader | ||||
|             .executeBlocking(getImageRequest(context, url, placeholderResId).build()) | ||||
|             .image | ||||
|             ?.toBitmap() | ||||
|  | ||||
|     fun loadAvatar(target: ImageView, images: List<Image>) { | ||||
|     fun loadAvatar( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|     ) { | ||||
|         loadImageDefault(target, images, R.drawable.placeholder_person) | ||||
|     } | ||||
|  | ||||
|     fun loadAvatar(target: ImageView, url: String?) { | ||||
|     fun loadAvatar( | ||||
|         target: ImageView, | ||||
|         url: String?, | ||||
|     ) { | ||||
|         loadImageDefault(target, url, R.drawable.placeholder_person) | ||||
|     } | ||||
|  | ||||
|     fun loadThumbnail(target: ImageView, images: List<Image>) { | ||||
|     fun loadThumbnail( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|     ) { | ||||
|         loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video) | ||||
|     } | ||||
|  | ||||
|     fun loadThumbnail(target: ImageView, url: String?) { | ||||
|     fun loadThumbnail( | ||||
|         target: ImageView, | ||||
|         url: String?, | ||||
|     ) { | ||||
|         loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video) | ||||
|     } | ||||
|  | ||||
|     fun loadScaledDownThumbnail(context: Context, images: List<Image>, target: Target): Disposable { | ||||
|     fun loadScaledDownThumbnail( | ||||
|         context: Context, | ||||
|         images: List<Image>, | ||||
|         target: Target, | ||||
|     ): Disposable { | ||||
|         val url = ImageStrategy.choosePreferredImage(images) | ||||
|         val request = getImageRequest(context, url, R.drawable.placeholder_thumbnail_video) | ||||
|             .target(target) | ||||
|             .transformations(object : Transformation { | ||||
|                 override val cacheKey = "COIL_PLAYER_THUMBNAIL_TRANSFORMATION_KEY" | ||||
|         val request = | ||||
|             getImageRequest(context, url, R.drawable.placeholder_thumbnail_video) | ||||
|                 .target(target) | ||||
|                 .transformations( | ||||
|                     object : Transformation() { | ||||
|                         override val cacheKey = "COIL_PLAYER_THUMBNAIL_TRANSFORMATION_KEY" | ||||
|  | ||||
|                 override suspend fun transform(input: Bitmap, size: Size): Bitmap { | ||||
|                     if (MainActivity.DEBUG) { | ||||
|                         Log.d(TAG, "Thumbnail - transform() called") | ||||
|                     } | ||||
|                         override suspend fun transform( | ||||
|                             input: Bitmap, | ||||
|                             size: Size, | ||||
|                         ): Bitmap { | ||||
|                             if (MainActivity.DEBUG) { | ||||
|                                 Log.d(TAG, "Thumbnail - transform() called") | ||||
|                             } | ||||
|  | ||||
|                     val notificationThumbnailWidth = min( | ||||
|                         context.resources.getDimension(R.dimen.player_notification_thumbnail_width), | ||||
|                         input.width.toFloat() | ||||
|                     ).toInt() | ||||
|                             val notificationThumbnailWidth = | ||||
|                                 min( | ||||
|                                     context.resources.getDimension(R.dimen.player_notification_thumbnail_width), | ||||
|                                     input.width.toFloat(), | ||||
|                                 ).toInt() | ||||
|  | ||||
|                     var newHeight = input.height / (input.width / notificationThumbnailWidth) | ||||
|                     val result = input.scale(notificationThumbnailWidth, newHeight) | ||||
|                             var newHeight = input.height / (input.width / notificationThumbnailWidth) | ||||
|                             val result = input.scale(notificationThumbnailWidth, newHeight) | ||||
|  | ||||
|                     return if (result == input || !result.isMutable) { | ||||
|                         // create a new mutable bitmap to prevent strange crashes on some | ||||
|                         // devices (see #4638) | ||||
|                         newHeight = input.height / (input.width / (notificationThumbnailWidth - 1)) | ||||
|                         input.scale(notificationThumbnailWidth, newHeight) | ||||
|                     } else { | ||||
|                         result | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .build() | ||||
|                             return if (result == input || !result.isMutable) { | ||||
|                                 // create a new mutable bitmap to prevent strange crashes on some | ||||
|                                 // devices (see #4638) | ||||
|                                 newHeight = input.height / (input.width / (notificationThumbnailWidth - 1)) | ||||
|                                 input.scale(notificationThumbnailWidth, newHeight) | ||||
|                             } else { | ||||
|                                 result | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                 ).build() | ||||
|  | ||||
|         return context.imageLoader.enqueue(request) | ||||
|     } | ||||
|  | ||||
|     fun loadDetailsThumbnail(target: ImageView, images: List<Image>) { | ||||
|     fun loadDetailsThumbnail( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|     ) { | ||||
|         val url = ImageStrategy.choosePreferredImage(images) | ||||
|         loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false) | ||||
|     } | ||||
|  | ||||
|     fun loadBanner(target: ImageView, images: List<Image>) { | ||||
|     fun loadBanner( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|     ) { | ||||
|         loadImageDefault(target, images, R.drawable.placeholder_channel_banner) | ||||
|     } | ||||
|  | ||||
|     fun loadPlaylistThumbnail(target: ImageView, images: List<Image>) { | ||||
|     fun loadPlaylistThumbnail( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|     ) { | ||||
|         loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist) | ||||
|     } | ||||
|  | ||||
|     fun loadPlaylistThumbnail(target: ImageView, url: String?) { | ||||
|     fun loadPlaylistThumbnail( | ||||
|         target: ImageView, | ||||
|         url: String?, | ||||
|     ) { | ||||
|         loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist) | ||||
|     } | ||||
|  | ||||
|     private fun loadImageDefault( | ||||
|         target: ImageView, | ||||
|         images: List<Image>, | ||||
|         @DrawableRes placeholderResId: Int | ||||
|         @DrawableRes placeholderResId: Int, | ||||
|     ) { | ||||
|         loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId) | ||||
|     } | ||||
| @@ -112,11 +151,12 @@ object CoilHelper { | ||||
|         target: ImageView, | ||||
|         url: String?, | ||||
|         @DrawableRes placeholderResId: Int, | ||||
|         showPlaceholder: Boolean = true | ||||
|         showPlaceholder: Boolean = true, | ||||
|     ) { | ||||
|         val request = getImageRequest(target.context, url, placeholderResId, showPlaceholder) | ||||
|             .target(target) | ||||
|             .build() | ||||
|         val request = | ||||
|             getImageRequest(target.context, url, placeholderResId, showPlaceholder) | ||||
|                 .target(target) | ||||
|                 .build() | ||||
|         target.context.imageLoader.enqueue(request) | ||||
|     } | ||||
|  | ||||
| @@ -124,14 +164,15 @@ object CoilHelper { | ||||
|         context: Context, | ||||
|         url: String?, | ||||
|         @DrawableRes placeholderResId: Int, | ||||
|         showPlaceholderWhileLoading: Boolean = true | ||||
|         showPlaceholderWhileLoading: Boolean = true, | ||||
|     ): ImageRequest.Builder { | ||||
|         // if the URL was chosen with `choosePreferredImage` it will be null, but check again | ||||
|         // `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case | ||||
|         // for URLs stored in the database) | ||||
|         val takenUrl = url?.takeIf { it.isNotEmpty() && ImageStrategy.shouldLoadImages() } | ||||
|  | ||||
|         return ImageRequest.Builder(context) | ||||
|         return ImageRequest | ||||
|             .Builder(context) | ||||
|             .data(takenUrl) | ||||
|             .error(placeholderResId) | ||||
|             .memoryCacheKey(takenUrl) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne