From 1af798b04b8a455e81b47794b6f1ae18805c8dab Mon Sep 17 00:00:00 2001 From: Siddhesh Naik Date: Wed, 28 Feb 2024 00:33:46 +0530 Subject: [PATCH 01/11] Introducing Jetpack Compose in NewPipe This pull request integrates Jetpack Compose into NewPipe by: - Adding the necessary dependencies and setup. - This is part of the NewPipe rewrite and fulfils the requirement for the planned settings page redesign. - Introducing a Toolbar composable with theming that aligns with NewPipe's design. Note: - Theme colors are generated using the Material Theme builder (https://m3.material.io/styles/color/overview). --- app/build.gradle | 14 ++ .../java/org/schabi/newpipe/ui/Toolbar.kt | 137 ++++++++++++++++++ .../java/org/schabi/newpipe/ui/theme/Color.kt | 63 ++++++++ .../org/schabi/newpipe/ui/theme/SizeTokens.kt | 13 ++ .../java/org/schabi/newpipe/ui/theme/Theme.kt | 79 ++++++++++ 5 files changed, 306 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt create mode 100644 app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt create mode 100644 app/src/main/java/org/schabi/newpipe/ui/theme/SizeTokens.kt create mode 100644 app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt diff --git a/app/build.gradle b/app/build.gradle index 28a20819511..5502025a78e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,6 +92,7 @@ android { buildFeatures { viewBinding true + compose true } packagingOptions { @@ -103,6 +104,10 @@ android { 'META-INF/COPYRIGHT'] } } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.3" + } } ext { @@ -284,6 +289,12 @@ dependencies { // Date and time formatting implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final" + // Jetpack Compose + implementation(platform('androidx.compose:compose-bom:2024.02.01')) + implementation 'androidx.compose.material3:material3' + implementation 'androidx.activity:activity-compose' + implementation 'androidx.compose.ui:ui-tooling-preview' + /** Debugging **/ // Memory leak detection debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}" @@ -293,6 +304,9 @@ dependencies { debugImplementation "com.facebook.stetho:stetho:${stethoVersion}" debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}" + // Jetpack Compose + debugImplementation 'androidx.compose.ui:ui-tooling' + /** Testing **/ testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.6.0' diff --git a/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt b/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt new file mode 100644 index 00000000000..b788932a269 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt @@ -0,0 +1,137 @@ +package org.schabi.newpipe.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.R +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.SizeTokens + +@Composable +fun TextAction(text: String, modifier: Modifier = Modifier) { + Text(text = text, color = MaterialTheme.colorScheme.onSurface, modifier = modifier) +} + +@Composable +fun NavigationIcon() { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", + modifier = Modifier.padding(horizontal = SizeTokens.SpacingExtraSmall) + ) +} + +@Composable +fun SearchSuggestionItem(text: String) { + // TODO: Add more components here to display all the required details of a search suggestion item. + Text(text = text) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Toolbar( + title: String, + modifier: Modifier = Modifier, + hasNavigationIcon: Boolean = true, + hasSearch: Boolean = false, + onSearchQueryChange: ((String) -> List)? = null, + actions: @Composable RowScope.() -> Unit = {} +) { + var isSearchActive by remember { mutableStateOf(false) } + var query by remember { mutableStateOf("") } + + Column { + TopAppBar( + title = { Text(text = title) }, + modifier = modifier, + navigationIcon = { if (hasNavigationIcon) NavigationIcon() }, + actions = { + actions() + if (hasSearch) { + IconButton(onClick = { isSearchActive = true }) { + Icon( + painterResource(id = R.drawable.ic_search), + contentDescription = stringResource(id = R.string.search), + tint = MaterialTheme.colorScheme.onSurface + ) + } + } + } + ) + if (isSearchActive) { + SearchBar( + query = query, + onQueryChange = { query = it }, + onSearch = {}, + placeholder = { + Text(text = stringResource(id = R.string.search)) + }, + active = true, + onActiveChange = { + isSearchActive = it + }, + colors = SearchBarDefaults.colors( + containerColor = MaterialTheme.colorScheme.background, + inputFieldColors = SearchBarDefaults.inputFieldColors( + focusedTextColor = MaterialTheme.colorScheme.onBackground, + unfocusedTextColor = MaterialTheme.colorScheme.onBackground + ) + ) + ) { + onSearchQueryChange?.invoke(query)?.takeIf { it.isNotEmpty() } + ?.map { suggestionText -> SearchSuggestionItem(text = suggestionText) } + ?: run { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Column { + Text(text = "╰(°●°╰)") + Text(text = stringResource(id = R.string.search_no_results)) + } + } + } + } + } + } +} + +@Preview +@Composable +fun ToolbarPreview() { + AppTheme { + Toolbar( + title = "Title", + hasSearch = true, + onSearchQueryChange = { emptyList() }, + actions = { + TextAction(text = "Action1") + TextAction(text = "Action2") + } + ) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt new file mode 100644 index 00000000000..b61906ebed6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt @@ -0,0 +1,63 @@ +package org.schabi.newpipe.ui.theme + +import androidx.compose.ui.graphics.Color + +val md_theme_light_primary = Color(0xFFBB171C) +val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_primaryContainer = Color(0xFFFFDAD6) +val md_theme_light_onPrimaryContainer = Color(0xFF410002) +val md_theme_light_secondary = Color(0xFF984061) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFFFD9E2) +val md_theme_light_onSecondaryContainer = Color(0xFF3E001D) +val md_theme_light_tertiary = Color(0xFF006874) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFF97F0FF) +val md_theme_light_onTertiaryContainer = Color(0xFF001F24) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFEEEEEE) +val md_theme_light_onBackground = Color(0xFF1B1B1B) +val md_theme_light_surface = Color(0xFFE53835) +val md_theme_light_onSurface = Color(0xFFFFFFFF) +val md_theme_light_surfaceVariant = Color(0xFFF5DDDB) +val md_theme_light_onSurfaceVariant = Color(0xFF534341) +val md_theme_light_outline = Color(0xFF857371) +val md_theme_light_inverseOnSurface = Color(0xFFD6F6FF) +val md_theme_light_inverseSurface = Color(0xFF00363F) +val md_theme_light_inversePrimary = Color(0xFFFFB4AC) +val md_theme_light_surfaceTint = Color(0xFFBB171C) +val md_theme_light_outlineVariant = Color(0xFFD8C2BF) +val md_theme_light_scrim = Color(0xFF000000) + +val md_theme_dark_primary = Color(0xFFFFB4AC) +val md_theme_dark_onPrimary = Color(0xFF690006) +val md_theme_dark_primaryContainer = Color(0xFF93000D) +val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD6) +val md_theme_dark_secondary = Color(0xFFFFB1C8) +val md_theme_dark_onSecondary = Color(0xFF5E1133) +val md_theme_dark_secondaryContainer = Color(0xFF7B2949) +val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9E2) +val md_theme_dark_tertiary = Color(0xFF4FD8EB) +val md_theme_dark_onTertiary = Color(0xFF00363D) +val md_theme_dark_tertiaryContainer = Color(0xFF004F58) +val md_theme_dark_onTertiaryContainer = Color(0xFF97F0FF) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF212121) +val md_theme_dark_onBackground = Color(0xFFFFFFFF) +val md_theme_dark_surface = Color(0xFF992521) +val md_theme_dark_onSurface = Color(0xFFFFFFFF) +val md_theme_dark_surfaceVariant = Color(0xFF534341) +val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BF) +val md_theme_dark_outline = Color(0xFFA08C8A) +val md_theme_dark_inverseOnSurface = Color(0xFF001F25) +val md_theme_dark_inverseSurface = Color(0xFFA6EEFF) +val md_theme_dark_inversePrimary = Color(0xFFBB171C) +val md_theme_dark_surfaceTint = Color(0xFFFFB4AC) +val md_theme_dark_outlineVariant = Color(0xFF534341) +val md_theme_dark_scrim = Color(0xFF000000) diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/SizeTokens.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/SizeTokens.kt new file mode 100644 index 00000000000..d8104d7aea8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/SizeTokens.kt @@ -0,0 +1,13 @@ +package org.schabi.newpipe.ui.theme + +import androidx.compose.ui.unit.dp + +internal object SizeTokens { + val SpacingExtraSmall = 4.dp + val SpacingSmall = 8.dp + val SpacingMedium = 16.dp + val SpacingLarge = 24.dp + val SpacingExtraLarge = 32.dp + + val SpaceMinSize = 44.dp // Minimum tappable size required for accessibility +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt new file mode 100644 index 00000000000..846794d725c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt @@ -0,0 +1,79 @@ +package org.schabi.newpipe.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + outlineVariant = md_theme_light_outlineVariant, + scrim = md_theme_light_scrim, +) + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + outlineVariant = md_theme_dark_outlineVariant, + scrim = md_theme_dark_scrim, +) + +@Composable +fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = if (useDarkTheme) DarkColors else LightColors, + content = content + ) +} From 73e3a69aaf78a1d7a7f2f4c54e22d718b7c3093a Mon Sep 17 00:00:00 2001 From: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:53:04 +0530 Subject: [PATCH 02/11] Migrate image loading from Picasso to Coil (#11201) * Load notification icons using Coil * Migrate to Coil from Picasso * Clean up Picasso leftovers * Enable RGB-565 for low-end devices * Added Coil helper method * Add annotation * Simplify newImageLoader implementation * Use Coil's default disk and memory cache config * Enable crossfade animation * Correct method name * Fix thumbnail not being displayed in media notification --- app/build.gradle | 3 +- app/src/main/java/org/schabi/newpipe/App.java | 24 +- .../org/schabi/newpipe/about/AboutActivity.kt | 4 +- .../fragments/detail/VideoDetailFragment.java | 29 +-- .../list/channel/ChannelFragment.java | 20 +- .../list/comments/CommentRepliesFragment.java | 4 +- .../list/playlist/PlaylistFragment.java | 11 +- .../newpipe/info_list/StreamSegmentItem.kt | 44 ++-- .../holder/ChannelMiniInfoItemHolder.java | 4 +- .../holder/CommentInfoItemHolder.java | 8 +- .../holder/PlaylistMiniInfoItemHolder.java | 4 +- .../holder/StreamMiniInfoItemHolder.java | 4 +- .../java/org/schabi/newpipe/ktx/Bitmap.kt | 13 + .../newpipe/local/feed/item/StreamItem.kt | 4 +- .../feed/notifications/NotificationHelper.kt | 58 +---- .../local/holder/LocalPlaylistItemHolder.java | 7 +- .../holder/LocalPlaylistStreamItemHolder.java | 6 +- .../LocalStatisticStreamItemHolder.java | 6 +- .../holder/RemotePlaylistItemHolder.java | 7 +- .../local/subscription/item/ChannelItem.kt | 4 +- .../item/PickerSubscriptionItem.kt | 4 +- .../org/schabi/newpipe/player/Player.java | 87 +++---- .../playqueue/PlayQueueItemBuilder.java | 4 +- .../SeekbarPreviewThumbnailHolder.java | 7 +- .../settings/ContentSettingsFragment.java | 20 +- .../settings/DebugSettingsFragment.java | 9 - .../settings/SelectChannelFragment.java | 4 +- .../settings/SelectPlaylistFragment.java | 17 +- .../external_communication/ShareUtils.java | 21 +- .../schabi/newpipe/util/image/CoilHelper.kt | 142 +++++++++++ .../newpipe/util/image/PicassoHelper.java | 224 ------------------ app/src/main/res/values-ar-rLY/strings.xml | 2 - app/src/main/res/values-ar/strings.xml | 2 - app/src/main/res/values-az/strings.xml | 2 - app/src/main/res/values-be/strings.xml | 2 - app/src/main/res/values-bg/strings.xml | 1 - app/src/main/res/values-bn/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 2 - app/src/main/res/values-ckb/strings.xml | 2 - app/src/main/res/values-cs/strings.xml | 2 - app/src/main/res/values-da/strings.xml | 2 - app/src/main/res/values-de/strings.xml | 2 - app/src/main/res/values-el/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values-et/strings.xml | 2 - app/src/main/res/values-eu/strings.xml | 2 - app/src/main/res/values-fa/strings.xml | 2 - app/src/main/res/values-fi/strings.xml | 2 - app/src/main/res/values-fr/strings.xml | 2 - app/src/main/res/values-gl/strings.xml | 2 - app/src/main/res/values-he/strings.xml | 2 - app/src/main/res/values-hi/strings.xml | 2 - app/src/main/res/values-hr/strings.xml | 2 - app/src/main/res/values-hu/strings.xml | 2 - app/src/main/res/values-in/strings.xml | 2 - app/src/main/res/values-is/strings.xml | 2 - app/src/main/res/values-it/strings.xml | 2 - app/src/main/res/values-ja/strings.xml | 2 - app/src/main/res/values-ka/strings.xml | 2 - app/src/main/res/values-ko/strings.xml | 2 - app/src/main/res/values-lt/strings.xml | 2 - app/src/main/res/values-lv/strings.xml | 2 - app/src/main/res/values-ml/strings.xml | 2 - app/src/main/res/values-nb-rNO/strings.xml | 2 - app/src/main/res/values-nl-rBE/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 2 - app/src/main/res/values-nqo/strings.xml | 2 - app/src/main/res/values-or/strings.xml | 2 - app/src/main/res/values-pa/strings.xml | 2 - app/src/main/res/values-pl/strings.xml | 2 - app/src/main/res/values-pt-rBR/strings.xml | 2 - app/src/main/res/values-pt-rPT/strings.xml | 2 - app/src/main/res/values-pt/strings.xml | 2 - app/src/main/res/values-ro/strings.xml | 2 - app/src/main/res/values-ru/strings.xml | 2 - app/src/main/res/values-ryu/strings.xml | 2 - app/src/main/res/values-sat/strings.xml | 2 - app/src/main/res/values-sc/strings.xml | 2 - app/src/main/res/values-sk/strings.xml | 2 - app/src/main/res/values-so/strings.xml | 2 - app/src/main/res/values-sr/strings.xml | 2 - app/src/main/res/values-sv/strings.xml | 2 - app/src/main/res/values-te/strings.xml | 2 - app/src/main/res/values-tr/strings.xml | 2 - app/src/main/res/values-uk/strings.xml | 2 - app/src/main/res/values-vi/strings.xml | 2 - app/src/main/res/values-zh-rCN/strings.xml | 2 - app/src/main/res/values-zh-rHK/strings.xml | 2 - app/src/main/res/values-zh-rTW/strings.xml | 2 - app/src/main/res/values/settings_keys.xml | 1 - app/src/main/res/values/strings.xml | 2 - app/src/main/res/xml/debug_settings.xml | 7 - 92 files changed, 330 insertions(+), 596 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt create mode 100644 app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java diff --git a/app/build.gradle b/app/build.gradle index 4652cf6e5ba..1ba5aca389b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -267,8 +267,7 @@ dependencies { implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}" // Image loading - //noinspection GradleDependency --> 2.8 is the last version, not 2.71828! - implementation "com.squareup.picasso:picasso:2.8" + implementation 'io.coil-kt:coil:2.6.0' // Markdown library for Android implementation "io.noties.markwon:core:${markwonVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index d92425d200e..a47e0f2fdd4 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -1,5 +1,6 @@ package org.schabi.newpipe; +import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; @@ -8,6 +9,7 @@ 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; @@ -20,10 +22,9 @@ import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; 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; @@ -32,6 +33,9 @@ import java.util.List; import java.util.Objects; +import coil.ImageLoader; +import coil.ImageLoaderFactory; +import coil.util.DebugLogger; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; @@ -57,7 +61,7 @@ * along with NewPipe. If not, see . */ -public class App extends Application { +public class App extends Application implements ImageLoaderFactory { public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; private static final String TAG = App.class.toString(); @@ -108,20 +112,22 @@ public void onCreate() { // Initialize image loader final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - PicassoHelper.init(this); ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this, prefs.getString(getString(R.string.image_quality_key), getString(R.string.image_quality_default)))); - PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG - && prefs.getBoolean(getString(R.string.show_image_indicators_key), false)); configureRxJavaErrorHandler(); } + @NonNull @Override - public void onTerminate() { - super.onTerminate(); - PicassoHelper.terminate(); + 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() { diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt index 7f148e9b5c2..0d0d0d48dd4 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt @@ -167,8 +167,8 @@ class AboutActivity : AppCompatActivity() { "https://square.github.io/okhttp/", StandardLicenses.APACHE2 ), SoftwareComponent( - "Picasso", "2013", "Square, Inc.", - "https://square.github.io/picasso/", StandardLicenses.APACHE2 + "Coil", "2023", "Coil Contributors", + "https://coil-kt.github.io/coil/", StandardLicenses.APACHE2 ), SoftwareComponent( "PrettyTime", "2012 - 2020", "Lincoln Baxter, III", diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 95b54f65a70..96523321b14 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -116,7 +116,7 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.PicassoHelper; +import org.schabi.newpipe.util.image.CoilHelper; import java.util.ArrayList; import java.util.Iterator; @@ -127,6 +127,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import coil.util.CoilUtils; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -159,8 +160,6 @@ public final class VideoDetailFragment private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; private static final String EMPTY_TAB_TAG = "EMPTY TAB"; - private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG"; - // tabs private boolean showComments; private boolean showRelatedItems; @@ -1471,7 +1470,10 @@ public void showLoading() { } } - PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG); + CoilUtils.dispose(binding.detailThumbnailImageView); + CoilUtils.dispose(binding.detailSubChannelThumbnailView); + CoilUtils.dispose(binding.overlayThumbnail); + binding.detailThumbnailImageView.setImageBitmap(null); binding.detailSubChannelThumbnailView.setImageBitmap(null); } @@ -1562,8 +1564,8 @@ public void handleResult(@NonNull final StreamInfo info) { binding.detailSecondaryControlPanel.setVisibility(View.GONE); checkUpdateProgressInfo(info); - PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding.detailThumbnailImageView); + CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView, + info.getThumbnails()); showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, binding.detailMetaInfoSeparator, disposables); @@ -1613,8 +1615,8 @@ private void displayUploaderAsSubChannel(final StreamInfo info) { binding.detailUploaderTextView.setVisibility(View.GONE); } - PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding.detailSubChannelThumbnailView); + CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView, + info.getUploaderAvatars()); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); binding.detailUploaderThumbnailView.setVisibility(View.GONE); } @@ -1645,11 +1647,11 @@ private void displayBothUploaderAndSubChannel(final StreamInfo info) { binding.detailUploaderTextView.setVisibility(View.GONE); } - PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding.detailSubChannelThumbnailView); + CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView, + info.getSubChannelAvatars()); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); - PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding.detailUploaderThumbnailView); + CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView, + info.getUploaderAvatars()); binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE); } @@ -2403,8 +2405,7 @@ private void updateOverlayData(@Nullable final String overlayTitle, binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageDrawable(null); - PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding.overlayThumbnail); + CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails); } private void setOverlayPlayPauseImage(final boolean playerIsPlaying) { 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 fd382adbf46..3890e48659d 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 @@ -50,15 +50,16 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.StateSaver; -import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.image.CoilHelper; +import org.schabi.newpipe.util.image.ImageStrategy; import java.util.List; import java.util.Queue; import java.util.concurrent.TimeUnit; +import coil.util.CoilUtils; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; @@ -73,7 +74,6 @@ public class ChannelFragment extends BaseStateFragment implements StateSaver.WriteRead { private static final int BUTTON_DEBOUNCE_INTERVAL = 100; - private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG"; @State protected int serviceId = Constants.NO_SERVICE_ID; @@ -576,7 +576,9 @@ private void runWorker(final boolean forceLoad) { @Override public void showLoading() { super.showLoading(); - PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG); + CoilUtils.dispose(binding.channelAvatarView); + CoilUtils.dispose(binding.channelBannerImage); + CoilUtils.dispose(binding.subChannelAvatarView); animate(binding.channelSubscribeButton, false, 100); } @@ -587,17 +589,15 @@ public void handleResult(@NonNull final ChannelInfo result) { setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName()); if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) { - PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG) - .into(binding.channelBannerImage); + CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners()); } else { // do not waste space for the banner, if the user disabled images or there is not one binding.channelBannerImage.setImageDrawable(null); } - PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG) - .into(binding.channelAvatarView); - PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG) - .into(binding.subChannelAvatarView); + CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars()); + CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView, + result.getParentChannelAvatars()); binding.channelTitleView.setText(result.getName()); binding.channelSubscriberView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java index 304eaf55a18..4eb73520fba 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java @@ -23,8 +23,8 @@ import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.text.TextLinkifier; import java.util.Queue; @@ -82,7 +82,7 @@ protected Supplier getListHeaderSupplier() { final CommentsInfoItem item = commentsInfoItem; // load the author avatar - PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar); + CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars()); binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages() ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 6410fb9ee75..d4607a9ffb1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -53,7 +53,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PlayButtonHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.PicassoHelper; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.text.TextEllipsizer; import java.util.ArrayList; @@ -62,6 +62,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import coil.util.CoilUtils; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; @@ -71,8 +72,6 @@ public class PlaylistFragment extends BaseListInfoFragment implements PlaylistControlViewHolder { - private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG"; - private CompositeDisposable disposables; private Subscription bookmarkReactor; private AtomicBoolean isBookmarkButtonReady; @@ -276,7 +275,7 @@ public void showLoading() { animate(headerBinding.getRoot(), false, 200); animateHideRecyclerViewAllowingScrolling(itemsList); - PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG); + CoilUtils.dispose(headerBinding.uploaderAvatarView); animate(headerBinding.uploaderLayout, false, 200); } @@ -327,8 +326,8 @@ public void handleResult(@NonNull final PlaylistInfo result) { R.drawable.ic_radio) ); } else { - PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG) - .into(headerBinding.uploaderAvatarView); + CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView, + result.getUploaderAvatars()); } streamCount = result.getStreamCount(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt index 1e52d316808..83e0c408289 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt @@ -1,19 +1,18 @@ package org.schabi.newpipe.info_list import android.view.View -import android.widget.ImageView -import android.widget.TextView -import com.xwray.groupie.GroupieViewHolder -import com.xwray.groupie.Item +import com.xwray.groupie.viewbinding.BindableItem +import com.xwray.groupie.viewbinding.GroupieViewHolder import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.ItemStreamSegmentBinding import org.schabi.newpipe.extractor.stream.StreamSegment import org.schabi.newpipe.util.Localization -import org.schabi.newpipe.util.image.PicassoHelper +import org.schabi.newpipe.util.image.CoilHelper class StreamSegmentItem( private val item: StreamSegment, private val onClick: StreamSegmentAdapter.StreamSegmentListener -) : Item() { +) : BindableItem() { companion object { const val PAYLOAD_SELECT = 1 @@ -21,31 +20,32 @@ class StreamSegmentItem( var isSelected = false - override fun bind(viewHolder: GroupieViewHolder, position: Int) { - item.previewUrl?.let { - PicassoHelper.loadThumbnail(it) - .into(viewHolder.root.findViewById(R.id.previewImage)) - } - viewHolder.root.findViewById(R.id.textViewTitle).text = item.title + override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) { + CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl) + viewBinding.textViewTitle.text = item.title if (item.channelName == null) { - viewHolder.root.findViewById(R.id.textViewChannel).visibility = View.GONE + viewBinding.textViewChannel.visibility = View.GONE // When the channel name is displayed there is less space // and thus the segment title needs to be only one line height. // But when there is no channel name displayed, the title can be two lines long. // The default maxLines value is set to 1 to display all elements in the AS preview, - viewHolder.root.findViewById(R.id.textViewTitle).maxLines = 2 + viewBinding.textViewTitle.maxLines = 2 } else { - viewHolder.root.findViewById(R.id.textViewChannel).text = item.channelName - viewHolder.root.findViewById(R.id.textViewChannel).visibility = View.VISIBLE + viewBinding.textViewChannel.text = item.channelName + viewBinding.textViewChannel.visibility = View.VISIBLE } - viewHolder.root.findViewById(R.id.textViewStartSeconds).text = + viewBinding.textViewStartSeconds.text = Localization.getDurationString(item.startTimeSeconds.toLong()) - viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } - viewHolder.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true } - viewHolder.root.isSelected = isSelected + viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } + viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true } + viewBinding.root.isSelected = isSelected } - override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList) { + override fun bind( + viewHolder: GroupieViewHolder, + position: Int, + payloads: MutableList + ) { if (payloads.contains(PAYLOAD_SELECT)) { viewHolder.root.isSelected = isSelected return @@ -54,4 +54,6 @@ class StreamSegmentItem( } override fun getLayout() = R.layout.item_stream_segment + + override fun initializeViewBinding(view: View) = ItemStreamSegmentBinding.bind(view) } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 7afc05c6c25..92a5054e130 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -13,8 +13,8 @@ import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.CoilHelper; public class ChannelMiniInfoItemHolder extends InfoItemHolder { private final ImageView itemThumbnailView; @@ -56,7 +56,7 @@ public void updateFromItem(final InfoItem infoItem, itemAdditionalDetailView.setText(getDetailLine(item)); } - PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView); + CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getThumbnails()); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java index 839aa1813f3..a3316d3fecc 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java @@ -27,8 +27,8 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.text.TextEllipsizer; public class CommentInfoItemHolder extends InfoItemHolder { @@ -82,14 +82,12 @@ public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder, @Override public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof CommentsInfoItem)) { + if (!(infoItem instanceof CommentsInfoItem item)) { return; } - final CommentsInfoItem item = (CommentsInfoItem) infoItem; - // load the author avatar - PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView); + CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getUploaderAvatars()); if (ImageStrategy.shouldLoadImages()) { itemThumbnailView.setVisibility(View.VISIBLE); itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding, diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index c9216d9a9e5..b7949318d58 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -9,8 +9,8 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.CoilHelper; public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; @@ -46,7 +46,7 @@ public void updateFromItem(final InfoItem infoItem, .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); - PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView); + CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnails()); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 01f3be6b328..32fa8bf608b 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.StreamTypeUtil; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.util.concurrent.TimeUnit; @@ -87,7 +87,7 @@ public void updateFromItem(final InfoItem infoItem, } // Default thumbnail is shown on error, while loading and if the url is empty - PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView); + CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, item.getThumbnails()); itemView.setOnClickListener(view -> { if (itemBuilder.getOnStreamSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt new file mode 100644 index 00000000000..4a3c3e071d4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt @@ -0,0 +1,13 @@ +package org.schabi.newpipe.ktx + +import android.graphics.Bitmap +import android.graphics.Rect +import androidx.core.graphics.BitmapCompat + +@Suppress("NOTHING_TO_INLINE") +inline fun Bitmap.scale( + width: Int, + height: Int, + srcRect: Rect? = null, + scaleInLinearSpace: Boolean = true, +) = BitmapCompat.createScaledBitmap(this, width, height, srcRect, scaleInLinearSpace) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index 4a071d6df75..030bb7a7668 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -19,7 +19,7 @@ import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.StreamTypeUtil -import org.schabi.newpipe.util.image.PicassoHelper +import org.schabi.newpipe.util.image.CoilHelper import java.util.concurrent.TimeUnit import java.util.function.Consumer @@ -101,7 +101,7 @@ data class StreamItem( viewBinding.itemProgressView.visibility = View.GONE } - PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView) + CoilHelper.loadThumbnail(viewBinding.itemThumbnailView, stream.thumbnailUrl) if (itemVersion != ItemVersion.MINI) { viewBinding.itemAdditionalDetails.text = 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 8ea89368d6b..659088ef2fd 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 @@ -6,7 +6,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Bitmap -import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings @@ -16,20 +15,17 @@ import androidx.core.app.PendingIntentCompat import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.preference.PreferenceManager -import com.squareup.picasso.Picasso -import com.squareup.picasso.Target 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.image.PicassoHelper +import org.schabi.newpipe.util.image.CoilHelper /** * Helper for everything related to show notifications about new streams to the user. */ class NotificationHelper(val context: Context) { private val manager = NotificationManagerCompat.from(context) - private val iconLoadingTargets = ArrayList() /** * Show notifications for new streams from a single channel. The individual notifications are @@ -68,51 +64,23 @@ class NotificationHelper(val context: Context) { summaryBuilder.setStyle(style) // open the channel page when clicking on the summary notification + val intent = NavigationHelper + .getChannelIntent(context, data.serviceId, data.url) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) summaryBuilder.setContentIntent( - PendingIntentCompat.getActivity( - context, - data.pseudoId, - NavigationHelper - .getChannelIntent(context, data.serviceId, data.url) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - 0, - false - ) + PendingIntentCompat.getActivity(context, data.pseudoId, intent, 0, false) ) - // a Target is like a listener for image loading events - val target = object : Target { - override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { - // set channel icon only if there is actually one (for Android versions < 7.0) - summaryBuilder.setLargeIcon(bitmap) - - // Show individual stream notifications, set channel icon only if there is actually - // one - showStreamNotifications(newStreams, data.serviceId, bitmap) - // Show summary notification - manager.notify(data.pseudoId, summaryBuilder.build()) - - iconLoadingTargets.remove(this) // allow it to be garbage-collected - } - - override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { - // Show individual stream notifications - showStreamNotifications(newStreams, data.serviceId, null) - // Show summary notification - manager.notify(data.pseudoId, summaryBuilder.build()) - iconLoadingTargets.remove(this) // allow it to be garbage-collected - } - - override fun onPrepareLoad(placeHolderDrawable: Drawable) { - // Nothing to do - } - } + val avatarIcon = + CoilHelper.loadBitmapBlocking(context, data.avatarUrl, R.drawable.ic_newpipe_triangle_white) - // add the target to the list to hold a strong reference and prevent it from being garbage - // collected, since Picasso only holds weak references to targets - iconLoadingTargets.add(target) + summaryBuilder.setLargeIcon(avatarIcon) - PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target) + // Show individual stream notifications, set channel icon only if there is actually + // one + showStreamNotifications(newStreams, data.serviceId, avatarIcon) + // Show summary notification + manager.notify(data.pseudoId, summaryBuilder.build()) } private fun showStreamNotifications( diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index 336f5cfe30b..a11438374d1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.CoilHelper; import java.time.format.DateTimeFormatter; @@ -30,17 +30,16 @@ public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final Vie public void updateFromItem(final LocalItem localItem, final HistoryRecordManager historyRecordManager, final DateTimeFormatter dateTimeFormatter) { - if (!(localItem instanceof PlaylistMetadataEntry)) { + if (!(localItem instanceof PlaylistMetadataEntry item)) { return; } - final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; itemTitleView.setText(item.name); itemStreamCountView.setText(Localization.localizeStreamCountMini( itemStreamCountView.getContext(), item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); - PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView); + CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.thumbnailUrl); if (item instanceof PlaylistDuplicatesEntry && ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index 89a714fd7f6..7dc71bfb4ac 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -83,8 +83,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) - .into(itemThumbnailView); + CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, + item.getStreamEntity().getThumbnailUrl()); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index 150a35eb59c..f26a76ad9f7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -117,8 +117,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) - .into(itemThumbnailView); + CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, + item.getStreamEntity().getThumbnailUrl()); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 7657320634c..f79f3c78532 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -8,8 +8,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.image.CoilHelper; import java.time.format.DateTimeFormatter; @@ -29,10 +29,9 @@ public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, public void updateFromItem(final LocalItem localItem, final HistoryRecordManager historyRecordManager, final DateTimeFormatter dateTimeFormatter) { - if (!(localItem instanceof PlaylistRemoteEntity)) { + if (!(localItem instanceof PlaylistRemoteEntity item)) { return; } - final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; itemTitleView.setText(item.getName()); itemStreamCountView.setText(Localization.localizeStreamCountMini( @@ -45,7 +44,7 @@ public void updateFromItem(final LocalItem localItem, itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId())); } - PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); + CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl()); super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index bc39dafe632..ca626e704f1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -9,7 +9,7 @@ import org.schabi.newpipe.R import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.OnClickGesture -import org.schabi.newpipe.util.image.PicassoHelper +import org.schabi.newpipe.util.image.CoilHelper class ChannelItem( private val infoItem: ChannelInfoItem, @@ -39,7 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView) + CoilHelper.loadAvatar(itemThumbnailView, infoItem.thumbnails) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt index 3a4c6e41b99..da35447e38d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt @@ -10,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.util.image.PicassoHelper +import org.schabi.newpipe.util.image.CoilHelper data class PickerSubscriptionItem( val subscriptionEntity: SubscriptionEntity, @@ -21,7 +21,7 @@ data class PickerSubscriptionItem( override fun getSpanSize(spanCount: Int, position: Int): Int = 1 override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) { - PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView) + CoilHelper.loadAvatar(viewBinding.thumbnailView, subscriptionEntity.avatarUrl) viewBinding.titleView.text = subscriptionEntity.name viewBinding.selectedHighlight.isVisible = isSelected } 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 49e72328e40..40da9139dff 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -60,6 +60,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.DrawableKt; import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; @@ -77,8 +78,6 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.video.VideoSize; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; @@ -86,8 +85,8 @@ import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -118,14 +117,15 @@ import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; +import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; +import coil.target.Target; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -174,7 +174,6 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; - private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -246,12 +245,6 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); - // This is the only listener we need for thumbnail loading, since there is always at most only - // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, - // which would otherwise be garbage collected since Picasso holds weak references to targets. - @NonNull - private final Target currentThumbnailTarget; - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -295,8 +288,6 @@ public Player(@NonNull final PlayerService service) { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); - currentThumbnailTarget = getCurrentThumbnailTarget(); - // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -602,7 +593,6 @@ public void destroy() { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -776,67 +766,52 @@ private void unregisterBroadcastReceiver() { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private Target getCurrentThumbnailTarget() { - // a Picasso target is just a listener for thumbnail loading events - return new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap - + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" - + from + "]"); - } - // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. - onThumbnailLoaded(bitmap); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); - // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. - onThumbnailLoaded(null); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onPrepareLoad() called"); - } - } - }; - } - private void loadCurrentThumbnail(final List thumbnails) { if (DEBUG) { Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = [" + thumbnails.size() + "]"); } - // first cancel any previous loading - cancelLoadingCurrentThumbnail(); - // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media - // session metadata while the new thumbnail is being loaded by Picasso. + // session metadata while the new thumbnail is being loaded by Coil. onThumbnailLoaded(null); if (thumbnails.isEmpty()) { return; } // scale down the notification thumbnail for performance - PicassoHelper.loadScaledDownThumbnail(context, thumbnails) - .tag(PICASSO_PLAYER_THUMBNAIL_TAG) - .into(currentThumbnailTarget); - } + final var target = new Target() { + @Override + public void onError(@Nullable final Drawable 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) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onStart() called"); + } + } - private void cancelLoadingCurrentThumbnail() { - // cancel the Picasso job associated with the player thumbnail, if any - PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); + @Override + public void onSuccess(@NonNull final Drawable 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)); + } + }; + CoilHelper.INSTANCE.loadScaledDownThumbnail(context, thumbnails, target); } private void onThumbnailLoaded(@Nullable final Bitmap bitmap) { // Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the // thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since - // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Picasso's target. + // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Coil's target. if (currentThumbnail != bitmap) { currentThumbnail = bitmap; UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index 066f92c2607..8994aef79df 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -6,8 +6,8 @@ import android.view.View; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.image.CoilHelper; public class PlayQueueItemBuilder { private static final String TAG = PlayQueueItemBuilder.class.toString(); @@ -33,7 +33,7 @@ public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueu holder.itemDurationView.setVisibility(View.GONE); } - PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView); + CoilHelper.INSTANCE.loadThumbnail(holder.itemThumbnailView, item.getThumbnails()); holder.itemRoot.setOnClickListener(view -> { if (onItemClickListener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java index 26065de1572..d09664aeb40 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java @@ -13,8 +13,9 @@ import com.google.common.base.Stopwatch; +import org.schabi.newpipe.App; import org.schabi.newpipe.extractor.stream.Frameset; -import org.schabi.newpipe.util.image.PicassoHelper; +import org.schabi.newpipe.util.image.CoilHelper; import java.util.Comparator; import java.util.List; @@ -177,8 +178,8 @@ private Bitmap getBitMapFrom(final String url) { Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'"); // Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient - // Ensure that your are not running on the main-Thread this will otherwise hang - final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get(); + // Ensure that you are not running on the main thread, otherwise this will hang + final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getApp(), url); if (sw != null) { Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took " diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index ec2bed67a44..2cda1b4ea2d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -13,10 +13,9 @@ import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.image.PreferredImageQuality; -import java.io.IOException; +import coil.Coil; public class ContentSettingsFragment extends BasePreferenceFragment { private String youtubeRestrictedModeEnabledKey; @@ -42,14 +41,17 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro (preference, newValue) -> { ImageStrategy.setPreferredImageQuality(PreferredImageQuality .fromPreferenceKey(requireContext(), (String) newValue)); - try { - PicassoHelper.clearCache(preference.getContext()); - Toast.makeText(preference.getContext(), - R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT) - .show(); - } catch (final IOException e) { - Log.e(TAG, "Unable to clear Picasso cache", e); + final var loader = Coil.imageLoader(preference.getContext()); + if (loader.getMemoryCache() != null) { + loader.getMemoryCache().clear(); } + if (loader.getDiskCache() != null) { + loader.getDiskCache().clear(); + } + Toast.makeText(preference.getContext(), + R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT) + .show(); + return true; }); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index d78ade49df6..c6abb5405aa 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -10,7 +10,6 @@ import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; -import org.schabi.newpipe.util.image.PicassoHelper; import java.util.Optional; @@ -25,8 +24,6 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro findPreference(getString(R.string.allow_heap_dumping_key)); final Preference showMemoryLeaksPreference = findPreference(getString(R.string.show_memory_leaks_key)); - final Preference showImageIndicatorsPreference = - findPreference(getString(R.string.show_image_indicators_key)); final Preference checkNewStreamsPreference = findPreference(getString(R.string.check_new_streams_key)); final Preference crashTheAppPreference = @@ -38,7 +35,6 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro assert allowHeapDumpingPreference != null; assert showMemoryLeaksPreference != null; - assert showImageIndicatorsPreference != null; assert checkNewStreamsPreference != null; assert crashTheAppPreference != null; assert showErrorSnackbarPreference != null; @@ -61,11 +57,6 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available); } - showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> { - PicassoHelper.setIndicatorsEnabled((Boolean) newValue); - return true; - }); - checkNewStreamsPreference.setOnPreferenceClickListener(preference -> { NotificationWorker.runNow(preference.getContext()); return true; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 37335421d16..c566313e37a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -19,8 +19,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.local.subscription.SubscriptionManager; -import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; import java.util.Vector; @@ -190,7 +190,7 @@ public void onBindViewHolder(final SelectChannelItemHolder holder, final int pos final SubscriptionEntity entry = subscriptions.get(position); holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView); + CoilHelper.INSTANCE.loadAvatar(holder.thumbnailView, entry.getAvatarUrl()); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index 36abef9e5ca..c340dca2231 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -27,7 +27,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.util.image.PicassoHelper; +import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; import java.util.Vector; @@ -154,20 +154,15 @@ public void onBindViewHolder(@NonNull final SelectPlaylistItemHolder holder, final int position) { final PlaylistLocalItem selectedItem = playlists.get(position); - if (selectedItem instanceof PlaylistMetadataEntry) { - final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); - + if (selectedItem instanceof PlaylistMetadataEntry entry) { holder.titleView.setText(entry.name); holder.view.setOnClickListener(view -> clickedItem(position)); - PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView); - - } else if (selectedItem instanceof PlaylistRemoteEntity) { - final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); - + CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, entry.thumbnailUrl); + } else if (selectedItem instanceof PlaylistRemoteEntity entry) { holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl()) - .into(holder.thumbnailView); + CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, + entry.getThumbnailUrl()); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java index 7524e5413c5..21a4b117562 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java @@ -24,8 +24,8 @@ import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; -import org.schabi.newpipe.util.image.PicassoHelper; import java.io.File; import java.io.FileOutputStream; @@ -278,7 +278,7 @@ public static void shareText(@NonNull final Context context, * @param content the content to share * @param images a set of possible {@link Image}s of the subject, among which to choose with * {@link ImageStrategy#choosePreferredImage(List)} since that's likely to - * provide an image that is in Picasso's cache + * provide an image that is in Coil's cache */ public static void shareText(@NonNull final Context context, @NonNull final String title, @@ -335,15 +335,7 @@ public static void copyToClipboard(@NonNull final Context context, final String } /** - * Generate a {@link ClipData} with the image of the content shared, if it's in the app cache. - * - *

- * In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...) - * when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache} - * used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the - * thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null} - * will be returned. - *

+ * Generate a {@link ClipData} with the image of the content shared. * *

* In order to display the image in the content preview of the Android share sheet, an URI of @@ -359,9 +351,8 @@ public static void copyToClipboard(@NonNull final Context context, final String *

* *

- * This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the - * thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by - * the Picasso library inside {@link PicassoHelper}. + * This method will call {@link CoilHelper#loadBitmapBlocking(Context, String)} to get the + * thumbnail of the content. *

* *

@@ -378,7 +369,7 @@ private static ClipData generateClipDataForImagePreview( @NonNull final Context context, @NonNull final String thumbnailUrl) { try { - final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl); + final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(context, thumbnailUrl); if (bitmap == null) { return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt new file mode 100644 index 00000000000..ab7cd79a895 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt @@ -0,0 +1,142 @@ +package org.schabi.newpipe.util.image + +import android.content.Context +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.ImageRequest +import coil.size.Size +import coil.target.Target +import coil.transform.Transformation +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.Image +import org.schabi.newpipe.ktx.scale +import kotlin.math.min + +object CoilHelper { + private val TAG = CoilHelper::class.java.simpleName + + @JvmOverloads + 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() + } + + fun loadAvatar(target: ImageView, images: List) { + loadImageDefault(target, images, R.drawable.placeholder_person) + } + + fun loadAvatar(target: ImageView, url: String?) { + loadImageDefault(target, url, R.drawable.placeholder_person) + } + + fun loadThumbnail(target: ImageView, images: List) { + loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video) + } + + fun loadThumbnail(target: ImageView, url: String?) { + loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video) + } + + fun loadScaledDownThumbnail(context: Context, images: List, target: Target) { + 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" + + 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() + + 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() + + context.imageLoader.enqueue(request) + } + + fun loadDetailsThumbnail(target: ImageView, images: List) { + val url = ImageStrategy.choosePreferredImage(images) + loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false) + } + + fun loadBanner(target: ImageView, images: List) { + loadImageDefault(target, images, R.drawable.placeholder_channel_banner) + } + + fun loadPlaylistThumbnail(target: ImageView, images: List) { + loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist) + } + + fun loadPlaylistThumbnail(target: ImageView, url: String?) { + loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist) + } + + private fun loadImageDefault( + target: ImageView, + images: List, + @DrawableRes placeholderResId: Int + ) { + loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId) + } + + private fun loadImageDefault( + target: ImageView, + url: String?, + @DrawableRes placeholderResId: Int, + showPlaceholder: Boolean = true + ) { + val request = getImageRequest(target.context, url, placeholderResId, showPlaceholder) + .target(target) + .build() + target.context.imageLoader.enqueue(request) + } + + private fun getImageRequest( + context: Context, + url: String?, + @DrawableRes placeholderResId: Int, + 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) + .data(takenUrl) + .error(placeholderResId) + .apply { + if (takenUrl != null || showPlaceholderWhileLoading) { + placeholder(placeholderResId) + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java deleted file mode 100644 index 4b116bdf906..00000000000 --- a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.schabi.newpipe.util.image; - -import static org.schabi.newpipe.MainActivity.DEBUG; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.util.image.ImageStrategy.choosePreferredImage; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.graphics.BitmapCompat; - -import com.squareup.picasso.Cache; -import com.squareup.picasso.LruCache; -import com.squareup.picasso.OkHttp3Downloader; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.RequestCreator; -import com.squareup.picasso.Transformation; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.Image; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; - -public final class PicassoHelper { - private static final String TAG = PicassoHelper.class.getSimpleName(); - private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = - "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; - - private PicassoHelper() { - } - - private static Cache picassoCache; - private static OkHttpClient picassoDownloaderClient; - - // suppress because terminate() is called in App.onTerminate(), preventing leaks - @SuppressLint("StaticFieldLeak") - private static Picasso picassoInstance; - - - public static void init(final Context context) { - picassoCache = new LruCache(10 * 1024 * 1024); - picassoDownloaderClient = new OkHttpClient.Builder() - .cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"), - 50L * 1024L * 1024L)) - // this should already be the default timeout in OkHttp3, but just to be sure... - .callTimeout(15, TimeUnit.SECONDS) - .build(); - - picassoInstance = new Picasso.Builder(context) - .memoryCache(picassoCache) // memory cache - .downloader(new OkHttp3Downloader(picassoDownloaderClient)) // disk cache - .defaultBitmapConfig(Bitmap.Config.RGB_565) - .build(); - } - - public static void terminate() { - picassoCache = null; - picassoDownloaderClient = null; - - if (picassoInstance != null) { - picassoInstance.shutdown(); - picassoInstance = null; - } - } - - public static void clearCache(final Context context) throws IOException { - picassoInstance.shutdown(); - picassoCache.clear(); // clear memory cache - final okhttp3.Cache diskCache = picassoDownloaderClient.cache(); - if (diskCache != null) { - diskCache.delete(); // clear disk cache - } - init(context); - } - - public static void cancelTag(final Object tag) { - picassoInstance.cancelTag(tag); - } - - public static void setIndicatorsEnabled(final boolean enabled) { - picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging - } - - - public static RequestCreator loadAvatar(@NonNull final List images) { - return loadImageDefault(images, R.drawable.placeholder_person); - } - - public static RequestCreator loadAvatar(@Nullable final String url) { - return loadImageDefault(url, R.drawable.placeholder_person); - } - - public static RequestCreator loadThumbnail(@NonNull final List images) { - return loadImageDefault(images, R.drawable.placeholder_thumbnail_video); - } - - public static RequestCreator loadThumbnail(@Nullable final String url) { - return loadImageDefault(url, R.drawable.placeholder_thumbnail_video); - } - - public static RequestCreator loadDetailsThumbnail(@NonNull final List images) { - return loadImageDefault(choosePreferredImage(images), - R.drawable.placeholder_thumbnail_video, false); - } - - public static RequestCreator loadBanner(@NonNull final List images) { - return loadImageDefault(images, R.drawable.placeholder_channel_banner); - } - - public static RequestCreator loadPlaylistThumbnail(@NonNull final List images) { - return loadImageDefault(images, R.drawable.placeholder_thumbnail_playlist); - } - - public static RequestCreator loadPlaylistThumbnail(@Nullable final String url) { - return loadImageDefault(url, R.drawable.placeholder_thumbnail_playlist); - } - - public static RequestCreator loadSeekbarThumbnailPreview(@Nullable final String url) { - return picassoInstance.load(url); - } - - public static RequestCreator loadNotificationIcon(@Nullable final String url) { - return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white); - } - - - public static RequestCreator loadScaledDownThumbnail(final Context context, - @NonNull final List images) { - // scale down the notification thumbnail for performance - return PicassoHelper.loadThumbnail(images) - .transform(new Transformation() { - @Override - public Bitmap transform(final Bitmap source) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - transform() called"); - } - - final float notificationThumbnailWidth = Math.min( - context.getResources() - .getDimension(R.dimen.player_notification_thumbnail_width), - source.getWidth()); - - final Bitmap result = BitmapCompat.createScaledBitmap( - source, - (int) notificationThumbnailWidth, - (int) (source.getHeight() - / (source.getWidth() / notificationThumbnailWidth)), - null, - true); - - if (result == source || !result.isMutable()) { - // create a new mutable bitmap to prevent strange crashes on some - // devices (see #4638) - final Bitmap copied = BitmapCompat.createScaledBitmap( - source, - (int) notificationThumbnailWidth - 1, - (int) (source.getHeight() / (source.getWidth() - / (notificationThumbnailWidth - 1))), - null, - true); - source.recycle(); - return copied; - } else { - source.recycle(); - return result; - } - } - - @Override - public String key() { - return PLAYER_THUMBNAIL_TRANSFORMATION_KEY; - } - }); - } - - @Nullable - public static Bitmap getImageFromCacheIfPresent(@NonNull final String imageUrl) { - // URLs in the internal cache finish with \n so we need to add \n to image URLs - return picassoCache.get(imageUrl + "\n"); - } - - - private static RequestCreator loadImageDefault(@NonNull final List images, - @DrawableRes final int placeholderResId) { - return loadImageDefault(choosePreferredImage(images), placeholderResId); - } - - private static RequestCreator loadImageDefault(@Nullable final String url, - @DrawableRes final int placeholderResId) { - return loadImageDefault(url, placeholderResId, true); - } - - private static RequestCreator loadImageDefault(@Nullable final String url, - @DrawableRes final int placeholderResId, - final boolean showPlaceholderWhileLoading) { - // 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) - if (isNullOrEmpty(url) || !ImageStrategy.shouldLoadImages()) { - return picassoInstance - .load((String) null) - .placeholder(placeholderResId) // show placeholder when no image should load - .error(placeholderResId); - } else { - final RequestCreator requestCreator = picassoInstance - .load(url) - .error(placeholderResId); - if (showPlaceholderWhileLoading) { - requestCreator.placeholder(placeholderResId); - } - return requestCreator; - } - } -} diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml index 077cf1106ca..290d9f6ab01 100644 --- a/app/src/main/res/values-ar-rLY/strings.xml +++ b/app/src/main/res/values-ar-rLY/strings.xml @@ -302,7 +302,6 @@ %s مشارك جلب البيانات الوصفية… - إظهار مؤشرات الصور انقر للتنزيل %s تعطيل الوضع السريع , @@ -830,7 +829,6 @@ لقد اشتركت الآن في هذه القناة بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط إنشاء اسم فريد - أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة فشل الاتصال الآمن يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe. البث السابق diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 82173d75802..902fe8c3a22 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -674,8 +674,6 @@ معاينة مصغرة على شريط التمرير وضع علامة على تمت مشاهدته أُعجب بها منشئ المحتوى - أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة - إظهار مؤشرات الصور اقتراحات البحث عن بعد اقتراحات البحث المحلية اسحب العناصر لإزالتها diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 66bfe75dee9..9c9c15c964e 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -505,8 +505,6 @@ Məlumat əldə edilir… Elementlərdə orijinal əvvəlki vaxtı göstər Yaşam dövrəsi xaricindəki xətaları bildir - Şəkil göstəricilərini göstər - Şəkillərin üzərində mənbəsini göstərən Picasso rəngli lentləri göstər: şəbəkə üçün qırmızı, disk üçün mavi və yaddaş üçün yaşıl Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır Bağla Fayl silindiyi üçün irəliləyiş itirildi diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index dceaaf6c560..5ddf30c2a5e 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -630,8 +630,6 @@ Перайсці на вэбсайт Правядзіце пальцам па элементах, каб выдаліць іх Адмяніць пастаянную мініяцюру - Паказаць індыкатары выявы - Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці Апрацоўка стужкі… Вам будзе прапанавана, дзе захоўваць кожную спампоўку Загрузка стужкі… diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index bc235446c3d..9b8c06bc581 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -539,7 +539,6 @@ Това видео е с възрастова граница. \n \nВключете „%1$s“ в настройките ако искате да го пуснете. - Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта) Автоматична (тази на устройството) Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания) Избете плейлист diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 853b04b64f7..8fe38988a56 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -589,7 +589,6 @@ নতুন ধারা কম্পাঙ্ক দেখো পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন - ছবিরূপ সূচক দেখাও দেখিও না যেকোনো নেটওয়ার্ক পরেরটা ক্রমে রাখা হয়েছে diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 871af187b25..dd08e352638 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -625,7 +625,6 @@ No mostris Baixa qualitat (més petit) Alta qualitat (més gran) - Mostra indicadors de la imatge Desactiva l\'entunelament del contingut si en els videos hi ha una pantalla negre o tartamudegen Mostra detalls del canal No s\'ha establert una carpeta de descàrregues, selecciona la carpeta per defecte ara @@ -669,7 +668,6 @@ Comentari fixat Mostra \"Força el tancament del reproductor\" Mostra una opció de fallada quan s\'utilitza el reproductor - Mostra les cintes de color Picasso a la part superior de les imatges que indiquen la seva font: vermell per a la xarxa, blau per al disc i verd per a la memòria El LeakCanary no està disponible Comprovant freqüència Es necesita una conexió a Internet diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index e6e375a4cc6..d14449cf1cf 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -631,8 +631,6 @@ پیشان نەدرێت کواڵێتی نزم (بچووکتر) کواڵێتی بەرز (گەورەتر) - پیشاندانی شریتە ڕەنگکراوەکانی پیکاسۆ لەسەرووی وێنەکانەوە بۆ بەدیار خستنی سەرچاوەکانیان : سوور بۆ تۆڕ ، شین بۆ دیسک و سەوز بۆ بیرگە - پیشاندانی دیارخەرەکانی وێنە پێشنیازکراوەکانی گەڕانی ڕیمۆت پێشنیازکراوەکانی گەڕانی نێوخۆیی دیارکردن وەک بینراو diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 34537446cce..e6263f6e021 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -654,8 +654,6 @@ %s stahování dokončena %s stahováních dokončeno - Zobrazit barevné pásky Picasso na obrázcích označujících jejich zdroj: červená pro síť, modrá pro disk a zelená pro paměť - Zobrazit indikátory obrázků Vzdálené návrhy vyhledávání Lokální návrhy vyhledávání Pokud je vypnuté automatické otáčení, nespouštět video v mini přehrávači, ale přepnout se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 601bc37523d..cbb59d591ed 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -516,7 +516,6 @@ Behandler… Det kan tage et øjeblik Vis hukommelseslækager Deaktivér medietunneling - Vis billedindikatorer Netværkskrav Alle netværk Kontrolfrekvens @@ -695,7 +694,6 @@ Ofte stillede spørgsmål Hvis du har problemer med at bruge appen, bør du tjekke disse svar på almindelige spørgsmål! Se på webside - Vis Picasso-farvede bånd oven på billeder, der angiver deres kilde: rød for netværk, blå for disk og grøn for hukommelse Du kører den nyeste version af NewPipe Pga. ExoPlayer-begrænsninger blev søgevarigheden sat til %d sekunder Vis kun ikke-grupperede abonnementer diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fcc38bf0bc0..028ff86cac5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -637,8 +637,6 @@ Aus Als gesehen markieren Vom Ersteller mit Herz versehen - Farbige Picasso-Bänder über den Bildern anzeigen, die deren Quelle angeben: rot für Netzwerk, blau für Festplatte und grün für Speicher - Bildindikatoren anzeigen Entfernte Suchvorschläge Lokale Suchvorschläge diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6e9525459ae..58a70c270b6 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -634,8 +634,6 @@ Προεπισκόπηση στην μπάρα αναζήτησης Σήμανση ως αναπαραχθέν Επισημάνθηκε από τον δημιουργό - Εμφάνιση χρωματιστής κορδέλας πάνω στις εικόνες, που δείχνει την πηγή τους: κόκκινη για δίκτυο, μπλε για δίσκο και πράσινο για μνήμη - Εμφάνιση δεικτών εικόνων Προτάσεις απομακρυσμένης αναζήτησης Προτάσεις τοπικής αναζήτησης diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c7b780a1f05..a44c7293785 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -647,8 +647,6 @@ Los comentarios están deshabilitados De corazón por el creador Marcar como visto - Mostrar cintas de colores Picasso encima de las imágenes indicando su origen: rojo para la red, azul para el disco y verde para la memoria - Mostrar indicadores de imagen Sugerencias de búsqueda remota Sugerencias de búsqueda local diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 1d3fcdcb8a8..0e6e082c993 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -634,8 +634,6 @@ \n \nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave. Märgi vaadatuks - Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu - Näita piltide allikat Kaugotsingu soovitused Kohaliku otsingu soovitused Üksuse eemaldamiseks viipa diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 6f94a8f625d..076b50d10a0 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -641,8 +641,6 @@ Deskarga amaituta %s Deskarga amaituta - Irudien gainean Picasso koloretako zintak erakutsi, jatorria adieraziz: gorria sarerako, urdina diskorako eta berdea memoriarako - Erakutsi irudi-adierazleak Urruneko bilaketaren iradokizunak Tokiko bilaketa-iradokizunak Ikusi gisa markatu diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 2afeaf286a7..02ac369d744 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -597,7 +597,6 @@ رنگی کردن آگاهی گشودن با نشانه به عنوان دیده شده - نمایش روبان‌های رنگی پیکاسو در بالای تصویرها کهنشانگر منبعشان است: قرمز برای شبکه ، آبی برای دیسک و سبز برای حافظه درخواست از اندروید برای سفارشی‌سازی رنگ آگاهی براساس رنگ اصلی در بندانگشتی (توجّه داشته باشید که روی همهٔ افزاره‌ها در دسترس نیست) این ویدیو محدود به سن است. \n @@ -635,7 +634,6 @@ قلب‌شده به دست ایجادگر پیشنهادهای جست‌وجوی محلّی پیشنهادهای جست‌وجوی دوردست - نمایش نشانگرهای تصویر بارگیری پایان یافت %s بارگیری پایان یافتند diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 67350d7ba2b..d72402e770d 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -634,8 +634,6 @@ Latauskansiota ei vielä asetettu, valitse ensin oletuslatauskansio Kommentit poistettu käytöstä Merkitse katsotuksi - Näytä Picasso-värjätyt nauhat kuvien päällä osoittaakseen lähteen: punainen tarkoittaa verkkoa, sininen tarkoittaa levytilaa ja vihreä tarkoittaa muistia - Näytä kuvailmaisimet Etähakuehdotukset Paikalliset hakuehdotukset Lisää seuraavaksi diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 66418e7cfea..f6a3d62850f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -646,10 +646,8 @@ Prévisualisation de la barre de progression sur la miniature Marquer comme visionné Apprécié par le créateur - Afficher les indicateurs d’image Suggestions de recherche distante Suggestions de recherche locale - Affiche les rubans colorés de Picasso au-dessus des images indiquant leur source : rouge pour le réseau, bleu pour le disque et vert pour la mémoire %1$s téléchargement supprimé %1$s téléchargements supprimés diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index efe1b7f5ea3..4b087a88200 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -627,7 +627,6 @@ %s descargas finalizadas Miniatura na barra de busca - Mostrar indicadores de imaxe Desactive o túnel multimedia se experimentar unha pantalla en negro ou interrupcións na reprodución. Desactivar túnel multimedia Engadido á cola @@ -664,7 +663,6 @@ A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado Procesando... Pode devagar un momento Crear unha notificación de erro - Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria Novos elementos Predefinido do ExoPlayer Amosar \"Travar o reprodutor\" diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index befde2d5316..1da62fc757c 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -654,8 +654,6 @@ תמונה מוקטנת בסרגל הנגינה סומן בלב על ידי היוצר סימון כנצפה - הצגת סרטים בסגנון פיקאסו בראש התמונות לציון המקור שלהם: אדום זה מהרשת, כחול מהכונן וירוק מהזיכרון - הצגת מחווני תמונות הצעות חיפוש מרוחקות הצעות חיפוש מקומיות diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 51455fafbbd..6e185156d74 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -570,7 +570,6 @@ कतारबद्ध हुआ स्ट्रीम विवरण लोड हो रहे हैं… प्रोसेस हो रहा है… कुछ समय लग सकता है - छवि संकेतक दिखाएं प्लेयर का उपयोग करते समय क्रैश विकल्प दिखाता है नई स्ट्रीमों के लिए जांच चलाएं एक त्रुटि स्नैकबार दिखाएं @@ -668,7 +667,6 @@ लीक-कैनरी उपलब्ध नहीं है एक त्रुटी हुई है, नोटीफिकेशन देखें यदि वीडियो प्लेबैक पर आप काली स्क्रीन या रुक-रुक कर वीडियो चलने का अनुभव करते हैं तो मीडिया टनलिंग को अक्षम करें। - छवियों के शीर्ष पर पिकासो रंगीन रिबन दिखाएँ जो उनके स्रोत को दर्शाता है: नेटवर्क के लिए लाल, डिस्क के लिए नीला और मेमोरी के लिए हरा त्रुटी की नोटीफिकेशन बनाएं इस डाउनलोड को पुनर्प्राप्त नहीं किया जा सकता अभी तक कोई डाउनलोड फ़ोल्डर सेट नहीं किया गया है, अब डिफ़ॉल्ट डाउनलोड फ़ोल्डर चुनें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 419f4619ed6..3a945bba0ee 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -646,7 +646,6 @@ %s pruža ovaj razlog: Obrada u tijeku … Može malo potrajati Za ukljanjanje stavki povuci ih - Prikaži indikatore slike Preuzimanje je gotovo %s preuzimanja su gotova @@ -655,7 +654,6 @@ Pokreni glavni player u cjeloekranskom prikazu Dodaj u popis kao sljedeći Dodano u popis kao sljedeći - Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju Izbrisano %1$s preuzimanje Izbrisana %1$s preuzimanja diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5a402c94c2e..f867041485c 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -570,7 +570,6 @@ Az eltávolítás utáni, fragment vagy activity életcikluson kívüli, nem kézbesíthető Rx kivételek jelentésének kényszerítése Eredeti „ennyi ideje” megjelenítése az elemeken Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor. - Picasso színes szalagok megjelenítése a képek fölött, megjelölve a forrásukat: piros a hálózathoz, kék a lemezhez, zöld a memóriához Minden letöltésnél meg fogja kérdezni, hogy hova mentse el Válasszon egy példányt Lista legutóbbi frissítése: %s @@ -657,7 +656,6 @@ \nBiztos benne\? Ez nem vonható vissza! A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken Lejátszó összeomlasztása - Képjelölők megjelenítése A „lejátszó összeomlasztása” lehetőség megjelenítése Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor Hangmagasság megtartása (torzítást okozhat) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 71900400eb3..9a6472692f2 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -631,13 +631,11 @@ Disukai oleh kreator Saran pencarian lokal Saran pencarian remote - Tampilkan indikator gambar Menghapus %1$s unduhan tambahkan ke selanjutnya telah ditambahkan ke selanjutnya - Tampilkan Ribon bewarna Picasso di atas gambar yang mengindikasikan asalnya: merah untuk jaringan, biru untuk disk dan hijau untuk memori Jangan memulai memutar video di mini player, tapi nyalakan langsung di mode layar penuh, jika rotasi otomatis terkunci. Anda tetap dapat mengakses mini player dengan keluar dari layar penuh Memproses… Mungkin butuh waktu sebentar Periksa Pembaruan diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index ed5ebe99bdb..d921cfac0e0 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -657,8 +657,6 @@ Upprunalegir textar frá þjónustu verða sýnilegir í atriðum Slökkva á fjölmiðlagöngum Slökktu á fjölmiðlagöngum ef þú finnur fyrir svörtum skjá eða stami við spilun myndbandar - Sýna myndvísa - Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni Sýna „Hrynja spilara“ Sýna valkost til að hrynja spilara Hrynja forrit diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2a5ac16d39e..9d1c4bc0b6e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -644,8 +644,6 @@ Commenti disattivati Apprezzato dall\'autore Segna come visto - Mostra gli indicatori colorati Picasso sopra le immagini, per indicare la loro fonte: rosso per la rete, blu per il disco e verde per la memoria - Mostra indicatori immagine Suggerimenti di ricerca remoti Suggerimenti di ricerca locali diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 74924125a2a..7e7cf83a236 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -634,8 +634,6 @@ %s つのダウンロードが完了しました - ピカソは、画像の上に、画像の出所を識別する色彩記章を表示します: 赤はネットワーク、青はディスク、緑はメモリ - 画像に標識を表示 処理中… 少し時間がかかるかもしれません 新しいバージョンを手動で確認します アップデートを確認中… diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index ecb2a8495b7..4344c245d2c 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -384,7 +384,6 @@ ორიგინალური ტექსტები სერვისებიდან ხილული იქნება ნაკადის ერთეულებში მედია გვირაბის გათიშვა გამორთეთ მედია გვირაბი, თუ ვიდეოს დაკვრისას შავი ეკრანი ან ჭუჭყი გაქვთ - გამოსახულების ინდიკატორების ჩვენება აჩვენე \"დამკვრელის დამსხვრევა\" აჩვენებს ავარიის ვარიანტს დამკვრელის გამოყენებისას გაუშვით შემოწმება ახალი ნაკადებისთვის @@ -683,7 +682,6 @@ გამოწერების იმპორტი ვერ მოხერხდა შეატყობინეთ სასიცოცხლო ციკლის შეცდომებს იძულებითი მოხსენება შეუსაბამო Rx გამონაკლისების შესახებ ფრაგმენტის ან აქტივობის სასიცოცხლო ციკლის გარეთ განკარგვის შემდეგ - აჩვენეთ პიკასოს ფერადი ლენტები სურათების თავზე, სადაც მითითებულია მათი წყარო: წითელი ქსელისთვის, ლურჯი დისკისთვის და მწვანე მეხსიერებისთვის სწრაფი კვების რეჟიმი ამაზე მეტ ინფორმაციას არ იძლევა. „%s“-ის არხის ჩატვირთვა ვერ მოხერხდა. ხელმისაწვდომია ზოგიერთ სერვისში, როგორც წესი, ბევრად უფრო სწრაფია, მაგრამ შეიძლება დააბრუნოს შეზღუდული რაოდენობის ელემენტი და ხშირად არასრული ინფორმაცია (მაგ. ხანგრძლივობის გარეშე, ელემენტის ტიპი, არ არის ლაივის სტატუსი) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 939d97441ba..5d5dfa0239e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -545,8 +545,6 @@ 미디어 터널링 비활성화 서비스의 원본 텍스트가 스트림 항목에 표시됩니다 동영상 재생 시 검은 화면이 나타나거나 끊김 현상이 발생하면 미디어 터널링을 비활성화하세요. - 이미지 표시기 표시 - 원본을 나타내는 이미지 위에 피카소 컬러 리본 표시: 네트워크는 빨간색, 디스크는 파란색, 메모리는 녹색 \"플레이어 충돌\" 표시 플레이어를 사용할 때 충돌 옵션을 표시합니다 새로운 스트림 확인 실행 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index c68e49bdd62..2294a357dfe 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -644,8 +644,6 @@ Nerodyti Širdelė nuo kurėjo Pažymėti kaip peržiūrėtą - Rodyti „Picasso“ spalvotas juosteles ant vaizdų, nurodančių jų šaltinį: raudona tinklui, mėlyna diskui ir žalia atmintis - Rodyti vaizdo indikatorius Nuotolinės paieškos pasiūlymai Vietinės paieškos pasiūlymai diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 58b9a9d7623..a28d3e6073f 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -625,12 +625,10 @@ Nesākt video atskaņošanu samazinātā režīmā, bet pilnekrāna režīmā, ja automātiskā rotācija ir izslēgta Izslēgt multivides tuneļošanu Izslēdziet multivides tuneļošanu, ja jums video atskaņošanas laikā parādās melns ekrāns vai aizķeršanās - Rādīt krāsainas lentes virs attēliem, norādot to avotu: sarkana - tīkls, zila - disks, zaļa - atmiņa Ieslēgt teksta atlasīšanu video aprakstā Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi Pārvelciet objektus, lai tos noņemtu Lokālie meklēšanas ieteikumi - Rādīt attēlu indikatorus Augstas kvalitātes (lielāks) Pārbaudīt atjauninājumus Manuāli pārbaudīt, vai ir atjauninājumi diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index c439593f7c8..ee4b88a41d3 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -595,7 +595,6 @@ രണ്ടാം പ്രവർത്തന ബട്ടൺ ആദ്യ പ്രവർത്തന ബട്ടൺ വീഡിയോ കാണുമ്പോൾ കറുത്ത സ്ക്രീൻ, അവ്യക്തത അനുഭവിക്കുന്നു എങ്കിൽ മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക - ഉറവിടം തിരിച്ചറിയാൻ പിക്കാസോ കളർഡ് റിബൺ ചിത്രങ്ങളുടെ മുകളിൽ കാണിക്കുക: നെറ്റ്‌വർക്കിന് ചുവപ്പ്, ഡിസ്കിനു നീല, മെമ്മറിയിക്ക് പച്ച സീക്ബാർ ചെറുചിത്രം പ്രദർശനം സ്നേഹത്തോടെ സൃഷ്ടാവ് ഡിസ്ക്രിപ്ഷനിലെ ടെക്സ്റ്റ്‌ സെലക്ട്‌ ചെയ്യുവാൻ അനുവദിക്കാതെ ഇരിക്കുക @@ -630,7 +629,6 @@ കാണിക്കരുത് കുറഞ്ഞ നിലവാരം (ചെറുത് ) ഉയർന്ന നിലവാരം (വലിയത് ) - ഇമേജ് ഇൻഡിക്കേറ്ററുകൾ കാണിക്കുക മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക ഡൌൺലോഡ് ഫോൾഡർ ഇത് വരെയും സെറ്റ് ചെയ്തിട്ടില്ല, സ്ഥിര ഡൌൺലോഡ് ഫോൾഡർ ഇപ്പോൾ തിരഞ്ഞെക്കുക അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമായിരിക്കുന്നു diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 416ebfd025b..7acf7f4cd78 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -644,9 +644,7 @@ Lokale søkeforslag Marker som sett Ikke start videoer i minispilleren, men bytt til fullskjermsmodus direkte dersom auto-rotering er låst. Du har fremdeles tilgang til minispilleren ved å avslutte fullskjermsvisningen - Vis Picasso-fargede bånd på toppen av bilder for å indikere kilde: Rød for nettverk, blå for disk, og grønn for minne Hjertemerket av skaperen - Vis bildeindikatorer Dra elementer for å fjerne dem Start hovedspiller i fullskjerm Still i kø neste diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index 637eb1751e6..c744de62c25 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -611,7 +611,6 @@ Geen download map ingesteld, kies nu de standaard download map Niet tonen Reacties zijn uitgeschakeld - Toon afbeeldingsindicatoren Speler melding Configureer actieve stream melding Meldingen diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b4629a03f62..26e328ed4df 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -632,8 +632,6 @@ Lage kwaliteit (kleiner) Hoge kwaliteit (groter) Zoekbalk miniatuurafbeelding voorbeeld - Toon Picasso-gekleurde linten bovenop afbeeldingen die hun bron aangeven: rood voor netwerk, blauw voor schijf en groen voor geheugen - Afbeeldings­indicatoren tonen Reacties zijn uitgeschakeld Zoeksuggesties op afstand Lokale zoeksuggesties diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml index 94e7ae2f49b..f1960b980aa 100644 --- a/app/src/main/res/values-nqo/strings.xml +++ b/app/src/main/res/values-nqo/strings.xml @@ -440,8 +440,6 @@ ߗߋߢߊߟߌ ߟߎ߬ ߞߟߏߜߍ߫ ߓߐߛߎ߲ߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߕߐ߫ ߟߋ߬ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ ߘߐ߫ ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߣߴߌ ߞߊ߬ ߥߊ߲߬ߊߥߊ߲߬ ߝߌ߲ ߦߋ߫ ߥߟߊ߫ ߜߊߘߊ߲ߜߊߘߊ߲ߠߌ߲ ߦߋߡߍ߲ߕߊ ߘߏ߫ ߘߐߛߊߙߌ߫ ߕߎߡߊ - ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ߫ ߦߌ߬ߘߊ߬ߟߊ߲ ߠߎ߫ ߝߍ߲߬ߛߍ߲߫ - ߏ߬ ߦߋ߫ ߔߌߛߊߞߏ߫ ߟߊ߫ ߡߙߎߝߋ߫ ߞߟߐ߬ߡߊ ߟߎ߫ ߟߋ߬ ߝߍ߲߬ߛߍ߲߬ ߠߊ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߎ߫ ߞߎ߲߬ߘߐ߫ ߞߵߊ߬ߟߎ߬ ߓߐߛߎ߲ ߦߌ߬ߘߊ߬: ߥߎߟߋ߲߬ߡߊ߲ ߦߋ߫ ߞߙߏ߬ߝߏ ߕߊ ߘߌ߫߸ ߓߊ߯ߡߊ ߦߋ߫ ߝߘߍ߬ ߜߍߟߍ߲ ߕߊ ߘߌ߫ ߊ߬ߣߌ߫ ߝߙߌߛߌߡߊ ߦߋ߫ ߦߟߌߕߏߟߊ߲ ߕߊ ߘߌ߫ ߟߊ߬ߓߐ߬ߟߌ ߦߴߌߘߐ߫… ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬ ߞߊ߲߬ߞߎߡߊ ߟߎ߬ ߟߊߛߊ߬ߣߍ߲ ߠߋ߬ diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index f9faa8324db..59a6a739abe 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -342,7 +342,6 @@ ସେବାଗୁଡିକରୁ ମୂଳ ଲେଖା ଷ୍ଟ୍ରିମ୍ ଆଇଟମ୍ ଗୁଡିକରେ ଦୃଶ୍ୟମାନ ହେବ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ ଯଦି ଆପଣ ଏକ କଳା ପରଦା ଅନୁଭବ କରନ୍ତି କିମ୍ବା ଭିଡିଓ ପ୍ଲେବେକ୍ ଉପରେ ଝୁଣ୍ଟି ପଡ଼ନ୍ତି ତେବେ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ । - ଚିତ୍ରଗୁଡ଼ିକର ଉପରେ ପିକାସୋ ରଙ୍ଗୀନ ଫିତା ଦେଖାନ୍ତୁ: ସେମାନଙ୍କର ଉତ୍ସକୁ ସୂଚାଇଥାଏ: ନେଟୱାର୍କ ପାଇଁ ନାଲି, ଡିସ୍କ ପାଇଁ ନୀଳ ଏବଂ ସ୍ମୃତି ପାଇଁ ସବୁଜ ଆମଦାନି କରନ୍ତୁ ଠାରୁ ଆମଦାନୀ କରନ୍ତୁ ଆମଦାନି… @@ -657,7 +656,6 @@ ଅଟୋ-ଜେନେରେଟ୍ (କୌଣସି ଅପଲୋଡର୍ ମିଳିଲା ନାହିଁ) ପୁରଣ କରନ୍ତୁ କ୍ୟାପସନ୍ - ପ୍ରତିଛବି ସୂଚକ ଦେଖାନ୍ତୁ ପ୍ଲେୟାର ବ୍ୟବହାର କରିବା ସମୟରେ ଏକ କ୍ରାସ୍ ବିକଳ୍ପ ଦେଖାଏ ନୂତନ ଷ୍ଟ୍ରିମ୍ ପାଇଁ ଯାଞ୍ଚ ଚଲାନ୍ତୁ ଆପ୍ କ୍ରାସ୍ କରନ୍ତୁ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 814cdb8851d..d612ff75b17 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -626,8 +626,6 @@ ਨਿਊਪਾਈਪ ਖਾਮੀ ਤੋਂ ਪ੍ਰਭਾਵਤ ਹੋਈ ਹੈ, ਇੱਥੇ ਨੱਪ ਕੇ ਰਿਪੋਰਟ ਕਰੋ ਇੱਕ ਖਾਮੀ ਪ੍ਰਭਾਵੀ ਹੋਈ ਹੈ, ਨੋਟੀਫੀਕੇਸ਼ਨ ਵੇਖੋ ਆਈਟਮਾਂ ਨੂੰ ਇੱਕ ਪਾਸੇ ਖਿੱਚ ਕੇ ਹਟਾਓ - ਦ੍ਰਿਸ਼ ਸੂਚਕ ਵਿਖਾਓ - ਦ੍ਰਿਸ਼ਾਂ ਦੇ ਉੱਪਰ ਉਹਨਾਂ ਦੀ ਸਰੋਤ-ਪਛਾਣ ਲਈ ਪਿਕਾਸੋ ਦੇ ਰੰਗਦਾਰ ਰਿਬਨ ਵਿਖਾਓ : ਨੈੱਟਵਰਕ ਲਈ ਲਾਲ, ਡਿਸਕ ਲਈ ਨੀਲੇ ਤੇ ਮੈਮਰੀ ਲਈ ਹਰੇ ਨਵੀਂ ਸਟ੍ਰੀਮ ਦੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਪਿੰਨ ਕੀਤੀ ਟਿੱਪਣੀ ਅੱਪਡੇਟ ਦੀ ਉਪਲੱਬਧਤਾ ਪਰਖੀ ਜਾ ਰਹੀ… diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 01f242cd9c7..b7c517ffc86 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -649,8 +649,6 @@ Nie pokazuj Serduszko od twórcy Oznacz jako obejrzane - Pokazuj kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci - Pokazuj wskaźniki obrazu Zdalne podpowiedzi wyszukiwania Lokalne podpowiedzi wyszukiwania diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5a203e0f25e..3d4b5762111 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -644,7 +644,6 @@ Os comentários estão desabilitados Marcar como assistido Curtido pelo criador - Exibir fitas coloridas no topo das imagens indicando sua fonte: vermelho para rede, azul para disco e verde para memória %1$s download excluído %1$s downloads excluídos @@ -655,7 +654,6 @@ %s downloads concluídos %s downloads concluídos - Mostrar indicadores de imagem Adicionado na próxima posição da fila Enfileira a próxima Deslize os itens para remove-los diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 498a49a53bb..23310adec64 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -644,8 +644,6 @@ Ainda não foi definida uma pasta de descarregamento, escolha agora a pasta de descarregamento padrão Comentários estão desativados Marcar como visto - Mostrar fitas coloridas de Picasso em cima das imagens que indicam a sua fonte: vermelho para rede, azul para disco e verde para memória - Mostrar indicadores de imagem Sugestões de pesquisa remotas Sugestões de pesquisa locais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5534dbf0b54..99e5cbd040c 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -644,8 +644,6 @@ Baixa qualidade (menor) Alta qualidade (maior) Os comentários estão desativados - Mostrar fitas coloridas de Picasso em cima das imagens que indicam a sua fonte: vermelho para rede, azul para disco e verde para memória - Mostrar indicadores de imagem Sugestões de pesquisa remotas Sugestões de pesquisa locais diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index bcef4d9524f..a5eb2114328 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -660,8 +660,6 @@ Calitate scăzută (mai mică) Calitate înaltă (mai mare) Miniatură de previzualizare în bara de derulare - Afișați panglici colorate de Picasso deasupra imaginilor, indicând sursa acestora: roșu pentru rețea, albastru pentru disc și verde pentru memorie - Afișați indicatorii de imagine Dezactivați tunelarea media dacă întâmpinați un ecran negru sau blocaje la redarea video. Procesarea.. Poate dura un moment Verifică dacă există actualizări diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9e79b165fc8..667e5413dd2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -652,8 +652,6 @@ Миниатюра над полосой прокрутки Автору видео понравилось это Пометить проигранным - Picasso: указать цветом источник изображений (красный — сеть, синий — диск, зелёный — память) - Цветные метки на изображениях Серверные предложения поиска Локальные предложения поиска diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml index 5a4f35de5f9..e970950e0a2 100644 --- a/app/src/main/res/values-ryu/strings.xml +++ b/app/src/main/res/values-ryu/strings.xml @@ -646,8 +646,6 @@ %sちぬダウンロードぬかんりょうさびたん %sちぬダウンロードぬかんりょうさびたん - ピカソー、がぞうぬういに、がぞうくとぅどぅくるしーきびちするしきさいきしーょうひょうじさびーん: あかーネットワーク、あおーディスク、みどぅれーメモリ - やしがぞうんかいふぃいょうしきひょうじ しーょりちゅう… くーてーんじがんがかかいんかむしりやびらん みーさるバージョンしーゅどうでぃかくにんさびーん アップデートかくにんちゅう… diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml index a5959086e46..9ede53a7691 100644 --- a/app/src/main/res/values-sat/strings.xml +++ b/app/src/main/res/values-sat/strings.xml @@ -270,7 +270,6 @@ LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱡᱤᱭᱚᱱ ᱪᱤᱠᱤ ᱠᱷᱚᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱷᱮᱜᱟᱨ ᱠᱚ ᱚᱱᱚᱞ ᱢᱮ - ᱱᱮᱴᱣᱟᱨᱠ ᱞᱟᱹᱜᱤᱫ red, ᱰᱤᱥᱠ ᱞᱟᱹᱜᱤᱫ blue ᱟᱨ ᱢᱮᱢᱚᱨᱤ ᱞᱟᱹᱜᱤᱫ green ᱯᱞᱮᱭᱟᱨ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱮ ᱨᱮ ᱠᱨᱟᱥ ᱚᱯᱥᱚᱱ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ ᱤᱢᱯᱳᱨᱴ ᱤᱢᱯᱚᱨᱴ @@ -540,7 +539,6 @@ ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾ - ᱪᱤᱛᱟᱹᱨ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 6b89b32c3c1..fce5280e128 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -634,8 +634,6 @@ Sos cummentos sunt disabilitados Su creadore b\'at postu unu coro Marca comente pompiadu - Ammustra sos listrones colorados de Picasso in subra de sas immàgines chi indicant sa fonte issoro: ruja pro sa retze, biaita pro su discu e birde pro sa memòria - Ammustra sos indicadores de immàgines Impòsitos de chirca remota Impòsitos de chirca locales diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e3aa5b6bce3..5c4210b71a9 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -653,8 +653,6 @@ Nízka kvalita (menšie) Vysoká kvalita (väčšie) Náhľad miniatúry pri vyhľadávaní - Zobrazí farebné pásiky Picasso na obrázkoch podľa ich zdroja: červený pre sieť, modrý pre disk a zelený pre pamäť - Zobraziť indikátory obrázka Potiahnutím vymazať Komentáre sú zakázané Ak je automatické otáčanie zablokované, nespustí videá v miniprehrávači, ale prepne sa do celoobrazovkového režimu. Do miniprehrávača sa dostanete po ukončení režimu celej obrazovky diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index e37c3a36d31..72e661a1b05 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -633,8 +633,6 @@ Fallooyinka waa laxidhay Kahelay soosaaraha Waan daawaday - Soo bandhig shaambado midabka Picasso leh sawirrada dushooda oo tilmaamaya isha laga keenay: guduud waa khadka, buluug waa kaydka gudaha, cagaar waa kaydka K/G - Tus tilmaamayaasha sawirka Soojeedinada raadinta banaanka Soojeedinada raadinta gudaha Cabirka soodaarida udhexeeya diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 5f14a0e51c2..2300cd92979 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -644,7 +644,6 @@ Означи као одгледано Коментари су онемогућени Обрађивање… Може потрајати пар тренутака - Прикажи индикаторе слике Не покрећите видео снимке у мини-плејеру, већ директно пређите на режим целог екрана, ако је аутоматска ротација закључана. И даље можете приступити мини-плејеру тако што ћете изаћи из целог екрана Покрени главни плејер преко целог екрана Срушите плејер @@ -740,7 +739,6 @@ Обавештења за пријаву грешака Увезите или извезите праћења из менија са 3 тачке Аудио снимак - Прикажите Picasso обојене траке на врху слика које указују на њихов извор: црвена за мрежу, плава за диск и зелена за меморију Направите обавештење о грешци Проценат Користите најновију верзију NewPipe-а diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8cf57f6054f..50948fd571e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -633,7 +633,6 @@ Du kan välja det natt-tema du föredrar nedan Välj det natt-tema du föredrar — %s Sökradens förhandsvisningsminiatyr - Visa bildindikatorer Lokala sökningsförslag Tog bort %1$s nedladdning @@ -651,7 +650,6 @@ Svep objekt för att ta bort dem Förslag via fjärrsökning Starta inte videor i minispelaren, utan byt till helskärmsläge direkt, om automatisk rotation är låst. Du kan fortfarande komma åt minispelaren genom att gå ut ur helskärmsläge - Visa Picasso färgade band ovanpå bilderna som anger deras källa: rött för nätverk, blått för disk och grönt för minne Sök efter uppdateringar Kolla manuellt efter nya versioner Söker efter uppdateringar… diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index b2846823c94..90708580a1c 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -442,7 +442,6 @@ ప్లేబ్యాక్ స్పీడ్ నియంత్రణలు ఏమిలేదు మీరు బ్లాక్ స్క్రీన్ లేదా చలనచిత్రం ప్లేబ్యాక్‌లో అంతరాయాన్ని అనుభవిస్తే మీడియా టన్నెలింగ్‌ను నిలిపివేయండి - చిత్రాల మూలాన్ని సూచించే విధంగా వాటి పైభాగంలో పికాసో రంగు రిబ్బన్‌లను చూపండి: నెట్‌వర్క్ కోసం ఎరుపు, డిస్క్ కోసం నీలం మరియు మెమరీ కోసం ఆకుపచ్చ లోపం స్నాక్‌బార్‌ని చూపండి మీరు NewPipe యొక్క తాజా సంస్కరణను అమలు చేస్తున్నారు NewPipe నవీకరణ అందుబాటులో ఉంది! @@ -455,7 +454,6 @@ తక్కువ నాణ్యత (చిన్నది) చూపించవద్దు మీడియా టన్నెలింగ్‌ని నిలిపివేయండి - చిత్ర సూచికలను చూపు కొత్త స్ట్రీమ్‌ల కోసం తనిఖీని అమలు చేయండి ఎర్రర్ నోటిఫికేషన్‌ను సృష్టించండి దిగుమతి diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index adecbf3ffef..29933bdc673 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -633,8 +633,6 @@ Yorumlar devre dışı Yaratıcısınca kalplendi İzlendi olarak işaretle - Resimlerin üzerinde kaynaklarını gösteren Picasso renkli şeritler göster: ağ için kırmızı, disk için mavi ve bellek için yeşil - Resim göstergelerini göster Uzak arama önerileri Yerel arama önerileri diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c347086093d..86350d40e96 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -646,8 +646,6 @@ Мініатюра з попереднім переглядом на повзунку поступу Вподобано автором Позначити переглянутим - Показувати кольорові стрічки Пікассо поверх зображень із зазначенням їх джерела: червоний для мережі, синій для диска та зелений для пам’яті - Показати індикатори зображень Віддалені пропозиції пошуку Локальні пошукові пропозиції diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 0d71173afbc..15f5a86d472 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -624,8 +624,6 @@ Bình luận đã bị tắt Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem - Hiển thị các dải băng màu Picasso trên đầu các hình ảnh cho biết nguồn của chúng: màu đỏ cho mạng, màu lam cho đĩa và màu lục cho bộ nhớ - Hiện dấu chỉ hình ảnh Đề xuất tìm kiếm trên mạng Đề xuất tìm kiếm cục bộ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fa8d31022cf..7b303621b9c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -624,8 +624,6 @@ 高品质(较大) 被创作者喜爱 标记为已观看 - 在图像顶部显示毕加索彩带,指示其来源:红色代表网络,蓝色代表磁盘,绿色代表内存 - 显示图像指示器 远程搜索建议 本地搜索建议 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 518a1290fc5..9f581b43f59 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -668,9 +668,7 @@ 你係咪要刪除呢個谷? 淨係顯示未成谷嘅訂閱 - 啲圖都要騷 Picasso 三色碼顯示源頭:紅碼係網絡上高落嚟,藍碼係儲存喺磁碟本地,綠碼係潛伏喺記憶體中 服務原本嘅字會騷返喺串流項目上面 - 影像要推三色碼 若果播片嘅時候窒下窒下或者黑畫面,就停用多媒體隧道啦。 點樣用 Google 匯出嚟匯入 YouTube 訂閱: \n diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c404edeca2b..a4ad3578f82 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -624,8 +624,6 @@ 拖動列縮圖預覽 被創作者加心號 標記為已觀看 - 在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體 - 顯示圖片指示器 遠端搜尋建議 本機搜尋建議 diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index fb68a464d5a..e31cebb92ac 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -241,7 +241,6 @@ show_memory_leaks_key allow_disposed_exceptions_key show_original_time_ago_key - show_image_indicators_key show_crash_the_player_key check_new_streams crash_the_app_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56140441cd0..bff35e5d9ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -486,8 +486,6 @@ Disable media tunneling Disable media tunneling if you experience a black screen or stuttering on video playback. Media tunneling was disabled by default on your device because your device model is known to not support it. - Show image indicators - Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory Show \"Crash the player\" Shows a crash option when using the player Run check for new streams diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 84bb281f31e..d97c5aa1a2b 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -34,13 +34,6 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> - - Date: Fri, 5 Jul 2024 08:29:21 +0530 Subject: [PATCH 03/11] Revert "Migrate image loading from Picasso to Coil (#11201)" This reverts commit 73e3a69aaf78a1d7a7f2f4c54e22d718b7c3093a. --- app/build.gradle | 3 +- app/src/main/java/org/schabi/newpipe/App.java | 24 +- .../org/schabi/newpipe/about/AboutActivity.kt | 4 +- .../fragments/detail/VideoDetailFragment.java | 29 ++- .../list/channel/ChannelFragment.java | 20 +- .../list/comments/CommentRepliesFragment.java | 4 +- .../list/playlist/PlaylistFragment.java | 11 +- .../newpipe/info_list/StreamSegmentItem.kt | 44 ++-- .../holder/ChannelMiniInfoItemHolder.java | 4 +- .../holder/CommentInfoItemHolder.java | 8 +- .../holder/PlaylistMiniInfoItemHolder.java | 4 +- .../holder/StreamMiniInfoItemHolder.java | 4 +- .../java/org/schabi/newpipe/ktx/Bitmap.kt | 13 - .../newpipe/local/feed/item/StreamItem.kt | 4 +- .../feed/notifications/NotificationHelper.kt | 58 ++++- .../local/holder/LocalPlaylistItemHolder.java | 7 +- .../holder/LocalPlaylistStreamItemHolder.java | 6 +- .../LocalStatisticStreamItemHolder.java | 6 +- .../holder/RemotePlaylistItemHolder.java | 7 +- .../local/subscription/item/ChannelItem.kt | 4 +- .../item/PickerSubscriptionItem.kt | 4 +- .../org/schabi/newpipe/player/Player.java | 87 ++++--- .../playqueue/PlayQueueItemBuilder.java | 4 +- .../SeekbarPreviewThumbnailHolder.java | 7 +- .../settings/ContentSettingsFragment.java | 20 +- .../settings/DebugSettingsFragment.java | 9 + .../settings/SelectChannelFragment.java | 4 +- .../settings/SelectPlaylistFragment.java | 17 +- .../external_communication/ShareUtils.java | 21 +- .../schabi/newpipe/util/image/CoilHelper.kt | 142 ----------- .../newpipe/util/image/PicassoHelper.java | 224 ++++++++++++++++++ app/src/main/res/values-ar-rLY/strings.xml | 2 + app/src/main/res/values-ar/strings.xml | 2 + app/src/main/res/values-az/strings.xml | 2 + app/src/main/res/values-be/strings.xml | 2 + app/src/main/res/values-bg/strings.xml | 1 + app/src/main/res/values-bn/strings.xml | 1 + app/src/main/res/values-ca/strings.xml | 2 + app/src/main/res/values-ckb/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 2 + app/src/main/res/values-da/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-el/strings.xml | 2 + app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values-et/strings.xml | 2 + app/src/main/res/values-eu/strings.xml | 2 + app/src/main/res/values-fa/strings.xml | 2 + app/src/main/res/values-fi/strings.xml | 2 + app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-gl/strings.xml | 2 + app/src/main/res/values-he/strings.xml | 2 + app/src/main/res/values-hi/strings.xml | 2 + app/src/main/res/values-hr/strings.xml | 2 + app/src/main/res/values-hu/strings.xml | 2 + app/src/main/res/values-in/strings.xml | 2 + app/src/main/res/values-is/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values-ka/strings.xml | 2 + app/src/main/res/values-ko/strings.xml | 2 + app/src/main/res/values-lt/strings.xml | 2 + app/src/main/res/values-lv/strings.xml | 2 + app/src/main/res/values-ml/strings.xml | 2 + app/src/main/res/values-nb-rNO/strings.xml | 2 + app/src/main/res/values-nl-rBE/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 2 + app/src/main/res/values-nqo/strings.xml | 2 + app/src/main/res/values-or/strings.xml | 2 + app/src/main/res/values-pa/strings.xml | 2 + app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values-pt-rBR/strings.xml | 2 + app/src/main/res/values-pt-rPT/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 2 + app/src/main/res/values-ro/strings.xml | 2 + app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values-ryu/strings.xml | 2 + app/src/main/res/values-sat/strings.xml | 2 + app/src/main/res/values-sc/strings.xml | 2 + app/src/main/res/values-sk/strings.xml | 2 + app/src/main/res/values-so/strings.xml | 2 + app/src/main/res/values-sr/strings.xml | 2 + app/src/main/res/values-sv/strings.xml | 2 + app/src/main/res/values-te/strings.xml | 2 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values-uk/strings.xml | 2 + app/src/main/res/values-vi/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rHK/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/debug_settings.xml | 7 + 92 files changed, 596 insertions(+), 330 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt create mode 100644 app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java diff --git a/app/build.gradle b/app/build.gradle index 1ba5aca389b..4652cf6e5ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -267,7 +267,8 @@ dependencies { implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}" // Image loading - implementation 'io.coil-kt:coil:2.6.0' + //noinspection GradleDependency --> 2.8 is the last version, not 2.71828! + implementation "com.squareup.picasso:picasso:2.8" // Markdown library for Android implementation "io.noties.markwon:core:${markwonVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index a47e0f2fdd4..d92425d200e 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -1,6 +1,5 @@ package org.schabi.newpipe; -import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; @@ -9,7 +8,6 @@ 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; @@ -22,9 +20,10 @@ import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; 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; @@ -33,9 +32,6 @@ import java.util.List; import java.util.Objects; -import coil.ImageLoader; -import coil.ImageLoaderFactory; -import coil.util.DebugLogger; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; @@ -61,7 +57,7 @@ * along with NewPipe. If not, see . */ -public class App extends Application implements ImageLoaderFactory { +public class App extends Application { public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; private static final String TAG = App.class.toString(); @@ -112,22 +108,20 @@ public void onCreate() { // Initialize image loader final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + PicassoHelper.init(this); ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this, prefs.getString(getString(R.string.image_quality_key), getString(R.string.image_quality_default)))); + PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG + && prefs.getBoolean(getString(R.string.show_image_indicators_key), false)); 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(); + public void onTerminate() { + super.onTerminate(); + PicassoHelper.terminate(); } protected Downloader getDownloader() { diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt index 0d0d0d48dd4..7f148e9b5c2 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt @@ -167,8 +167,8 @@ class AboutActivity : AppCompatActivity() { "https://square.github.io/okhttp/", StandardLicenses.APACHE2 ), SoftwareComponent( - "Coil", "2023", "Coil Contributors", - "https://coil-kt.github.io/coil/", StandardLicenses.APACHE2 + "Picasso", "2013", "Square, Inc.", + "https://square.github.io/picasso/", StandardLicenses.APACHE2 ), SoftwareComponent( "PrettyTime", "2012 - 2020", "Lincoln Baxter, III", diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 96523321b14..95b54f65a70 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -116,7 +116,7 @@ import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.CoilHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import java.util.ArrayList; import java.util.Iterator; @@ -127,7 +127,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import coil.util.CoilUtils; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -160,6 +159,8 @@ public final class VideoDetailFragment private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; private static final String EMPTY_TAB_TAG = "EMPTY TAB"; + private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG"; + // tabs private boolean showComments; private boolean showRelatedItems; @@ -1470,10 +1471,7 @@ public void showLoading() { } } - CoilUtils.dispose(binding.detailThumbnailImageView); - CoilUtils.dispose(binding.detailSubChannelThumbnailView); - CoilUtils.dispose(binding.overlayThumbnail); - + PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG); binding.detailThumbnailImageView.setImageBitmap(null); binding.detailSubChannelThumbnailView.setImageBitmap(null); } @@ -1564,8 +1562,8 @@ public void handleResult(@NonNull final StreamInfo info) { binding.detailSecondaryControlPanel.setVisibility(View.GONE); checkUpdateProgressInfo(info); - CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView, - info.getThumbnails()); + PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailThumbnailImageView); showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, binding.detailMetaInfoSeparator, disposables); @@ -1615,8 +1613,8 @@ private void displayUploaderAsSubChannel(final StreamInfo info) { binding.detailUploaderTextView.setVisibility(View.GONE); } - CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView, - info.getUploaderAvatars()); + PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailSubChannelThumbnailView); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); binding.detailUploaderThumbnailView.setVisibility(View.GONE); } @@ -1647,11 +1645,11 @@ private void displayBothUploaderAndSubChannel(final StreamInfo info) { binding.detailUploaderTextView.setVisibility(View.GONE); } - CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView, - info.getSubChannelAvatars()); + PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailSubChannelThumbnailView); binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); - CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView, - info.getUploaderAvatars()); + PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.detailUploaderThumbnailView); binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE); } @@ -2405,7 +2403,8 @@ private void updateOverlayData(@Nullable final String overlayTitle, binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageDrawable(null); - CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails); + PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG) + .into(binding.overlayThumbnail); } private void setOverlayPlayPauseImage(final boolean playerIsPlaying) { 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 3890e48659d..fd382adbf46 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 @@ -50,16 +50,15 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.StateSaver; +import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.CoilHelper; -import org.schabi.newpipe.util.image.ImageStrategy; import java.util.List; import java.util.Queue; import java.util.concurrent.TimeUnit; -import coil.util.CoilUtils; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; @@ -74,6 +73,7 @@ public class ChannelFragment extends BaseStateFragment implements StateSaver.WriteRead { private static final int BUTTON_DEBOUNCE_INTERVAL = 100; + private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG"; @State protected int serviceId = Constants.NO_SERVICE_ID; @@ -576,9 +576,7 @@ private void runWorker(final boolean forceLoad) { @Override public void showLoading() { super.showLoading(); - CoilUtils.dispose(binding.channelAvatarView); - CoilUtils.dispose(binding.channelBannerImage); - CoilUtils.dispose(binding.subChannelAvatarView); + PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG); animate(binding.channelSubscribeButton, false, 100); } @@ -589,15 +587,17 @@ public void handleResult(@NonNull final ChannelInfo result) { setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName()); if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) { - CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners()); + PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG) + .into(binding.channelBannerImage); } else { // do not waste space for the banner, if the user disabled images or there is not one binding.channelBannerImage.setImageDrawable(null); } - CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars()); - CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView, - result.getParentChannelAvatars()); + PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG) + .into(binding.channelAvatarView); + PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG) + .into(binding.subChannelAvatarView); binding.channelTitleView.setText(result.getName()); binding.channelSubscriberView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java index 4eb73520fba..304eaf55a18 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java @@ -23,8 +23,8 @@ import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.text.TextLinkifier; import java.util.Queue; @@ -82,7 +82,7 @@ protected Supplier getListHeaderSupplier() { final CommentsInfoItem item = commentsInfoItem; // load the author avatar - CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars()); + PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar); binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages() ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index d4607a9ffb1..6410fb9ee75 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -53,7 +53,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PlayButtonHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.CoilHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.text.TextEllipsizer; import java.util.ArrayList; @@ -62,7 +62,6 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import coil.util.CoilUtils; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; @@ -72,6 +71,8 @@ public class PlaylistFragment extends BaseListInfoFragment implements PlaylistControlViewHolder { + private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG"; + private CompositeDisposable disposables; private Subscription bookmarkReactor; private AtomicBoolean isBookmarkButtonReady; @@ -275,7 +276,7 @@ public void showLoading() { animate(headerBinding.getRoot(), false, 200); animateHideRecyclerViewAllowingScrolling(itemsList); - CoilUtils.dispose(headerBinding.uploaderAvatarView); + PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG); animate(headerBinding.uploaderLayout, false, 200); } @@ -326,8 +327,8 @@ public void handleResult(@NonNull final PlaylistInfo result) { R.drawable.ic_radio) ); } else { - CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView, - result.getUploaderAvatars()); + PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG) + .into(headerBinding.uploaderAvatarView); } streamCount = result.getStreamCount(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt index 83e0c408289..1e52d316808 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt @@ -1,18 +1,19 @@ package org.schabi.newpipe.info_list import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import com.xwray.groupie.viewbinding.GroupieViewHolder +import android.widget.ImageView +import android.widget.TextView +import com.xwray.groupie.GroupieViewHolder +import com.xwray.groupie.Item import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.ItemStreamSegmentBinding import org.schabi.newpipe.extractor.stream.StreamSegment import org.schabi.newpipe.util.Localization -import org.schabi.newpipe.util.image.CoilHelper +import org.schabi.newpipe.util.image.PicassoHelper class StreamSegmentItem( private val item: StreamSegment, private val onClick: StreamSegmentAdapter.StreamSegmentListener -) : BindableItem() { +) : Item() { companion object { const val PAYLOAD_SELECT = 1 @@ -20,32 +21,31 @@ class StreamSegmentItem( var isSelected = false - override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) { - CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl) - viewBinding.textViewTitle.text = item.title + override fun bind(viewHolder: GroupieViewHolder, position: Int) { + item.previewUrl?.let { + PicassoHelper.loadThumbnail(it) + .into(viewHolder.root.findViewById(R.id.previewImage)) + } + viewHolder.root.findViewById(R.id.textViewTitle).text = item.title if (item.channelName == null) { - viewBinding.textViewChannel.visibility = View.GONE + viewHolder.root.findViewById(R.id.textViewChannel).visibility = View.GONE // When the channel name is displayed there is less space // and thus the segment title needs to be only one line height. // But when there is no channel name displayed, the title can be two lines long. // The default maxLines value is set to 1 to display all elements in the AS preview, - viewBinding.textViewTitle.maxLines = 2 + viewHolder.root.findViewById(R.id.textViewTitle).maxLines = 2 } else { - viewBinding.textViewChannel.text = item.channelName - viewBinding.textViewChannel.visibility = View.VISIBLE + viewHolder.root.findViewById(R.id.textViewChannel).text = item.channelName + viewHolder.root.findViewById(R.id.textViewChannel).visibility = View.VISIBLE } - viewBinding.textViewStartSeconds.text = + viewHolder.root.findViewById(R.id.textViewStartSeconds).text = Localization.getDurationString(item.startTimeSeconds.toLong()) - viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } - viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true } - viewBinding.root.isSelected = isSelected + viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } + viewHolder.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true } + viewHolder.root.isSelected = isSelected } - override fun bind( - viewHolder: GroupieViewHolder, - position: Int, - payloads: MutableList - ) { + override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList) { if (payloads.contains(PAYLOAD_SELECT)) { viewHolder.root.isSelected = isSelected return @@ -54,6 +54,4 @@ class StreamSegmentItem( } override fun getLayout() = R.layout.item_stream_segment - - override fun initializeViewBinding(view: View) = ItemStreamSegmentBinding.bind(view) } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 92a5054e130..7afc05c6c25 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -13,8 +13,8 @@ import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.CoilHelper; public class ChannelMiniInfoItemHolder extends InfoItemHolder { private final ImageView itemThumbnailView; @@ -56,7 +56,7 @@ public void updateFromItem(final InfoItem infoItem, itemAdditionalDetailView.setText(getDetailLine(item)); } - CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getThumbnails()); + PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java index a3316d3fecc..839aa1813f3 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java @@ -27,8 +27,8 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.text.TextEllipsizer; public class CommentInfoItemHolder extends InfoItemHolder { @@ -82,12 +82,14 @@ public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder, @Override public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { - if (!(infoItem instanceof CommentsInfoItem item)) { + if (!(infoItem instanceof CommentsInfoItem)) { return; } + final CommentsInfoItem item = (CommentsInfoItem) infoItem; + // load the author avatar - CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getUploaderAvatars()); + PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView); if (ImageStrategy.shouldLoadImages()) { itemThumbnailView.setVisibility(View.VISIBLE); itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding, diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index b7949318d58..c9216d9a9e5 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -9,8 +9,8 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.CoilHelper; public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; @@ -46,7 +46,7 @@ public void updateFromItem(final InfoItem infoItem, .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); - CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnails()); + PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 32fa8bf608b..01f3be6b328 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.StreamTypeUtil; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.util.concurrent.TimeUnit; @@ -87,7 +87,7 @@ public void updateFromItem(final InfoItem infoItem, } // Default thumbnail is shown on error, while loading and if the url is empty - CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, item.getThumbnails()); + PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnStreamSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt deleted file mode 100644 index 4a3c3e071d4..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.schabi.newpipe.ktx - -import android.graphics.Bitmap -import android.graphics.Rect -import androidx.core.graphics.BitmapCompat - -@Suppress("NOTHING_TO_INLINE") -inline fun Bitmap.scale( - width: Int, - height: Int, - srcRect: Rect? = null, - scaleInLinearSpace: Boolean = true, -) = BitmapCompat.createScaledBitmap(this, width, height, srcRect, scaleInLinearSpace) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index 030bb7a7668..4a071d6df75 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -19,7 +19,7 @@ import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.StreamTypeUtil -import org.schabi.newpipe.util.image.CoilHelper +import org.schabi.newpipe.util.image.PicassoHelper import java.util.concurrent.TimeUnit import java.util.function.Consumer @@ -101,7 +101,7 @@ data class StreamItem( viewBinding.itemProgressView.visibility = View.GONE } - CoilHelper.loadThumbnail(viewBinding.itemThumbnailView, stream.thumbnailUrl) + PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView) if (itemVersion != ItemVersion.MINI) { viewBinding.itemAdditionalDetails.text = 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 659088ef2fd..8ea89368d6b 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 @@ -6,6 +6,7 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings @@ -15,17 +16,20 @@ import androidx.core.app.PendingIntentCompat import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.preference.PreferenceManager +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target 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.image.CoilHelper +import org.schabi.newpipe.util.image.PicassoHelper /** * Helper for everything related to show notifications about new streams to the user. */ class NotificationHelper(val context: Context) { private val manager = NotificationManagerCompat.from(context) + private val iconLoadingTargets = ArrayList() /** * Show notifications for new streams from a single channel. The individual notifications are @@ -64,23 +68,51 @@ class NotificationHelper(val context: Context) { summaryBuilder.setStyle(style) // open the channel page when clicking on the summary notification - val intent = NavigationHelper - .getChannelIntent(context, data.serviceId, data.url) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) summaryBuilder.setContentIntent( - PendingIntentCompat.getActivity(context, data.pseudoId, intent, 0, false) + PendingIntentCompat.getActivity( + context, + data.pseudoId, + NavigationHelper + .getChannelIntent(context, data.serviceId, data.url) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + 0, + false + ) ) - val avatarIcon = - CoilHelper.loadBitmapBlocking(context, data.avatarUrl, R.drawable.ic_newpipe_triangle_white) + // a Target is like a listener for image loading events + val target = object : Target { + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + // set channel icon only if there is actually one (for Android versions < 7.0) + summaryBuilder.setLargeIcon(bitmap) + + // Show individual stream notifications, set channel icon only if there is actually + // one + showStreamNotifications(newStreams, data.serviceId, bitmap) + // Show summary notification + manager.notify(data.pseudoId, summaryBuilder.build()) + + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { + // Show individual stream notifications + showStreamNotifications(newStreams, data.serviceId, null) + // Show summary notification + manager.notify(data.pseudoId, summaryBuilder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable) { + // Nothing to do + } + } - summaryBuilder.setLargeIcon(avatarIcon) + // add the target to the list to hold a strong reference and prevent it from being garbage + // collected, since Picasso only holds weak references to targets + iconLoadingTargets.add(target) - // Show individual stream notifications, set channel icon only if there is actually - // one - showStreamNotifications(newStreams, data.serviceId, avatarIcon) - // Show summary notification - manager.notify(data.pseudoId, summaryBuilder.build()) + PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target) } private fun showStreamNotifications( diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index a11438374d1..336f5cfe30b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.image.CoilHelper; import java.time.format.DateTimeFormatter; @@ -30,16 +30,17 @@ public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final Vie public void updateFromItem(final LocalItem localItem, final HistoryRecordManager historyRecordManager, final DateTimeFormatter dateTimeFormatter) { - if (!(localItem instanceof PlaylistMetadataEntry item)) { + if (!(localItem instanceof PlaylistMetadataEntry)) { return; } + final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; itemTitleView.setText(item.name); itemStreamCountView.setText(Localization.localizeStreamCountMini( itemStreamCountView.getContext(), item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); - CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.thumbnailUrl); + PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView); if (item instanceof PlaylistDuplicatesEntry && ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java index 7dc71bfb4ac..89a714fd7f6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistStreamItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -83,8 +83,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, - item.getStreamEntity().getThumbnailUrl()); + PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) + .into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java index f26a76ad9f7..150a35eb59c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalStatisticStreamItemHolder.java @@ -16,8 +16,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.views.AnimatedProgressBar; import java.time.format.DateTimeFormatter; @@ -117,8 +117,8 @@ public void updateFromItem(final LocalItem localItem, } // Default thumbnail is shown on error, while loading and if the url is empty - CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, - item.getStreamEntity().getThumbnailUrl()); + PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl()) + .into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index f79f3c78532..7657320634c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -8,8 +8,8 @@ import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.image.CoilHelper; import java.time.format.DateTimeFormatter; @@ -29,9 +29,10 @@ public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, public void updateFromItem(final LocalItem localItem, final HistoryRecordManager historyRecordManager, final DateTimeFormatter dateTimeFormatter) { - if (!(localItem instanceof PlaylistRemoteEntity item)) { + if (!(localItem instanceof PlaylistRemoteEntity)) { return; } + final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; itemTitleView.setText(item.getName()); itemStreamCountView.setText(Localization.localizeStreamCountMini( @@ -44,7 +45,7 @@ public void updateFromItem(final LocalItem localItem, itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId())); } - CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl()); + PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index ca626e704f1..bc39dafe632 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -9,7 +9,7 @@ import org.schabi.newpipe.R import org.schabi.newpipe.extractor.channel.ChannelInfoItem import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.OnClickGesture -import org.schabi.newpipe.util.image.CoilHelper +import org.schabi.newpipe.util.image.PicassoHelper class ChannelItem( private val infoItem: ChannelInfoItem, @@ -39,7 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - CoilHelper.loadAvatar(itemThumbnailView, infoItem.thumbnails) + PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt index da35447e38d..3a4c6e41b99 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/PickerSubscriptionItem.kt @@ -10,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.util.image.CoilHelper +import org.schabi.newpipe.util.image.PicassoHelper data class PickerSubscriptionItem( val subscriptionEntity: SubscriptionEntity, @@ -21,7 +21,7 @@ data class PickerSubscriptionItem( override fun getSpanSize(spanCount: Int, position: Int): Int = 1 override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) { - CoilHelper.loadAvatar(viewBinding.thumbnailView, subscriptionEntity.avatarUrl) + PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView) viewBinding.titleView.text = subscriptionEntity.name viewBinding.selectedHighlight.isVisible = isSelected } 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 40da9139dff..49e72328e40 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -60,7 +60,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.graphics.drawable.DrawableKt; import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; @@ -78,6 +77,8 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.video.VideoSize; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; @@ -85,8 +86,8 @@ import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; @@ -117,15 +118,14 @@ import org.schabi.newpipe.util.DependentPreferenceHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; -import coil.target.Target; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -174,6 +174,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -245,6 +246,12 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + // This is the only listener we need for thumbnail loading, since there is always at most only + // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, + // which would otherwise be garbage collected since Picasso holds weak references to targets. + @NonNull + private final Target currentThumbnailTarget; + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -288,6 +295,8 @@ public Player(@NonNull final PlayerService service) { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + currentThumbnailTarget = getCurrentThumbnailTarget(); + // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -593,6 +602,7 @@ public void destroy() { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); + cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -766,52 +776,67 @@ private void unregisterBroadcastReceiver() { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading + private Target getCurrentThumbnailTarget() { + // a Picasso target is just a listener for thumbnail loading events + return new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap + + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + + from + "]"); + } + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + onThumbnailLoaded(bitmap); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + onThumbnailLoaded(null); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onPrepareLoad() called"); + } + } + }; + } + private void loadCurrentThumbnail(final List thumbnails) { if (DEBUG) { Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = [" + thumbnails.size() + "]"); } + // first cancel any previous loading + cancelLoadingCurrentThumbnail(); + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media - // session metadata while the new thumbnail is being loaded by Coil. + // session metadata while the new thumbnail is being loaded by Picasso. onThumbnailLoaded(null); if (thumbnails.isEmpty()) { return; } // scale down the notification thumbnail for performance - final var target = new Target() { - @Override - public void onError(@Nullable final Drawable 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) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onStart() called"); - } - } + PicassoHelper.loadScaledDownThumbnail(context, thumbnails) + .tag(PICASSO_PLAYER_THUMBNAIL_TAG) + .into(currentThumbnailTarget); + } - @Override - public void onSuccess(@NonNull final Drawable 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)); - } - }; - CoilHelper.INSTANCE.loadScaledDownThumbnail(context, thumbnails, target); + private void cancelLoadingCurrentThumbnail() { + // cancel the Picasso job associated with the player thumbnail, if any + PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } private void onThumbnailLoaded(@Nullable final Bitmap bitmap) { // Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the // thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since - // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Coil's target. + // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Picasso's target. if (currentThumbnail != bitmap) { currentThumbnail = bitmap; UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index 8994aef79df..066f92c2607 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -6,8 +6,8 @@ import android.view.View; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.image.CoilHelper; public class PlayQueueItemBuilder { private static final String TAG = PlayQueueItemBuilder.class.toString(); @@ -33,7 +33,7 @@ public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueu holder.itemDurationView.setVisibility(View.GONE); } - CoilHelper.INSTANCE.loadThumbnail(holder.itemThumbnailView, item.getThumbnails()); + PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView); holder.itemRoot.setOnClickListener(view -> { if (onItemClickListener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java index d09664aeb40..26065de1572 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java @@ -13,9 +13,8 @@ import com.google.common.base.Stopwatch; -import org.schabi.newpipe.App; import org.schabi.newpipe.extractor.stream.Frameset; -import org.schabi.newpipe.util.image.CoilHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import java.util.Comparator; import java.util.List; @@ -178,8 +177,8 @@ private Bitmap getBitMapFrom(final String url) { Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'"); // 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); + // Ensure that your are not running on the main-Thread this will otherwise hang + final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get(); if (sw != null) { Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took " diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 2cda1b4ea2d..ec2bed67a44 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -13,9 +13,10 @@ import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.image.PreferredImageQuality; -import coil.Coil; +import java.io.IOException; public class ContentSettingsFragment extends BasePreferenceFragment { private String youtubeRestrictedModeEnabledKey; @@ -41,17 +42,14 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro (preference, newValue) -> { ImageStrategy.setPreferredImageQuality(PreferredImageQuality .fromPreferenceKey(requireContext(), (String) newValue)); - final var loader = Coil.imageLoader(preference.getContext()); - if (loader.getMemoryCache() != null) { - loader.getMemoryCache().clear(); + try { + PicassoHelper.clearCache(preference.getContext()); + Toast.makeText(preference.getContext(), + R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT) + .show(); + } catch (final IOException e) { + Log.e(TAG, "Unable to clear Picasso cache", e); } - if (loader.getDiskCache() != null) { - loader.getDiskCache().clear(); - } - Toast.makeText(preference.getContext(), - R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT) - .show(); - return true; }); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index c6abb5405aa..d78ade49df6 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -10,6 +10,7 @@ import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; +import org.schabi.newpipe.util.image.PicassoHelper; import java.util.Optional; @@ -24,6 +25,8 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro findPreference(getString(R.string.allow_heap_dumping_key)); final Preference showMemoryLeaksPreference = findPreference(getString(R.string.show_memory_leaks_key)); + final Preference showImageIndicatorsPreference = + findPreference(getString(R.string.show_image_indicators_key)); final Preference checkNewStreamsPreference = findPreference(getString(R.string.check_new_streams_key)); final Preference crashTheAppPreference = @@ -35,6 +38,7 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro assert allowHeapDumpingPreference != null; assert showMemoryLeaksPreference != null; + assert showImageIndicatorsPreference != null; assert checkNewStreamsPreference != null; assert crashTheAppPreference != null; assert showErrorSnackbarPreference != null; @@ -57,6 +61,11 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available); } + showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> { + PicassoHelper.setIndicatorsEnabled((Boolean) newValue); + return true; + }); + checkNewStreamsPreference.setOnPreferenceClickListener(preference -> { NotificationWorker.runNow(preference.getContext()); return true; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index c566313e37a..37335421d16 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -19,8 +19,8 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.local.subscription.SubscriptionManager; +import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; import java.util.Vector; @@ -190,7 +190,7 @@ public void onBindViewHolder(final SelectChannelItemHolder holder, final int pos final SubscriptionEntity entry = subscriptions.get(position); holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - CoilHelper.INSTANCE.loadAvatar(holder.thumbnailView, entry.getAvatarUrl()); + PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index c340dca2231..36abef9e5ca 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -27,7 +27,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.util.image.CoilHelper; +import org.schabi.newpipe.util.image.PicassoHelper; import java.util.List; import java.util.Vector; @@ -154,15 +154,20 @@ public void onBindViewHolder(@NonNull final SelectPlaylistItemHolder holder, final int position) { final PlaylistLocalItem selectedItem = playlists.get(position); - if (selectedItem instanceof PlaylistMetadataEntry entry) { + if (selectedItem instanceof PlaylistMetadataEntry) { + final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); + holder.titleView.setText(entry.name); holder.view.setOnClickListener(view -> clickedItem(position)); - CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, entry.thumbnailUrl); - } else if (selectedItem instanceof PlaylistRemoteEntity entry) { + PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView); + + } else if (selectedItem instanceof PlaylistRemoteEntity) { + final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); + holder.titleView.setText(entry.getName()); holder.view.setOnClickListener(view -> clickedItem(position)); - CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, - entry.getThumbnailUrl()); + PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl()) + .into(holder.thumbnailView); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java index 21a4b117562..7524e5413c5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java @@ -24,8 +24,8 @@ import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.Image; -import org.schabi.newpipe.util.image.CoilHelper; import org.schabi.newpipe.util.image.ImageStrategy; +import org.schabi.newpipe.util.image.PicassoHelper; import java.io.File; import java.io.FileOutputStream; @@ -278,7 +278,7 @@ public static void shareText(@NonNull final Context context, * @param content the content to share * @param images a set of possible {@link Image}s of the subject, among which to choose with * {@link ImageStrategy#choosePreferredImage(List)} since that's likely to - * provide an image that is in Coil's cache + * provide an image that is in Picasso's cache */ public static void shareText(@NonNull final Context context, @NonNull final String title, @@ -335,7 +335,15 @@ public static void copyToClipboard(@NonNull final Context context, final String } /** - * Generate a {@link ClipData} with the image of the content shared. + * Generate a {@link ClipData} with the image of the content shared, if it's in the app cache. + * + *

+ * In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...) + * when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache} + * used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the + * thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null} + * will be returned. + *

* *

* In order to display the image in the content preview of the Android share sheet, an URI of @@ -351,8 +359,9 @@ public static void copyToClipboard(@NonNull final Context context, final String *

* *

- * This method will call {@link CoilHelper#loadBitmapBlocking(Context, String)} to get the - * thumbnail of the content. + * This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the + * thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by + * the Picasso library inside {@link PicassoHelper}. *

* *

@@ -369,7 +378,7 @@ private static ClipData generateClipDataForImagePreview( @NonNull final Context context, @NonNull final String thumbnailUrl) { try { - final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(context, thumbnailUrl); + final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl); if (bitmap == null) { return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt deleted file mode 100644 index ab7cd79a895..00000000000 --- a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt +++ /dev/null @@ -1,142 +0,0 @@ -package org.schabi.newpipe.util.image - -import android.content.Context -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.ImageRequest -import coil.size.Size -import coil.target.Target -import coil.transform.Transformation -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.R -import org.schabi.newpipe.extractor.Image -import org.schabi.newpipe.ktx.scale -import kotlin.math.min - -object CoilHelper { - private val TAG = CoilHelper::class.java.simpleName - - @JvmOverloads - 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() - } - - fun loadAvatar(target: ImageView, images: List) { - loadImageDefault(target, images, R.drawable.placeholder_person) - } - - fun loadAvatar(target: ImageView, url: String?) { - loadImageDefault(target, url, R.drawable.placeholder_person) - } - - fun loadThumbnail(target: ImageView, images: List) { - loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video) - } - - fun loadThumbnail(target: ImageView, url: String?) { - loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video) - } - - fun loadScaledDownThumbnail(context: Context, images: List, target: Target) { - 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" - - 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() - - 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() - - context.imageLoader.enqueue(request) - } - - fun loadDetailsThumbnail(target: ImageView, images: List) { - val url = ImageStrategy.choosePreferredImage(images) - loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false) - } - - fun loadBanner(target: ImageView, images: List) { - loadImageDefault(target, images, R.drawable.placeholder_channel_banner) - } - - fun loadPlaylistThumbnail(target: ImageView, images: List) { - loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist) - } - - fun loadPlaylistThumbnail(target: ImageView, url: String?) { - loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist) - } - - private fun loadImageDefault( - target: ImageView, - images: List, - @DrawableRes placeholderResId: Int - ) { - loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId) - } - - private fun loadImageDefault( - target: ImageView, - url: String?, - @DrawableRes placeholderResId: Int, - showPlaceholder: Boolean = true - ) { - val request = getImageRequest(target.context, url, placeholderResId, showPlaceholder) - .target(target) - .build() - target.context.imageLoader.enqueue(request) - } - - private fun getImageRequest( - context: Context, - url: String?, - @DrawableRes placeholderResId: Int, - 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) - .data(takenUrl) - .error(placeholderResId) - .apply { - if (takenUrl != null || showPlaceholderWhileLoading) { - placeholder(placeholderResId) - } - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java new file mode 100644 index 00000000000..4b116bdf906 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java @@ -0,0 +1,224 @@ +package org.schabi.newpipe.util.image; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; +import static org.schabi.newpipe.util.image.ImageStrategy.choosePreferredImage; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.BitmapCompat; + +import com.squareup.picasso.Cache; +import com.squareup.picasso.LruCache; +import com.squareup.picasso.OkHttp3Downloader; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.RequestCreator; +import com.squareup.picasso.Transformation; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.Image; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; + +public final class PicassoHelper { + private static final String TAG = PicassoHelper.class.getSimpleName(); + private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = + "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; + + private PicassoHelper() { + } + + private static Cache picassoCache; + private static OkHttpClient picassoDownloaderClient; + + // suppress because terminate() is called in App.onTerminate(), preventing leaks + @SuppressLint("StaticFieldLeak") + private static Picasso picassoInstance; + + + public static void init(final Context context) { + picassoCache = new LruCache(10 * 1024 * 1024); + picassoDownloaderClient = new OkHttpClient.Builder() + .cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"), + 50L * 1024L * 1024L)) + // this should already be the default timeout in OkHttp3, but just to be sure... + .callTimeout(15, TimeUnit.SECONDS) + .build(); + + picassoInstance = new Picasso.Builder(context) + .memoryCache(picassoCache) // memory cache + .downloader(new OkHttp3Downloader(picassoDownloaderClient)) // disk cache + .defaultBitmapConfig(Bitmap.Config.RGB_565) + .build(); + } + + public static void terminate() { + picassoCache = null; + picassoDownloaderClient = null; + + if (picassoInstance != null) { + picassoInstance.shutdown(); + picassoInstance = null; + } + } + + public static void clearCache(final Context context) throws IOException { + picassoInstance.shutdown(); + picassoCache.clear(); // clear memory cache + final okhttp3.Cache diskCache = picassoDownloaderClient.cache(); + if (diskCache != null) { + diskCache.delete(); // clear disk cache + } + init(context); + } + + public static void cancelTag(final Object tag) { + picassoInstance.cancelTag(tag); + } + + public static void setIndicatorsEnabled(final boolean enabled) { + picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging + } + + + public static RequestCreator loadAvatar(@NonNull final List images) { + return loadImageDefault(images, R.drawable.placeholder_person); + } + + public static RequestCreator loadAvatar(@Nullable final String url) { + return loadImageDefault(url, R.drawable.placeholder_person); + } + + public static RequestCreator loadThumbnail(@NonNull final List images) { + return loadImageDefault(images, R.drawable.placeholder_thumbnail_video); + } + + public static RequestCreator loadThumbnail(@Nullable final String url) { + return loadImageDefault(url, R.drawable.placeholder_thumbnail_video); + } + + public static RequestCreator loadDetailsThumbnail(@NonNull final List images) { + return loadImageDefault(choosePreferredImage(images), + R.drawable.placeholder_thumbnail_video, false); + } + + public static RequestCreator loadBanner(@NonNull final List images) { + return loadImageDefault(images, R.drawable.placeholder_channel_banner); + } + + public static RequestCreator loadPlaylistThumbnail(@NonNull final List images) { + return loadImageDefault(images, R.drawable.placeholder_thumbnail_playlist); + } + + public static RequestCreator loadPlaylistThumbnail(@Nullable final String url) { + return loadImageDefault(url, R.drawable.placeholder_thumbnail_playlist); + } + + public static RequestCreator loadSeekbarThumbnailPreview(@Nullable final String url) { + return picassoInstance.load(url); + } + + public static RequestCreator loadNotificationIcon(@Nullable final String url) { + return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white); + } + + + public static RequestCreator loadScaledDownThumbnail(final Context context, + @NonNull final List images) { + // scale down the notification thumbnail for performance + return PicassoHelper.loadThumbnail(images) + .transform(new Transformation() { + @Override + public Bitmap transform(final Bitmap source) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - transform() called"); + } + + final float notificationThumbnailWidth = Math.min( + context.getResources() + .getDimension(R.dimen.player_notification_thumbnail_width), + source.getWidth()); + + final Bitmap result = BitmapCompat.createScaledBitmap( + source, + (int) notificationThumbnailWidth, + (int) (source.getHeight() + / (source.getWidth() / notificationThumbnailWidth)), + null, + true); + + if (result == source || !result.isMutable()) { + // create a new mutable bitmap to prevent strange crashes on some + // devices (see #4638) + final Bitmap copied = BitmapCompat.createScaledBitmap( + source, + (int) notificationThumbnailWidth - 1, + (int) (source.getHeight() / (source.getWidth() + / (notificationThumbnailWidth - 1))), + null, + true); + source.recycle(); + return copied; + } else { + source.recycle(); + return result; + } + } + + @Override + public String key() { + return PLAYER_THUMBNAIL_TRANSFORMATION_KEY; + } + }); + } + + @Nullable + public static Bitmap getImageFromCacheIfPresent(@NonNull final String imageUrl) { + // URLs in the internal cache finish with \n so we need to add \n to image URLs + return picassoCache.get(imageUrl + "\n"); + } + + + private static RequestCreator loadImageDefault(@NonNull final List images, + @DrawableRes final int placeholderResId) { + return loadImageDefault(choosePreferredImage(images), placeholderResId); + } + + private static RequestCreator loadImageDefault(@Nullable final String url, + @DrawableRes final int placeholderResId) { + return loadImageDefault(url, placeholderResId, true); + } + + private static RequestCreator loadImageDefault(@Nullable final String url, + @DrawableRes final int placeholderResId, + final boolean showPlaceholderWhileLoading) { + // 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) + if (isNullOrEmpty(url) || !ImageStrategy.shouldLoadImages()) { + return picassoInstance + .load((String) null) + .placeholder(placeholderResId) // show placeholder when no image should load + .error(placeholderResId); + } else { + final RequestCreator requestCreator = picassoInstance + .load(url) + .error(placeholderResId); + if (showPlaceholderWhileLoading) { + requestCreator.placeholder(placeholderResId); + } + return requestCreator; + } + } +} diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml index 290d9f6ab01..077cf1106ca 100644 --- a/app/src/main/res/values-ar-rLY/strings.xml +++ b/app/src/main/res/values-ar-rLY/strings.xml @@ -302,6 +302,7 @@ %s مشارك جلب البيانات الوصفية… + إظهار مؤشرات الصور انقر للتنزيل %s تعطيل الوضع السريع , @@ -829,6 +830,7 @@ لقد اشتركت الآن في هذه القناة بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط إنشاء اسم فريد + أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة فشل الاتصال الآمن يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe. البث السابق diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 902fe8c3a22..82173d75802 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -674,6 +674,8 @@ معاينة مصغرة على شريط التمرير وضع علامة على تمت مشاهدته أُعجب بها منشئ المحتوى + أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة + إظهار مؤشرات الصور اقتراحات البحث عن بعد اقتراحات البحث المحلية اسحب العناصر لإزالتها diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 9c9c15c964e..66bfe75dee9 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -505,6 +505,8 @@ Məlumat əldə edilir… Elementlərdə orijinal əvvəlki vaxtı göstər Yaşam dövrəsi xaricindəki xətaları bildir + Şəkil göstəricilərini göstər + Şəkillərin üzərində mənbəsini göstərən Picasso rəngli lentləri göstər: şəbəkə üçün qırmızı, disk üçün mavi və yaddaş üçün yaşıl Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır Bağla Fayl silindiyi üçün irəliləyiş itirildi diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 5ddf30c2a5e..dceaaf6c560 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -630,6 +630,8 @@ Перайсці на вэбсайт Правядзіце пальцам па элементах, каб выдаліць іх Адмяніць пастаянную мініяцюру + Паказаць індыкатары выявы + Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці Апрацоўка стужкі… Вам будзе прапанавана, дзе захоўваць кожную спампоўку Загрузка стужкі… diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 9b8c06bc581..bc235446c3d 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -539,6 +539,7 @@ Това видео е с възрастова граница. \n \nВключете „%1$s“ в настройките ако искате да го пуснете. + Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта) Автоматична (тази на устройството) Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания) Избете плейлист diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 8fe38988a56..853b04b64f7 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -589,6 +589,7 @@ নতুন ধারা কম্পাঙ্ক দেখো পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন + ছবিরূপ সূচক দেখাও দেখিও না যেকোনো নেটওয়ার্ক পরেরটা ক্রমে রাখা হয়েছে diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index dd08e352638..871af187b25 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -625,6 +625,7 @@ No mostris Baixa qualitat (més petit) Alta qualitat (més gran) + Mostra indicadors de la imatge Desactiva l\'entunelament del contingut si en els videos hi ha una pantalla negre o tartamudegen Mostra detalls del canal No s\'ha establert una carpeta de descàrregues, selecciona la carpeta per defecte ara @@ -668,6 +669,7 @@ Comentari fixat Mostra \"Força el tancament del reproductor\" Mostra una opció de fallada quan s\'utilitza el reproductor + Mostra les cintes de color Picasso a la part superior de les imatges que indiquen la seva font: vermell per a la xarxa, blau per al disc i verd per a la memòria El LeakCanary no està disponible Comprovant freqüència Es necesita una conexió a Internet diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index d14449cf1cf..e6e375a4cc6 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -631,6 +631,8 @@ پیشان نەدرێت کواڵێتی نزم (بچووکتر) کواڵێتی بەرز (گەورەتر) + پیشاندانی شریتە ڕەنگکراوەکانی پیکاسۆ لەسەرووی وێنەکانەوە بۆ بەدیار خستنی سەرچاوەکانیان : سوور بۆ تۆڕ ، شین بۆ دیسک و سەوز بۆ بیرگە + پیشاندانی دیارخەرەکانی وێنە پێشنیازکراوەکانی گەڕانی ڕیمۆت پێشنیازکراوەکانی گەڕانی نێوخۆیی دیارکردن وەک بینراو diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e6263f6e021..34537446cce 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -654,6 +654,8 @@ %s stahování dokončena %s stahováních dokončeno + Zobrazit barevné pásky Picasso na obrázcích označujících jejich zdroj: červená pro síť, modrá pro disk a zelená pro paměť + Zobrazit indikátory obrázků Vzdálené návrhy vyhledávání Lokální návrhy vyhledávání Pokud je vypnuté automatické otáčení, nespouštět video v mini přehrávači, ale přepnout se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index cbb59d591ed..601bc37523d 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -516,6 +516,7 @@ Behandler… Det kan tage et øjeblik Vis hukommelseslækager Deaktivér medietunneling + Vis billedindikatorer Netværkskrav Alle netværk Kontrolfrekvens @@ -694,6 +695,7 @@ Ofte stillede spørgsmål Hvis du har problemer med at bruge appen, bør du tjekke disse svar på almindelige spørgsmål! Se på webside + Vis Picasso-farvede bånd oven på billeder, der angiver deres kilde: rød for netværk, blå for disk og grøn for hukommelse Du kører den nyeste version af NewPipe Pga. ExoPlayer-begrænsninger blev søgevarigheden sat til %d sekunder Vis kun ikke-grupperede abonnementer diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 028ff86cac5..fcc38bf0bc0 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -637,6 +637,8 @@ Aus Als gesehen markieren Vom Ersteller mit Herz versehen + Farbige Picasso-Bänder über den Bildern anzeigen, die deren Quelle angeben: rot für Netzwerk, blau für Festplatte und grün für Speicher + Bildindikatoren anzeigen Entfernte Suchvorschläge Lokale Suchvorschläge diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 58a70c270b6..6e9525459ae 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -634,6 +634,8 @@ Προεπισκόπηση στην μπάρα αναζήτησης Σήμανση ως αναπαραχθέν Επισημάνθηκε από τον δημιουργό + Εμφάνιση χρωματιστής κορδέλας πάνω στις εικόνες, που δείχνει την πηγή τους: κόκκινη για δίκτυο, μπλε για δίσκο και πράσινο για μνήμη + Εμφάνιση δεικτών εικόνων Προτάσεις απομακρυσμένης αναζήτησης Προτάσεις τοπικής αναζήτησης diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a44c7293785..c7b780a1f05 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -647,6 +647,8 @@ Los comentarios están deshabilitados De corazón por el creador Marcar como visto + Mostrar cintas de colores Picasso encima de las imágenes indicando su origen: rojo para la red, azul para el disco y verde para la memoria + Mostrar indicadores de imagen Sugerencias de búsqueda remota Sugerencias de búsqueda local diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0e6e082c993..1d3fcdcb8a8 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -634,6 +634,8 @@ \n \nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave. Märgi vaadatuks + Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu + Näita piltide allikat Kaugotsingu soovitused Kohaliku otsingu soovitused Üksuse eemaldamiseks viipa diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 076b50d10a0..6f94a8f625d 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -641,6 +641,8 @@ Deskarga amaituta %s Deskarga amaituta + Irudien gainean Picasso koloretako zintak erakutsi, jatorria adieraziz: gorria sarerako, urdina diskorako eta berdea memoriarako + Erakutsi irudi-adierazleak Urruneko bilaketaren iradokizunak Tokiko bilaketa-iradokizunak Ikusi gisa markatu diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 02ac369d744..2afeaf286a7 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -597,6 +597,7 @@ رنگی کردن آگاهی گشودن با نشانه به عنوان دیده شده + نمایش روبان‌های رنگی پیکاسو در بالای تصویرها کهنشانگر منبعشان است: قرمز برای شبکه ، آبی برای دیسک و سبز برای حافظه درخواست از اندروید برای سفارشی‌سازی رنگ آگاهی براساس رنگ اصلی در بندانگشتی (توجّه داشته باشید که روی همهٔ افزاره‌ها در دسترس نیست) این ویدیو محدود به سن است. \n @@ -634,6 +635,7 @@ قلب‌شده به دست ایجادگر پیشنهادهای جست‌وجوی محلّی پیشنهادهای جست‌وجوی دوردست + نمایش نشانگرهای تصویر بارگیری پایان یافت %s بارگیری پایان یافتند diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index d72402e770d..67350d7ba2b 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -634,6 +634,8 @@ Latauskansiota ei vielä asetettu, valitse ensin oletuslatauskansio Kommentit poistettu käytöstä Merkitse katsotuksi + Näytä Picasso-värjätyt nauhat kuvien päällä osoittaakseen lähteen: punainen tarkoittaa verkkoa, sininen tarkoittaa levytilaa ja vihreä tarkoittaa muistia + Näytä kuvailmaisimet Etähakuehdotukset Paikalliset hakuehdotukset Lisää seuraavaksi diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f6a3d62850f..66418e7cfea 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -646,8 +646,10 @@ Prévisualisation de la barre de progression sur la miniature Marquer comme visionné Apprécié par le créateur + Afficher les indicateurs d’image Suggestions de recherche distante Suggestions de recherche locale + Affiche les rubans colorés de Picasso au-dessus des images indiquant leur source : rouge pour le réseau, bleu pour le disque et vert pour la mémoire %1$s téléchargement supprimé %1$s téléchargements supprimés diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 4b087a88200..efe1b7f5ea3 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -627,6 +627,7 @@ %s descargas finalizadas Miniatura na barra de busca + Mostrar indicadores de imaxe Desactive o túnel multimedia se experimentar unha pantalla en negro ou interrupcións na reprodución. Desactivar túnel multimedia Engadido á cola @@ -663,6 +664,7 @@ A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado Procesando... Pode devagar un momento Crear unha notificación de erro + Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria Novos elementos Predefinido do ExoPlayer Amosar \"Travar o reprodutor\" diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 1da62fc757c..befde2d5316 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -654,6 +654,8 @@ תמונה מוקטנת בסרגל הנגינה סומן בלב על ידי היוצר סימון כנצפה + הצגת סרטים בסגנון פיקאסו בראש התמונות לציון המקור שלהם: אדום זה מהרשת, כחול מהכונן וירוק מהזיכרון + הצגת מחווני תמונות הצעות חיפוש מרוחקות הצעות חיפוש מקומיות diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 6e185156d74..51455fafbbd 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -570,6 +570,7 @@ कतारबद्ध हुआ स्ट्रीम विवरण लोड हो रहे हैं… प्रोसेस हो रहा है… कुछ समय लग सकता है + छवि संकेतक दिखाएं प्लेयर का उपयोग करते समय क्रैश विकल्प दिखाता है नई स्ट्रीमों के लिए जांच चलाएं एक त्रुटि स्नैकबार दिखाएं @@ -667,6 +668,7 @@ लीक-कैनरी उपलब्ध नहीं है एक त्रुटी हुई है, नोटीफिकेशन देखें यदि वीडियो प्लेबैक पर आप काली स्क्रीन या रुक-रुक कर वीडियो चलने का अनुभव करते हैं तो मीडिया टनलिंग को अक्षम करें। + छवियों के शीर्ष पर पिकासो रंगीन रिबन दिखाएँ जो उनके स्रोत को दर्शाता है: नेटवर्क के लिए लाल, डिस्क के लिए नीला और मेमोरी के लिए हरा त्रुटी की नोटीफिकेशन बनाएं इस डाउनलोड को पुनर्प्राप्त नहीं किया जा सकता अभी तक कोई डाउनलोड फ़ोल्डर सेट नहीं किया गया है, अब डिफ़ॉल्ट डाउनलोड फ़ोल्डर चुनें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 3a945bba0ee..419f4619ed6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -646,6 +646,7 @@ %s pruža ovaj razlog: Obrada u tijeku … Može malo potrajati Za ukljanjanje stavki povuci ih + Prikaži indikatore slike Preuzimanje je gotovo %s preuzimanja su gotova @@ -654,6 +655,7 @@ Pokreni glavni player u cjeloekranskom prikazu Dodaj u popis kao sljedeći Dodano u popis kao sljedeći + Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju Izbrisano %1$s preuzimanje Izbrisana %1$s preuzimanja diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index f867041485c..5a402c94c2e 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -570,6 +570,7 @@ Az eltávolítás utáni, fragment vagy activity életcikluson kívüli, nem kézbesíthető Rx kivételek jelentésének kényszerítése Eredeti „ennyi ideje” megjelenítése az elemeken Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor. + Picasso színes szalagok megjelenítése a képek fölött, megjelölve a forrásukat: piros a hálózathoz, kék a lemezhez, zöld a memóriához Minden letöltésnél meg fogja kérdezni, hogy hova mentse el Válasszon egy példányt Lista legutóbbi frissítése: %s @@ -656,6 +657,7 @@ \nBiztos benne\? Ez nem vonható vissza! A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken Lejátszó összeomlasztása + Képjelölők megjelenítése A „lejátszó összeomlasztása” lehetőség megjelenítése Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor Hangmagasság megtartása (torzítást okozhat) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 9a6472692f2..71900400eb3 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -631,11 +631,13 @@ Disukai oleh kreator Saran pencarian lokal Saran pencarian remote + Tampilkan indikator gambar Menghapus %1$s unduhan tambahkan ke selanjutnya telah ditambahkan ke selanjutnya + Tampilkan Ribon bewarna Picasso di atas gambar yang mengindikasikan asalnya: merah untuk jaringan, biru untuk disk dan hijau untuk memori Jangan memulai memutar video di mini player, tapi nyalakan langsung di mode layar penuh, jika rotasi otomatis terkunci. Anda tetap dapat mengakses mini player dengan keluar dari layar penuh Memproses… Mungkin butuh waktu sebentar Periksa Pembaruan diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index d921cfac0e0..ed5ebe99bdb 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -657,6 +657,8 @@ Upprunalegir textar frá þjónustu verða sýnilegir í atriðum Slökkva á fjölmiðlagöngum Slökktu á fjölmiðlagöngum ef þú finnur fyrir svörtum skjá eða stami við spilun myndbandar + Sýna myndvísa + Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni Sýna „Hrynja spilara“ Sýna valkost til að hrynja spilara Hrynja forrit diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9d1c4bc0b6e..2a5ac16d39e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -644,6 +644,8 @@ Commenti disattivati Apprezzato dall\'autore Segna come visto + Mostra gli indicatori colorati Picasso sopra le immagini, per indicare la loro fonte: rosso per la rete, blu per il disco e verde per la memoria + Mostra indicatori immagine Suggerimenti di ricerca remoti Suggerimenti di ricerca locali diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7e7cf83a236..74924125a2a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -634,6 +634,8 @@ %s つのダウンロードが完了しました + ピカソは、画像の上に、画像の出所を識別する色彩記章を表示します: 赤はネットワーク、青はディスク、緑はメモリ + 画像に標識を表示 処理中… 少し時間がかかるかもしれません 新しいバージョンを手動で確認します アップデートを確認中… diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 4344c245d2c..ecb2a8495b7 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -384,6 +384,7 @@ ორიგინალური ტექსტები სერვისებიდან ხილული იქნება ნაკადის ერთეულებში მედია გვირაბის გათიშვა გამორთეთ მედია გვირაბი, თუ ვიდეოს დაკვრისას შავი ეკრანი ან ჭუჭყი გაქვთ + გამოსახულების ინდიკატორების ჩვენება აჩვენე \"დამკვრელის დამსხვრევა\" აჩვენებს ავარიის ვარიანტს დამკვრელის გამოყენებისას გაუშვით შემოწმება ახალი ნაკადებისთვის @@ -682,6 +683,7 @@ გამოწერების იმპორტი ვერ მოხერხდა შეატყობინეთ სასიცოცხლო ციკლის შეცდომებს იძულებითი მოხსენება შეუსაბამო Rx გამონაკლისების შესახებ ფრაგმენტის ან აქტივობის სასიცოცხლო ციკლის გარეთ განკარგვის შემდეგ + აჩვენეთ პიკასოს ფერადი ლენტები სურათების თავზე, სადაც მითითებულია მათი წყარო: წითელი ქსელისთვის, ლურჯი დისკისთვის და მწვანე მეხსიერებისთვის სწრაფი კვების რეჟიმი ამაზე მეტ ინფორმაციას არ იძლევა. „%s“-ის არხის ჩატვირთვა ვერ მოხერხდა. ხელმისაწვდომია ზოგიერთ სერვისში, როგორც წესი, ბევრად უფრო სწრაფია, მაგრამ შეიძლება დააბრუნოს შეზღუდული რაოდენობის ელემენტი და ხშირად არასრული ინფორმაცია (მაგ. ხანგრძლივობის გარეშე, ელემენტის ტიპი, არ არის ლაივის სტატუსი) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 5d5dfa0239e..939d97441ba 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -545,6 +545,8 @@ 미디어 터널링 비활성화 서비스의 원본 텍스트가 스트림 항목에 표시됩니다 동영상 재생 시 검은 화면이 나타나거나 끊김 현상이 발생하면 미디어 터널링을 비활성화하세요. + 이미지 표시기 표시 + 원본을 나타내는 이미지 위에 피카소 컬러 리본 표시: 네트워크는 빨간색, 디스크는 파란색, 메모리는 녹색 \"플레이어 충돌\" 표시 플레이어를 사용할 때 충돌 옵션을 표시합니다 새로운 스트림 확인 실행 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 2294a357dfe..c68e49bdd62 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -644,6 +644,8 @@ Nerodyti Širdelė nuo kurėjo Pažymėti kaip peržiūrėtą + Rodyti „Picasso“ spalvotas juosteles ant vaizdų, nurodančių jų šaltinį: raudona tinklui, mėlyna diskui ir žalia atmintis + Rodyti vaizdo indikatorius Nuotolinės paieškos pasiūlymai Vietinės paieškos pasiūlymai diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index a28d3e6073f..58b9a9d7623 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -625,10 +625,12 @@ Nesākt video atskaņošanu samazinātā režīmā, bet pilnekrāna režīmā, ja automātiskā rotācija ir izslēgta Izslēgt multivides tuneļošanu Izslēdziet multivides tuneļošanu, ja jums video atskaņošanas laikā parādās melns ekrāns vai aizķeršanās + Rādīt krāsainas lentes virs attēliem, norādot to avotu: sarkana - tīkls, zila - disks, zaļa - atmiņa Ieslēgt teksta atlasīšanu video aprakstā Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi Pārvelciet objektus, lai tos noņemtu Lokālie meklēšanas ieteikumi + Rādīt attēlu indikatorus Augstas kvalitātes (lielāks) Pārbaudīt atjauninājumus Manuāli pārbaudīt, vai ir atjauninājumi diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index ee4b88a41d3..c439593f7c8 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -595,6 +595,7 @@ രണ്ടാം പ്രവർത്തന ബട്ടൺ ആദ്യ പ്രവർത്തന ബട്ടൺ വീഡിയോ കാണുമ്പോൾ കറുത്ത സ്ക്രീൻ, അവ്യക്തത അനുഭവിക്കുന്നു എങ്കിൽ മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക + ഉറവിടം തിരിച്ചറിയാൻ പിക്കാസോ കളർഡ് റിബൺ ചിത്രങ്ങളുടെ മുകളിൽ കാണിക്കുക: നെറ്റ്‌വർക്കിന് ചുവപ്പ്, ഡിസ്കിനു നീല, മെമ്മറിയിക്ക് പച്ച സീക്ബാർ ചെറുചിത്രം പ്രദർശനം സ്നേഹത്തോടെ സൃഷ്ടാവ് ഡിസ്ക്രിപ്ഷനിലെ ടെക്സ്റ്റ്‌ സെലക്ട്‌ ചെയ്യുവാൻ അനുവദിക്കാതെ ഇരിക്കുക @@ -629,6 +630,7 @@ കാണിക്കരുത് കുറഞ്ഞ നിലവാരം (ചെറുത് ) ഉയർന്ന നിലവാരം (വലിയത് ) + ഇമേജ് ഇൻഡിക്കേറ്ററുകൾ കാണിക്കുക മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക ഡൌൺലോഡ് ഫോൾഡർ ഇത് വരെയും സെറ്റ് ചെയ്തിട്ടില്ല, സ്ഥിര ഡൌൺലോഡ് ഫോൾഡർ ഇപ്പോൾ തിരഞ്ഞെക്കുക അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമായിരിക്കുന്നു diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 7acf7f4cd78..416ebfd025b 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -644,7 +644,9 @@ Lokale søkeforslag Marker som sett Ikke start videoer i minispilleren, men bytt til fullskjermsmodus direkte dersom auto-rotering er låst. Du har fremdeles tilgang til minispilleren ved å avslutte fullskjermsvisningen + Vis Picasso-fargede bånd på toppen av bilder for å indikere kilde: Rød for nettverk, blå for disk, og grønn for minne Hjertemerket av skaperen + Vis bildeindikatorer Dra elementer for å fjerne dem Start hovedspiller i fullskjerm Still i kø neste diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml index c744de62c25..637eb1751e6 100644 --- a/app/src/main/res/values-nl-rBE/strings.xml +++ b/app/src/main/res/values-nl-rBE/strings.xml @@ -611,6 +611,7 @@ Geen download map ingesteld, kies nu de standaard download map Niet tonen Reacties zijn uitgeschakeld + Toon afbeeldingsindicatoren Speler melding Configureer actieve stream melding Meldingen diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 26e328ed4df..b4629a03f62 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -632,6 +632,8 @@ Lage kwaliteit (kleiner) Hoge kwaliteit (groter) Zoekbalk miniatuurafbeelding voorbeeld + Toon Picasso-gekleurde linten bovenop afbeeldingen die hun bron aangeven: rood voor netwerk, blauw voor schijf en groen voor geheugen + Afbeeldings­indicatoren tonen Reacties zijn uitgeschakeld Zoeksuggesties op afstand Lokale zoeksuggesties diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml index f1960b980aa..94e7ae2f49b 100644 --- a/app/src/main/res/values-nqo/strings.xml +++ b/app/src/main/res/values-nqo/strings.xml @@ -440,6 +440,8 @@ ߗߋߢߊߟߌ ߟߎ߬ ߞߟߏߜߍ߫ ߓߐߛߎ߲ߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߕߐ߫ ߟߋ߬ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ ߘߐ߫ ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߣߴߌ ߞߊ߬ ߥߊ߲߬ߊߥߊ߲߬ ߝߌ߲ ߦߋ߫ ߥߟߊ߫ ߜߊߘߊ߲ߜߊߘߊ߲ߠߌ߲ ߦߋߡߍ߲ߕߊ ߘߏ߫ ߘߐߛߊߙߌ߫ ߕߎߡߊ + ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ߫ ߦߌ߬ߘߊ߬ߟߊ߲ ߠߎ߫ ߝߍ߲߬ߛߍ߲߫ + ߏ߬ ߦߋ߫ ߔߌߛߊߞߏ߫ ߟߊ߫ ߡߙߎߝߋ߫ ߞߟߐ߬ߡߊ ߟߎ߫ ߟߋ߬ ߝߍ߲߬ߛߍ߲߬ ߠߊ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߎ߫ ߞߎ߲߬ߘߐ߫ ߞߵߊ߬ߟߎ߬ ߓߐߛߎ߲ ߦߌ߬ߘߊ߬: ߥߎߟߋ߲߬ߡߊ߲ ߦߋ߫ ߞߙߏ߬ߝߏ ߕߊ ߘߌ߫߸ ߓߊ߯ߡߊ ߦߋ߫ ߝߘߍ߬ ߜߍߟߍ߲ ߕߊ ߘߌ߫ ߊ߬ߣߌ߫ ߝߙߌߛߌߡߊ ߦߋ߫ ߦߟߌߕߏߟߊ߲ ߕߊ ߘߌ߫ ߟߊ߬ߓߐ߬ߟߌ ߦߴߌߘߐ߫… ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬ ߞߊ߲߬ߞߎߡߊ ߟߎ߬ ߟߊߛߊ߬ߣߍ߲ ߠߋ߬ diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 59a6a739abe..f9faa8324db 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -342,6 +342,7 @@ ସେବାଗୁଡିକରୁ ମୂଳ ଲେଖା ଷ୍ଟ୍ରିମ୍ ଆଇଟମ୍ ଗୁଡିକରେ ଦୃଶ୍ୟମାନ ହେବ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ ଯଦି ଆପଣ ଏକ କଳା ପରଦା ଅନୁଭବ କରନ୍ତି କିମ୍ବା ଭିଡିଓ ପ୍ଲେବେକ୍ ଉପରେ ଝୁଣ୍ଟି ପଡ଼ନ୍ତି ତେବେ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ । + ଚିତ୍ରଗୁଡ଼ିକର ଉପରେ ପିକାସୋ ରଙ୍ଗୀନ ଫିତା ଦେଖାନ୍ତୁ: ସେମାନଙ୍କର ଉତ୍ସକୁ ସୂଚାଇଥାଏ: ନେଟୱାର୍କ ପାଇଁ ନାଲି, ଡିସ୍କ ପାଇଁ ନୀଳ ଏବଂ ସ୍ମୃତି ପାଇଁ ସବୁଜ ଆମଦାନି କରନ୍ତୁ ଠାରୁ ଆମଦାନୀ କରନ୍ତୁ ଆମଦାନି… @@ -656,6 +657,7 @@ ଅଟୋ-ଜେନେରେଟ୍ (କୌଣସି ଅପଲୋଡର୍ ମିଳିଲା ନାହିଁ) ପୁରଣ କରନ୍ତୁ କ୍ୟାପସନ୍ + ପ୍ରତିଛବି ସୂଚକ ଦେଖାନ୍ତୁ ପ୍ଲେୟାର ବ୍ୟବହାର କରିବା ସମୟରେ ଏକ କ୍ରାସ୍ ବିକଳ୍ପ ଦେଖାଏ ନୂତନ ଷ୍ଟ୍ରିମ୍ ପାଇଁ ଯାଞ୍ଚ ଚଲାନ୍ତୁ ଆପ୍ କ୍ରାସ୍ କରନ୍ତୁ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index d612ff75b17..814cdb8851d 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -626,6 +626,8 @@ ਨਿਊਪਾਈਪ ਖਾਮੀ ਤੋਂ ਪ੍ਰਭਾਵਤ ਹੋਈ ਹੈ, ਇੱਥੇ ਨੱਪ ਕੇ ਰਿਪੋਰਟ ਕਰੋ ਇੱਕ ਖਾਮੀ ਪ੍ਰਭਾਵੀ ਹੋਈ ਹੈ, ਨੋਟੀਫੀਕੇਸ਼ਨ ਵੇਖੋ ਆਈਟਮਾਂ ਨੂੰ ਇੱਕ ਪਾਸੇ ਖਿੱਚ ਕੇ ਹਟਾਓ + ਦ੍ਰਿਸ਼ ਸੂਚਕ ਵਿਖਾਓ + ਦ੍ਰਿਸ਼ਾਂ ਦੇ ਉੱਪਰ ਉਹਨਾਂ ਦੀ ਸਰੋਤ-ਪਛਾਣ ਲਈ ਪਿਕਾਸੋ ਦੇ ਰੰਗਦਾਰ ਰਿਬਨ ਵਿਖਾਓ : ਨੈੱਟਵਰਕ ਲਈ ਲਾਲ, ਡਿਸਕ ਲਈ ਨੀਲੇ ਤੇ ਮੈਮਰੀ ਲਈ ਹਰੇ ਨਵੀਂ ਸਟ੍ਰੀਮ ਦੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਪਿੰਨ ਕੀਤੀ ਟਿੱਪਣੀ ਅੱਪਡੇਟ ਦੀ ਉਪਲੱਬਧਤਾ ਪਰਖੀ ਜਾ ਰਹੀ… diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b7c517ffc86..01f242cd9c7 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -649,6 +649,8 @@ Nie pokazuj Serduszko od twórcy Oznacz jako obejrzane + Pokazuj kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci + Pokazuj wskaźniki obrazu Zdalne podpowiedzi wyszukiwania Lokalne podpowiedzi wyszukiwania diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3d4b5762111..5a203e0f25e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -644,6 +644,7 @@ Os comentários estão desabilitados Marcar como assistido Curtido pelo criador + Exibir fitas coloridas no topo das imagens indicando sua fonte: vermelho para rede, azul para disco e verde para memória %1$s download excluído %1$s downloads excluídos @@ -654,6 +655,7 @@ %s downloads concluídos %s downloads concluídos + Mostrar indicadores de imagem Adicionado na próxima posição da fila Enfileira a próxima Deslize os itens para remove-los diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 23310adec64..498a49a53bb 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -644,6 +644,8 @@ Ainda não foi definida uma pasta de descarregamento, escolha agora a pasta de descarregamento padrão Comentários estão desativados Marcar como visto + Mostrar fitas coloridas de Picasso em cima das imagens que indicam a sua fonte: vermelho para rede, azul para disco e verde para memória + Mostrar indicadores de imagem Sugestões de pesquisa remotas Sugestões de pesquisa locais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 99e5cbd040c..5534dbf0b54 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -644,6 +644,8 @@ Baixa qualidade (menor) Alta qualidade (maior) Os comentários estão desativados + Mostrar fitas coloridas de Picasso em cima das imagens que indicam a sua fonte: vermelho para rede, azul para disco e verde para memória + Mostrar indicadores de imagem Sugestões de pesquisa remotas Sugestões de pesquisa locais diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index a5eb2114328..bcef4d9524f 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -660,6 +660,8 @@ Calitate scăzută (mai mică) Calitate înaltă (mai mare) Miniatură de previzualizare în bara de derulare + Afișați panglici colorate de Picasso deasupra imaginilor, indicând sursa acestora: roșu pentru rețea, albastru pentru disc și verde pentru memorie + Afișați indicatorii de imagine Dezactivați tunelarea media dacă întâmpinați un ecran negru sau blocaje la redarea video. Procesarea.. Poate dura un moment Verifică dacă există actualizări diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 667e5413dd2..9e79b165fc8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -652,6 +652,8 @@ Миниатюра над полосой прокрутки Автору видео понравилось это Пометить проигранным + Picasso: указать цветом источник изображений (красный — сеть, синий — диск, зелёный — память) + Цветные метки на изображениях Серверные предложения поиска Локальные предложения поиска diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml index e970950e0a2..5a4f35de5f9 100644 --- a/app/src/main/res/values-ryu/strings.xml +++ b/app/src/main/res/values-ryu/strings.xml @@ -646,6 +646,8 @@ %sちぬダウンロードぬかんりょうさびたん %sちぬダウンロードぬかんりょうさびたん + ピカソー、がぞうぬういに、がぞうくとぅどぅくるしーきびちするしきさいきしーょうひょうじさびーん: あかーネットワーク、あおーディスク、みどぅれーメモリ + やしがぞうんかいふぃいょうしきひょうじ しーょりちゅう… くーてーんじがんがかかいんかむしりやびらん みーさるバージョンしーゅどうでぃかくにんさびーん アップデートかくにんちゅう… diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml index 9ede53a7691..a5959086e46 100644 --- a/app/src/main/res/values-sat/strings.xml +++ b/app/src/main/res/values-sat/strings.xml @@ -270,6 +270,7 @@ LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱡᱤᱭᱚᱱ ᱪᱤᱠᱤ ᱠᱷᱚᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱷᱮᱜᱟᱨ ᱠᱚ ᱚᱱᱚᱞ ᱢᱮ + ᱱᱮᱴᱣᱟᱨᱠ ᱞᱟᱹᱜᱤᱫ red, ᱰᱤᱥᱠ ᱞᱟᱹᱜᱤᱫ blue ᱟᱨ ᱢᱮᱢᱚᱨᱤ ᱞᱟᱹᱜᱤᱫ green ᱯᱞᱮᱭᱟᱨ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱮ ᱨᱮ ᱠᱨᱟᱥ ᱚᱯᱥᱚᱱ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ ᱤᱢᱯᱳᱨᱴ ᱤᱢᱯᱚᱨᱴ @@ -539,6 +540,7 @@ ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾ + ᱪᱤᱛᱟᱹᱨ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index fce5280e128..6b89b32c3c1 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -634,6 +634,8 @@ Sos cummentos sunt disabilitados Su creadore b\'at postu unu coro Marca comente pompiadu + Ammustra sos listrones colorados de Picasso in subra de sas immàgines chi indicant sa fonte issoro: ruja pro sa retze, biaita pro su discu e birde pro sa memòria + Ammustra sos indicadores de immàgines Impòsitos de chirca remota Impòsitos de chirca locales diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 5c4210b71a9..e3aa5b6bce3 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -653,6 +653,8 @@ Nízka kvalita (menšie) Vysoká kvalita (väčšie) Náhľad miniatúry pri vyhľadávaní + Zobrazí farebné pásiky Picasso na obrázkoch podľa ich zdroja: červený pre sieť, modrý pre disk a zelený pre pamäť + Zobraziť indikátory obrázka Potiahnutím vymazať Komentáre sú zakázané Ak je automatické otáčanie zablokované, nespustí videá v miniprehrávači, ale prepne sa do celoobrazovkového režimu. Do miniprehrávača sa dostanete po ukončení režimu celej obrazovky diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index 72e661a1b05..e37c3a36d31 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -633,6 +633,8 @@ Fallooyinka waa laxidhay Kahelay soosaaraha Waan daawaday + Soo bandhig shaambado midabka Picasso leh sawirrada dushooda oo tilmaamaya isha laga keenay: guduud waa khadka, buluug waa kaydka gudaha, cagaar waa kaydka K/G + Tus tilmaamayaasha sawirka Soojeedinada raadinta banaanka Soojeedinada raadinta gudaha Cabirka soodaarida udhexeeya diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 2300cd92979..5f14a0e51c2 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -644,6 +644,7 @@ Означи као одгледано Коментари су онемогућени Обрађивање… Може потрајати пар тренутака + Прикажи индикаторе слике Не покрећите видео снимке у мини-плејеру, већ директно пређите на режим целог екрана, ако је аутоматска ротација закључана. И даље можете приступити мини-плејеру тако што ћете изаћи из целог екрана Покрени главни плејер преко целог екрана Срушите плејер @@ -739,6 +740,7 @@ Обавештења за пријаву грешака Увезите или извезите праћења из менија са 3 тачке Аудио снимак + Прикажите Picasso обојене траке на врху слика које указују на њихов извор: црвена за мрежу, плава за диск и зелена за меморију Направите обавештење о грешци Проценат Користите најновију верзију NewPipe-а diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 50948fd571e..8cf57f6054f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -633,6 +633,7 @@ Du kan välja det natt-tema du föredrar nedan Välj det natt-tema du föredrar — %s Sökradens förhandsvisningsminiatyr + Visa bildindikatorer Lokala sökningsförslag Tog bort %1$s nedladdning @@ -650,6 +651,7 @@ Svep objekt för att ta bort dem Förslag via fjärrsökning Starta inte videor i minispelaren, utan byt till helskärmsläge direkt, om automatisk rotation är låst. Du kan fortfarande komma åt minispelaren genom att gå ut ur helskärmsläge + Visa Picasso färgade band ovanpå bilderna som anger deras källa: rött för nätverk, blått för disk och grönt för minne Sök efter uppdateringar Kolla manuellt efter nya versioner Söker efter uppdateringar… diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 90708580a1c..b2846823c94 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -442,6 +442,7 @@ ప్లేబ్యాక్ స్పీడ్ నియంత్రణలు ఏమిలేదు మీరు బ్లాక్ స్క్రీన్ లేదా చలనచిత్రం ప్లేబ్యాక్‌లో అంతరాయాన్ని అనుభవిస్తే మీడియా టన్నెలింగ్‌ను నిలిపివేయండి + చిత్రాల మూలాన్ని సూచించే విధంగా వాటి పైభాగంలో పికాసో రంగు రిబ్బన్‌లను చూపండి: నెట్‌వర్క్ కోసం ఎరుపు, డిస్క్ కోసం నీలం మరియు మెమరీ కోసం ఆకుపచ్చ లోపం స్నాక్‌బార్‌ని చూపండి మీరు NewPipe యొక్క తాజా సంస్కరణను అమలు చేస్తున్నారు NewPipe నవీకరణ అందుబాటులో ఉంది! @@ -454,6 +455,7 @@ తక్కువ నాణ్యత (చిన్నది) చూపించవద్దు మీడియా టన్నెలింగ్‌ని నిలిపివేయండి + చిత్ర సూచికలను చూపు కొత్త స్ట్రీమ్‌ల కోసం తనిఖీని అమలు చేయండి ఎర్రర్ నోటిఫికేషన్‌ను సృష్టించండి దిగుమతి diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 29933bdc673..adecbf3ffef 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -633,6 +633,8 @@ Yorumlar devre dışı Yaratıcısınca kalplendi İzlendi olarak işaretle + Resimlerin üzerinde kaynaklarını gösteren Picasso renkli şeritler göster: ağ için kırmızı, disk için mavi ve bellek için yeşil + Resim göstergelerini göster Uzak arama önerileri Yerel arama önerileri diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 86350d40e96..c347086093d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -646,6 +646,8 @@ Мініатюра з попереднім переглядом на повзунку поступу Вподобано автором Позначити переглянутим + Показувати кольорові стрічки Пікассо поверх зображень із зазначенням їх джерела: червоний для мережі, синій для диска та зелений для пам’яті + Показати індикатори зображень Віддалені пропозиції пошуку Локальні пошукові пропозиції diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 15f5a86d472..0d71173afbc 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -624,6 +624,8 @@ Bình luận đã bị tắt Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem + Hiển thị các dải băng màu Picasso trên đầu các hình ảnh cho biết nguồn của chúng: màu đỏ cho mạng, màu lam cho đĩa và màu lục cho bộ nhớ + Hiện dấu chỉ hình ảnh Đề xuất tìm kiếm trên mạng Đề xuất tìm kiếm cục bộ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7b303621b9c..fa8d31022cf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -624,6 +624,8 @@ 高品质(较大) 被创作者喜爱 标记为已观看 + 在图像顶部显示毕加索彩带,指示其来源:红色代表网络,蓝色代表磁盘,绿色代表内存 + 显示图像指示器 远程搜索建议 本地搜索建议 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 9f581b43f59..518a1290fc5 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -668,7 +668,9 @@ 你係咪要刪除呢個谷? 淨係顯示未成谷嘅訂閱 + 啲圖都要騷 Picasso 三色碼顯示源頭:紅碼係網絡上高落嚟,藍碼係儲存喺磁碟本地,綠碼係潛伏喺記憶體中 服務原本嘅字會騷返喺串流項目上面 + 影像要推三色碼 若果播片嘅時候窒下窒下或者黑畫面,就停用多媒體隧道啦。 點樣用 Google 匯出嚟匯入 YouTube 訂閱: \n diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a4ad3578f82..c404edeca2b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -624,6 +624,8 @@ 拖動列縮圖預覽 被創作者加心號 標記為已觀看 + 在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體 + 顯示圖片指示器 遠端搜尋建議 本機搜尋建議 diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index e31cebb92ac..fb68a464d5a 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -241,6 +241,7 @@ show_memory_leaks_key allow_disposed_exceptions_key show_original_time_ago_key + show_image_indicators_key show_crash_the_player_key check_new_streams crash_the_app_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bff35e5d9ea..56140441cd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -486,6 +486,8 @@ Disable media tunneling Disable media tunneling if you experience a black screen or stuttering on video playback. Media tunneling was disabled by default on your device because your device model is known to not support it. + Show image indicators + Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory Show \"Crash the player\" Shows a crash option when using the player Run check for new streams diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index d97c5aa1a2b..84bb281f31e 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -34,6 +34,13 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + Date: Mon, 5 Aug 2024 00:27:18 +0200 Subject: [PATCH 04/11] Added daggerhilt for better dependency injection for Compose Viewmodels --- app/build.gradle | 17 ++++++++++++++++- app/src/main/java/org/schabi/newpipe/App.java | 2 ++ build.gradle | 6 ++++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4652cf6e5ba..ec3493828a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,6 +9,7 @@ plugins { id "kotlin-parcelize" id "checkstyle" id "org.sonarqube" version "4.0.0.2929" + id "dagger.hilt.android.plugin" } android { @@ -122,6 +123,8 @@ ext { googleAutoServiceVersion = '1.1.1' groupieVersion = '2.10.1' markwonVersion = '4.6.2' + hiltVersion = '1.2.0' + daggerVersion = '2.51.1' leakCanaryVersion = '2.12' stethoVersion = '1.6.0' @@ -203,7 +206,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.0' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.2' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ @@ -295,6 +298,14 @@ dependencies { implementation 'androidx.activity:activity-compose' implementation 'androidx.compose.ui:ui-tooling-preview' + // hilt + implementation("androidx.hilt:hilt-navigation-compose:${hiltVersion}") + kapt("androidx.hilt:hilt-compiler:${hiltVersion}") + + // dagger + implementation("com.google.dagger:hilt-android:${daggerVersion}") + kapt("com.google.dagger:hilt-android-compiler:${daggerVersion}") + /** Debugging **/ // Memory leak detection debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}" @@ -315,6 +326,10 @@ dependencies { androidTestImplementation "androidx.test:runner:1.5.2" androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}" androidTestImplementation "org.assertj:assertj-core:3.24.2" + + // dagger + testImplementation("com.google.dagger:hilt-android-testing${daggerVersion}") + androidTestImplementation("com.google.dagger:hilt-android-testing${daggerVersion}") } static String getGitWorkingBranch() { diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index d92425d200e..35be89489a7 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Objects; +import dagger.hilt.android.HiltAndroidApp; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; @@ -57,6 +58,7 @@ * along with NewPipe. If not, see . */ +@HiltAndroidApp public class App extends Application { public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID; private static final String TAG = App.class.toString(); diff --git a/build.gradle b/build.gradle index 6d19a6f8a84..35c1b25da38 100644 --- a/build.gradle +++ b/build.gradle @@ -2,14 +2,15 @@ buildscript { ext.kotlin_version = '1.9.10' + ext.dagger_version ='2.51.1' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.0' + classpath 'com.android.tools.build:gradle:8.5.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - + classpath("com.google.dagger:hilt-android-gradle-plugin:$dagger_version") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -19,6 +20,7 @@ allprojects { repositories { google() mavenCentral() + gradlePluginPortal() maven { url "https://jitpack.io" } maven { url "https://repo.clojars.org" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d022615ff6d..efe1dac97da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From e1169dcc1f5e5468b73bfadc01f67f28700bdd5d Mon Sep 17 00:00:00 2001 From: brais Date: Mon, 5 Aug 2024 00:34:35 +0200 Subject: [PATCH 05/11] fixed inversed colors of surface for light theme --- app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt index b61906ebed6..28b3e65c437 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/Color.kt @@ -20,8 +20,8 @@ val md_theme_light_onError = Color(0xFFFFFFFF) val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_background = Color(0xFFEEEEEE) val md_theme_light_onBackground = Color(0xFF1B1B1B) -val md_theme_light_surface = Color(0xFFE53835) -val md_theme_light_onSurface = Color(0xFFFFFFFF) +val md_theme_light_surface = Color(0xFFFFFFFF) +val md_theme_light_onSurface = Color(0xFFE53835) val md_theme_light_surfaceVariant = Color(0xFFF5DDDB) val md_theme_light_onSurfaceVariant = Color(0xFF534341) val md_theme_light_outline = Color(0xFF857371) From 91af8b0b98a551e37709e191a448536669767322 Mon Sep 17 00:00:00 2001 From: brais Date: Mon, 5 Aug 2024 00:42:01 +0200 Subject: [PATCH 06/11] Added the new compose screen with its components and events --- .../java/org/schabi/newpipe/MainActivity.java | 3 + .../IrreversiblePreference.kt | 86 ++++++++ .../switch_preference/SwitchPreference.kt | 74 +++++++ .../history_cache/HistoryCacheEvent.kt | 12 ++ .../HistoryCacheSettingsScreen.kt | 102 +++++++++ .../history_cache/HistoryCacheUiState.kt | 12 ++ .../components/CachePreferences.kt | 204 ++++++++++++++++++ .../components/HistoryPreferences.kt | 116 ++++++++++ 8 files changed, 609 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 17569412572..a74d30c74de 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -98,6 +98,9 @@ import java.util.List; import java.util.Objects; +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @SuppressWarnings("ConstantConditions") diff --git a/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt b/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt new file mode 100644 index 00000000000..b66e4eb5fc6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt @@ -0,0 +1,86 @@ +package org.schabi.newpipe.settings.components.irreversible_preference + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.SizeTokens.SpacingExtraSmall +import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium + +@Composable +fun IrreversiblePreferenceComponent( + title: String, + summary: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + Row( + modifier = Modifier + .clickable { + if (enabled) { + onClick() + } + } + .then(modifier), + verticalAlignment = Alignment.CenterVertically, + ) { + val alpha by remember { + derivedStateOf { + if (enabled) 1f else 0.38f + } + } + Column( + modifier = Modifier.padding(SpacingMedium) + ) { + Text( + text = title, + modifier = Modifier.alpha(alpha), + ) + Spacer(modifier = Modifier.padding(SpacingExtraSmall)) + Text( + text = summary, + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.alpha(alpha * 0.6f), + ) + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun IrreversiblePreferenceComponentPreview() { + val title = "Wipe cached metadata" + val summary = "Remove all cached webpage data" + AppTheme { + Column { + + IrreversiblePreferenceComponent( + title = title, + summary = summary, + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + IrreversiblePreferenceComponent( + title = title, + summary = summary, + onClick = {}, + modifier = Modifier.fillMaxWidth(), + enabled = false + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt b/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt new file mode 100644 index 00000000000..29015badfae --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt @@ -0,0 +1,74 @@ +package org.schabi.newpipe.settings.components.switch_preference + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.SizeTokens.SpacingExtraSmall +import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium + +@Composable +fun SwitchPreferenceComponent( + title: String, + summary: String, + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + modifier = Modifier.padding(SpacingMedium) + ) { + Text(text = title) + Spacer(modifier = Modifier.padding(SpacingExtraSmall)) + Text( + text = summary, + style = MaterialTheme.typography.labelSmall, + modifier = Modifier.alpha(0.6f) + ) + } + + Switch( + checked = isChecked, + onCheckedChange = onCheckedChange, + modifier = Modifier.padding(SpacingMedium) + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun SwitchPreferenceComponentPreview() { + val title = "Watch history" + val subtitle = "Keep track of watched videos" + var isChecked = false + AppTheme { + SwitchPreferenceComponent( + title = title, + summary = subtitle, + isChecked = isChecked, + onCheckedChange = { + isChecked = it + }, + modifier = Modifier.fillMaxWidth() + ) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt new file mode 100644 index 00000000000..f899803fa38 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt @@ -0,0 +1,12 @@ +package org.schabi.newpipe.settings.presentation.history_cache + +sealed class HistoryCacheEvent { + data class OnUpdateBooleanPreference(val key: String, val isEnabled: Boolean) : + HistoryCacheEvent() + + data class OnClickWipeCachedMetadata(val key: String) : HistoryCacheEvent() + data class OnClickClearWatchHistory(val key: String) : HistoryCacheEvent() + data class OnClickDeletePlaybackPositions(val key: String) : HistoryCacheEvent() + data class OnClickClearSearchHistory(val key: String) : HistoryCacheEvent() + data class OnClickReCaptchaCookies(val key: String) : HistoryCacheEvent() +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt new file mode 100644 index 00000000000..9fc87fbec2f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt @@ -0,0 +1,102 @@ +package org.schabi.newpipe.settings.presentation.history_cache + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import kotlinx.coroutines.launch +import org.schabi.newpipe.settings.presentation.history_cache.components.CachePreferencesComponent +import org.schabi.newpipe.settings.presentation.history_cache.components.HistoryPreferencesComponent +import org.schabi.newpipe.ui.theme.AppTheme + +@Composable +fun HistoryCacheScreen( + modifier: Modifier = Modifier, + viewModel: HistoryCacheSettingsViewModel = hiltViewModel(), +) { + val state by viewModel.state.collectAsState() + HistoryCacheComponent( + state = state, + onEvent = viewModel::onEvent, + modifier = modifier + ) +} + +@Composable +fun HistoryCacheComponent( + state: HistoryCacheUiState, + onEvent: (HistoryCacheEvent) -> Unit, + modifier: Modifier = Modifier, +) { + val snackBarHostState = remember { SnackbarHostState() } + Scaffold( + modifier = modifier, + snackbarHost = { + SnackbarHost(snackBarHostState) + } + ) { padding -> + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .padding(padding) + .verticalScroll(scrollState), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + HistoryPreferencesComponent( + state = state.switchPreferencesUiState, + onEvent = onEvent, + modifier = Modifier.fillMaxWidth() + ) + val coroutineScope = rememberCoroutineScope() + CachePreferencesComponent( + onEvent = onEvent, + onShowSnackbar = { + coroutineScope.launch { + snackBarHostState.showSnackbar(it) + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun HistoryCacheComponentPreview() { + val state by remember { + mutableStateOf( + HistoryCacheUiState() + ) + } + AppTheme( + useDarkTheme = false + ) { + Surface { + HistoryCacheComponent( + state = state, + onEvent = { + }, + modifier = Modifier.fillMaxSize() + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt new file mode 100644 index 00000000000..1d346244eeb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt @@ -0,0 +1,12 @@ +package org.schabi.newpipe.settings.presentation.history_cache + +data class HistoryCacheUiState( + val switchPreferencesUiState: SwitchPreferencesUiState = SwitchPreferencesUiState() +) + +data class SwitchPreferencesUiState( + val watchHistoryEnabled: Boolean = false, + val resumePlaybackEnabled: Boolean = false, + val positionsInListsEnabled: Boolean = false, + val searchHistoryEnabled: Boolean = false, +) diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt new file mode 100644 index 00000000000..6657b78272a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt @@ -0,0 +1,204 @@ +package org.schabi.newpipe.settings.presentation.history_cache.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.R +import org.schabi.newpipe.settings.components.irreversible_preference.IrreversiblePreferenceComponent +import org.schabi.newpipe.settings.presentation.history_cache.HistoryCacheEvent +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium + +@Composable +fun CachePreferencesComponent( + onEvent: (HistoryCacheEvent) -> Unit, + onShowSnackbar: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val preferences = rememberPreferences() + var dialogTitle by remember { mutableStateOf("") } + var dialogOnClick by remember { mutableStateOf({}) } + var isDialogVisible by remember { mutableStateOf(false) } + + Column( + modifier = modifier, + ) { + Text( + stringResource(id = R.string.settings_category_clear_data_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(SpacingMedium) + ) + preferences.forEach { + val elementDialogTitle = it.dialogTitle?.let { dialogTitle -> + stringResource(dialogTitle) + } + val elementSnackBarText = stringResource(it.snackBarText) + val key = stringResource(id = it.keyId) + val onClick = { + if (elementDialogTitle != null) { + dialogTitle = elementDialogTitle + isDialogVisible = true + dialogOnClick = { + onEvent(it.event(key)) + isDialogVisible = false + onShowSnackbar(elementSnackBarText) + } + } else { + onEvent(it.event(key)) + onShowSnackbar(elementSnackBarText) + } + } + IrreversiblePreferenceComponent( + title = stringResource(id = it.title), + summary = stringResource(it.summary), + onClick = onClick, + modifier = Modifier.fillMaxWidth() + ) + } + + CacheAlertDialog( + isDialogVisible = isDialogVisible, + dialogTitle = dialogTitle, + onClickCancel = { isDialogVisible = false }, + onClick = dialogOnClick + ) + } +} + +@Composable +private fun rememberPreferences(): List { + val preferences by remember { + mutableStateOf( + listOf( + IrreversiblePreferenceUiState( + title = R.string.metadata_cache_wipe_title, + summary = R.string.metadata_cache_wipe_summary, + dialogTitle = null, + snackBarText = R.string.metadata_cache_wipe_complete_notice, + event = { HistoryCacheEvent.OnClickWipeCachedMetadata(it) }, + keyId = R.string.metadata_cache_wipe_key, + ), + IrreversiblePreferenceUiState( + title = R.string.clear_views_history_title, + summary = R.string.clear_views_history_summary, + dialogTitle = R.string.delete_view_history_alert, + snackBarText = R.string.watch_history_deleted, + event = { HistoryCacheEvent.OnClickClearWatchHistory(it) }, + keyId = R.string.clear_views_history_key, + ), + IrreversiblePreferenceUiState( + title = R.string.clear_playback_states_title, + summary = R.string.clear_playback_states_summary, + dialogTitle = R.string.delete_playback_states_alert, + snackBarText = R.string.watch_history_states_deleted, + event = { HistoryCacheEvent.OnClickDeletePlaybackPositions(it) }, + keyId = R.string.clear_playback_states_key, + ), + IrreversiblePreferenceUiState( + title = R.string.clear_search_history_title, + summary = R.string.clear_search_history_summary, + dialogTitle = R.string.delete_search_history_alert, + snackBarText = R.string.search_history_deleted, + event = { HistoryCacheEvent.OnClickClearSearchHistory(it) }, + keyId = R.string.clear_search_history_key, + ), + IrreversiblePreferenceUiState( + title = R.string.clear_cookie_title, + summary = R.string.clear_cookie_summary, + dialogTitle = null, + snackBarText = R.string.recaptcha_cookies_cleared, + event = { HistoryCacheEvent.OnClickReCaptchaCookies(it) }, + keyId = R.string.recaptcha_cookies_key, + ) + ) + ) + } + return preferences +} + +private data class IrreversiblePreferenceUiState( + val title: Int, + val summary: Int, + val dialogTitle: Int?, + val snackBarText: Int, + val enabled: Boolean = true, + val event: (String) -> HistoryCacheEvent, + val keyId: Int, +) + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +private fun CachePreferencesComponentPreview() { + AppTheme { + Scaffold { padding -> + CachePreferencesComponent( + onEvent = {}, + onShowSnackbar = {}, + modifier = Modifier + .fillMaxWidth() + .padding(padding) + ) + } + } +} + +@Composable +private fun CacheAlertDialog( + isDialogVisible: Boolean, + dialogTitle: String, + onClickCancel: () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + if (isDialogVisible) { + AlertDialog( + onDismissRequest = onClickCancel, + confirmButton = { + TextButton(onClick = onClick) { + Text(text = "Delete") + } + }, + dismissButton = { + TextButton(onClick = onClickCancel) { + Text(text = "Cancel") + } + }, + title = { + Text(text = dialogTitle) + }, + text = { + Text(text = "This is an irreversible action") + }, + modifier = modifier + ) + } +} + +@Preview(backgroundColor = 0xFFFFFFFF) +@Composable +private fun CacheAlertDialogPreview() { + AppTheme { + Scaffold { padding -> + CacheAlertDialog( + isDialogVisible = true, + dialogTitle = "Delete view history", + onClickCancel = {}, + onClick = {}, + modifier = Modifier.padding(padding) + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt new file mode 100644 index 00000000000..f929e387ab7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt @@ -0,0 +1,116 @@ +package org.schabi.newpipe.settings.presentation.history_cache.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.R +import org.schabi.newpipe.settings.components.switch_preference.SwitchPreferenceComponent +import org.schabi.newpipe.settings.presentation.history_cache.HistoryCacheEvent +import org.schabi.newpipe.settings.presentation.history_cache.SwitchPreferencesUiState +import org.schabi.newpipe.ui.theme.AppTheme + +@Composable +fun HistoryPreferencesComponent( + state: SwitchPreferencesUiState, + onEvent: (HistoryCacheEvent) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + val preferences = rememberSwitchPreferencesUiState(state) + preferences.forEach { preference -> + val key = stringResource(preference.keyId) + SwitchPreferenceComponent( + title = stringResource(id = preference.titleId), + summary = stringResource(id = preference.summaryId), + isChecked = preference.isEnabled, + onCheckedChange = { + onEvent(HistoryCacheEvent.OnUpdateBooleanPreference(key, it)) + }, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Composable +private fun rememberSwitchPreferencesUiState( + state: SwitchPreferencesUiState, +): List { + val preferences by remember { + mutableStateOf( + listOf( + SwitchPreferencesIdState( + titleId = R.string.enable_watch_history_title, + summaryId = R.string.enable_watch_history_summary, + isEnabled = state.watchHistoryEnabled, + keyId = R.string.enable_watch_history_key + ), + SwitchPreferencesIdState( + titleId = R.string.enable_playback_resume_title, + summaryId = R.string.enable_playback_resume_summary, + isEnabled = state.resumePlaybackEnabled, + keyId = R.string.enable_playback_resume_key + ), + SwitchPreferencesIdState( + titleId = R.string.enable_playback_state_lists_title, + summaryId = R.string.enable_playback_state_lists_summary, + isEnabled = state.positionsInListsEnabled, + keyId = R.string.enable_playback_state_lists_key + ), + SwitchPreferencesIdState( + titleId = R.string.enable_search_history_title, + summaryId = R.string.enable_search_history_summary, + isEnabled = state.searchHistoryEnabled, + keyId = R.string.enable_search_history_key + ) + ) + ) + } + return preferences +} + +private data class SwitchPreferencesIdState( + val titleId: Int, + val summaryId: Int, + val isEnabled: Boolean, + val keyId: Int, +) + +@Preview(showBackground = true) +@Composable +private fun SwitchPreferencesComponentPreview() { + var state by remember { + mutableStateOf( + SwitchPreferencesUiState() + ) + } + AppTheme( + useDarkTheme = false + ) { + Scaffold { padding -> + HistoryPreferencesComponent( + state = state, + onEvent = { + // Mock behaviour to preview + state = state.copy( + watchHistoryEnabled = !state.watchHistoryEnabled + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(padding) + ) + } + } +} From 7a498d8b723713e73ad8ea23605885036994c3f5 Mon Sep 17 00:00:00 2001 From: brais Date: Sun, 11 Aug 2024 18:31:51 +0200 Subject: [PATCH 07/11] changed a bit the components to make the screen stable for compose --- .../switch_preference/SwitchPreference.kt | 1 - .../history_cache/HistoryCacheEvent.kt | 12 -- .../HistoryCacheSettingsScreen.kt | 60 ++++-- .../HistoryCacheSettingsViewModel.kt | 189 ++++++++++++++++ ...UiState.kt => SwitchPreferencesUiState.kt} | 6 +- .../components/CachePreferences.kt | 203 +++++++----------- .../components/HistoryPreferences.kt | 98 ++++----- .../history_cache/events/HistoryCacheEvent.kt | 10 + .../events/HistoryCacheUiEvent.kt | 9 + 9 files changed, 375 insertions(+), 213 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt rename app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/{HistoryCacheUiState.kt => SwitchPreferencesUiState.kt} (70%) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheEvent.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheUiEvent.kt diff --git a/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt b/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt index 29015badfae..61a64e8633d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/components/switch_preference/SwitchPreference.kt @@ -31,7 +31,6 @@ fun SwitchPreferenceComponent( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Column( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center, diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt deleted file mode 100644 index f899803fa38..00000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.schabi.newpipe.settings.presentation.history_cache - -sealed class HistoryCacheEvent { - data class OnUpdateBooleanPreference(val key: String, val isEnabled: Boolean) : - HistoryCacheEvent() - - data class OnClickWipeCachedMetadata(val key: String) : HistoryCacheEvent() - data class OnClickClearWatchHistory(val key: String) : HistoryCacheEvent() - data class OnClickDeletePlaybackPositions(val key: String) : HistoryCacheEvent() - data class OnClickClearSearchHistory(val key: String) : HistoryCacheEvent() - data class OnClickReCaptchaCookies(val key: String) : HistoryCacheEvent() -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt index 9fc87fbec2f..f512a3d652b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt @@ -7,45 +7,75 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel -import kotlinx.coroutines.launch +import org.schabi.newpipe.R import org.schabi.newpipe.settings.presentation.history_cache.components.CachePreferencesComponent import org.schabi.newpipe.settings.presentation.history_cache.components.HistoryPreferencesComponent +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowClearWatchHistorySnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeletePlaybackSnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowReCaptchaCookiesSnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar import org.schabi.newpipe.ui.theme.AppTheme @Composable -fun HistoryCacheScreen( +fun HistoryCacheSettingsScreen( modifier: Modifier = Modifier, viewModel: HistoryCacheSettingsViewModel = hiltViewModel(), ) { + val snackBarHostState = remember { SnackbarHostState() } + val playBackPositionsDeleted = stringResource(R.string.watch_history_states_deleted) + val watchHistoryDeleted = stringResource(R.string.watch_history_deleted) + val wipeCachedMetadataSnackbar = stringResource(R.string.metadata_cache_wipe_complete_notice) + val deleteSearchHistory = stringResource(R.string.search_history_deleted) + val clearReCaptchaCookiesSnackbar = stringResource(R.string.recaptcha_cookies_cleared) + + LaunchedEffect(key1 = true) { + viewModel.eventFlow.collect { event -> + val message = when (event) { + is ShowDeletePlaybackSnackbar -> playBackPositionsDeleted + is ShowClearWatchHistorySnackbar -> watchHistoryDeleted + is ShowWipeCachedMetadataSnackbar -> wipeCachedMetadataSnackbar + is ShowDeleteSearchHistorySnackbar -> deleteSearchHistory + is ShowReCaptchaCookiesSnackbar -> clearReCaptchaCookiesSnackbar + } + + snackBarHostState.showSnackbar(message) + } + } + val state by viewModel.state.collectAsState() HistoryCacheComponent( state = state, onEvent = viewModel::onEvent, + snackBarHostState = snackBarHostState, modifier = modifier ) } @Composable fun HistoryCacheComponent( - state: HistoryCacheUiState, + state: SwitchPreferencesUiState, onEvent: (HistoryCacheEvent) -> Unit, + snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, ) { - val snackBarHostState = remember { SnackbarHostState() } Scaffold( modifier = modifier, snackbarHost = { @@ -61,18 +91,15 @@ fun HistoryCacheComponent( verticalArrangement = Arrangement.Center, ) { HistoryPreferencesComponent( - state = state.switchPreferencesUiState, - onEvent = onEvent, - modifier = Modifier.fillMaxWidth() + state = state, + onEvent = { key, value -> + onEvent(HistoryCacheEvent.OnUpdateBooleanPreference(key, value)) + }, + modifier = Modifier.fillMaxWidth(), ) - val coroutineScope = rememberCoroutineScope() + HorizontalDivider(Modifier.fillMaxWidth()) CachePreferencesComponent( - onEvent = onEvent, - onShowSnackbar = { - coroutineScope.launch { - snackBarHostState.showSnackbar(it) - } - }, + onEvent = { onEvent(it) }, modifier = Modifier.fillMaxWidth() ) } @@ -84,7 +111,7 @@ fun HistoryCacheComponent( private fun HistoryCacheComponentPreview() { val state by remember { mutableStateOf( - HistoryCacheUiState() + SwitchPreferencesUiState() ) } AppTheme( @@ -95,6 +122,7 @@ private fun HistoryCacheComponentPreview() { state = state, onEvent = { }, + snackBarHostState = SnackbarHostState(), modifier = Modifier.fillMaxSize() ) } diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt new file mode 100644 index 00000000000..94f056ed338 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt @@ -0,0 +1,189 @@ +package org.schabi.newpipe.settings.presentation.history_cache + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.schabi.newpipe.DownloaderImpl +import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.ReCaptchaActivity +import org.schabi.newpipe.error.UserAction +import org.schabi.newpipe.error.usecases.OpenErrorActivity +import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteSearchHistory +import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteStreamStateHistory +import org.schabi.newpipe.settings.domain.usecases.DeleteWatchHistory +import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreference +import org.schabi.newpipe.settings.domain.usecases.update_boolean_preference.UpdatePreference +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearSearchHistory +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearWatchHistory +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickDeletePlaybackPositions +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickReCaptchaCookies +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickWipeCachedMetadata +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnUpdateBooleanPreference +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowClearWatchHistorySnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeletePlaybackSnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar +import org.schabi.newpipe.util.InfoCache +import javax.inject.Inject + +@HiltViewModel +class HistoryCacheSettingsViewModel @Inject constructor( + private val updateBooleanPreference: UpdatePreference, + private val updateStringPreference: UpdatePreference, + private val getBooleanPreference: GetPreference, + private val deleteWatchHistory: DeleteWatchHistory, + private val deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory, + private val deleteCompleteSearchHistory: DeleteCompleteSearchHistory, + private val openErrorActivity: OpenErrorActivity, +) : ViewModel() { + private val _state = MutableStateFlow(SwitchPreferencesUiState()) + val state: StateFlow = _state.asStateFlow() + + private val _eventFlow = MutableSharedFlow() + val eventFlow = _eventFlow.asSharedFlow() + + init { + viewModelScope.launch { + getBooleanPreference(R.string.enable_watch_history_key, true).collect { preference -> + _state.update { oldState -> + oldState.copy( + watchHistoryEnabled = preference + ) + } + } + } + + viewModelScope.launch { + getBooleanPreference(R.string.enable_playback_resume_key, true).collect { preference -> + _state.update { oldState -> + oldState.copy( + resumePlaybackEnabled = preference + ) + } + } + } + + viewModelScope.launch { + getBooleanPreference( + R.string.enable_playback_state_lists_key, + true + ).collect { preference -> + _state.update { oldState -> + oldState.copy( + positionsInListsEnabled = preference + ) + } + } + } + viewModelScope.launch { + getBooleanPreference(R.string.enable_search_history_key, true).collect { preference -> + _state.update { oldState -> + oldState.copy( + searchHistoryEnabled = preference + ) + } + } + } + } + + fun onEvent(event: HistoryCacheEvent) { + when (event) { + is OnUpdateBooleanPreference -> { + viewModelScope.launch { + updateBooleanPreference(event.key, event.isEnabled) + } + } + + is OnClickWipeCachedMetadata -> { + InfoCache.getInstance().clearCache() + viewModelScope.launch { + _eventFlow.emit(ShowWipeCachedMetadataSnackbar) + } + } + + is OnClickClearWatchHistory -> { + viewModelScope.launch { + deleteWatchHistory( + onDeletePlaybackStates = { + viewModelScope.launch { + _eventFlow.emit(ShowDeletePlaybackSnackbar) + } + }, + onDeleteWholeStreamHistory = { + viewModelScope.launch { + _eventFlow.emit(ShowClearWatchHistorySnackbar) + } + }, + onRemoveOrphanedRecords = { + // TODO: ask why original did nothing + } + ) + } + } + + is OnClickDeletePlaybackPositions -> { + viewModelScope.launch { + deleteCompleteStreamStateHistory( + Dispatchers.IO, + onError = { error -> + openErrorActivity( + ErrorInfo( + error, + UserAction.DELETE_FROM_HISTORY, + "Delete playback states" + ) + ) + }, + onSuccess = { + viewModelScope.launch { + _eventFlow.emit(ShowDeletePlaybackSnackbar) + } + } + ) + } + } + + is OnClickClearSearchHistory -> { + viewModelScope.launch { + deleteCompleteSearchHistory( + dispatcher = Dispatchers.IO, + onError = { error -> + openErrorActivity( + ErrorInfo( + error, + UserAction.DELETE_FROM_HISTORY, + "Delete search history" + ) + ) + }, + onSuccess = { + viewModelScope.launch { + _eventFlow.emit(ShowDeleteSearchHistorySnackbar) + } + } + ) + } + } + + is OnClickReCaptchaCookies -> { + viewModelScope.launch { + updateStringPreference(event.key, "") + DownloaderImpl.getInstance() + .setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "") + _eventFlow.emit(HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar) + } + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt similarity index 70% rename from app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt rename to app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt index 1d346244eeb..77de7a45552 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheUiState.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt @@ -1,9 +1,7 @@ package org.schabi.newpipe.settings.presentation.history_cache -data class HistoryCacheUiState( - val switchPreferencesUiState: SwitchPreferencesUiState = SwitchPreferencesUiState() -) - +import androidx.compose.runtime.Stable +@Stable data class SwitchPreferencesUiState( val watchHistoryEnabled: Boolean = false, val resumePlaybackEnabled: Boolean = false, diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt index 6657b78272a..6f967085411 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt @@ -18,21 +18,32 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import org.schabi.newpipe.R import org.schabi.newpipe.settings.components.irreversible_preference.IrreversiblePreferenceComponent -import org.schabi.newpipe.settings.presentation.history_cache.HistoryCacheEvent +import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium @Composable fun CachePreferencesComponent( onEvent: (HistoryCacheEvent) -> Unit, - onShowSnackbar: (String) -> Unit, modifier: Modifier = Modifier, ) { - val preferences = rememberPreferences() var dialogTitle by remember { mutableStateOf("") } var dialogOnClick by remember { mutableStateOf({}) } var isDialogVisible by remember { mutableStateOf(false) } + val deleteViewHistory = stringResource(id = R.string.delete_view_history_alert) + val deletePlayBacks = stringResource(id = R.string.delete_playback_states_alert) + val deleteSearchHistory = stringResource(id = R.string.delete_search_history_alert) + + val onOpenDialog: (String, HistoryCacheEvent) -> Unit = { title, eventType -> + dialogTitle = title + isDialogVisible = true + dialogOnClick = { + onEvent(eventType) + isDialogVisible = false + } + } + Column( modifier = modifier, ) { @@ -41,104 +52,61 @@ fun CachePreferencesComponent( style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(SpacingMedium) ) - preferences.forEach { - val elementDialogTitle = it.dialogTitle?.let { dialogTitle -> - stringResource(dialogTitle) - } - val elementSnackBarText = stringResource(it.snackBarText) - val key = stringResource(id = it.keyId) - val onClick = { - if (elementDialogTitle != null) { - dialogTitle = elementDialogTitle - isDialogVisible = true - dialogOnClick = { - onEvent(it.event(key)) - isDialogVisible = false - onShowSnackbar(elementSnackBarText) - } - } else { - onEvent(it.event(key)) - onShowSnackbar(elementSnackBarText) - } - } - IrreversiblePreferenceComponent( - title = stringResource(id = it.title), - summary = stringResource(it.summary), - onClick = onClick, - modifier = Modifier.fillMaxWidth() - ) - } - - CacheAlertDialog( - isDialogVisible = isDialogVisible, - dialogTitle = dialogTitle, - onClickCancel = { isDialogVisible = false }, - onClick = dialogOnClick + IrreversiblePreferenceComponent( + title = stringResource(id = R.string.metadata_cache_wipe_title), + summary = stringResource(id = R.string.metadata_cache_wipe_summary), + onClick = { onEvent(HistoryCacheEvent.OnClickWipeCachedMetadata(R.string.metadata_cache_wipe_key)) }, + modifier = Modifier.fillMaxWidth() ) - } -} - -@Composable -private fun rememberPreferences(): List { - val preferences by remember { - mutableStateOf( - listOf( - IrreversiblePreferenceUiState( - title = R.string.metadata_cache_wipe_title, - summary = R.string.metadata_cache_wipe_summary, - dialogTitle = null, - snackBarText = R.string.metadata_cache_wipe_complete_notice, - event = { HistoryCacheEvent.OnClickWipeCachedMetadata(it) }, - keyId = R.string.metadata_cache_wipe_key, - ), - IrreversiblePreferenceUiState( - title = R.string.clear_views_history_title, - summary = R.string.clear_views_history_summary, - dialogTitle = R.string.delete_view_history_alert, - snackBarText = R.string.watch_history_deleted, - event = { HistoryCacheEvent.OnClickClearWatchHistory(it) }, - keyId = R.string.clear_views_history_key, - ), - IrreversiblePreferenceUiState( - title = R.string.clear_playback_states_title, - summary = R.string.clear_playback_states_summary, - dialogTitle = R.string.delete_playback_states_alert, - snackBarText = R.string.watch_history_states_deleted, - event = { HistoryCacheEvent.OnClickDeletePlaybackPositions(it) }, - keyId = R.string.clear_playback_states_key, - ), - IrreversiblePreferenceUiState( - title = R.string.clear_search_history_title, - summary = R.string.clear_search_history_summary, - dialogTitle = R.string.delete_search_history_alert, - snackBarText = R.string.search_history_deleted, - event = { HistoryCacheEvent.OnClickClearSearchHistory(it) }, - keyId = R.string.clear_search_history_key, - ), - IrreversiblePreferenceUiState( - title = R.string.clear_cookie_title, - summary = R.string.clear_cookie_summary, - dialogTitle = null, - snackBarText = R.string.recaptcha_cookies_cleared, - event = { HistoryCacheEvent.OnClickReCaptchaCookies(it) }, - keyId = R.string.recaptcha_cookies_key, + IrreversiblePreferenceComponent( + title = stringResource(id = R.string.clear_views_history_title), + summary = stringResource(id = R.string.clear_views_history_summary), + onClick = { + onOpenDialog( + deleteViewHistory, + HistoryCacheEvent.OnClickClearWatchHistory(R.string.clear_views_history_key) ) - ) + }, + modifier = Modifier.fillMaxWidth() + ) + IrreversiblePreferenceComponent( + title = stringResource(id = R.string.clear_playback_states_title), + summary = stringResource(id = R.string.clear_playback_states_summary), + onClick = { + onOpenDialog( + deletePlayBacks, + HistoryCacheEvent.OnClickDeletePlaybackPositions(R.string.clear_playback_states_key) + ) + }, + modifier = Modifier.fillMaxWidth() + ) + IrreversiblePreferenceComponent( + title = stringResource(id = R.string.clear_search_history_title), + summary = stringResource(id = R.string.clear_search_history_summary), + onClick = { + onOpenDialog( + deleteSearchHistory, + HistoryCacheEvent.OnClickClearSearchHistory(R.string.clear_search_history_key) + ) + }, + modifier = Modifier.fillMaxWidth() + ) + IrreversiblePreferenceComponent( + title = stringResource(id = R.string.clear_cookie_title), + summary = stringResource(id = R.string.clear_cookie_summary), + onClick = { onEvent(HistoryCacheEvent.OnClickReCaptchaCookies(R.string.recaptcha_cookies_key)) }, + modifier = Modifier.fillMaxWidth() ) + if (isDialogVisible) { + CacheAlertDialog( + dialogTitle = dialogTitle, + onClickCancel = { isDialogVisible = false }, + onClick = dialogOnClick + ) + } } - return preferences } -private data class IrreversiblePreferenceUiState( - val title: Int, - val summary: Int, - val dialogTitle: Int?, - val snackBarText: Int, - val enabled: Boolean = true, - val event: (String) -> HistoryCacheEvent, - val keyId: Int, -) - @Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) @Composable private fun CachePreferencesComponentPreview() { @@ -146,7 +114,6 @@ private fun CachePreferencesComponentPreview() { Scaffold { padding -> CachePreferencesComponent( onEvent = {}, - onShowSnackbar = {}, modifier = Modifier .fillMaxWidth() .padding(padding) @@ -157,34 +124,31 @@ private fun CachePreferencesComponentPreview() { @Composable private fun CacheAlertDialog( - isDialogVisible: Boolean, dialogTitle: String, onClickCancel: () -> Unit, onClick: () -> Unit, modifier: Modifier = Modifier, ) { - if (isDialogVisible) { - AlertDialog( - onDismissRequest = onClickCancel, - confirmButton = { - TextButton(onClick = onClick) { - Text(text = "Delete") - } - }, - dismissButton = { - TextButton(onClick = onClickCancel) { - Text(text = "Cancel") - } - }, - title = { - Text(text = dialogTitle) - }, - text = { - Text(text = "This is an irreversible action") - }, - modifier = modifier - ) - } + AlertDialog( + onDismissRequest = onClickCancel, + confirmButton = { + TextButton(onClick = onClick) { + Text(text = "Delete") + } + }, + dismissButton = { + TextButton(onClick = onClickCancel) { + Text(text = "Cancel") + } + }, + title = { + Text(text = dialogTitle) + }, + text = { + Text(text = "This is an irreversible action") + }, + modifier = modifier + ) } @Preview(backgroundColor = 0xFFFFFFFF) @@ -193,7 +157,6 @@ private fun CacheAlertDialogPreview() { AppTheme { Scaffold { padding -> CacheAlertDialog( - isDialogVisible = true, dialogTitle = "Delete view history", onClickCancel = {}, onClick = {}, diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt index f929e387ab7..f1978bad4bd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt @@ -14,79 +14,57 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import org.schabi.newpipe.R import org.schabi.newpipe.settings.components.switch_preference.SwitchPreferenceComponent -import org.schabi.newpipe.settings.presentation.history_cache.HistoryCacheEvent import org.schabi.newpipe.settings.presentation.history_cache.SwitchPreferencesUiState import org.schabi.newpipe.ui.theme.AppTheme @Composable fun HistoryPreferencesComponent( state: SwitchPreferencesUiState, - onEvent: (HistoryCacheEvent) -> Unit, + onEvent: (Int, Boolean) -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier, ) { - val preferences = rememberSwitchPreferencesUiState(state) - preferences.forEach { preference -> - val key = stringResource(preference.keyId) - SwitchPreferenceComponent( - title = stringResource(id = preference.titleId), - summary = stringResource(id = preference.summaryId), - isChecked = preference.isEnabled, - onCheckedChange = { - onEvent(HistoryCacheEvent.OnUpdateBooleanPreference(key, it)) - }, - modifier = Modifier.fillMaxWidth() - ) - } - } -} - -@Composable -private fun rememberSwitchPreferencesUiState( - state: SwitchPreferencesUiState, -): List { - val preferences by remember { - mutableStateOf( - listOf( - SwitchPreferencesIdState( - titleId = R.string.enable_watch_history_title, - summaryId = R.string.enable_watch_history_summary, - isEnabled = state.watchHistoryEnabled, - keyId = R.string.enable_watch_history_key - ), - SwitchPreferencesIdState( - titleId = R.string.enable_playback_resume_title, - summaryId = R.string.enable_playback_resume_summary, - isEnabled = state.resumePlaybackEnabled, - keyId = R.string.enable_playback_resume_key - ), - SwitchPreferencesIdState( - titleId = R.string.enable_playback_state_lists_title, - summaryId = R.string.enable_playback_state_lists_summary, - isEnabled = state.positionsInListsEnabled, - keyId = R.string.enable_playback_state_lists_key - ), - SwitchPreferencesIdState( - titleId = R.string.enable_search_history_title, - summaryId = R.string.enable_search_history_summary, - isEnabled = state.searchHistoryEnabled, - keyId = R.string.enable_search_history_key - ) - ) + SwitchPreferenceComponent( + title = stringResource(id = R.string.enable_watch_history_title), + summary = stringResource(id = R.string.enable_watch_history_summary), + isChecked = state.watchHistoryEnabled, + onCheckedChange = { + onEvent(R.string.enable_watch_history_key, it) + }, + modifier = Modifier.fillMaxWidth() + ) + SwitchPreferenceComponent( + title = stringResource(id = R.string.enable_playback_resume_title), + summary = stringResource(id = R.string.enable_playback_resume_summary), + isChecked = state.resumePlaybackEnabled, + onCheckedChange = { + onEvent(R.string.enable_playback_resume_key, it) + }, + modifier = Modifier.fillMaxWidth() + ) + SwitchPreferenceComponent( + title = stringResource(id = R.string.enable_playback_state_lists_title), + summary = stringResource(id = R.string.enable_playback_state_lists_summary), + isChecked = state.positionsInListsEnabled, + onCheckedChange = { + onEvent(R.string.enable_playback_state_lists_key, it) + }, + modifier = Modifier.fillMaxWidth() + ) + SwitchPreferenceComponent( + title = stringResource(id = R.string.enable_search_history_title), + summary = stringResource(id = R.string.enable_search_history_summary), + isChecked = state.searchHistoryEnabled, + onCheckedChange = { + onEvent(R.string.enable_search_history_key, it) + }, + modifier = Modifier.fillMaxWidth() ) } - return preferences } -private data class SwitchPreferencesIdState( - val titleId: Int, - val summaryId: Int, - val isEnabled: Boolean, - val keyId: Int, -) - @Preview(showBackground = true) @Composable private fun SwitchPreferencesComponentPreview() { @@ -101,7 +79,7 @@ private fun SwitchPreferencesComponentPreview() { Scaffold { padding -> HistoryPreferencesComponent( state = state, - onEvent = { + onEvent = { _, _ -> // Mock behaviour to preview state = state.copy( watchHistoryEnabled = !state.watchHistoryEnabled @@ -109,7 +87,7 @@ private fun SwitchPreferencesComponentPreview() { }, modifier = Modifier .fillMaxWidth() - .padding(padding) + .padding(padding), ) } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheEvent.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheEvent.kt new file mode 100644 index 00000000000..0bd5d49c596 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheEvent.kt @@ -0,0 +1,10 @@ +package org.schabi.newpipe.settings.presentation.history_cache.events + +sealed class HistoryCacheEvent { + data class OnUpdateBooleanPreference(val key: Int, val isEnabled: Boolean) : HistoryCacheEvent() + data class OnClickWipeCachedMetadata(val key: Int) : HistoryCacheEvent() + data class OnClickClearWatchHistory(val key: Int) : HistoryCacheEvent() + data class OnClickDeletePlaybackPositions(val key: Int) : HistoryCacheEvent() + data class OnClickClearSearchHistory(val key: Int) : HistoryCacheEvent() + data class OnClickReCaptchaCookies(val key: Int) : HistoryCacheEvent() +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheUiEvent.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheUiEvent.kt new file mode 100644 index 00000000000..c40c1b45fdc --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/events/HistoryCacheUiEvent.kt @@ -0,0 +1,9 @@ +package org.schabi.newpipe.settings.presentation.history_cache.events + +sealed class HistoryCacheUiEvent { + data object ShowDeletePlaybackSnackbar : HistoryCacheUiEvent() + data object ShowDeleteSearchHistorySnackbar : HistoryCacheUiEvent() + data object ShowClearWatchHistorySnackbar : HistoryCacheUiEvent() + data object ShowReCaptchaCookiesSnackbar : HistoryCacheUiEvent() + data object ShowWipeCachedMetadataSnackbar : HistoryCacheUiEvent() +} From 86dbfea7cd5053b7ca0eb4b5acaaece7a96d385b Mon Sep 17 00:00:00 2001 From: brais Date: Tue, 13 Aug 2024 16:16:41 +0200 Subject: [PATCH 08/11] updated gradle --- app/build.gradle | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ec3493828a0..65bf4a74b3b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -328,8 +328,8 @@ dependencies { androidTestImplementation "org.assertj:assertj-core:3.24.2" // dagger - testImplementation("com.google.dagger:hilt-android-testing${daggerVersion}") - androidTestImplementation("com.google.dagger:hilt-android-testing${daggerVersion}") + testImplementation("com.google.dagger:hilt-android-testing:${daggerVersion}") + androidTestImplementation("com.google.dagger:hilt-android-testing:${daggerVersion}") } static String getGitWorkingBranch() { diff --git a/build.gradle b/build.gradle index 35c1b25da38..97a619d4d53 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.5.1' + classpath 'com.android.tools.build:gradle:8.5.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath("com.google.dagger:hilt-android-gradle-plugin:$dagger_version") // NOTE: Do not place your application dependencies here; they belong From 005719215d001be5c2f364deba2cfdd12b2943ff Mon Sep 17 00:00:00 2001 From: brais Date: Sun, 1 Sep 2024 18:31:24 +0200 Subject: [PATCH 09/11] Changed business logic to be in its own files --- .../newpipe/dependency_injection/AppModule.kt | 27 ++++ .../dependency_injection/DatabaseModule.kt | 56 +++++++ .../error/usecases/OpenErrorActivity.kt | 18 +++ .../dependency_injection/SettingsModule.kt | 138 ++++++++++++++++++ .../repositories/HistoryRecordRepository.kt | 11 ++ .../HistoryRecordRepositoryFake.kt | 64 ++++++++ .../HistoryRecordRepositoryImpl.kt | 39 +++++ .../usecases/DeleteCompleteSearchHistory.kt | 20 +++ .../DeleteCompleteStreamStateHistory.kt | 20 +++ .../domain/usecases/DeletePlaybackStates.kt | 20 +++ .../domain/usecases/DeleteWatchHistory.kt | 69 +++++++++ .../domain/usecases/RemoveOrphanedRecords.kt | 21 +++ .../usecases/get_preference/GetPreference.kt | 7 + .../get_preference/GetPreferenceFake.kt | 16 ++ .../get_preference/GetPreferenceImpl.kt | 49 +++++++ .../update_preference/UpdatePreference.kt | 5 + .../update_preference/UpdatePreferenceFake.kt | 16 ++ .../update_preference/UpdatePreferenceImpl.kt | 18 +++ 18 files changed, 614 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/dependency_injection/AppModule.kt create mode 100644 app/src/main/java/org/schabi/newpipe/dependency_injection/DatabaseModule.kt create mode 100644 app/src/main/java/org/schabi/newpipe/error/usecases/OpenErrorActivity.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/dependency_injection/SettingsModule.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepository.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryFake.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryImpl.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteSearchHistory.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteStreamStateHistory.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeletePlaybackStates.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteWatchHistory.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/RemoveOrphanedRecords.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreference.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceFake.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceImpl.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreference.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceFake.kt create mode 100644 app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceImpl.kt diff --git a/app/src/main/java/org/schabi/newpipe/dependency_injection/AppModule.kt b/app/src/main/java/org/schabi/newpipe/dependency_injection/AppModule.kt new file mode 100644 index 00000000000..1365f80f8d0 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/dependency_injection/AppModule.kt @@ -0,0 +1,27 @@ +package org.schabi.newpipe.dependency_injection + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.schabi.newpipe.error.usecases.OpenErrorActivity +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + @Provides + @Singleton + fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context) + + @Provides + @Singleton + fun provideOpenActivity( + @ApplicationContext context: Context, + ): OpenErrorActivity = OpenErrorActivity(context) +} diff --git a/app/src/main/java/org/schabi/newpipe/dependency_injection/DatabaseModule.kt b/app/src/main/java/org/schabi/newpipe/dependency_injection/DatabaseModule.kt new file mode 100644 index 00000000000..4e20fe30c06 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/dependency_injection/DatabaseModule.kt @@ -0,0 +1,56 @@ +package org.schabi.newpipe.dependency_injection + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.schabi.newpipe.database.AppDatabase +import org.schabi.newpipe.database.AppDatabase.DATABASE_NAME +import org.schabi.newpipe.database.Migrations.MIGRATION_1_2 +import org.schabi.newpipe.database.Migrations.MIGRATION_2_3 +import org.schabi.newpipe.database.Migrations.MIGRATION_3_4 +import org.schabi.newpipe.database.Migrations.MIGRATION_4_5 +import org.schabi.newpipe.database.Migrations.MIGRATION_5_6 +import org.schabi.newpipe.database.Migrations.MIGRATION_6_7 +import org.schabi.newpipe.database.Migrations.MIGRATION_7_8 +import org.schabi.newpipe.database.Migrations.MIGRATION_8_9 +import org.schabi.newpipe.database.history.dao.SearchHistoryDAO +import org.schabi.newpipe.database.history.dao.StreamHistoryDAO +import org.schabi.newpipe.database.stream.dao.StreamDAO +import org.schabi.newpipe.database.stream.dao.StreamStateDAO +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +class DatabaseModule { + + @Provides + @Singleton + fun provideAppDatabase(@ApplicationContext appContext: Context): AppDatabase = + Room.databaseBuilder( + appContext, + AppDatabase::class.java, + DATABASE_NAME + ).addMigrations( + MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, + MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9 + ).build() + + @Provides + fun provideStreamStateDao(appDatabase: AppDatabase): StreamStateDAO = + appDatabase.streamStateDAO() + + @Provides + fun providesStreamDao(appDatabase: AppDatabase): StreamDAO = appDatabase.streamDAO() + + @Provides + fun provideStreamHistoryDao(appDatabase: AppDatabase): StreamHistoryDAO = + appDatabase.streamHistoryDAO() + + @Provides + fun provideSearchHistoryDao(appDatabase: AppDatabase): SearchHistoryDAO = + appDatabase.searchHistoryDAO() +} diff --git a/app/src/main/java/org/schabi/newpipe/error/usecases/OpenErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/usecases/OpenErrorActivity.kt new file mode 100644 index 00000000000..5168c0d3c59 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/usecases/OpenErrorActivity.kt @@ -0,0 +1,18 @@ +package org.schabi.newpipe.error.usecases + +import android.content.Context +import android.content.Intent +import org.schabi.newpipe.error.ErrorActivity +import org.schabi.newpipe.error.ErrorInfo + +class OpenErrorActivity( + private val context: Context, +) { + operator fun invoke(errorInfo: ErrorInfo) { + val intent = Intent(context, ErrorActivity::class.java) + intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + context.startActivity(intent) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/dependency_injection/SettingsModule.kt b/app/src/main/java/org/schabi/newpipe/settings/dependency_injection/SettingsModule.kt new file mode 100644 index 00000000000..d0f271025cc --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/dependency_injection/SettingsModule.kt @@ -0,0 +1,138 @@ +package org.schabi.newpipe.settings.dependency_injection + +import android.content.Context +import android.content.SharedPreferences +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.schabi.newpipe.database.history.dao.SearchHistoryDAO +import org.schabi.newpipe.database.history.dao.StreamHistoryDAO +import org.schabi.newpipe.database.stream.dao.StreamDAO +import org.schabi.newpipe.database.stream.dao.StreamStateDAO +import org.schabi.newpipe.error.usecases.OpenErrorActivity +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepositoryImpl +import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteSearchHistory +import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteStreamStateHistory +import org.schabi.newpipe.settings.domain.usecases.DeletePlaybackStates +import org.schabi.newpipe.settings.domain.usecases.DeleteWatchHistory +import org.schabi.newpipe.settings.domain.usecases.RemoveOrphanedRecords +import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreference +import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreferenceImpl +import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreference +import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreferenceImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object SettingsModule { + + @Provides + @Singleton + fun provideGetBooleanPreference( + sharedPreferences: SharedPreferences, + @ApplicationContext context: Context, + ): GetPreference = GetPreferenceImpl(sharedPreferences, context) + + @Provides + @Singleton + fun provideGetStringPreference( + sharedPreferences: SharedPreferences, + @ApplicationContext context: Context, + ): GetPreference = GetPreferenceImpl(sharedPreferences, context) + + @Provides + @Singleton + fun provideUpdateBooleanPreference( + sharedPreferences: SharedPreferences, + @ApplicationContext context: Context, + ): UpdatePreference = UpdatePreferenceImpl(context, sharedPreferences) { key, value -> + putBoolean( + key, + value + ) + } + + @Provides + @Singleton + fun provideUpdateStringPreference( + sharedPreferences: SharedPreferences, + @ApplicationContext context: Context, + ): UpdatePreference = UpdatePreferenceImpl(context, sharedPreferences) { key, value -> + putString( + key, + value + ) + } + + @Provides + @Singleton + fun provideUpdateIntPreference( + sharedPreferences: SharedPreferences, + @ApplicationContext context: Context, + ): UpdatePreference = UpdatePreferenceImpl(context, sharedPreferences) { key, value -> + putInt(key, value) + } + + @Provides + @Singleton + fun provideHistoryRecordRepository( + streamStateDao: StreamStateDAO, + streamHistoryDAO: StreamHistoryDAO, + streamDAO: StreamDAO, + searchHistoryDAO: SearchHistoryDAO, + ): HistoryRecordRepository = HistoryRecordRepositoryImpl( + streamStateDao = streamStateDao, + streamHistoryDAO = streamHistoryDAO, + streamDAO = streamDAO, + searchHistoryDAO = searchHistoryDAO, + ) + + @Provides + @Singleton + fun provideDeletePlaybackStatesUseCase( + historyRecordRepository: HistoryRecordRepository, + ): DeletePlaybackStates = DeletePlaybackStates( + historyRecordRepository = historyRecordRepository, + ) + + @Provides + @Singleton + fun provideDeleteWholeStreamHistoryUseCase( + historyRecordRepository: HistoryRecordRepository, + ): DeleteCompleteStreamStateHistory = DeleteCompleteStreamStateHistory( + historyRecordRepository = historyRecordRepository, + ) + + @Provides + @Singleton + fun provideRemoveOrphanedRecordsUseCase( + historyRecordRepository: HistoryRecordRepository, + ): RemoveOrphanedRecords = RemoveOrphanedRecords( + historyRecordRepository = historyRecordRepository, + ) + + @Provides + @Singleton + fun provideDeleteCompleteSearchHistoryUseCase( + historyRecordRepository: HistoryRecordRepository, + ): DeleteCompleteSearchHistory = DeleteCompleteSearchHistory( + historyRecordRepository = historyRecordRepository, + ) + + @Provides + @Singleton + fun provideDeleteWatchHistoryUseCase( + deletePlaybackStates: DeletePlaybackStates, + deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory, + removeOrphanedRecords: RemoveOrphanedRecords, + openErrorActivity: OpenErrorActivity, + ): DeleteWatchHistory = DeleteWatchHistory( + deletePlaybackStates = deletePlaybackStates, + deleteCompleteStreamStateHistory = deleteCompleteStreamStateHistory, + removeOrphanedRecords = removeOrphanedRecords, + openErrorActivity = openErrorActivity + ) +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepository.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepository.kt new file mode 100644 index 00000000000..3b9edaa48ff --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepository.kt @@ -0,0 +1,11 @@ +package org.schabi.newpipe.settings.domain.repositories + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow + +interface HistoryRecordRepository { + fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow + fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow + fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow + fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryFake.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryFake.kt new file mode 100644 index 00000000000..6e03fec475d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryFake.kt @@ -0,0 +1,64 @@ +package org.schabi.newpipe.settings.domain.repositories + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.update +import org.schabi.newpipe.database.history.model.SearchHistoryEntry +import org.schabi.newpipe.database.history.model.StreamHistoryEntity +import org.schabi.newpipe.database.stream.model.StreamEntity +import org.schabi.newpipe.database.stream.model.StreamStateEntity + +class HistoryRecordRepositoryFake : HistoryRecordRepository { + private val _searchHistory: MutableStateFlow> = MutableStateFlow( + emptyList() + ) + val searchHistory = _searchHistory.asStateFlow() + private val _streamHistory = MutableStateFlow>(emptyList()) + val streamHistory = _streamHistory.asStateFlow() + private val _streams = MutableStateFlow>(emptyList()) + val streams = _streams.asStateFlow() + private val _streamStates = MutableStateFlow>(emptyList()) + val streamStates = _streamStates.asStateFlow() + + override fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow = flow { + val count = streamStates.value.size + _streamStates.update { + emptyList() + } + emit(count) + }.flowOn(dispatcher) + + override fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow = flow { + val count = streamHistory.value.size + _streamHistory.update { + emptyList() + } + emit(count) + }.flowOn(dispatcher) + + override fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow = flow { + val orphanedStreams = streams.value.filter { stream -> + !streamHistory.value.any { it.streamUid == stream.uid } + } + + val deletedCount = orphanedStreams.size + + _streams.update { oldStreams -> + oldStreams.filter { it !in orphanedStreams } + } + + emit(deletedCount) + }.flowOn(dispatcher) + + override fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow = flow { + val count = searchHistory.value.size + _searchHistory.update { + emptyList() + } + emit(count) + }.flowOn(dispatcher) +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryImpl.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryImpl.kt new file mode 100644 index 00000000000..b1d4abeebcb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/repositories/HistoryRecordRepositoryImpl.kt @@ -0,0 +1,39 @@ +package org.schabi.newpipe.settings.domain.repositories + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import org.schabi.newpipe.database.history.dao.SearchHistoryDAO +import org.schabi.newpipe.database.history.dao.StreamHistoryDAO +import org.schabi.newpipe.database.stream.dao.StreamDAO +import org.schabi.newpipe.database.stream.dao.StreamStateDAO + +class HistoryRecordRepositoryImpl( + private val streamStateDao: StreamStateDAO, + private val streamHistoryDAO: StreamHistoryDAO, + private val streamDAO: StreamDAO, + private val searchHistoryDAO: SearchHistoryDAO, +) : HistoryRecordRepository { + override fun deleteCompleteStreamState(dispatcher: CoroutineDispatcher): Flow = + flow { + val deletedCount = streamStateDao.deleteAll() + emit(deletedCount) + }.flowOn(dispatcher) + + override fun deleteWholeStreamHistory(dispatcher: CoroutineDispatcher): Flow = + flow { + val deletedCount = streamHistoryDAO.deleteAll() + emit(deletedCount) + }.flowOn(dispatcher) + + override fun removeOrphanedRecords(dispatcher: CoroutineDispatcher): Flow = flow { + val deletedCount = streamDAO.deleteOrphans() + emit(deletedCount) + }.flowOn(dispatcher) + + override fun deleteCompleteSearchHistory(dispatcher: CoroutineDispatcher): Flow = flow { + val deletedCount = searchHistoryDAO.deleteAll() + emit(deletedCount) + }.flowOn(dispatcher) +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteSearchHistory.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteSearchHistory.kt new file mode 100644 index 00000000000..7b2c2d99a88 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteSearchHistory.kt @@ -0,0 +1,20 @@ +package org.schabi.newpipe.settings.domain.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.take +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository + +class DeleteCompleteSearchHistory( + private val historyRecordRepository: HistoryRecordRepository, +) { + suspend operator fun invoke( + dispatcher: CoroutineDispatcher, + onError: (Throwable) -> Unit, + onSuccess: () -> Unit, + ) = historyRecordRepository.deleteCompleteSearchHistory(dispatcher).catch { error -> + onError(error) + }.take(1).collect { + onSuccess() + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteStreamStateHistory.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteStreamStateHistory.kt new file mode 100644 index 00000000000..0b516966dca --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteCompleteStreamStateHistory.kt @@ -0,0 +1,20 @@ +package org.schabi.newpipe.settings.domain.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.take +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository + +class DeleteCompleteStreamStateHistory( + private val historyRecordRepository: HistoryRecordRepository, +) { + suspend operator fun invoke( + dispatcher: CoroutineDispatcher, + onError: (Throwable) -> Unit, + onSuccess: () -> Unit, + ) = historyRecordRepository.deleteWholeStreamHistory(dispatcher).catch { + onError(it) + }.take(1).collect { + onSuccess() + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeletePlaybackStates.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeletePlaybackStates.kt new file mode 100644 index 00000000000..e8416a492e1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeletePlaybackStates.kt @@ -0,0 +1,20 @@ +package org.schabi.newpipe.settings.domain.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.take +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository + +class DeletePlaybackStates( + private val historyRecordRepository: HistoryRecordRepository, +) { + suspend operator fun invoke( + dispatcher: CoroutineDispatcher, + onError: (Throwable) -> Unit, + onSuccess: () -> Unit, + ) = historyRecordRepository.deleteCompleteStreamState(dispatcher).catch { + onError(it) + }.take(1).collect { + onSuccess() + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteWatchHistory.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteWatchHistory.kt new file mode 100644 index 00000000000..403f6f1c026 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/DeleteWatchHistory.kt @@ -0,0 +1,69 @@ +package org.schabi.newpipe.settings.domain.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.error.UserAction +import org.schabi.newpipe.error.usecases.OpenErrorActivity + +class DeleteWatchHistory( + private val deletePlaybackStates: DeletePlaybackStates, + private val deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory, + private val removeOrphanedRecords: RemoveOrphanedRecords, + private val openErrorActivity: OpenErrorActivity, +) { + suspend operator fun invoke( + onDeletePlaybackStates: () -> Unit, + onDeleteWholeStreamHistory: () -> Unit, + onRemoveOrphanedRecords: () -> Unit, + dispatcher: CoroutineDispatcher = Dispatchers.IO, + ) = coroutineScope { + launch { + deletePlaybackStates( + dispatcher, + onError = { error -> + openErrorActivity( + ErrorInfo( + error, + UserAction.DELETE_FROM_HISTORY, + "Delete playback states" + ) + ) + }, + onSuccess = onDeletePlaybackStates + ) + } + launch { + deleteCompleteStreamStateHistory( + dispatcher, + onError = { error -> + openErrorActivity( + ErrorInfo( + error, + UserAction.DELETE_FROM_HISTORY, + "Delete from history" + ) + ) + }, + onSuccess = onDeleteWholeStreamHistory + ) + } + launch { + removeOrphanedRecords( + dispatcher, + onError = { error -> + openErrorActivity( + ErrorInfo( + error, + UserAction.DELETE_FROM_HISTORY, + "Clear orphaned records" + ) + ) + }, + onSuccess = onRemoveOrphanedRecords + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/RemoveOrphanedRecords.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/RemoveOrphanedRecords.kt new file mode 100644 index 00000000000..42e9cd00d4d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/RemoveOrphanedRecords.kt @@ -0,0 +1,21 @@ +package org.schabi.newpipe.settings.domain.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.take +import org.schabi.newpipe.settings.domain.repositories.HistoryRecordRepository + +class RemoveOrphanedRecords( + private val historyRecordRepository: HistoryRecordRepository, +) { + suspend operator fun invoke( + dispatcher: CoroutineDispatcher, + onError: (Throwable) -> Unit, + onSuccess: () -> Unit, + ) = + historyRecordRepository.removeOrphanedRecords(dispatcher).catch { + onError(it) + }.take(1).collect { + onSuccess() + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreference.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreference.kt new file mode 100644 index 00000000000..19ed9d226d8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreference.kt @@ -0,0 +1,7 @@ +package org.schabi.newpipe.settings.domain.usecases.get_preference + +import kotlinx.coroutines.flow.Flow + +fun interface GetPreference { + operator fun invoke(key: Int, defaultValue: T): Flow +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceFake.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceFake.kt new file mode 100644 index 00000000000..805a3008356 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceFake.kt @@ -0,0 +1,16 @@ +package org.schabi.newpipe.settings.domain.usecases.get_preference + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map + +class GetPreferenceFake( + private val preferences: MutableStateFlow>, +) : GetPreference { + override fun invoke(key: Int, defaultValue: T): Flow { + return preferences.asStateFlow().map { + it[key] ?: defaultValue + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceImpl.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceImpl.kt new file mode 100644 index 00000000000..bb87025b761 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/get_preference/GetPreferenceImpl.kt @@ -0,0 +1,49 @@ +package org.schabi.newpipe.settings.domain.usecases.get_preference + +import android.content.Context +import android.content.SharedPreferences +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +class GetPreferenceImpl( + private val sharedPreferences: SharedPreferences, + private val context: Context, +) : GetPreference { + override fun invoke(key: Int, defaultValue: T): Flow { + val keyString = context.getString(key) + return sharedPreferences.getFlowForKey(keyString, defaultValue) + } + + private fun SharedPreferences.getFlowForKey(key: String, defaultValue: T) = callbackFlow { + val listener = + SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey -> + if (key == changedKey) { + val updated = getPreferenceValue(key, defaultValue) + trySend(updated) + } + } + registerOnSharedPreferenceChangeListener(listener) + println("Current value for $key: ${getPreferenceValue(key, defaultValue)}") + if (contains(key)) { + send(getPreferenceValue(key, defaultValue)) + } + awaitClose { + unregisterOnSharedPreferenceChangeListener(listener) + cancel() + } + } + + @Suppress("UNCHECKED_CAST") + private fun SharedPreferences.getPreferenceValue(key: String, defaultValue: T): T { + return when (defaultValue) { + is Boolean -> getBoolean(key, defaultValue) as T + is Int -> getInt(key, defaultValue) as T + is Long -> getLong(key, defaultValue) as T + is Float -> getFloat(key, defaultValue) as T + is String -> getString(key, defaultValue) as T + else -> throw IllegalArgumentException("Unsupported type") + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreference.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreference.kt new file mode 100644 index 00000000000..0a1b0e966f2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreference.kt @@ -0,0 +1,5 @@ +package org.schabi.newpipe.settings.domain.usecases.update_preference + +fun interface UpdatePreference { + suspend operator fun invoke(key: Int, value: T) +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceFake.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceFake.kt new file mode 100644 index 00000000000..a67677584e9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceFake.kt @@ -0,0 +1,16 @@ +package org.schabi.newpipe.settings.domain.usecases.update_preference + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +class UpdatePreferenceFake( + private val preferences: MutableStateFlow>, +) : UpdatePreference { + override suspend fun invoke(key: Int, value: T) { + preferences.update { + it.apply { + put(key, value) + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceImpl.kt b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceImpl.kt new file mode 100644 index 00000000000..e51ec731b1c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/domain/usecases/update_preference/UpdatePreferenceImpl.kt @@ -0,0 +1,18 @@ +package org.schabi.newpipe.settings.domain.usecases.update_preference + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit + +class UpdatePreferenceImpl( + private val context: Context, + private val sharedPreferences: SharedPreferences, + private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor, +) : UpdatePreference { + override suspend operator fun invoke(key: Int, value: T) { + val stringKey = context.getString(key) + sharedPreferences.edit { + setter(stringKey, value) + } + } +} From ff42678dd3301be9135e25ee02bd0d708f911573 Mon Sep 17 00:00:00 2001 From: brais Date: Sun, 1 Sep 2024 18:35:06 +0200 Subject: [PATCH 10/11] Updated view and viewmodel and remapped the fragments/activities --- .../history/StatisticsPlaylistFragment.java | 67 +++++++++++++++++-- .../newpipe/settings/SettingsActivity.java | 2 + .../IrreversiblePreference.kt | 13 ++-- .../history_cache/HistoryCacheFragment.kt | 39 +++++++++++ .../HistoryCacheSettingsScreen.kt | 19 ++++-- .../HistoryCacheSettingsViewModel.kt | 37 +++++++--- .../components/CachePreferences.kt | 11 ++- .../components/HistoryPreferences.kt | 2 +- .../{ => state}/SwitchPreferencesUiState.kt | 2 +- app/src/main/res/xml/main_settings.xml | 2 +- 10 files changed, 161 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheFragment.kt rename app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/{ => state}/SwitchPreferencesUiState.kt (80%) diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 1fea7e1559c..4801936e615 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.viewbinding.ViewBinding; import com.google.android.material.snackbar.Snackbar; @@ -26,6 +27,7 @@ import org.schabi.newpipe.databinding.PlaylistControlBinding; import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; @@ -34,7 +36,6 @@ import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.settings.HistorySettingsFragment; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.PlayButtonHelper; @@ -161,14 +162,72 @@ public void held(final LocalItem selectedItem) { @Override public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == R.id.action_history_clear) { - HistorySettingsFragment - .openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables); + openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables); } else { return super.onOptionsItemSelected(item); } return true; } + private static void openDeleteWatchHistoryDialog( + @NonNull final Context context, + final HistoryRecordManager recordManager, + final CompositeDisposable disposables + ) { + new AlertDialog.Builder(context) + .setTitle(R.string.delete_view_history_alert) + .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) + .setPositiveButton(R.string.delete, ((dialog, which) -> { + disposables.add(getDeletePlaybackStatesDisposable(context, recordManager)); + disposables.add(getWholeStreamHistoryDisposable(context, recordManager)); + disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager)); + })) + .show(); + } + + private static Disposable getDeletePlaybackStatesDisposable( + @NonNull final Context context, + final HistoryRecordManager recordManager + ) { + return recordManager.deleteCompleteStreamStateHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(context, + R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(), + throwable -> ErrorUtil.openActivity(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Delete playback states")) + ); + } + + private static Disposable getWholeStreamHistoryDisposable( + @NonNull final Context context, + final HistoryRecordManager recordManager + ) { + return recordManager.deleteWholeStreamHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(context, + R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(), + throwable -> ErrorUtil.openActivity(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Delete from history")) + ); + } + + private static Disposable getRemoveOrphanedRecordsDisposable( + @NonNull final Context context, final HistoryRecordManager recordManager) { + return recordManager.removeOrphanedRecords() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> { + }, + throwable -> ErrorUtil.openActivity(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Clear orphaned records")) + ); + } + /////////////////////////////////////////////////////////////////////////// // Fragment LifeCycle - Loading /////////////////////////////////////////////////////////////////////////// @@ -307,7 +366,7 @@ private void toggleSortMode() { sortMode = StatisticSortMode.LAST_PLAYED; setTitle(getString(R.string.title_last_played)); headerBinding.sortButtonIcon.setImageResource( - R.drawable.ic_filter_list); + R.drawable.ic_filter_list); headerBinding.sortButtonText.setText(R.string.title_most_played); } startLoading(true); diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 529e5344220..0c349ec4056 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; +import dagger.hilt.android.AndroidEntryPoint; import icepick.Icepick; import icepick.State; @@ -64,6 +65,7 @@ * along with NewPipe. If not, see . */ +@AndroidEntryPoint public class SettingsActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, PreferenceSearchResultListener { diff --git a/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt b/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt index b66e4eb5fc6..f9cc79099fe 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/components/irreversible_preference/IrreversiblePreference.kt @@ -28,14 +28,13 @@ fun IrreversiblePreferenceComponent( modifier: Modifier = Modifier, enabled: Boolean = true, ) { + val clickModifier = if (enabled) { + Modifier.clickable { onClick() } + } else { + Modifier + } Row( - modifier = Modifier - .clickable { - if (enabled) { - onClick() - } - } - .then(modifier), + modifier = clickModifier.then(modifier), verticalAlignment = Alignment.CenterVertically, ) { val alpha by remember { diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheFragment.kt new file mode 100644 index 00000000000..d9a2f49c555 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheFragment.kt @@ -0,0 +1,39 @@ +package org.schabi.newpipe.settings.presentation.history_cache + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import org.schabi.newpipe.fragments.list.comments.CommentsFragment +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.util.KEY_SERVICE_ID +import org.schabi.newpipe.util.KEY_URL + +class HistoryCacheFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ) = ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + AppTheme { + HistoryCacheSettingsScreen( + modifier = Modifier.fillMaxSize() + ) + } + } + } + + companion object { + @JvmStatic + fun getInstance(serviceId: Int, url: String?) = CommentsFragment().apply { + arguments = bundleOf(KEY_SERVICE_ID to serviceId, KEY_URL to url) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt index f512a3d652b..9a246257a7e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsScreen.kt @@ -32,6 +32,7 @@ import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCach import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowReCaptchaCookiesSnackbar import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar +import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState import org.schabi.newpipe.ui.theme.AppTheme @Composable @@ -47,6 +48,7 @@ fun HistoryCacheSettingsScreen( val clearReCaptchaCookiesSnackbar = stringResource(R.string.recaptcha_cookies_cleared) LaunchedEffect(key1 = true) { + viewModel.onInit() viewModel.eventFlow.collect { event -> val message = when (event) { is ShowDeletePlaybackSnackbar -> playBackPositionsDeleted @@ -60,10 +62,12 @@ fun HistoryCacheSettingsScreen( } } - val state by viewModel.state.collectAsState() + val switchPreferencesUiState by viewModel.switchState.collectAsState() + val recaptchaCookiesEnabled by viewModel.captchaCookies.collectAsState() HistoryCacheComponent( - state = state, - onEvent = viewModel::onEvent, + switchPreferences = switchPreferencesUiState, + recaptchaCookiesEnabled = recaptchaCookiesEnabled, + onEvent = { viewModel.onEvent(it) }, snackBarHostState = snackBarHostState, modifier = modifier ) @@ -71,7 +75,8 @@ fun HistoryCacheSettingsScreen( @Composable fun HistoryCacheComponent( - state: SwitchPreferencesUiState, + switchPreferences: SwitchPreferencesUiState, + recaptchaCookiesEnabled: Boolean, onEvent: (HistoryCacheEvent) -> Unit, snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, @@ -91,7 +96,7 @@ fun HistoryCacheComponent( verticalArrangement = Arrangement.Center, ) { HistoryPreferencesComponent( - state = state, + state = switchPreferences, onEvent = { key, value -> onEvent(HistoryCacheEvent.OnUpdateBooleanPreference(key, value)) }, @@ -99,6 +104,7 @@ fun HistoryCacheComponent( ) HorizontalDivider(Modifier.fillMaxWidth()) CachePreferencesComponent( + recaptchaCookiesEnabled = recaptchaCookiesEnabled, onEvent = { onEvent(it) }, modifier = Modifier.fillMaxWidth() ) @@ -119,7 +125,8 @@ private fun HistoryCacheComponentPreview() { ) { Surface { HistoryCacheComponent( - state = state, + switchPreferences = state, + recaptchaCookiesEnabled = false, onEvent = { }, snackBarHostState = SnackbarHostState(), diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt index 94f056ed338..49a8d3f1f62 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/HistoryCacheSettingsViewModel.kt @@ -21,7 +21,7 @@ import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteSearchHistory import org.schabi.newpipe.settings.domain.usecases.DeleteCompleteStreamStateHistory import org.schabi.newpipe.settings.domain.usecases.DeleteWatchHistory import org.schabi.newpipe.settings.domain.usecases.get_preference.GetPreference -import org.schabi.newpipe.settings.domain.usecases.update_boolean_preference.UpdatePreference +import org.schabi.newpipe.settings.domain.usecases.update_preference.UpdatePreference import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearSearchHistory import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheEvent.OnClickClearWatchHistory @@ -34,29 +34,44 @@ import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCach import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeletePlaybackSnackbar import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowDeleteSearchHistorySnackbar import org.schabi.newpipe.settings.presentation.history_cache.events.HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar +import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState import org.schabi.newpipe.util.InfoCache import javax.inject.Inject @HiltViewModel class HistoryCacheSettingsViewModel @Inject constructor( - private val updateBooleanPreference: UpdatePreference, private val updateStringPreference: UpdatePreference, + private val updateBooleanPreference: UpdatePreference, + private val getStringPreference: GetPreference, private val getBooleanPreference: GetPreference, private val deleteWatchHistory: DeleteWatchHistory, private val deleteCompleteStreamStateHistory: DeleteCompleteStreamStateHistory, private val deleteCompleteSearchHistory: DeleteCompleteSearchHistory, private val openErrorActivity: OpenErrorActivity, ) : ViewModel() { - private val _state = MutableStateFlow(SwitchPreferencesUiState()) - val state: StateFlow = _state.asStateFlow() + private val _switchState = MutableStateFlow(SwitchPreferencesUiState()) + val switchState: StateFlow = _switchState.asStateFlow() + + private val _captchaCookies = MutableStateFlow(false) + val captchaCookies: StateFlow = _captchaCookies.asStateFlow() private val _eventFlow = MutableSharedFlow() val eventFlow = _eventFlow.asSharedFlow() - init { + fun onInit() { + + viewModelScope.launch { + val flow = getStringPreference(R.string.recaptcha_cookies_key, "") + flow.collect { preference -> + _captchaCookies.update { + preference.isNotEmpty() + } + } + } + viewModelScope.launch { getBooleanPreference(R.string.enable_watch_history_key, true).collect { preference -> - _state.update { oldState -> + _switchState.update { oldState -> oldState.copy( watchHistoryEnabled = preference ) @@ -66,7 +81,7 @@ class HistoryCacheSettingsViewModel @Inject constructor( viewModelScope.launch { getBooleanPreference(R.string.enable_playback_resume_key, true).collect { preference -> - _state.update { oldState -> + _switchState.update { oldState -> oldState.copy( resumePlaybackEnabled = preference ) @@ -79,7 +94,7 @@ class HistoryCacheSettingsViewModel @Inject constructor( R.string.enable_playback_state_lists_key, true ).collect { preference -> - _state.update { oldState -> + _switchState.update { oldState -> oldState.copy( positionsInListsEnabled = preference ) @@ -88,7 +103,7 @@ class HistoryCacheSettingsViewModel @Inject constructor( } viewModelScope.launch { getBooleanPreference(R.string.enable_search_history_key, true).collect { preference -> - _state.update { oldState -> + _switchState.update { oldState -> oldState.copy( searchHistoryEnabled = preference ) @@ -126,7 +141,7 @@ class HistoryCacheSettingsViewModel @Inject constructor( } }, onRemoveOrphanedRecords = { - // TODO: ask why original did nothing + // TODO: ask why original in android fragments did nothing } ) } @@ -181,7 +196,7 @@ class HistoryCacheSettingsViewModel @Inject constructor( updateStringPreference(event.key, "") DownloaderImpl.getInstance() .setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "") - _eventFlow.emit(HistoryCacheUiEvent.ShowWipeCachedMetadataSnackbar) + _eventFlow.emit(ShowWipeCachedMetadataSnackbar) } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt index 6f967085411..c525938141c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/CachePreferences.kt @@ -24,6 +24,7 @@ import org.schabi.newpipe.ui.theme.SizeTokens.SpacingMedium @Composable fun CachePreferencesComponent( + recaptchaCookiesEnabled: Boolean, onEvent: (HistoryCacheEvent) -> Unit, modifier: Modifier = Modifier, ) { @@ -55,7 +56,9 @@ fun CachePreferencesComponent( IrreversiblePreferenceComponent( title = stringResource(id = R.string.metadata_cache_wipe_title), summary = stringResource(id = R.string.metadata_cache_wipe_summary), - onClick = { onEvent(HistoryCacheEvent.OnClickWipeCachedMetadata(R.string.metadata_cache_wipe_key)) }, + onClick = { + onEvent(HistoryCacheEvent.OnClickWipeCachedMetadata(R.string.metadata_cache_wipe_key)) + }, modifier = Modifier.fillMaxWidth() ) IrreversiblePreferenceComponent( @@ -94,7 +97,10 @@ fun CachePreferencesComponent( IrreversiblePreferenceComponent( title = stringResource(id = R.string.clear_cookie_title), summary = stringResource(id = R.string.clear_cookie_summary), - onClick = { onEvent(HistoryCacheEvent.OnClickReCaptchaCookies(R.string.recaptcha_cookies_key)) }, + onClick = { + onEvent(HistoryCacheEvent.OnClickReCaptchaCookies(R.string.recaptcha_cookies_key)) + }, + enabled = recaptchaCookiesEnabled, modifier = Modifier.fillMaxWidth() ) if (isDialogVisible) { @@ -113,6 +119,7 @@ private fun CachePreferencesComponentPreview() { AppTheme { Scaffold { padding -> CachePreferencesComponent( + recaptchaCookiesEnabled = false, onEvent = {}, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt index f1978bad4bd..c9ae14e413f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/components/HistoryPreferences.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import org.schabi.newpipe.R import org.schabi.newpipe.settings.components.switch_preference.SwitchPreferenceComponent -import org.schabi.newpipe.settings.presentation.history_cache.SwitchPreferencesUiState +import org.schabi.newpipe.settings.presentation.history_cache.state.SwitchPreferencesUiState import org.schabi.newpipe.ui.theme.AppTheme @Composable diff --git a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/state/SwitchPreferencesUiState.kt similarity index 80% rename from app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt rename to app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/state/SwitchPreferencesUiState.kt index 77de7a45552..887aaf5acb9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/SwitchPreferencesUiState.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/presentation/history_cache/state/SwitchPreferencesUiState.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.settings.presentation.history_cache +package org.schabi.newpipe.settings.presentation.history_cache.state import androidx.compose.runtime.Stable @Stable diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml index 5f96989f979..7920b7a0644 100644 --- a/app/src/main/res/xml/main_settings.xml +++ b/app/src/main/res/xml/main_settings.xml @@ -23,7 +23,7 @@ app:iconSpaceReserved="false" /> From 0530a6bd65b8d668eed1fd5c526137d54eed5479 Mon Sep 17 00:00:00 2001 From: brais Date: Sun, 1 Sep 2024 18:36:10 +0200 Subject: [PATCH 11/11] fix imports in some files --- .../java/org/schabi/newpipe/error/ReCaptchaActivity.java | 4 +--- .../schabi/newpipe/settings/DownloadSettingsFragment.java | 7 +------ .../schabi/newpipe/settings/SettingsResourceRegistry.java | 3 --- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index 3c14cfe4cac..625932b914b 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -27,8 +27,6 @@ import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.util.ThemeHelper; -import java.io.UnsupportedEncodingException; - /* * Created by beneth on 06.12.16. * @@ -190,7 +188,7 @@ private void handleCookiesFromUrl(@Nullable final String url) { String abuseCookie = url.substring(abuseStart + 13, abuseEnd); abuseCookie = Utils.decodeUrlUtf8(abuseCookie); handleCookies(abuseCookie); - } catch (UnsupportedEncodingException | StringIndexOutOfBoundsException e) { + } catch (final StringIndexOutOfBoundsException e) { if (MainActivity.DEBUG) { e.printStackTrace(); Log.d(TAG, "handleCookiesFromUrl: invalid google abuse starting at " diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 472db6afe6f..0738b6f72f3 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URI; public class DownloadSettingsFragment extends BasePreferenceFragment { @@ -123,11 +122,7 @@ private void showPathInSummary(final String prefKey, @StringRes final int defaul return; } - try { - rawUri = decodeUrlUtf8(rawUri); - } catch (final UnsupportedEncodingException e) { - // nothing to do - } + rawUri = decodeUrlUtf8(rawUri); target.setSummary(rawUri); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java index 06e0a7c1eae..b41ad904b07 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java @@ -3,9 +3,7 @@ import androidx.annotation.NonNull; import androidx.annotation.XmlRes; import androidx.fragment.app.Fragment; - import org.schabi.newpipe.R; - import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -35,7 +33,6 @@ private SettingsResourceRegistry() { add(ContentSettingsFragment.class, R.xml.content_settings); add(DebugSettingsFragment.class, R.xml.debug_settings).setSearchable(false); add(DownloadSettingsFragment.class, R.xml.download_settings); - add(HistorySettingsFragment.class, R.xml.history_settings); add(NotificationSettingsFragment.class, R.xml.notifications_settings); add(PlayerNotificationSettingsFragment.class, R.xml.player_notification_settings); add(UpdateSettingsFragment.class, R.xml.update_settings);