Feature: details page's theme based on cover (#41)
* Using MaterialKolor & Pallete to generate MaterialTheme based on seedColor * Fix MangaActionRow's color * Merge ImageRequest methods into one * add backdrop color * support dynamic color in EnhancedSource's metadata view * dynamic color for MetadataViewScreen * dynamic color for all dialogs * Remove palette buttons * add settings preference * Update MaterialKolor version to 1.6.1 to fix DynamicMaterialTheme & Add PalleteStyle * split DynamicMaterialTheme content * Missed text color in Tsumino & Cleanup & revert some unneeded changes * Change icon color to secondary * color for EditMangaDialog * Custom Spinner's Dropdown style * TextSelectionHandle color * color for add-tag dialog * setting for Style & Animate * Palette does not support Hardware bitmap * using MaterialTheme instead of DynamicMaterialTheme when no need for color animation * reduce color on backdrop * save/load manga's cover color & ratio with Preferences * Trying to decode bitmap directly from response when coverCache is not available * Update MangaCover.vibrantCoverColor map with new color * Hide Style & Animate settings if theme-cover-based is not enable * Only generate color if theme-cover-based is enabled * seedColor by vibrantColor from MangaCover: setPaletteColor won't needed (almost), only when failed to load vibrantColor from MangaCover * seedColor uses remember directly to the hashmap to recompose whenever cover is updated/changed. Drop successState.seedColor * generate favorite manga's color every time, instead of just update ratio * Move MangaCoverMetadata to coil
This commit is contained in:
parent
acfaab6102
commit
0a7f828d77
29 changed files with 1007 additions and 88 deletions
|
|
@ -256,7 +256,8 @@ dependencies {
|
||||||
implementation(libs.swipe)
|
implementation(libs.swipe)
|
||||||
implementation(libs.compose.webview)
|
implementation(libs.compose.webview)
|
||||||
implementation(libs.compose.grid)
|
implementation(libs.compose.grid)
|
||||||
|
implementation(libs.palette.ktx)
|
||||||
|
implementation(libs.material.kolor)
|
||||||
|
|
||||||
implementation(libs.google.api.services.drive)
|
implementation(libs.google.api.services.drive)
|
||||||
implementation(libs.google.api.client.oauth)
|
implementation(libs.google.api.client.oauth)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.domain.ui
|
package eu.kanade.domain.ui
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import com.materialkolor.PaletteStyle
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
|
|
@ -28,6 +29,12 @@ class UiPreferences(
|
||||||
|
|
||||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||||
|
|
||||||
|
fun detailsPageThemeCoverBased() = preferenceStore.getBoolean("pref_details_page_theme_cover_based_key", true)
|
||||||
|
|
||||||
|
fun themeCoverBasedStyle() = preferenceStore.getEnum("pref_theme_cover_based_style_key", PaletteStyle.Vibrant)
|
||||||
|
|
||||||
|
fun themeCoverBasedAnimate() = preferenceStore.getBoolean("pref_theme_cover_based_animate_key", false)
|
||||||
|
|
||||||
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
||||||
|
|
||||||
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import com.materialkolor.ktx.blend
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
|
@ -129,6 +130,7 @@ fun MangaInfoBox(
|
||||||
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.background(MaterialTheme.colorScheme.inversePrimary.copy(alpha = 0.2f))
|
||||||
.blur(4.dp)
|
.blur(4.dp)
|
||||||
.alpha(0.2f),
|
.alpha(0.2f),
|
||||||
)
|
)
|
||||||
|
|
@ -241,7 +243,7 @@ fun MangaActionRow(
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = stringResource(MR.strings.action_web_view),
|
title = stringResource(MR.strings.action_web_view),
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
color = defaultActionButtonColor,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
onClick = onWebViewClicked,
|
onClick = onWebViewClicked,
|
||||||
onLongClick = onWebViewLongClicked,
|
onLongClick = onWebViewLongClicked,
|
||||||
)
|
)
|
||||||
|
|
@ -251,7 +253,7 @@ fun MangaActionRow(
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = stringResource(SYMR.strings.merge),
|
title = stringResource(SYMR.strings.merge),
|
||||||
icon = Icons.AutoMirrored.Outlined.CallMerge,
|
icon = Icons.AutoMirrored.Outlined.CallMerge,
|
||||||
color = defaultActionButtonColor,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
onClick = onMergeClicked,
|
onClick = onMergeClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,13 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import com.materialkolor.PaletteStyle
|
||||||
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
|
|
@ -42,6 +45,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
getThemeGroup(uiPreferences = uiPreferences),
|
getThemeGroup(uiPreferences = uiPreferences),
|
||||||
|
getDetailsPageThemeGroup(uiPreferences = uiPreferences),
|
||||||
getDisplayGroup(uiPreferences = uiPreferences),
|
getDisplayGroup(uiPreferences = uiPreferences),
|
||||||
// SY -->
|
// SY -->
|
||||||
getNavbarGroup(uiPreferences = uiPreferences),
|
getNavbarGroup(uiPreferences = uiPreferences),
|
||||||
|
|
@ -100,6 +104,38 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getDetailsPageThemeGroup(
|
||||||
|
uiPreferences: UiPreferences,
|
||||||
|
): Preference.PreferenceGroup {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val detailsPageThemeCoverBased by remember {
|
||||||
|
Injekt.get<UiPreferences>().detailsPageThemeCoverBased().asState(scope)
|
||||||
|
}
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_details_page_theme),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = uiPreferences.detailsPageThemeCoverBased(),
|
||||||
|
title = stringResource(MR.strings.pref_details_page_theme_cover_based),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = uiPreferences.themeCoverBasedStyle(),
|
||||||
|
title = stringResource(MR.strings.pref_theme_cover_based_style),
|
||||||
|
enabled = detailsPageThemeCoverBased,
|
||||||
|
entries = PaletteStyle.entries
|
||||||
|
.associateWith { it.name }
|
||||||
|
.toImmutableMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = uiPreferences.themeCoverBasedAnimate(),
|
||||||
|
title = stringResource(MR.strings.pref_theme_cover_based_animate),
|
||||||
|
enabled = detailsPageThemeCoverBased,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDisplayGroup(
|
private fun getDisplayGroup(
|
||||||
uiPreferences: UiPreferences,
|
uiPreferences: UiPreferences,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.di.SYPreferenceModule
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
|
|
@ -167,6 +168,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
||||||
|
|
||||||
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
||||||
|
|
||||||
|
MangaCoverMetadata.load()
|
||||||
|
|
||||||
// Updates widget update
|
// Updates widget update
|
||||||
with(WidgetManager(Injekt.get(), Injekt.get())) {
|
with(WidgetManager(Injekt.get(), Injekt.get())) {
|
||||||
init(ProcessLifecycleOwner.get().lifecycleScope)
|
init(ProcessLifecycleOwner.get().lifecycleScope)
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,22 @@ import coil3.fetch.SourceFetchResult
|
||||||
import coil3.getOrDefault
|
import coil3.getOrDefault
|
||||||
import coil3.request.Options
|
import coil3.request.Options
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER_KEY
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER_KEY
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.internal.http.HTTP_NOT_MODIFIED
|
import okhttp3.internal.http.HTTP_NOT_MODIFIED
|
||||||
|
import okio.BufferedSource
|
||||||
import okio.FileSystem
|
import okio.FileSystem
|
||||||
import okio.Path.Companion.toOkioPath
|
import okio.Path.Companion.toOkioPath
|
||||||
import okio.Source
|
import okio.Source
|
||||||
|
|
@ -31,7 +37,10 @@ import okio.source
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.domain.manga.model.asMangaCover
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
@ -48,7 +57,8 @@ import java.io.IOException
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
class MangaCoverFetcher(
|
class MangaCoverFetcher(
|
||||||
private val url: String?,
|
private val mangaCover: MangaCover,
|
||||||
|
private val url: String? = mangaCover.url,
|
||||||
private val isLibraryManga: Boolean,
|
private val isLibraryManga: Boolean,
|
||||||
private val options: Options,
|
private val options: Options,
|
||||||
private val coverFileLazy: Lazy<File?>,
|
private val coverFileLazy: Lazy<File?>,
|
||||||
|
|
@ -59,9 +69,15 @@ class MangaCoverFetcher(
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
|
|
||||||
|
private val uiPreferences: UiPreferences = Injekt.get()
|
||||||
|
private val fileScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
private val diskCacheKey: String
|
private val diskCacheKey: String
|
||||||
get() = diskCacheKeyLazy.value
|
get() = diskCacheKeyLazy.value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called each time a cover is displayed
|
||||||
|
*/
|
||||||
override suspend fun fetch(): FetchResult {
|
override suspend fun fetch(): FetchResult {
|
||||||
// Use custom cover if exists
|
// Use custom cover if exists
|
||||||
val useCustomCover = options.extras.getOrDefault(USE_CUSTOM_COVER_KEY)
|
val useCustomCover = options.extras.getOrDefault(USE_CUSTOM_COVER_KEY)
|
||||||
|
|
@ -83,6 +99,7 @@ class MangaCoverFetcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fileLoader(file: File): FetchResult {
|
private fun fileLoader(file: File): FetchResult {
|
||||||
|
setRatioAndColorsInScope(mangaCover, ogFile = file)
|
||||||
return SourceFetchResult(
|
return SourceFetchResult(
|
||||||
source = ImageSource(
|
source = ImageSource(
|
||||||
file = file.toOkioPath(),
|
file = file.toOkioPath(),
|
||||||
|
|
@ -95,6 +112,7 @@ class MangaCoverFetcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fileUriLoader(uri: String): FetchResult {
|
private fun fileUriLoader(uri: String): FetchResult {
|
||||||
|
setRatioAndColorsInScope(mangaCover)
|
||||||
val source = UniFile.fromUri(options.context, uri.toUri())!!
|
val source = UniFile.fromUri(options.context, uri.toUri())!!
|
||||||
.openInputStream()
|
.openInputStream()
|
||||||
.source()
|
.source()
|
||||||
|
|
@ -128,6 +146,7 @@ class MangaCoverFetcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from snapshot
|
// Read from snapshot
|
||||||
|
setRatioAndColorsInScope(mangaCover, bufferedSource = snapshot.toImageSource().source())
|
||||||
return SourceFetchResult(
|
return SourceFetchResult(
|
||||||
source = snapshot.toImageSource(),
|
source = snapshot.toImageSource(),
|
||||||
mimeType = "image/*",
|
mimeType = "image/*",
|
||||||
|
|
@ -148,6 +167,7 @@ class MangaCoverFetcher(
|
||||||
// Read from disk cache
|
// Read from disk cache
|
||||||
snapshot = writeToDiskCache(response)
|
snapshot = writeToDiskCache(response)
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
|
setRatioAndColorsInScope(mangaCover, bufferedSource = snapshot.toImageSource().source())
|
||||||
return SourceFetchResult(
|
return SourceFetchResult(
|
||||||
source = snapshot.toImageSource(),
|
source = snapshot.toImageSource(),
|
||||||
mimeType = "image/*",
|
mimeType = "image/*",
|
||||||
|
|
@ -155,6 +175,13 @@ class MangaCoverFetcher(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRatioAndColorsInScope(
|
||||||
|
mangaCover,
|
||||||
|
bufferedSource = ImageSource(
|
||||||
|
source = responseBody.source(),
|
||||||
|
fileSystem = FileSystem.SYSTEM,
|
||||||
|
).source(),
|
||||||
|
)
|
||||||
// Read from response if cache is unused or unusable
|
// Read from response if cache is unused or unusable
|
||||||
return SourceFetchResult(
|
return SourceFetchResult(
|
||||||
source = ImageSource(source = responseBody.source(), fileSystem = FileSystem.SYSTEM),
|
source = ImageSource(source = responseBody.source(), fileSystem = FileSystem.SYSTEM),
|
||||||
|
|
@ -293,6 +320,27 @@ class MangaCoverFetcher(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [setRatioAndColorsInScope] is called whenever a cover is loaded with [MangaCoverFetcher.fetch]
|
||||||
|
*
|
||||||
|
* @param bufferedSource if not null then it will load bitmap from [BufferedSource], regardless of [ogFile]
|
||||||
|
* @param ogFile if not null then it will load bitmap from [File]. If it's null then it will try to load bitmap
|
||||||
|
* from [CoverCache] using either [CoverCache.customCoverCacheDir] or [CoverCache.cacheDir]
|
||||||
|
* @param force if true (default) then it will always re-calculate ratio & color for favorite mangas.
|
||||||
|
* This is useful when a favorite manga updates/changes its cover. If false then it will only update ratio.
|
||||||
|
*/
|
||||||
|
private fun setRatioAndColorsInScope(
|
||||||
|
mangaCover: MangaCover,
|
||||||
|
bufferedSource: BufferedSource? = null,
|
||||||
|
ogFile: File? = null,
|
||||||
|
force: Boolean = true
|
||||||
|
) {
|
||||||
|
if (!uiPreferences.detailsPageThemeCoverBased().get()) return
|
||||||
|
fileScope.launch {
|
||||||
|
MangaCoverMetadata.setRatioAndColors(mangaCover, bufferedSource, ogFile, force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum class Type {
|
private enum class Type {
|
||||||
File,
|
File,
|
||||||
URI,
|
URI,
|
||||||
|
|
@ -308,7 +356,7 @@ class MangaCoverFetcher(
|
||||||
|
|
||||||
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
|
||||||
return MangaCoverFetcher(
|
return MangaCoverFetcher(
|
||||||
url = data.thumbnailUrl,
|
mangaCover = data.asMangaCover(),
|
||||||
isLibraryManga = data.favorite,
|
isLibraryManga = data.favorite,
|
||||||
options = options,
|
options = options,
|
||||||
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) },
|
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) },
|
||||||
|
|
@ -330,7 +378,7 @@ class MangaCoverFetcher(
|
||||||
|
|
||||||
override fun create(data: MangaCover, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: MangaCover, options: Options, imageLoader: ImageLoader): Fetcher {
|
||||||
return MangaCoverFetcher(
|
return MangaCoverFetcher(
|
||||||
url = data.url,
|
mangaCover = data,
|
||||||
isLibraryManga = data.isMangaFavorite,
|
isLibraryManga = data.isMangaFavorite,
|
||||||
options = options,
|
options = options,
|
||||||
coverFileLazy = lazy { coverCache.getCoverFile(data.url) },
|
coverFileLazy = lazy { coverCache.getCoverFile(data.url) },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package eu.kanade.tachiyomi.data.coil
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.palette.graphics.Palette
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import okio.BufferedSource
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object that holds info about a covers size ratio + dominant colors
|
||||||
|
* @author Jays2Kings
|
||||||
|
*/
|
||||||
|
object MangaCoverMetadata {
|
||||||
|
private val preferences by injectLazy<LibraryPreferences>()
|
||||||
|
private val coverCache by injectLazy<CoverCache>()
|
||||||
|
|
||||||
|
fun load() {
|
||||||
|
val ratios = preferences.coverRatios().get()
|
||||||
|
MangaCover.coverRatioMap = ConcurrentHashMap(
|
||||||
|
ratios.mapNotNull {
|
||||||
|
val splits = it.split("|")
|
||||||
|
val id = splits.firstOrNull()?.toLongOrNull()
|
||||||
|
val ratio = splits.lastOrNull()?.toFloatOrNull()
|
||||||
|
if (id != null && ratio != null) {
|
||||||
|
id to ratio
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMap(),
|
||||||
|
)
|
||||||
|
val colors = preferences.coverColors().get()
|
||||||
|
MangaCover.coverColorMap = ConcurrentHashMap(
|
||||||
|
colors.mapNotNull {
|
||||||
|
val splits = it.split("|")
|
||||||
|
val id = splits.firstOrNull()?.toLongOrNull()
|
||||||
|
val color = splits.getOrNull(1)?.toIntOrNull()
|
||||||
|
val textColor = splits.getOrNull(2)?.toIntOrNull()
|
||||||
|
if (id != null && color != null) {
|
||||||
|
id to (color to (textColor ?: 0))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [setRatioAndColors] generate cover's color & ratio by reading cover's bitmap from [CoverCache].
|
||||||
|
* It's called along with [MangaCoverFetcher.fetch] everytime a cover is **displayed** (anywhere).
|
||||||
|
*
|
||||||
|
* When called:
|
||||||
|
* - It removes saved colors from saved Prefs of [MangaCover.coverColorMap] if manga is not favorite.
|
||||||
|
* - If a favorite manga already restored [MangaCover.dominantCoverColors] then it
|
||||||
|
* will skip actually reading bitmap, only extract ratio. Except when [MangaCover.vibrantCoverColor]
|
||||||
|
* is not loaded then it will read bitmap & extract vibrant color.
|
||||||
|
* => always set [force] to true so it will always re-calculate ratio & color.
|
||||||
|
*
|
||||||
|
* Set [MangaCover.dominantCoverColors] for favorite manga only.
|
||||||
|
* Set [MangaCover.vibrantCoverColor] for all mangas.
|
||||||
|
*
|
||||||
|
* @param bufferedSource if not null then it will load bitmap from [BufferedSource], regardless of [ogFile]
|
||||||
|
* @param ogFile if not null then it will load bitmap from [File]. If it's null then it will try to load bitmap
|
||||||
|
* from [CoverCache] using either [CoverCache.customCoverCacheDir] or [CoverCache.cacheDir]
|
||||||
|
* @param force if true (default) then it will always re-calculate ratio & color for favorite mangas.
|
||||||
|
* This is useful when a favorite manga updates/changes its cover. If false then it will only update ratio.
|
||||||
|
*
|
||||||
|
* @author Jays2Kings
|
||||||
|
*/
|
||||||
|
fun setRatioAndColors(
|
||||||
|
mangaCover: MangaCover,
|
||||||
|
bufferedSource: BufferedSource? = null,
|
||||||
|
ogFile: File? = null,
|
||||||
|
force: Boolean = true,
|
||||||
|
) {
|
||||||
|
if (!mangaCover.isMangaFavorite) {
|
||||||
|
mangaCover.remove()
|
||||||
|
}
|
||||||
|
val file = ogFile
|
||||||
|
?: coverCache.getCustomCoverFile(mangaCover.mangaId).takeIf { it.exists() }
|
||||||
|
?: coverCache.getCoverFile(mangaCover.url)
|
||||||
|
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
val hasVibrantColor = if (mangaCover.isMangaFavorite) mangaCover.vibrantCoverColor != null else true
|
||||||
|
// If dominantCoverColors is not null, it means that color is restored from Prefs
|
||||||
|
// and also has vibrantCoverColor (e.g. new color caused by updated cover)
|
||||||
|
val updateRatioOnly = mangaCover.dominantCoverColors != null && hasVibrantColor && !force
|
||||||
|
if (updateRatioOnly) {
|
||||||
|
// Just trying to update ratio without actual reading bitmap (bitmap will be null)
|
||||||
|
// This is often when open a favorite manga
|
||||||
|
options.inJustDecodeBounds = true
|
||||||
|
} else {
|
||||||
|
options.inSampleSize = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmap = when {
|
||||||
|
bufferedSource != null -> BitmapFactory.decodeStream(bufferedSource.inputStream(), null, options)
|
||||||
|
// if the file exists and the there was still an error then the file is corrupted
|
||||||
|
file?.exists() == true -> BitmapFactory.decodeFile(file.path, options)
|
||||||
|
else -> { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap != null) {
|
||||||
|
Palette.from(bitmap).generate {
|
||||||
|
if (it == null) return@generate
|
||||||
|
if (mangaCover.isMangaFavorite) {
|
||||||
|
it.dominantSwatch?.let { swatch ->
|
||||||
|
mangaCover.dominantCoverColors = swatch.rgb to swatch.titleTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val color = it.getBestColor() ?: return@generate
|
||||||
|
mangaCover.vibrantCoverColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mangaCover.isMangaFavorite && options.outWidth != -1 && options.outHeight != -1) {
|
||||||
|
mangaCover.ratio = options.outWidth / options.outHeight.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MangaCover.remove() {
|
||||||
|
MangaCover.coverRatioMap.remove(mangaId)
|
||||||
|
MangaCover.coverColorMap.remove(mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun savePrefs() {
|
||||||
|
val mapCopy = MangaCover.coverRatioMap.toMap()
|
||||||
|
preferences.coverRatios().set(mapCopy.map { "${it.key}|${it.value}" }.toSet())
|
||||||
|
val mapColorCopy = MangaCover.coverColorMap.toMap()
|
||||||
|
preferences.coverColors().set(mapColorCopy.map { "${it.key}|${it.value.first}|${it.value.second}" }.toSet())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.data.coil
|
package eu.kanade.tachiyomi.data.coil
|
||||||
|
|
||||||
|
import androidx.palette.graphics.Palette
|
||||||
import coil3.Extras
|
import coil3.Extras
|
||||||
import coil3.getExtra
|
import coil3.getExtra
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
|
|
@ -42,3 +43,42 @@ val Options.customDecoder: Boolean
|
||||||
get() = getExtra(customDecoderKey)
|
get() = getExtra(customDecoderKey)
|
||||||
|
|
||||||
private val customDecoderKey = Extras.Key(default = false)
|
private val customDecoderKey = Extras.Key(default = false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the best [Palette.Swatch] from [Palette]
|
||||||
|
* @author Jays2Kings
|
||||||
|
*/
|
||||||
|
fun Palette.getBestColor(): Int? {
|
||||||
|
// How big is the vibrant color
|
||||||
|
val vibPopulation = vibrantSwatch?.population ?: -1
|
||||||
|
// Saturation of the most dominant color
|
||||||
|
val domSat = dominantSwatch?.hsl?.get(1) ?: 0f
|
||||||
|
// Brightness of the most dominant color
|
||||||
|
val domLum = dominantSwatch?.hsl?.get(2) ?: -1f
|
||||||
|
// How big is the muted color
|
||||||
|
val mutedPopulation = mutedSwatch?.population ?: -1
|
||||||
|
// Saturation of the muted color
|
||||||
|
val mutedSat = mutedSwatch?.hsl?.get(1) ?: 0f
|
||||||
|
// If muted color is 3 times bigger than vibrant color then minimum acceptable saturation
|
||||||
|
// for muted color is lower (more likely to use it even if it's not colorful)
|
||||||
|
val mutedSatMinAcceptable = if (mutedPopulation > vibPopulation * 3f) 0.1f else 0.25f
|
||||||
|
|
||||||
|
val dominantIsColorful = domSat >= .25f
|
||||||
|
val dominantBrightnessJustRight = domLum <= .8f && domLum > .2f
|
||||||
|
val vibrantIsConsiderableBigEnough = vibPopulation >= mutedPopulation * 0.75f
|
||||||
|
val mutedIsBig = mutedPopulation > vibPopulation * 1.5f
|
||||||
|
val mutedIsNotTooBoring = mutedSat > mutedSatMinAcceptable
|
||||||
|
|
||||||
|
return when {
|
||||||
|
dominantIsColorful && dominantBrightnessJustRight -> dominantSwatch
|
||||||
|
// use vibrant color even if it's only 0.75 times smaller than muted color
|
||||||
|
vibrantIsConsiderableBigEnough -> vibrantSwatch
|
||||||
|
// use muted color if it's 1.5 times bigger than vibrant color and colorful enough (above the limit)
|
||||||
|
mutedIsBig && mutedIsNotTooBoring -> mutedSwatch
|
||||||
|
// return major vibrant color variant with more favor of vibrant color (size x3)
|
||||||
|
else -> listOfNotNull(vibrantSwatch, lightVibrantSwatch, darkVibrantSwatch)
|
||||||
|
.maxByOrNull {
|
||||||
|
if (it === vibrantSwatch) vibPopulation * 3 else it.population
|
||||||
|
}
|
||||||
|
}?.rgb
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
||||||
|
|
@ -337,6 +338,11 @@ class MainActivity : BaseActivity() {
|
||||||
// SY -->
|
// SY -->
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
MangaCoverMetadata.savePrefs()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onProvideAssistContent(outContent: AssistContent) {
|
override fun onProvideAssistContent(outContent: AssistContent) {
|
||||||
super.onProvideAssistContent(outContent)
|
super.onProvideAssistContent(outContent)
|
||||||
when (val screen = navigator?.lastItem) {
|
when (val screen = navigator?.lastItem) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga
|
package eu.kanade.tachiyomi.ui.manga
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -17,6 +26,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
|
|
@ -31,8 +41,13 @@ import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
|
import eu.kanade.tachiyomi.widget.materialdialogs.binding
|
||||||
import exh.ui.metadata.adapters.MetadataUIUtil.getResourceColor
|
import eu.kanade.tachiyomi.widget.materialdialogs.dismissDialog
|
||||||
|
import eu.kanade.tachiyomi.widget.materialdialogs.setColors
|
||||||
|
import eu.kanade.tachiyomi.widget.materialdialogs.setNegativeButton
|
||||||
|
import eu.kanade.tachiyomi.widget.materialdialogs.setPositiveButton
|
||||||
|
import eu.kanade.tachiyomi.widget.materialdialogs.setTextEdit
|
||||||
|
import eu.kanade.tachiyomi.widget.materialdialogs.setTitle
|
||||||
import exh.util.dropBlank
|
import exh.util.dropBlank
|
||||||
import exh.util.trimOrNull
|
import exh.util.trimOrNull
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -42,6 +57,7 @@ import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditMangaDialog(
|
fun EditMangaDialog(
|
||||||
|
|
@ -61,6 +77,18 @@ fun EditMangaDialog(
|
||||||
var binding by remember {
|
var binding by remember {
|
||||||
mutableStateOf<EditMangaDialogBinding?>(null)
|
mutableStateOf<EditMangaDialogBinding?>(null)
|
||||||
}
|
}
|
||||||
|
val colors = EditMangaDialogColors(
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb(),
|
||||||
|
textHighlightColor = MaterialTheme.colorScheme.outline.toArgb(),
|
||||||
|
iconColor = MaterialTheme.colorScheme.primary.toArgb(),
|
||||||
|
tagColor = MaterialTheme.colorScheme.outlineVariant.toArgb(),
|
||||||
|
tagFocusColor = MaterialTheme.colorScheme.outline.toArgb(),
|
||||||
|
tagTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb(),
|
||||||
|
btnTextColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
|
||||||
|
btnBgColor = MaterialTheme.colorScheme.surfaceTint.toArgb(),
|
||||||
|
dropdownBgColor = MaterialTheme.colorScheme.surfaceVariant.toArgb(),
|
||||||
|
dialogBgColor = MaterialTheme.colorScheme.surfaceContainerHigh.toArgb(),
|
||||||
|
)
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|
@ -109,7 +137,13 @@ fun EditMangaDialog(
|
||||||
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
EditMangaDialogBinding.inflate(LayoutInflater.from(factoryContext))
|
||||||
.also { binding = it }
|
.also { binding = it }
|
||||||
.apply {
|
.apply {
|
||||||
onViewCreated(manga, factoryContext, this, scope)
|
onViewCreated(
|
||||||
|
manga,
|
||||||
|
factoryContext,
|
||||||
|
this,
|
||||||
|
scope,
|
||||||
|
colors,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.root
|
.root
|
||||||
},
|
},
|
||||||
|
|
@ -120,10 +154,29 @@ fun EditMangaDialog(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
class EditMangaDialogColors(
|
||||||
|
@ColorInt val textColor: Int,
|
||||||
|
@ColorInt val textHighlightColor: Int,
|
||||||
|
@ColorInt val iconColor: Int,
|
||||||
|
@ColorInt val tagColor: Int,
|
||||||
|
@ColorInt val tagFocusColor: Int,
|
||||||
|
@ColorInt val tagTextColor: Int,
|
||||||
|
@ColorInt val btnTextColor: Int,
|
||||||
|
@ColorInt val btnBgColor: Int,
|
||||||
|
@ColorInt val dropdownBgColor: Int,
|
||||||
|
@ColorInt val dialogBgColor: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun onViewCreated(
|
||||||
|
manga: Manga,
|
||||||
|
context: Context,
|
||||||
|
binding: EditMangaDialogBinding,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
colors: EditMangaDialogColors,
|
||||||
|
) {
|
||||||
loadCover(manga, binding)
|
loadCover(manga, binding)
|
||||||
|
|
||||||
val statusAdapter: ArrayAdapter<String> = ArrayAdapter(
|
val statusAdapter = SpinnerAdapter(
|
||||||
context,
|
context,
|
||||||
android.R.layout.simple_spinner_dropdown_item,
|
android.R.layout.simple_spinner_dropdown_item,
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -135,6 +188,7 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
||||||
MR.strings.cancelled,
|
MR.strings.cancelled,
|
||||||
MR.strings.on_hiatus,
|
MR.strings.on_hiatus,
|
||||||
).map { context.stringResource(it) },
|
).map { context.stringResource(it) },
|
||||||
|
colors,
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.status.adapter = statusAdapter
|
binding.status.adapter = statusAdapter
|
||||||
|
|
@ -153,6 +207,9 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Spinner's dropdown caret color
|
||||||
|
binding.status.backgroundTintList = ColorStateList.valueOf(colors.iconColor)
|
||||||
|
|
||||||
if (manga.isLocal()) {
|
if (manga.isLocal()) {
|
||||||
if (manga.title != manga.url) {
|
if (manga.title != manga.url) {
|
||||||
binding.title.setText(manga.title)
|
binding.title.setText(manga.title)
|
||||||
|
|
@ -163,7 +220,7 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
||||||
binding.mangaArtist.setText(manga.artist.orEmpty())
|
binding.mangaArtist.setText(manga.artist.orEmpty())
|
||||||
binding.thumbnailUrl.setText(manga.thumbnailUrl.orEmpty())
|
binding.thumbnailUrl.setText(manga.thumbnailUrl.orEmpty())
|
||||||
binding.mangaDescription.setText(manga.description.orEmpty())
|
binding.mangaDescription.setText(manga.description.orEmpty())
|
||||||
binding.mangaGenresTags.setChips(manga.genre.orEmpty().dropBlank(), scope)
|
binding.mangaGenresTags.setChips(manga.genre.orEmpty().dropBlank(), scope, colors)
|
||||||
} else {
|
} else {
|
||||||
if (manga.title != manga.ogTitle) {
|
if (manga.title != manga.ogTitle) {
|
||||||
binding.title.append(manga.title)
|
binding.title.append(manga.title)
|
||||||
|
|
@ -180,7 +237,7 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
||||||
if (manga.description != manga.ogDescription) {
|
if (manga.description != manga.ogDescription) {
|
||||||
binding.mangaDescription.append(manga.description.orEmpty())
|
binding.mangaDescription.append(manga.description.orEmpty())
|
||||||
}
|
}
|
||||||
binding.mangaGenresTags.setChips(manga.genre.orEmpty().dropBlank(), scope)
|
binding.mangaGenresTags.setChips(manga.genre.orEmpty().dropBlank(), scope, colors)
|
||||||
|
|
||||||
binding.title.hint = context.stringResource(SYMR.strings.title_hint, manga.ogTitle)
|
binding.title.hint = context.stringResource(SYMR.strings.title_hint, manga.ogTitle)
|
||||||
|
|
||||||
|
|
@ -189,27 +246,76 @@ private fun onViewCreated(manga: Manga, context: Context, binding: EditMangaDial
|
||||||
binding.mangaDescription.hint =
|
binding.mangaDescription.hint =
|
||||||
context.stringResource(
|
context.stringResource(
|
||||||
SYMR.strings.description_hint,
|
SYMR.strings.description_hint,
|
||||||
manga.ogDescription?.takeIf { it.isNotBlank() }?.replace("\n", " ")?.chop(20) ?: ""
|
manga.ogDescription?.takeIf { it.isNotBlank() }?.replace("\n", " ")?.chop(20) ?: "",
|
||||||
)
|
)
|
||||||
binding.thumbnailUrl.hint =
|
binding.thumbnailUrl.hint =
|
||||||
context.stringResource(
|
context.stringResource(
|
||||||
SYMR.strings.thumbnail_url_hint,
|
SYMR.strings.thumbnail_url_hint,
|
||||||
manga.ogThumbnailUrl?.let {
|
manga.ogThumbnailUrl?.let {
|
||||||
it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else ""
|
it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else ""
|
||||||
} ?: ""
|
} ?: "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
binding.mangaGenresTags.clearFocus()
|
binding.mangaGenresTags.clearFocus()
|
||||||
|
|
||||||
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope) }
|
listOf(
|
||||||
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope) }
|
binding.title,
|
||||||
|
binding.mangaAuthor,
|
||||||
|
binding.mangaArtist,
|
||||||
|
binding.thumbnailUrl,
|
||||||
|
binding.mangaDescription,
|
||||||
|
).forEach {
|
||||||
|
it.setTextColor(colors.textColor)
|
||||||
|
it.highlightColor = colors.textHighlightColor
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
it.textSelectHandle?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
it.setTextSelectHandle(drawable)
|
||||||
|
}
|
||||||
|
it.textSelectHandleLeft?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
it.setTextSelectHandleLeft(drawable)
|
||||||
|
}
|
||||||
|
it.textSelectHandleRight?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
it.setTextSelectHandleRight(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listOf(
|
||||||
|
binding.titleOutline,
|
||||||
|
binding.mangaAuthorOutline,
|
||||||
|
binding.mangaArtistOutline,
|
||||||
|
binding.thumbnailUrlOutline,
|
||||||
|
binding.mangaDescriptionOutline,
|
||||||
|
).forEach {
|
||||||
|
it.boxStrokeColor = colors.iconColor
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
it.cursorColor = ColorStateList.valueOf(colors.iconColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.resetTags.setTextColor(colors.btnTextColor)
|
||||||
|
binding.resetTags.setBackgroundColor(colors.btnBgColor)
|
||||||
|
binding.resetInfo.setTextColor(colors.btnTextColor)
|
||||||
|
binding.resetInfo.setBackgroundColor(colors.btnBgColor)
|
||||||
|
|
||||||
|
binding.resetTags.setOnClickListener { resetTags(manga, binding, scope, colors) }
|
||||||
|
binding.resetInfo.setOnClickListener { resetInfo(manga, binding, scope, colors) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetTags(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
private fun resetTags(
|
||||||
|
manga: Manga,
|
||||||
|
binding: EditMangaDialogBinding,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
colors: EditMangaDialogColors,
|
||||||
|
) {
|
||||||
if (manga.genre.isNullOrEmpty() || manga.isLocal()) {
|
if (manga.genre.isNullOrEmpty() || manga.isLocal()) {
|
||||||
binding.mangaGenresTags.setChips(emptyList(), scope)
|
binding.mangaGenresTags.setChips(emptyList(), scope, colors)
|
||||||
} else {
|
} else {
|
||||||
binding.mangaGenresTags.setChips(manga.ogGenre.orEmpty(), scope)
|
binding.mangaGenresTags.setChips(manga.ogGenre.orEmpty(), scope, colors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,52 +325,88 @@ private fun loadCover(manga: Manga, binding: EditMangaDialogBinding) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetInfo(manga: Manga, binding: EditMangaDialogBinding, scope: CoroutineScope) {
|
private fun resetInfo(
|
||||||
|
manga: Manga,
|
||||||
|
binding: EditMangaDialogBinding,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
colors: EditMangaDialogColors,
|
||||||
|
) {
|
||||||
binding.title.setText("")
|
binding.title.setText("")
|
||||||
binding.mangaAuthor.setText("")
|
binding.mangaAuthor.setText("")
|
||||||
binding.mangaArtist.setText("")
|
binding.mangaArtist.setText("")
|
||||||
binding.thumbnailUrl.setText("")
|
binding.thumbnailUrl.setText("")
|
||||||
binding.mangaDescription.setText("")
|
binding.mangaDescription.setText("")
|
||||||
resetTags(manga, binding, scope)
|
resetTags(manga, binding, scope, colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ChipGroup.setChips(items: List<String>, scope: CoroutineScope) {
|
private fun ChipGroup.setChips(
|
||||||
|
items: List<String>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
colors: EditMangaDialogColors,
|
||||||
|
) {
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
|
|
||||||
|
val colorStateList = ColorStateList(
|
||||||
|
arrayOf(
|
||||||
|
intArrayOf(android.R.attr.state_focused),
|
||||||
|
intArrayOf(android.R.attr.state_pressed),
|
||||||
|
intArrayOf(-android.R.attr.state_active),
|
||||||
|
),
|
||||||
|
intArrayOf(
|
||||||
|
colors.tagFocusColor,
|
||||||
|
colors.tagFocusColor,
|
||||||
|
colors.tagColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
items.asSequence().map { item ->
|
items.asSequence().map { item ->
|
||||||
Chip(context).apply {
|
Chip(context).apply {
|
||||||
text = item
|
text = item
|
||||||
|
setTextColor(colors.tagTextColor)
|
||||||
|
|
||||||
isCloseIconVisible = true
|
isCloseIconVisible = true
|
||||||
closeIcon?.setTint(context.getResourceColor(R.attr.colorAccent))
|
closeIcon?.setTint(colors.iconColor)
|
||||||
setOnCloseIconClickListener {
|
setOnCloseIconClickListener {
|
||||||
removeView(this)
|
removeView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chipBackgroundColor = colorStateList
|
||||||
}
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
addView(it)
|
addView(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val addTagChip = Chip(context).apply {
|
val addTagChip = Chip(context).apply {
|
||||||
setText(SYMR.strings.add_tag.getString(context))
|
text = SYMR.strings.add_tag.getString(context)
|
||||||
|
setTextColor(colors.tagTextColor)
|
||||||
|
|
||||||
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)?.apply {
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)?.apply {
|
||||||
isChipIconVisible = true
|
isChipIconVisible = true
|
||||||
setTint(context.getResourceColor(R.attr.colorAccent))
|
setTint(colors.iconColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chipBackgroundColor = colorStateList
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
var newTag: String? = null
|
var dialog: AlertDialog? = null
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
|
val builder = MaterialAlertDialogBuilder(context)
|
||||||
|
val binding = builder.binding(context)
|
||||||
.setTitle(SYMR.strings.add_tag.getString(context))
|
.setTitle(SYMR.strings.add_tag.getString(context))
|
||||||
.setTextInput {
|
.setPositiveButton(MR.strings.action_ok.getString(context)) {
|
||||||
newTag = it.trimOrNull()
|
dialog?.dismissDialog()
|
||||||
|
val newTag = it.trimOrNull()
|
||||||
|
if (newTag != null) setChips(items + listOfNotNull(newTag), scope, colors)
|
||||||
}
|
}
|
||||||
.setPositiveButton(MR.strings.action_ok.getString(context)) { _, _ ->
|
.setNegativeButton(MR.strings.action_cancel.getString(context)) {
|
||||||
if (newTag != null) setChips(items + listOfNotNull(newTag), scope)
|
dialog?.dismissDialog()
|
||||||
}
|
}
|
||||||
.setNegativeButton(MR.strings.action_cancel.getString(context), null)
|
.setTextEdit()
|
||||||
.show()
|
.setColors(colors)
|
||||||
|
|
||||||
|
dialog = builder.create()
|
||||||
|
dialog.setView(binding.root)
|
||||||
|
dialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addView(addTagChip)
|
addView(addTagChip)
|
||||||
|
|
@ -277,3 +419,48 @@ private fun ChipGroup.getTextStrings(): List<String> = children.mapNotNull {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
|
private class SpinnerAdapter(
|
||||||
|
context: Context,
|
||||||
|
@LayoutRes val resource: Int,
|
||||||
|
objects: List<String>,
|
||||||
|
val colors: EditMangaDialogColors,
|
||||||
|
) : ArrayAdapter<String>(context, resource, objects) {
|
||||||
|
private val mInflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
return createViewFromResource(mInflater, position, convertView, parent, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
return createViewFromResource(mInflater, position, convertView, parent, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createViewFromResource(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
position: Int,
|
||||||
|
convertView: View?,
|
||||||
|
parent: ViewGroup,
|
||||||
|
resource: Int,
|
||||||
|
): View {
|
||||||
|
val text: TextView
|
||||||
|
|
||||||
|
val view = convertView ?: inflater.inflate(resource, parent, false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If no custom field is assigned, assume the whole resource is a TextView
|
||||||
|
text = view as TextView
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
Timber.e("You must supply a resource ID for a TextView")
|
||||||
|
throw IllegalStateException("ArrayAdapter requires the resource ID to be a TextView", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
val item: String? = getItem(position)
|
||||||
|
if (item != null) text.text = item
|
||||||
|
|
||||||
|
text.setTextColor(colors.textColor)
|
||||||
|
text.setBackgroundColor(colors.dropdownBgColor)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
@ -12,8 +14,10 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
|
@ -23,8 +27,11 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.materialkolor.DynamicMaterialTheme
|
||||||
|
import com.materialkolor.rememberDynamicColorScheme
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||||
import eu.kanade.presentation.components.NavigatorAdaptiveSheet
|
import eu.kanade.presentation.components.NavigatorAdaptiveSheet
|
||||||
import eu.kanade.presentation.manga.ChapterSettingsDialog
|
import eu.kanade.presentation.manga.ChapterSettingsDialog
|
||||||
|
|
@ -116,10 +123,7 @@ class MangaScreen(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptic = LocalHapticFeedback.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val screenModel =
|
val screenModel =
|
||||||
rememberScreenModel { MangaScreenModel(context, mangaId, fromSource, smartSearchConfig != null) }
|
rememberScreenModel { MangaScreenModel(context, mangaId, fromSource, smartSearchConfig != null) }
|
||||||
|
|
||||||
|
|
@ -131,6 +135,40 @@ class MangaScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
val successState = state as MangaScreenModel.State.Success
|
val successState = state as MangaScreenModel.State.Success
|
||||||
|
val seedColorState = rememberUpdatedState(newValue = successState.seedColor)
|
||||||
|
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
||||||
|
|
||||||
|
if (uiPreferences.themeCoverBasedAnimate().get()) {
|
||||||
|
DynamicMaterialTheme(
|
||||||
|
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
|
||||||
|
useDarkTheme = isSystemInDarkTheme(),
|
||||||
|
style = uiPreferences.themeCoverBasedStyle().get(),
|
||||||
|
animate = uiPreferences.themeCoverBasedAnimate().get(),
|
||||||
|
content = { MaterialThemeContent(context, screenModel, successState) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val colorScheme = rememberDynamicColorScheme(
|
||||||
|
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
|
||||||
|
isDark = isSystemInDarkTheme(),
|
||||||
|
style = uiPreferences.themeCoverBasedStyle().get(),
|
||||||
|
)
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
content = { MaterialThemeContent(context, screenModel, successState) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MaterialThemeContent(
|
||||||
|
context: Context,
|
||||||
|
screenModel: MangaScreenModel,
|
||||||
|
successState: MangaScreenModel.State.Success,
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val isHttpSource = remember { successState.source is HttpSource }
|
val isHttpSource = remember { successState.source is HttpSource }
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
|
@ -162,7 +200,6 @@ class MangaScreen(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
MangaScreen(
|
MangaScreen(
|
||||||
state = successState,
|
state = successState,
|
||||||
snackbarHostState = screenModel.snackbarHostState,
|
snackbarHostState = screenModel.snackbarHostState,
|
||||||
|
|
@ -229,8 +266,8 @@ class MangaScreen(
|
||||||
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
||||||
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
||||||
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) },
|
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) },
|
||||||
onOpenPagePreview = {
|
onOpenPagePreview = { page ->
|
||||||
openPagePreview(context, successState.chapters.minByOrNull { it.chapter.sourceOrder }?.chapter, it)
|
openPagePreview(context, successState.chapters.minByOrNull { it.chapter.sourceOrder }?.chapter, page)
|
||||||
},
|
},
|
||||||
onMorePreviewsClicked = { openMorePagePreviews(navigator, successState.manga) },
|
onMorePreviewsClicked = { openMorePagePreviews(navigator, successState.manga) },
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
@ -289,6 +326,7 @@ class MangaScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MangaScreenModel.Dialog.DeleteChapters -> {
|
is MangaScreenModel.Dialog.DeleteChapters -> {
|
||||||
DeleteChaptersDialog(
|
DeleteChaptersDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
@ -311,6 +349,7 @@ class MangaScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog(
|
MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
manga = successState.manga,
|
manga = successState.manga,
|
||||||
|
|
@ -324,6 +363,7 @@ class MangaScreen(
|
||||||
scanlatorFilterActive = successState.scanlatorFilterActive,
|
scanlatorFilterActive = successState.scanlatorFilterActive,
|
||||||
onScanlatorFilterClicked = { showScanlatorsDialog = true },
|
onScanlatorFilterClicked = { showScanlatorsDialog = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
MangaScreenModel.Dialog.TrackSheet -> {
|
MangaScreenModel.Dialog.TrackSheet -> {
|
||||||
NavigatorAdaptiveSheet(
|
NavigatorAdaptiveSheet(
|
||||||
screen = TrackInfoDialogHomeScreen(
|
screen = TrackInfoDialogHomeScreen(
|
||||||
|
|
@ -335,6 +375,7 @@ class MangaScreen(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MangaScreenModel.Dialog.FullCover -> {
|
MangaScreenModel.Dialog.FullCover -> {
|
||||||
val sm = rememberScreenModel { MangaCoverScreenModel(successState.manga.id) }
|
val sm = rememberScreenModel { MangaCoverScreenModel(successState.manga.id) }
|
||||||
val manga by sm.state.collectAsState()
|
val manga by sm.state.collectAsState()
|
||||||
|
|
@ -361,6 +402,7 @@ class MangaScreen(
|
||||||
LoadingScreen(Modifier.systemBarsPadding())
|
LoadingScreen(Modifier.systemBarsPadding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MangaScreenModel.Dialog.SetFetchInterval -> {
|
is MangaScreenModel.Dialog.SetFetchInterval -> {
|
||||||
SetIntervalDialog(
|
SetIntervalDialog(
|
||||||
interval = dialog.manga.fetchInterval,
|
interval = dialog.manga.fetchInterval,
|
||||||
|
|
@ -378,6 +420,7 @@ class MangaScreen(
|
||||||
onPositiveClick = screenModel::updateMangaInfo,
|
onPositiveClick = screenModel::updateMangaInfo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MangaScreenModel.Dialog.EditMergedSettings -> {
|
is MangaScreenModel.Dialog.EditMergedSettings -> {
|
||||||
EditMergedSettingsDialog(
|
EditMergedSettingsDialog(
|
||||||
mergedData = dialog.mergedData,
|
mergedData = dialog.mergedData,
|
||||||
|
|
@ -402,14 +445,19 @@ class MangaScreen(
|
||||||
when (bulkFavoriteState.dialog) {
|
when (bulkFavoriteState.dialog) {
|
||||||
is BulkFavoriteScreenModel.Dialog.AddDuplicateManga ->
|
is BulkFavoriteScreenModel.Dialog.AddDuplicateManga ->
|
||||||
AddDuplicateMangaDialog(bulkFavoriteScreenModel)
|
AddDuplicateMangaDialog(bulkFavoriteScreenModel)
|
||||||
|
|
||||||
is BulkFavoriteScreenModel.Dialog.RemoveManga ->
|
is BulkFavoriteScreenModel.Dialog.RemoveManga ->
|
||||||
RemoveMangaDialog(bulkFavoriteScreenModel)
|
RemoveMangaDialog(bulkFavoriteScreenModel)
|
||||||
|
|
||||||
is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory ->
|
is BulkFavoriteScreenModel.Dialog.ChangeMangaCategory ->
|
||||||
ChangeMangaCategoryDialog(bulkFavoriteScreenModel)
|
ChangeMangaCategoryDialog(bulkFavoriteScreenModel)
|
||||||
|
|
||||||
is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory ->
|
is BulkFavoriteScreenModel.Dialog.ChangeMangasCategory ->
|
||||||
ChangeMangasCategoryDialog(bulkFavoriteScreenModel)
|
ChangeMangasCategoryDialog(bulkFavoriteScreenModel)
|
||||||
|
|
||||||
is BulkFavoriteScreenModel.Dialog.AllowDuplicate ->
|
is BulkFavoriteScreenModel.Dialog.AllowDuplicate ->
|
||||||
AllowDuplicateDialog(bulkFavoriteScreenModel)
|
AllowDuplicateDialog(bulkFavoriteScreenModel)
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
@ -539,8 +587,8 @@ class MangaScreen(
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openMetadataViewer(navigator: Navigator, manga: Manga) {
|
private fun openMetadataViewer(navigator: Navigator, manga: Manga, seedColor: Color? = null) {
|
||||||
navigator.push(MetadataViewScreen(manga.id, manga.source))
|
navigator.push(MetadataViewScreen(manga.id, manga.source, seedColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openMergedMangaWebview(context: Context, navigator: Navigator, mergedMangaData: MergedMangaData) {
|
private fun openMergedMangaWebview(context: Context, navigator: Navigator, mergedMangaData: MergedMangaData) {
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,16 @@ import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
|
import androidx.palette.graphics.Palette
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import coil3.Image
|
||||||
|
import coil3.executeBlocking
|
||||||
|
import coil3.imageLoader
|
||||||
|
import coil3.request.ImageRequest
|
||||||
|
import coil3.request.allowHardware
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
|
|
@ -57,8 +64,10 @@ import eu.kanade.tachiyomi.ui.manga.RelatedManga.Companion.sorted
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
||||||
|
import eu.kanade.tachiyomi.data.coil.getBestColor
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||||
|
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
|
|
@ -100,6 +109,7 @@ import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
|
import tachiyomi.core.common.util.lang.launchUI
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
|
|
@ -133,10 +143,12 @@ import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
|
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
|
||||||
import tachiyomi.domain.manga.model.CustomMangaInfo
|
import tachiyomi.domain.manga.model.CustomMangaInfo
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.model.MergeMangaSettingsUpdate
|
import tachiyomi.domain.manga.model.MergeMangaSettingsUpdate
|
||||||
import tachiyomi.domain.manga.model.MergedMangaReference
|
import tachiyomi.domain.manga.model.MergedMangaReference
|
||||||
import tachiyomi.domain.manga.model.applyFilter
|
import tachiyomi.domain.manga.model.applyFilter
|
||||||
|
import tachiyomi.domain.manga.model.asMangaCover
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.source.model.StubSource
|
import tachiyomi.domain.source.model.StubSource
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
|
@ -242,8 +254,6 @@ class MangaScreenModel(
|
||||||
val redirectFlow: MutableSharedFlow<EXHRedirect> = MutableSharedFlow()
|
val redirectFlow: MutableSharedFlow<EXHRedirect> = MutableSharedFlow()
|
||||||
|
|
||||||
data class EXHRedirect(val mangaId: Long)
|
data class EXHRedirect(val mangaId: Long)
|
||||||
|
|
||||||
var dedupe: Boolean = true
|
|
||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
private data class CombineState(
|
private data class CombineState(
|
||||||
|
|
@ -388,6 +398,7 @@ class MangaScreenModel(
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val manga = getMangaAndChapters.awaitManga(mangaId)
|
val manga = getMangaAndChapters.awaitManga(mangaId)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) else getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true))
|
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) else getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true))
|
||||||
.toChapterListItems(manga, null)
|
.toChapterListItems(manga, null)
|
||||||
|
|
@ -470,6 +481,76 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of the manga cover by loading cover with ImageRequest directly from network.
|
||||||
|
*/
|
||||||
|
private suspend fun setPaletteColor(model: Any, method: ImageRequestType = ImageRequestType.Enqueue) {
|
||||||
|
if (model is ImageRequest && model.defined.sizeResolver != null) return
|
||||||
|
|
||||||
|
val imageRequestBuilder = if (model is ImageRequest) {
|
||||||
|
model.newBuilder()
|
||||||
|
} else {
|
||||||
|
ImageRequest.Builder(context).data(model)
|
||||||
|
}
|
||||||
|
.allowHardware(false)
|
||||||
|
|
||||||
|
val generatePalette: (Image) -> Unit = { image ->
|
||||||
|
val bitmap = image.asDrawable(context.resources).getBitmapOrNull()
|
||||||
|
if (bitmap != null) {
|
||||||
|
Palette.from(bitmap).generate { palette ->
|
||||||
|
screenModelScope.launchUI {
|
||||||
|
palette?.getBestColor()?.let { vibrantColor ->
|
||||||
|
when (model) {
|
||||||
|
is Manga -> model.asMangaCover().vibrantCoverColor = vibrantColor
|
||||||
|
is MangaCover -> model.vibrantCoverColor = vibrantColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (method) {
|
||||||
|
ImageRequestType.Enqueue -> {
|
||||||
|
context.imageLoader.enqueue(
|
||||||
|
imageRequestBuilder
|
||||||
|
.target(
|
||||||
|
onSuccess = generatePalette,
|
||||||
|
onError = {
|
||||||
|
// TODO: handle error
|
||||||
|
// val file = coverCache.getCoverFile(manga!!)
|
||||||
|
// if (file.exists()) {
|
||||||
|
// file.delete()
|
||||||
|
// setPaletteColor()
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ImageRequestType.Execute -> {
|
||||||
|
context.imageLoader.execute(imageRequestBuilder.build())
|
||||||
|
.image?.let { image ->
|
||||||
|
generatePalette(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageRequestType.ExecuteBlocking -> {
|
||||||
|
context.imageLoader.executeBlocking(imageRequestBuilder.build())
|
||||||
|
.image?.let { image ->
|
||||||
|
generatePalette(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageRequestType.IOContext -> {
|
||||||
|
withIOContext {
|
||||||
|
context.imageLoader.execute(imageRequestBuilder.build())
|
||||||
|
.image?.let { image ->
|
||||||
|
generatePalette(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
private suspend fun syncTrackers() {
|
private suspend fun syncTrackers() {
|
||||||
if (!trackPreferences.autoSyncReadChapters().get()) return
|
if (!trackPreferences.autoSyncReadChapters().get()) return
|
||||||
|
|
@ -703,8 +784,8 @@ class MangaScreenModel(
|
||||||
mergedManga = networkToLocalManga.await(mergedManga)
|
mergedManga = networkToLocalManga.await(mergedManga)
|
||||||
|
|
||||||
getCategories.await(originalMangaId)
|
getCategories.await(originalMangaId)
|
||||||
.let { it ->
|
.let { categories ->
|
||||||
setMangaCategories.await(mergedManga.id, it.map { it.id })
|
setMangaCategories.await(mergedManga.id, categories.map { it.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
val originalMangaReference = MergedMangaReference(
|
val originalMangaReference = MergedMangaReference(
|
||||||
|
|
@ -1796,7 +1877,9 @@ class MangaScreenModel(
|
||||||
val relatedMangaCollection: List<RelatedManga>? = null,
|
val relatedMangaCollection: List<RelatedManga>? = null,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) : State {
|
) : State {
|
||||||
// KMK ->>
|
// KMK -->
|
||||||
|
val seedColor: Color? = MangaCover.vibrantCoverColorMap[manga.id]?.let { Color(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a value of null will be treated as still loading, so if all searching were failed and won't update
|
* a value of null will be treated as still loading, so if all searching were failed and won't update
|
||||||
* 'relatedMangaCollection` then we should return empty list
|
* 'relatedMangaCollection` then we should return empty list
|
||||||
|
|
@ -1978,3 +2061,10 @@ sealed interface RelatedManga {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
|
enum class ImageRequestType {
|
||||||
|
IOContext,
|
||||||
|
Enqueue,
|
||||||
|
Execute,
|
||||||
|
ExecuteBlocking,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import java.io.InputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache
|
* Call before updating [Manga.thumbnailUrl] to ensure old cover can be cleared from cache
|
||||||
*/
|
*/
|
||||||
fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSameUrl: Boolean): Manga {
|
fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSameUrl: Boolean): Manga {
|
||||||
// Never refresh covers if the new url is null, as the current url has possibly become invalid
|
// Never refresh covers if the new url is null, as the current url has possibly become invalid
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,135 @@
|
||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
package eu.kanade.tachiyomi.widget.materialdialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.widget.doAfterTextChanged
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
|
import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.EditMangaDialogColors
|
||||||
|
|
||||||
fun MaterialAlertDialogBuilder.setTextInput(
|
@Suppress("UnusedReceiverParameter")
|
||||||
hint: String? = null,
|
fun MaterialAlertDialogBuilder.binding(context: Context): DialogStubTextinputBinding {
|
||||||
prefill: String? = null,
|
return DialogStubTextinputBinding.inflate(LayoutInflater.from(context))
|
||||||
onTextChanged: (String) -> Unit,
|
}
|
||||||
): MaterialAlertDialogBuilder {
|
|
||||||
val binding = DialogStubTextinputBinding.inflate(LayoutInflater.from(context))
|
fun AlertDialog.dismissDialog() {
|
||||||
binding.textField.hint = hint
|
if (isShowing) {
|
||||||
binding.textField.editText?.apply {
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setTitle(title: String): DialogStubTextinputBinding {
|
||||||
|
alertTitle.text = title
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setPositiveButton(text: String, onClick: (String) -> Unit): DialogStubTextinputBinding {
|
||||||
|
positiveButton.text = text
|
||||||
|
|
||||||
|
positiveButton.setOnClickListener {
|
||||||
|
val textEdit = textField.editText
|
||||||
|
onClick(textEdit?.text?.toString() ?: "")
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setNegativeButton(text: String, onClick: () -> Unit): DialogStubTextinputBinding {
|
||||||
|
negativeButton.text = text
|
||||||
|
|
||||||
|
negativeButton.setOnClickListener {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setHint(hint: String? = null): DialogStubTextinputBinding {
|
||||||
|
textField.hint = hint
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setTextEdit(prefill: String? = null): DialogStubTextinputBinding {
|
||||||
|
textField.editText?.apply {
|
||||||
setText(prefill, TextView.BufferType.EDITABLE)
|
setText(prefill, TextView.BufferType.EDITABLE)
|
||||||
doAfterTextChanged {
|
|
||||||
onTextChanged(it?.toString() ?: "")
|
|
||||||
}
|
|
||||||
post {
|
post {
|
||||||
requestFocusFromTouch()
|
requestFocusFromTouch()
|
||||||
context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
|
context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return setView(binding.root)
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogStubTextinputBinding.setColors(colors: EditMangaDialogColors): DialogStubTextinputBinding {
|
||||||
|
textField.boxStrokeColor = colors.iconColor
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
textField.cursorColor = ColorStateList.valueOf(colors.iconColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.editText?.apply {
|
||||||
|
setTextColor(colors.textColor)
|
||||||
|
highlightColor = colors.textHighlightColor
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
textSelectHandle?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
setTextSelectHandle(drawable)
|
||||||
|
}
|
||||||
|
textSelectHandleLeft?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
setTextSelectHandleLeft(drawable)
|
||||||
|
}
|
||||||
|
textSelectHandleRight?.let { drawable ->
|
||||||
|
drawable.setTint(colors.iconColor)
|
||||||
|
setTextSelectHandleRight(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alertTitle.setTextColor(colors.textColor)
|
||||||
|
positiveButton.setTextColor(colors.iconColor)
|
||||||
|
negativeButton.setTextColor(colors.iconColor)
|
||||||
|
|
||||||
|
root.background = RoundedCornerDrawable(color = colors.dialogBgColor)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoundedCornerDrawable(val color: Int, private val cornerRadius: Float = 72f) : Drawable() {
|
||||||
|
|
||||||
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
val bounds = bounds
|
||||||
|
paint.color = color
|
||||||
|
canvas.drawRoundRect(
|
||||||
|
RectF(bounds.left.toFloat(), bounds.top.toFloat(), bounds.right.toFloat(), bounds.bottom.toFloat()),
|
||||||
|
cornerRadius,
|
||||||
|
cornerRadius,
|
||||||
|
paint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
paint.alpha = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat"))
|
||||||
|
override fun getOpacity(): Int {
|
||||||
|
return PixelFormat.TRANSLUCENT
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
paint.colorFilter = colorFilter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package exh.ui.metadata
|
package exh.ui.metadata
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
|
@ -16,11 +17,14 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import com.materialkolor.DynamicMaterialTheme
|
||||||
|
import com.materialkolor.PaletteStyle
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
|
@ -33,7 +37,11 @@ import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
class MetadataViewScreen(private val mangaId: Long, private val sourceId: Long) : Screen() {
|
class MetadataViewScreen(
|
||||||
|
private val mangaId: Long,
|
||||||
|
private val sourceId: Long,
|
||||||
|
private val seedColor: Color? = null,
|
||||||
|
) : Screen() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
|
@ -41,7 +49,9 @@ class MetadataViewScreen(private val mangaId: Long, private val sourceId: Long)
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
Scaffold(
|
|
||||||
|
@Composable
|
||||||
|
fun content() = Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = screenModel.manga.collectAsState().value?.title,
|
title = screenModel.manga.collectAsState().value?.title,
|
||||||
|
|
@ -96,5 +106,13 @@ class MetadataViewScreen(private val mangaId: Long, private val sourceId: Long)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicMaterialTheme(
|
||||||
|
seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
|
||||||
|
useDarkTheme = isSystemInDarkTheme(),
|
||||||
|
style = PaletteStyle.Vibrant,
|
||||||
|
animate = true,
|
||||||
|
content = { content() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package exh.ui.metadata.adapters
|
package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -20,8 +23,15 @@ import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
|
fun EHentaiDescription(
|
||||||
|
state: State.Success,
|
||||||
|
openMetadataViewer: () -> Unit,
|
||||||
|
search: (String) -> Unit,
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.secondary.toArgb()
|
||||||
|
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -40,20 +50,26 @@ fun EHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit, sea
|
||||||
}
|
}
|
||||||
?: meta.genre
|
?: meta.genre
|
||||||
?: context.stringResource(MR.strings.unknown)
|
?: context.stringResource(MR.strings.unknown)
|
||||||
|
binding.genre.setTextColor(textColor)
|
||||||
|
|
||||||
binding.visible.text = context.stringResource(SYMR.strings.is_visible, meta.visible ?: context.stringResource(MR.strings.unknown))
|
binding.visible.text = context.stringResource(SYMR.strings.is_visible, meta.visible ?: context.stringResource(MR.strings.unknown))
|
||||||
|
binding.visible.setTextColor(textColor)
|
||||||
|
|
||||||
binding.favorites.text = (meta.favorites ?: 0).toString()
|
binding.favorites.text = (meta.favorites ?: 0).toString()
|
||||||
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp)
|
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp, iconColor)
|
||||||
|
binding.favorites.setTextColor(textColor)
|
||||||
|
|
||||||
binding.uploader.text = meta.uploader ?: context.stringResource(MR.strings.unknown)
|
binding.uploader.text = meta.uploader ?: context.stringResource(MR.strings.unknown)
|
||||||
|
binding.uploader.setTextColor(textColor)
|
||||||
|
|
||||||
binding.size.text = MetadataUtil.humanReadableByteCount(meta.size ?: 0, true)
|
binding.size.text = MetadataUtil.humanReadableByteCount(meta.size ?: 0, true)
|
||||||
binding.size.bindDrawable(context, R.drawable.ic_outline_sd_card_24)
|
binding.size.bindDrawable(context, R.drawable.ic_outline_sd_card_24, iconColor)
|
||||||
|
binding.size.setTextColor(textColor)
|
||||||
|
|
||||||
val length = meta.length ?: 0
|
val length = meta.length ?: 0
|
||||||
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, length, length)
|
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, length, length)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor)
|
||||||
|
binding.pages.setTextColor(textColor)
|
||||||
|
|
||||||
val language = meta.language ?: context.stringResource(MR.strings.unknown)
|
val language = meta.language ?: context.stringResource(MR.strings.unknown)
|
||||||
binding.language.text = if (meta.translated == true) {
|
binding.language.text = if (meta.translated == true) {
|
||||||
|
|
@ -61,13 +77,18 @@ fun EHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit, sea
|
||||||
} else {
|
} else {
|
||||||
language
|
language
|
||||||
}
|
}
|
||||||
|
binding.language.setTextColor(textColor)
|
||||||
|
|
||||||
val ratingFloat = meta.averageRating?.toFloat()
|
val ratingFloat = meta.averageRating?.toFloat()
|
||||||
binding.ratingBar.rating = ratingFloat ?: 0F
|
binding.ratingBar.rating = ratingFloat ?: 0F
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
||||||
|
binding.ratingBar.supportProgressTintList = ColorStateList.valueOf(iconColor)
|
||||||
|
binding.ratingBar.supportSecondaryProgressTintList = ColorStateList.valueOf(ratingBarSecondaryColor)
|
||||||
|
binding.rating.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
binding.favorites,
|
binding.favorites,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -18,6 +20,8 @@ import tachiyomi.i18n.MR
|
||||||
@Composable
|
@Composable
|
||||||
fun EightMusesDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun EightMusesDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -29,8 +33,10 @@ fun EightMusesDescription(state: State.Success, openMetadataViewer: () -> Unit)
|
||||||
val binding = DescriptionAdapter8mBinding.bind(it)
|
val binding = DescriptionAdapter8mBinding.bind(it)
|
||||||
|
|
||||||
binding.title.text = meta.title ?: context.stringResource(MR.strings.unknown)
|
binding.title.text = meta.title ?: context.stringResource(MR.strings.unknown)
|
||||||
|
binding.title.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
binding.title.setOnLongClickListener {
|
binding.title.setOnLongClickListener {
|
||||||
context.copyToClipboard(
|
context.copyToClipboard(
|
||||||
|
|
@ -39,6 +45,7 @@ fun EightMusesDescription(state: State.Success, openMetadataViewer: () -> Unit)
|
||||||
)
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
binding.title.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.setOnClickListener {
|
binding.moreInfo.setOnClickListener {
|
||||||
openMetadataViewer()
|
openMetadataViewer()
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -18,6 +20,8 @@ import tachiyomi.i18n.sy.SYMR
|
||||||
@Composable
|
@Composable
|
||||||
fun HBrowseDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun HBrowseDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -29,9 +33,11 @@ fun HBrowseDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val binding = DescriptionAdapterHbBinding.bind(it)
|
val binding = DescriptionAdapterHbBinding.bind(it)
|
||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
|
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor)
|
||||||
|
binding.pages.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
binding.pages.setOnLongClickListener {
|
binding.pages.setOnLongClickListener {
|
||||||
context.copyToClipboard(
|
context.copyToClipboard(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package exh.ui.metadata.adapters
|
package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|
@ -20,6 +23,9 @@ import kotlin.math.round
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaDexDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun MangaDexDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
|
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -37,8 +43,12 @@ fun MangaDexDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + getRatingString(context, ratingFloat)
|
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + getRatingString(context, ratingFloat)
|
||||||
binding.rating.isVisible = ratingFloat != null
|
binding.rating.isVisible = ratingFloat != null
|
||||||
binding.ratingBar.isVisible = ratingFloat != null
|
binding.ratingBar.isVisible = ratingFloat != null
|
||||||
|
binding.ratingBar.supportProgressTintList = ColorStateList.valueOf(iconColor)
|
||||||
|
binding.ratingBar.supportSecondaryProgressTintList = ColorStateList.valueOf(ratingBarSecondaryColor)
|
||||||
|
binding.rating.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
binding.rating.setOnLongClickListener {
|
binding.rating.setOnLongClickListener {
|
||||||
context.copyToClipboard(
|
context.copyToClipboard(
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,13 @@ object MetadataUIUtil {
|
||||||
genreColor.color to context.stringResource(stringId)
|
genreColor.color to context.stringResource(stringId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TextView.bindDrawable(context: Context, @DrawableRes drawable: Int) {
|
fun TextView.bindDrawable(
|
||||||
|
context: Context,
|
||||||
|
@DrawableRes drawable: Int,
|
||||||
|
@ColorInt color: Int = context.getResourceColor(R.attr.colorAccent),
|
||||||
|
) {
|
||||||
ContextCompat.getDrawable(context, drawable)?.apply {
|
ContextCompat.getDrawable(context, drawable)?.apply {
|
||||||
setTint(context.getResourceColor(R.attr.colorAccent))
|
setTint(color)
|
||||||
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
|
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
|
||||||
setCompoundDrawables(this, null, null, null)
|
setCompoundDrawables(this, null, null, null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package exh.ui.metadata.adapters
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -25,6 +27,8 @@ import java.time.ZonedDateTime
|
||||||
@Composable
|
@Composable
|
||||||
fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -45,11 +49,13 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
it.second
|
it.second
|
||||||
} ?: categoriesString ?: context.stringResource(MR.strings.unknown)
|
} ?: categoriesString ?: context.stringResource(MR.strings.unknown)
|
||||||
}
|
}
|
||||||
|
binding.genre.setTextColor(textColor)
|
||||||
|
|
||||||
meta.favoritesCount?.let {
|
meta.favoritesCount?.let {
|
||||||
if (it == 0L) return@let
|
if (it == 0L) return@let
|
||||||
binding.favorites.text = it.toString()
|
binding.favorites.text = it.toString()
|
||||||
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp)
|
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp, iconColor)
|
||||||
|
binding.favorites.setTextColor(textColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT
|
binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT
|
||||||
|
|
@ -57,18 +63,22 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
ZonedDateTime
|
ZonedDateTime
|
||||||
.ofInstant(Instant.ofEpochSecond(meta.uploadDate ?: 0), ZoneId.systemDefault())
|
.ofInstant(Instant.ofEpochSecond(meta.uploadDate ?: 0), ZoneId.systemDefault())
|
||||||
)
|
)
|
||||||
|
binding.whenPosted.setTextColor(textColor)
|
||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(
|
binding.pages.text = context.pluralStringResource(
|
||||||
SYMR.plurals.num_pages,
|
SYMR.plurals.num_pages,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImageTypes.size,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImageTypes.size,
|
||||||
)
|
)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor)
|
||||||
|
binding.pages.setTextColor(textColor)
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
binding.id.text = "#" + (meta.nhId ?: 0)
|
binding.id.text = "#" + (meta.nhId ?: 0)
|
||||||
|
binding.id.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
binding.favorites,
|
binding.favorites,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package exh.ui.metadata.adapters
|
package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -22,6 +25,9 @@ import kotlin.math.round
|
||||||
@Composable
|
@Composable
|
||||||
fun PururinDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun PururinDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
|
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -38,21 +44,29 @@ fun PururinDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
it.second
|
it.second
|
||||||
} ?: genre?.name ?: context.stringResource(MR.strings.unknown)
|
} ?: genre?.name ?: context.stringResource(MR.strings.unknown)
|
||||||
}
|
}
|
||||||
|
binding.genre.setTextColor(textColor)
|
||||||
|
|
||||||
binding.uploader.text = meta.uploaderDisp ?: meta.uploader.orEmpty()
|
binding.uploader.text = meta.uploaderDisp ?: meta.uploader.orEmpty()
|
||||||
|
binding.uploader.setTextColor(textColor)
|
||||||
|
|
||||||
binding.size.text = meta.fileSize ?: context.stringResource(MR.strings.unknown)
|
binding.size.text = meta.fileSize ?: context.stringResource(MR.strings.unknown)
|
||||||
binding.size.bindDrawable(context, R.drawable.ic_outline_sd_card_24)
|
binding.size.bindDrawable(context, R.drawable.ic_outline_sd_card_24, iconColor)
|
||||||
|
binding.size.setTextColor(textColor)
|
||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.pages ?: 0, meta.pages ?: 0)
|
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.pages ?: 0, meta.pages ?: 0)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor)
|
||||||
|
binding.pages.setTextColor(textColor)
|
||||||
|
|
||||||
val ratingFloat = meta.averageRating?.toFloat()
|
val ratingFloat = meta.averageRating?.toFloat()
|
||||||
binding.ratingBar.rating = ratingFloat ?: 0F
|
binding.ratingBar.rating = ratingFloat ?: 0F
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
|
||||||
|
binding.ratingBar.supportProgressTintList = ColorStateList.valueOf(iconColor)
|
||||||
|
binding.ratingBar.supportSecondaryProgressTintList = ColorStateList.valueOf(ratingBarSecondaryColor)
|
||||||
|
binding.rating.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
binding.genre,
|
binding.genre,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package exh.ui.metadata.adapters
|
package exh.ui.metadata.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
@ -23,6 +26,9 @@ import kotlin.math.round
|
||||||
@Composable
|
@Composable
|
||||||
fun TsuminoDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
fun TsuminoDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
|
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { factoryContext ->
|
factory = { factoryContext ->
|
||||||
|
|
@ -37,22 +43,31 @@ fun TsuminoDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
binding.genre.setBackgroundColor(it.first)
|
binding.genre.setBackgroundColor(it.first)
|
||||||
it.second
|
it.second
|
||||||
} ?: meta.category ?: context.stringResource(MR.strings.unknown)
|
} ?: meta.category ?: context.stringResource(MR.strings.unknown)
|
||||||
|
binding.genre.setTextColor(textColor)
|
||||||
|
|
||||||
binding.favorites.text = (meta.favorites ?: 0).toString()
|
binding.favorites.text = (meta.favorites ?: 0).toString()
|
||||||
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp)
|
binding.favorites.bindDrawable(context, R.drawable.ic_book_24dp, iconColor)
|
||||||
|
binding.favorites.setTextColor(textColor)
|
||||||
|
|
||||||
binding.whenPosted.text = TsuminoSearchMetadata.TSUMINO_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
|
binding.whenPosted.text = TsuminoSearchMetadata.TSUMINO_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
|
||||||
|
binding.whenPosted.setTextColor(textColor)
|
||||||
|
|
||||||
binding.uploader.text = meta.uploader ?: context.stringResource(MR.strings.unknown)
|
binding.uploader.text = meta.uploader ?: context.stringResource(MR.strings.unknown)
|
||||||
|
binding.uploader.setTextColor(textColor)
|
||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
|
binding.pages.text = context.pluralStringResource(SYMR.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24)
|
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor)
|
||||||
|
binding.pages.setTextColor(textColor)
|
||||||
|
|
||||||
binding.ratingBar.rating = meta.averageRating ?: 0F
|
binding.ratingBar.rating = meta.averageRating ?: 0F
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.averageRating?.times(2))
|
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.averageRating?.times(2))
|
||||||
|
binding.ratingBar.supportProgressTintList = ColorStateList.valueOf(iconColor)
|
||||||
|
binding.ratingBar.supportSecondaryProgressTintList = ColorStateList.valueOf(ratingBarSecondaryColor)
|
||||||
|
binding.rating.setTextColor(textColor)
|
||||||
|
|
||||||
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)
|
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp, iconColor)
|
||||||
|
binding.moreInfo.setTextColor(textColor)
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
binding.favorites,
|
binding.favorites,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,24 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingHorizontal="24dp"
|
android:paddingHorizontal="24dp"
|
||||||
android:paddingVertical="16dp">
|
android:paddingVertical="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alertTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/text_field"
|
android:id="@+id/text_field"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -16,4 +26,25 @@
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
<LinearLayout
|
||||||
|
style="?android:attr/buttonBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/positiveButton"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/negativeButton"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:id="@+id/title_outline"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
|
@ -55,6 +56,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:id="@+id/manga_author_outline"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
|
@ -72,6 +74,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:id="@+id/manga_artist_outline"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
|
@ -89,6 +92,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:id="@+id/thumbnail_url_outline"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
|
@ -105,6 +109,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||||
|
android:id="@+id/manga_description_outline"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ class LibraryPreferences(
|
||||||
fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L)
|
fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L)
|
||||||
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
||||||
|
|
||||||
|
fun coverRatios() = preferenceStore.getStringSet("pref_library_cover_ratios_key", emptySet())
|
||||||
|
|
||||||
|
fun coverColors() = preferenceStore.getStringSet("pref_library_cover_colors_key", emptySet())
|
||||||
|
|
||||||
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
|
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
|
||||||
"library_update_restriction",
|
"library_update_restriction",
|
||||||
setOf(
|
setOf(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package tachiyomi.domain.manga.model
|
||||||
|
|
||||||
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the required data for MangaCoverFetcher
|
* Contains the required data for MangaCoverFetcher
|
||||||
|
|
@ -22,11 +23,68 @@ data class MangaCover(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val url: String? = customThumbnailUrl ?: ogUrl
|
val url: String? = customThumbnailUrl ?: ogUrl
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [vibrantCoverColor] is used to set the color theme in manga detail page.
|
||||||
|
* It contains color for all mangas, both in library or browsing.
|
||||||
|
*
|
||||||
|
* It reads/saves to a hashmap in [MangaCover.vibrantCoverColorMap] for multiple mangas.
|
||||||
|
*/
|
||||||
|
var vibrantCoverColor: Int?
|
||||||
|
get() = vibrantCoverColorMap[mangaId]
|
||||||
|
set(value) {
|
||||||
|
vibrantCoverColorMap[mangaId] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [dominantCoverColors] is used to set cover/text's color in Library (Favorite) grid view.
|
||||||
|
* It contains only color for in-library (favorite) mangas.
|
||||||
|
*
|
||||||
|
* It reads/saves to a hashmap in [MangaCover.coverColorMap].
|
||||||
|
*
|
||||||
|
* Format: <first: cover color, second: text color>.
|
||||||
|
*
|
||||||
|
* Set in *[MangaCoverMetadata.setRatioAndColors]* whenever browsing meets a favorite manga
|
||||||
|
* by loading from *[CoverCache]*.
|
||||||
|
*
|
||||||
|
* Get in *[CommonMangaItem.MangaCompactGridItem]*, *[CommonMangaItem.MangaComfortableGridItem]* and
|
||||||
|
* *[CommonMangaItem.MangaListItem]*
|
||||||
|
*/
|
||||||
|
@Suppress("KDocUnresolvedReference")
|
||||||
|
var dominantCoverColors: Pair<Int, Int>?
|
||||||
|
get() = coverColorMap[mangaId]
|
||||||
|
set(value) {
|
||||||
|
value ?: return
|
||||||
|
coverColorMap[mangaId] = value.first to value.second
|
||||||
|
}
|
||||||
|
|
||||||
|
var ratio: Float?
|
||||||
|
get() = coverRatioMap[mangaId]
|
||||||
|
set(value) {
|
||||||
|
value ?: return
|
||||||
|
coverRatioMap[mangaId] = value
|
||||||
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private val getCustomMangaInfo: GetCustomMangaInfo by injectLazy()
|
private val getCustomMangaInfo: GetCustomMangaInfo by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [vibrantCoverColorMap] store color generated while browsing library.
|
||||||
|
* It always empty at beginning each time app starts, then add more color while browsing.
|
||||||
|
*/
|
||||||
|
val vibrantCoverColorMap: HashMap<Long, Int?> = hashMapOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [coverColorMap] stores favorite manga's cover & text's color as a joined string in Prefs.
|
||||||
|
* They will be loaded each time *[App]* is initialized with *[MangaCoverMetadata.load]*.
|
||||||
|
*
|
||||||
|
* They will be saved back when *[MainActivity.onPause]* is triggered.
|
||||||
|
*/
|
||||||
|
@Suppress("KDocUnresolvedReference")
|
||||||
|
var coverColorMap = ConcurrentHashMap<Long, Pair<Int, Int>>()
|
||||||
|
|
||||||
|
var coverRatioMap = ConcurrentHashMap<Long, Float>()
|
||||||
}
|
}
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Manga.asMangaCover(): MangaCover {
|
fun Manga.asMangaCover(): MangaCover {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ sqlite = "2.4.0"
|
||||||
voyager = "1.0.0"
|
voyager = "1.0.0"
|
||||||
detekt = "1.23.6"
|
detekt = "1.23.6"
|
||||||
detektCompose = "0.3.12"
|
detektCompose = "0.3.12"
|
||||||
|
paletteKtxVersion = "1.0.0"
|
||||||
|
materialKolorVersion = "1.6.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
|
desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||||
|
|
@ -68,6 +70,8 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||||
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.2.0"
|
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.2.0"
|
||||||
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
|
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
|
||||||
compose-grid = "io.woong.compose.grid:grid:1.2.2"
|
compose-grid = "io.woong.compose.grid:grid:1.2.2"
|
||||||
|
palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtxVersion" }
|
||||||
|
material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolorVersion" }
|
||||||
|
|
||||||
swipe = "me.saket.swipe:swipe:1.3.0"
|
swipe = "me.saket.swipe:swipe:1.3.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,10 @@
|
||||||
<!-- "Today" instead of "2023-12-31" -->
|
<!-- "Today" instead of "2023-12-31" -->
|
||||||
<string name="pref_relative_format_summary">\"%1$s\" instead of \"%2$s\"</string>
|
<string name="pref_relative_format_summary">\"%1$s\" instead of \"%2$s\"</string>
|
||||||
<string name="pref_date_format">Date format</string>
|
<string name="pref_date_format">Date format</string>
|
||||||
|
<string name="pref_details_page_theme">Details page</string>
|
||||||
|
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
|
||||||
|
<string name="pref_theme_cover_based_style">Theme style</string>
|
||||||
|
<string name="pref_theme_cover_based_animate">Theme animate</string>
|
||||||
|
|
||||||
<string name="pref_manage_notifications">Manage notifications</string>
|
<string name="pref_manage_notifications">Manage notifications</string>
|
||||||
<string name="pref_app_language">App language</string>
|
<string name="pref_app_language">App language</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue