mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 12:17:12 +03:00
parent
4709cbedba
commit
645746aa43
94 changed files with 499 additions and 511 deletions
|
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
|
|||
|
||||
override fun key() = "extension_installer"
|
||||
|
||||
val entries get() = ExtensionInstaller.values().run {
|
||||
val entries get() = ExtensionInstaller.entries.run {
|
||||
if (context.hasMiuiPackageInstaller) {
|
||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||
} else {
|
||||
|
|
|
@ -73,8 +73,8 @@ class SetReadStatus(
|
|||
await(manga.id, read)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoChapters : Result()
|
||||
data object Success : Result()
|
||||
data object NoChapters : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ class SetSeenStatus(
|
|||
await(anime.id, seen)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoEpisodes : Result()
|
||||
data object Success : Result()
|
||||
data object NoEpisodes : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.anime
|
|||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
|
@ -53,7 +53,7 @@ private fun AnimeExtensionFilterContent(
|
|||
onClickLang: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(state.languages) { language ->
|
||||
|
|
|
@ -11,15 +11,15 @@ import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchCardRow
|
|||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.GlobalAnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun GlobalAnimeSearchScreen(
|
||||
state: GlobalAnimeSearchScreenModel.State,
|
||||
state: AnimeSearchScreenModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
|
|
|
@ -4,14 +4,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.search.MigrateAnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun MigrateAnimeSearchScreen(
|
||||
state: MigrateAnimeSearchScreenModel.State,
|
||||
state: AnimeSearchScreenModel.State,
|
||||
fromSourceId: Long?,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
|
@ -40,7 +41,7 @@ fun MigrateAnimeSearchScreen(
|
|||
},
|
||||
) { paddingValues ->
|
||||
GlobalSearchContent(
|
||||
fromSourceId = state.anime?.source ?: -1,
|
||||
fromSourceId = fromSourceId,
|
||||
items = state.filteredItems,
|
||||
contentPadding = paddingValues,
|
||||
getAnime = getAnime,
|
||||
|
|
|
@ -142,7 +142,7 @@ private fun AnimeExtension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT
|
|||
}
|
||||
|
||||
sealed class Result<out T> {
|
||||
object Loading : Result<Nothing>()
|
||||
object Error : Result<Nothing>()
|
||||
data object Loading : Result<Nothing>()
|
||||
data object Error : Result<Nothing>()
|
||||
data class Success<out T>(val value: T) : Result<T>()
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ private fun AnimeItem(
|
|||
Box(modifier = Modifier.width(96.dp)) {
|
||||
EntryComfortableGridItem(
|
||||
title = title,
|
||||
titleMaxLines = 3,
|
||||
coverData = cover,
|
||||
coverBadgeStart = {
|
||||
InLibraryBadge(enabled = isFavorite)
|
||||
|
|
|
@ -56,7 +56,7 @@ fun GlobalAnimeSearchToolbar(
|
|||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
if (progress in 1 until total) {
|
||||
if (progress in 1..< total) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress / total.toFloat(),
|
||||
modifier = Modifier
|
||||
|
|
|
@ -10,8 +10,8 @@ import eu.kanade.presentation.browse.GlobalSearchResultItem
|
|||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchCardRow
|
||||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.GlobalMangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
|
@ -19,7 +19,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||
|
||||
@Composable
|
||||
fun GlobalMangaSearchScreen(
|
||||
state: GlobalMangaSearchScreenModel.State,
|
||||
state: MangaSearchScreenModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.manga
|
|||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
|
@ -53,7 +53,7 @@ private fun ExtensionFilterContent(
|
|||
onClickLang: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(state.languages) { language ->
|
||||
|
|
|
@ -4,14 +4,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.migration.search.MigrateSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSourceFilter
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun MigrateMangaSearchScreen(
|
||||
state: MigrateSearchScreenModel.State,
|
||||
state: MangaSearchScreenModel.State,
|
||||
fromSourceId: Long?,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
|
@ -40,7 +41,7 @@ fun MigrateMangaSearchScreen(
|
|||
},
|
||||
) { paddingValues ->
|
||||
GlobalSearchContent(
|
||||
fromSourceId = state.manga?.source,
|
||||
fromSourceId = fromSourceId,
|
||||
items = state.filteredItems,
|
||||
contentPadding = paddingValues,
|
||||
getManga = getManga,
|
||||
|
|
|
@ -142,7 +142,7 @@ private fun MangaExtension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT
|
|||
}
|
||||
|
||||
sealed class Result<out T> {
|
||||
object Loading : Result<Nothing>()
|
||||
object Error : Result<Nothing>()
|
||||
data object Loading : Result<Nothing>()
|
||||
data object Error : Result<Nothing>()
|
||||
data class Success<out T>(val value: T) : Result<T>()
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ private fun MangaItem(
|
|||
Box(modifier = Modifier.width(96.dp)) {
|
||||
EntryComfortableGridItem(
|
||||
title = title,
|
||||
titleMaxLines = 3,
|
||||
coverData = cover,
|
||||
coverBadgeStart = {
|
||||
InLibraryBadge(enabled = isFavorite)
|
||||
|
|
|
@ -56,7 +56,7 @@ fun GlobalMangaSearchToolbar(
|
|||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
if (progress in 1 until total) {
|
||||
if (progress in 1..<total) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress / total.toFloat(),
|
||||
modifier = Modifier
|
||||
|
|
|
@ -96,7 +96,7 @@ fun EntryBottomActionMenu(
|
|||
var resetJob: Job? = remember { null }
|
||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
(0 until 9).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
(0..<9).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
resetJob?.cancel()
|
||||
resetJob = scope.launch {
|
||||
delay(1.seconds)
|
||||
|
@ -273,7 +273,7 @@ fun LibraryBottomActionMenu(
|
|||
var resetJob: Job? = remember { null }
|
||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
(0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
(0 ..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
resetJob?.cancel()
|
||||
resetJob = scope.launch {
|
||||
delay(1.seconds)
|
||||
|
|
|
@ -164,6 +164,7 @@ private fun BoxScope.CoverTextOverlay(
|
|||
fun EntryComfortableGridItem(
|
||||
isSelected: Boolean = false,
|
||||
title: String,
|
||||
titleMaxLines: Int = 2,
|
||||
coverData: EntryCover,
|
||||
coverAlpha: Float = 1f,
|
||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||
|
@ -205,6 +206,7 @@ fun EntryComfortableGridItem(
|
|||
title = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
minLines = 2,
|
||||
maxLines = titleMaxLines,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +256,7 @@ private fun GridItemTitle(
|
|||
title: String,
|
||||
style: TextStyle,
|
||||
minLines: Int,
|
||||
maxLines: Int = 2,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
|
@ -261,7 +264,7 @@ private fun GridItemTitle(
|
|||
fontSize = 12.sp,
|
||||
lineHeight = 18.sp,
|
||||
minLines = minLines,
|
||||
maxLines = 2,
|
||||
maxLines = maxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = style,
|
||||
)
|
||||
|
|
|
@ -169,7 +169,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||
Preference.PreferenceItem.ListPreference(
|
||||
pref = uiPreferences.tabletUiMode(),
|
||||
title = stringResource(R.string.pref_tablet_ui_mode),
|
||||
entries = TabletUiMode.values().associateWith { stringResource(it.titleResId) },
|
||||
entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
|
||||
onValueChanged = {
|
||||
context.toast(R.string.requires_app_restart)
|
||||
true
|
||||
|
@ -213,7 +213,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
for (i in 0..<parser.attributeCount) {
|
||||
if (parser.getAttributeName(i) == "name") {
|
||||
val langTag = parser.getAttributeValue(i)
|
||||
val displayName = LocaleHelper.getDisplayName(langTag)
|
||||
|
|
|
@ -33,7 +33,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
Preference.PreferenceItem.ListPreference(
|
||||
pref = readerPref.defaultReadingMode(),
|
||||
title = stringResource(R.string.pref_viewer_type),
|
||||
entries = ReadingModeType.values().drop(1)
|
||||
entries = ReadingModeType.entries.drop(1)
|
||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
|
@ -84,7 +84,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
Preference.PreferenceItem.ListPreference(
|
||||
pref = readerPreferences.defaultOrientationType(),
|
||||
title = stringResource(R.string.pref_rotation_type),
|
||||
entries = OrientationType.values().drop(1)
|
||||
entries = OrientationType.entries.drop(1)
|
||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
|
|
|
@ -70,7 +70,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
Preference.PreferenceItem.ListPreference(
|
||||
pref = securityPreferences.secureScreen(),
|
||||
title = stringResource(R.string.secure_screen),
|
||||
entries = SecurityPreferences.SecureScreenMode.values()
|
||||
entries = SecurityPreferences.SecureScreenMode.entries
|
||||
.associateWith { stringResource(it.titleResId) },
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)),
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.FlipToBack
|
||||
|
@ -47,7 +48,6 @@ import tachiyomi.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAni
|
|||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.model.AnimeSourceWithCount
|
||||
import tachiyomi.mi.data.AnimeDatabase
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
@ -135,7 +135,7 @@ class ClearAnimeDatabaseScreen : Screen() {
|
|||
.padding(contentPadding)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
items(s.items) { sourceWithCount ->
|
||||
|
@ -270,7 +270,7 @@ private class ClearAnimeDatabaseScreenModel : StateScreenModel<ClearAnimeDatabas
|
|||
}
|
||||
|
||||
sealed class State {
|
||||
object Loading : State()
|
||||
data object Loading : State()
|
||||
data class Ready(
|
||||
val items: List<AnimeSourceWithCount>,
|
||||
val selection: List<Long> = emptyList(),
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.FlipToBack
|
||||
|
@ -47,7 +48,6 @@ import tachiyomi.data.Database
|
|||
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
||||
import tachiyomi.domain.source.manga.model.MangaSourceWithCount
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
@ -135,7 +135,7 @@ class ClearDatabaseScreen : Screen() {
|
|||
.padding(contentPadding)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
items(s.items) { sourceWithCount ->
|
||||
|
@ -270,7 +270,7 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
|
|||
}
|
||||
|
||||
sealed class State {
|
||||
object Loading : State()
|
||||
data object Loading : State()
|
||||
data class Ready(
|
||||
val items: List<MangaSourceWithCount>,
|
||||
val selection: List<Long> = emptyList(),
|
||||
|
|
|
@ -75,7 +75,7 @@ private fun AppThemesList(
|
|||
onItemClick: (AppTheme) -> Unit,
|
||||
) {
|
||||
val appThemes = remember {
|
||||
AppTheme.values()
|
||||
AppTheme.entries
|
||||
.filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
||||
}
|
||||
LazyRow(
|
||||
|
|
|
@ -5,7 +5,7 @@ import eu.kanade.presentation.more.stats.data.StatsData
|
|||
|
||||
sealed class StatsScreenState {
|
||||
@Immutable
|
||||
object Loading : StatsScreenState()
|
||||
data object Loading : StatsScreenState()
|
||||
|
||||
@Immutable
|
||||
data class SuccessManga(
|
||||
|
|
|
@ -24,9 +24,9 @@ import tachiyomi.presentation.core.components.SettingsChipRow
|
|||
import tachiyomi.presentation.core.components.SliderItem
|
||||
import java.text.NumberFormat
|
||||
|
||||
private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it }
|
||||
private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it }
|
||||
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it }
|
||||
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
||||
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
||||
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it }
|
||||
|
||||
@Composable
|
||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||
|
|
|
@ -29,5 +29,5 @@ object AppInfo {
|
|||
*
|
||||
* @since extension-lib 1.5
|
||||
*/
|
||||
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.values().map { it.mime }
|
||||
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.entries.map { it.mime }
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ sealed class Location {
|
|||
}
|
||||
}
|
||||
|
||||
object Cache : Location()
|
||||
data object Cache : Location()
|
||||
|
||||
fun directory(context: Context): File {
|
||||
return when (this) {
|
||||
|
|
|
@ -67,7 +67,7 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
|
|||
|
||||
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
|
||||
val newFormBody = FormBody.Builder()
|
||||
for (i in 0 until oidFormBody.size) {
|
||||
for (i in 0..<oidFormBody.size) {
|
||||
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
|
||||
}
|
||||
newFormBody.add("access_token", token)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package eu.kanade.tachiyomi.extension.anime.model
|
||||
|
||||
sealed class AnimeLoadResult {
|
||||
class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult()
|
||||
class Untrusted(val extension: AnimeExtension.Untrusted) : AnimeLoadResult()
|
||||
object Error : AnimeLoadResult()
|
||||
data class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult()
|
||||
data class Untrusted(val extension: AnimeExtension.Untrusted) : AnimeLoadResult()
|
||||
data object Error : AnimeLoadResult()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package eu.kanade.tachiyomi.extension.manga.model
|
||||
|
||||
sealed class MangaLoadResult {
|
||||
class Success(val extension: MangaExtension.Installed) : MangaLoadResult()
|
||||
class Untrusted(val extension: MangaExtension.Untrusted) : MangaLoadResult()
|
||||
object Error : MangaLoadResult()
|
||||
data class Success(val extension: MangaExtension.Installed) : MangaLoadResult()
|
||||
data class Untrusted(val extension: MangaExtension.Untrusted) : MangaLoadResult()
|
||||
data object Error : MangaLoadResult()
|
||||
}
|
||||
|
|
|
@ -55,13 +55,13 @@ class AnimeExtensionFilterScreenModel(
|
|||
}
|
||||
|
||||
sealed class AnimeExtensionFilterEvent {
|
||||
object FailedFetchingLanguages : AnimeExtensionFilterEvent()
|
||||
data object FailedFetchingLanguages : AnimeExtensionFilterEvent()
|
||||
}
|
||||
|
||||
sealed class AnimeExtensionFilterState {
|
||||
|
||||
@Immutable
|
||||
object Loading : AnimeExtensionFilterState()
|
||||
data object Loading : AnimeExtensionFilterState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
|
|
|
@ -163,7 +163,7 @@ class AnimeExtensionDetailsScreenModel(
|
|||
}
|
||||
|
||||
sealed class AnimeExtensionDetailsEvent {
|
||||
object Uninstalled : AnimeExtensionDetailsEvent()
|
||||
data object Uninstalled : AnimeExtensionDetailsEvent()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -54,7 +54,7 @@ class MigrationAnimeScreenModel(
|
|||
}
|
||||
|
||||
sealed class MigrationAnimeEvent {
|
||||
object FailedFetchingFavorites : MigrationAnimeEvent()
|
||||
data object FailedFetchingFavorites : MigrationAnimeEvent()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.anime.migration.search
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnime
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class AnimeMigrateSearchScreenDialogScreenModel(
|
||||
val animeId: Long,
|
||||
getAnime: GetAnime = Injekt.get(),
|
||||
) : StateScreenModel<AnimeMigrateSearchScreenDialogScreenModel.State>(State()) {
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
val anime = getAnime.await(animeId)!!
|
||||
|
||||
mutableState.update {
|
||||
it.copy(anime = anime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update {
|
||||
it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val anime: Anime? = null,
|
||||
val dialog: Dialog? = null,
|
||||
)
|
||||
|
||||
sealed class Dialog {
|
||||
data class Migrate(val anime: Anime) : Dialog()
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import eu.kanade.presentation.browse.anime.MigrateAnimeSearchScreen
|
|||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreen
|
||||
|
||||
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||
class MigrateAnimeSearchScreen(private val animeId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
|
@ -20,28 +19,32 @@ class MigrateAnimeSearchScreen(private val animeId: Long) : Screen() {
|
|||
val screenModel = rememberScreenModel { MigrateAnimeSearchScreenModel(animeId = animeId) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
val dialogScreenModel = rememberScreenModel { AnimeMigrateSearchScreenDialogScreenModel(animeId = animeId) }
|
||||
val dialogState by dialogScreenModel.state.collectAsState()
|
||||
|
||||
MigrateAnimeSearchScreen(
|
||||
state = state,
|
||||
fromSourceId = dialogState.anime?.source,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
onSearch = { screenModel.search() },
|
||||
getAnime = { screenModel.getAnime(it) },
|
||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||
onToggleResults = screenModel::toggleFilterResults,
|
||||
onClickSource = {
|
||||
navigator.push(AnimeSourceSearchScreen(state.anime!!, it.id, state.searchQuery))
|
||||
navigator.push(AnimeSourceSearchScreen(dialogState.anime!!, it.id, state.searchQuery))
|
||||
},
|
||||
onClickItem = { screenModel.setDialog((MigrateAnimeSearchScreenModel.Dialog.Migrate(it))) },
|
||||
onClickItem = { dialogScreenModel.setDialog((AnimeMigrateSearchScreenDialogScreenModel.Dialog.Migrate(it))) },
|
||||
onLongClickItem = { navigator.push(AnimeScreen(it.id, true)) },
|
||||
)
|
||||
|
||||
when (val dialog = state.dialog) {
|
||||
is MigrateAnimeSearchScreenModel.Dialog.Migrate -> {
|
||||
when (val dialog = dialogState.dialog) {
|
||||
is AnimeMigrateSearchScreenDialogScreenModel.Dialog.Migrate -> {
|
||||
MigrateAnimeDialog(
|
||||
oldAnime = state.anime!!,
|
||||
oldAnime = dialogState.anime!!,
|
||||
newAnime = dialog.anime,
|
||||
screenModel = rememberScreenModel { MigrateAnimeDialogScreenModel() },
|
||||
onDismissRequest = { screenModel.setDialog(null) },
|
||||
onDismissRequest = { dialogScreenModel.setDialog(null) },
|
||||
onClickTitle = {
|
||||
navigator.push(AnimeScreen(dialog.anime.id, true))
|
||||
},
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.anime.migration.search
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnime
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
|
@ -17,81 +14,32 @@ class MigrateAnimeSearchScreenModel(
|
|||
val animeId: Long,
|
||||
initialExtensionFilter: String = "",
|
||||
getAnime: GetAnime = Injekt.get(),
|
||||
) : AnimeSearchScreenModel<MigrateAnimeSearchScreenModel.State>(State()) {
|
||||
) : AnimeSearchScreenModel() {
|
||||
|
||||
init {
|
||||
extensionFilter = initialExtensionFilter
|
||||
coroutineScope.launch {
|
||||
val anime = getAnime.await(animeId)!!
|
||||
|
||||
mutableState.update {
|
||||
it.copy(anime = anime, searchQuery = anime.title)
|
||||
it.copy(
|
||||
fromSourceId = anime.source,
|
||||
searchQuery = anime.title,
|
||||
)
|
||||
}
|
||||
|
||||
search(anime.title)
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<AnimeCatalogueSource> {
|
||||
return super.getEnabledSources()
|
||||
.filter { mutableState.value.sourceFilter != AnimeSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
.filter { state.value.sourceFilter != AnimeSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.id != state.value.anime!!.source },
|
||||
{ it.id != state.value.fromSourceId },
|
||||
{ "${it.id}" !in pinnedSources },
|
||||
{ "${it.name.lowercase()} (${it.lang})" },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSearchQuery(query: String?) {
|
||||
mutableState.update {
|
||||
it.copy(searchQuery = query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateItems(items: Map<AnimeCatalogueSource, AnimeSearchItemResult>) {
|
||||
mutableState.update {
|
||||
it.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItems(): Map<AnimeCatalogueSource, AnimeSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
|
||||
override fun setSourceFilter(filter: AnimeSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
override fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update {
|
||||
it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val anime: Anime? = null,
|
||||
val dialog: Dialog? = null,
|
||||
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: AnimeSourceFilter = AnimeSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<AnimeCatalogueSource, AnimeSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is AnimeSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
data class Migrate(val anime: Anime) : Dialog()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ class MigrateAnimeSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object FailedFetchingSourcesWithCount : Event()
|
||||
data object FailedFetchingSourcesWithCount : Event()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ class AnimeSourcesScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object FailedFetchingSources : Event()
|
||||
data object FailedFetchingSources : Event()
|
||||
}
|
||||
|
||||
data class Dialog(val source: AnimeSource)
|
||||
|
|
|
@ -358,8 +358,8 @@ class BrowseAnimeSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Listing(open val query: String?, open val filters: AnimeFilterList) {
|
||||
object Popular : Listing(query = GetRemoteAnime.QUERY_POPULAR, filters = AnimeFilterList())
|
||||
object Latest : Listing(query = GetRemoteAnime.QUERY_LATEST, filters = AnimeFilterList())
|
||||
data object Popular : Listing(query = GetRemoteAnime.QUERY_POPULAR, filters = AnimeFilterList())
|
||||
data object Latest : Listing(query = GetRemoteAnime.QUERY_LATEST, filters = AnimeFilterList())
|
||||
data class Search(override val query: String?, override val filters: AnimeFilterList) : Listing(query = query, filters = filters)
|
||||
|
||||
companion object {
|
||||
|
@ -374,7 +374,7 @@ class BrowseAnimeSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object Filter : Dialog()
|
||||
data object Filter : Dialog()
|
||||
data class RemoveAnime(val anime: Anime) : Dialog()
|
||||
data class AddDuplicateAnime(val anime: Anime, val duplicate: Anime) : Dialog()
|
||||
data class ChangeAnimeCategory(
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.produceState
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
||||
import eu.kanade.domain.entries.anime.model.toDomainAnime
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
|
@ -16,6 +15,8 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
|
@ -27,25 +28,27 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
abstract class AnimeSearchScreenModel<T>(
|
||||
initialState: T,
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
abstract class AnimeSearchScreenModel(
|
||||
initialState: State = State(),
|
||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
||||
private val extensionManager: AnimeExtensionManager = Injekt.get(),
|
||||
private val networkToLocalAnime: NetworkToLocalAnime = Injekt.get(),
|
||||
private val getAnime: GetAnime = Injekt.get(),
|
||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
||||
) : StateScreenModel<T>(initialState) {
|
||||
) : StateScreenModel<AnimeSearchScreenModel.State>(initialState) {
|
||||
|
||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||
private var searchJob: Job? = null
|
||||
|
||||
protected var query: String? = null
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
private val sources by lazy { getSelectedSources() }
|
||||
private val enabledLanguages = sourcePreferences.enabledLanguages().get()
|
||||
private val disabledSources = sourcePreferences.disabledAnimeSources().get()
|
||||
protected val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
|
||||
|
||||
private var lastQuery: String? = null
|
||||
private var lastSourceFilter: AnimeSourceFilter? = null
|
||||
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
private val sortComparator = { map: Map<AnimeCatalogueSource, AnimeSearchItemResult> ->
|
||||
compareBy<AnimeCatalogueSource>(
|
||||
{ (map[it] as? AnimeSearchItemResult.Success)?.isEmpty ?: true },
|
||||
|
@ -55,7 +58,7 @@ abstract class AnimeSearchScreenModel<T>(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun getAnime(initialAnime: Anime): State<Anime> {
|
||||
fun getAnime(initialAnime: Anime): androidx.compose.runtime.State<Anime> {
|
||||
return produceState(initialValue = initialAnime) {
|
||||
getAnime.subscribe(initialAnime.url, initialAnime.source)
|
||||
.filterNotNull()
|
||||
|
@ -66,10 +69,6 @@ abstract class AnimeSearchScreenModel<T>(
|
|||
}
|
||||
|
||||
open fun getEnabledSources(): List<AnimeCatalogueSource> {
|
||||
val enabledLanguages = sourcePreferences.enabledLanguages().get()
|
||||
val disabledSources = sourcePreferences.disabledAnimeSources().get()
|
||||
val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
|
||||
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { it.lang in enabledLanguages && "${it.id}" !in disabledSources }
|
||||
.sortedWith(
|
||||
|
@ -95,56 +94,91 @@ abstract class AnimeSearchScreenModel<T>(
|
|||
.filter { it in enabledSources }
|
||||
}
|
||||
|
||||
abstract fun updateSearchQuery(query: String?)
|
||||
|
||||
abstract fun updateItems(items: Map<AnimeCatalogueSource, AnimeSearchItemResult>)
|
||||
|
||||
abstract fun getItems(): Map<AnimeCatalogueSource, AnimeSearchItemResult>
|
||||
|
||||
private fun getAndUpdateItems(function: (Map<AnimeCatalogueSource, AnimeSearchItemResult>) -> Map<AnimeCatalogueSource, AnimeSearchItemResult>) {
|
||||
updateItems(function(getItems()))
|
||||
fun updateSearchQuery(query: String?) {
|
||||
mutableState.update { it.copy(searchQuery = query) }
|
||||
}
|
||||
|
||||
abstract fun setSourceFilter(filter: AnimeSourceFilter)
|
||||
fun setSourceFilter(filter: AnimeSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
search()
|
||||
}
|
||||
|
||||
abstract fun toggleFilterResults()
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) }
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
if (this.query == query) return
|
||||
fun search() {
|
||||
val query = state.value.searchQuery
|
||||
val sourceFilter = state.value.sourceFilter
|
||||
|
||||
this.query = query
|
||||
if (query.isNullOrBlank()) return
|
||||
val sameQuery = this.lastQuery == query
|
||||
if (sameQuery && this.lastSourceFilter == sourceFilter) return
|
||||
|
||||
this.lastQuery = query
|
||||
this.lastSourceFilter = sourceFilter
|
||||
|
||||
val sources = getSelectedSources()
|
||||
|
||||
// Reuse previous results if possible
|
||||
if (sameQuery) {
|
||||
val existingResults = state.value.items
|
||||
updateItems(sources.associateWith { existingResults[it] ?: AnimeSearchItemResult.Loading })
|
||||
} else {
|
||||
updateItems(sources.associateWith { AnimeSearchItemResult.Loading })
|
||||
}
|
||||
|
||||
val initialItems = getSelectedSources().associateWith { AnimeSearchItemResult.Loading }
|
||||
updateItems(initialItems)
|
||||
searchJob = ioCoroutineScope.launch {
|
||||
sources
|
||||
.map { source ->
|
||||
async {
|
||||
try {
|
||||
val page = withContext(coroutineDispatcher) {
|
||||
source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle()
|
||||
}
|
||||
sources.map { source ->
|
||||
async {
|
||||
if (state.value.items[source] !is AnimeSearchItemResult.Loading) {
|
||||
return@async
|
||||
}
|
||||
try {
|
||||
val page = withContext(coroutineDispatcher) {
|
||||
source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle()
|
||||
}
|
||||
|
||||
val titles = page.animes.map {
|
||||
networkToLocalAnime.await(it.toDomainAnime(source.id))
|
||||
}
|
||||
val titles = page.animes.map {
|
||||
networkToLocalAnime.await(it.toDomainAnime(source.id))
|
||||
}
|
||||
|
||||
getAndUpdateItems { items ->
|
||||
val mutableMap = items.toMutableMap()
|
||||
mutableMap[source] = AnimeSearchItemResult.Success(titles)
|
||||
mutableMap.toSortedMap(sortComparator(mutableMap))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
getAndUpdateItems { items ->
|
||||
val mutableMap = items.toMutableMap()
|
||||
mutableMap[source] = AnimeSearchItemResult.Error(e)
|
||||
mutableMap.toSortedMap(sortComparator(mutableMap))
|
||||
}
|
||||
if (isActive) {
|
||||
updateItem(source, AnimeSearchItemResult.Success(titles))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (isActive) {
|
||||
updateItem(source, AnimeSearchItemResult.Error(e))
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItems(items: Map<AnimeCatalogueSource, AnimeSearchItemResult>) {
|
||||
mutableState.update { it.copy(items = items.toSortedMap(sortComparator(items))) }
|
||||
}
|
||||
|
||||
private fun updateItem(source: AnimeCatalogueSource, result: AnimeSearchItemResult) {
|
||||
val mutableItems = state.value.items.toMutableMap()
|
||||
mutableItems[source] = result
|
||||
updateItems(mutableItems)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val fromSourceId: Long? = null,
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: AnimeSourceFilter = AnimeSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<AnimeCatalogueSource, AnimeSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is AnimeSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
}
|
||||
}
|
||||
|
||||
enum class AnimeSourceFilter {
|
||||
|
@ -153,7 +187,7 @@ enum class AnimeSourceFilter {
|
|||
}
|
||||
|
||||
sealed class AnimeSearchItemResult {
|
||||
object Loading : AnimeSearchItemResult()
|
||||
data object Loading : AnimeSearchItemResult()
|
||||
|
||||
data class Error(
|
||||
val throwable: Throwable,
|
||||
|
|
|
@ -59,7 +59,7 @@ class GlobalAnimeSearchScreen(
|
|||
state = state,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
onSearch = { screenModel.search() },
|
||||
getAnime = { screenModel.getAnime(it) },
|
||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||
onToggleResults = screenModel::toggleFilterResults,
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class GlobalAnimeSearchScreenModel(
|
||||
initialQuery: String = "",
|
||||
initialExtensionFilter: String? = null,
|
||||
) : AnimeSearchScreenModel<GlobalAnimeSearchScreenModel.State>(
|
||||
) : AnimeSearchScreenModel(
|
||||
State(
|
||||
searchQuery = initialQuery,
|
||||
),
|
||||
|
@ -17,50 +15,16 @@ class GlobalAnimeSearchScreenModel(
|
|||
init {
|
||||
extensionFilter = initialExtensionFilter
|
||||
if (initialQuery.isNotBlank() || !initialExtensionFilter.isNullOrBlank()) {
|
||||
search(initialQuery)
|
||||
if (extensionFilter != null) {
|
||||
// we're going to use custom extension filter instead
|
||||
setSourceFilter(AnimeSourceFilter.All)
|
||||
}
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<AnimeCatalogueSource> {
|
||||
return super.getEnabledSources()
|
||||
.filter { mutableState.value.sourceFilter != AnimeSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
}
|
||||
|
||||
override fun updateSearchQuery(query: String?) {
|
||||
mutableState.update {
|
||||
it.copy(searchQuery = query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateItems(items: Map<AnimeCatalogueSource, AnimeSearchItemResult>) {
|
||||
mutableState.update {
|
||||
it.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItems(): Map<AnimeCatalogueSource, AnimeSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
|
||||
override fun setSourceFilter(filter: AnimeSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
override fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: AnimeSourceFilter = AnimeSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<AnimeCatalogueSource, AnimeSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is AnimeSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
.filter { state.value.sourceFilter != AnimeSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,13 +55,13 @@ class MangaExtensionFilterScreenModel(
|
|||
}
|
||||
|
||||
sealed class MangaExtensionFilterEvent {
|
||||
object FailedFetchingLanguages : MangaExtensionFilterEvent()
|
||||
data object FailedFetchingLanguages : MangaExtensionFilterEvent()
|
||||
}
|
||||
|
||||
sealed class MangaExtensionFilterState {
|
||||
|
||||
@Immutable
|
||||
object Loading : MangaExtensionFilterState()
|
||||
data object Loading : MangaExtensionFilterState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
|
|
|
@ -163,7 +163,7 @@ class MangaExtensionDetailsScreenModel(
|
|||
}
|
||||
|
||||
sealed class MangaExtensionDetailsEvent {
|
||||
object Uninstalled : MangaExtensionDetailsEvent()
|
||||
data object Uninstalled : MangaExtensionDetailsEvent()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -54,7 +54,7 @@ class MigrationMangaScreenModel(
|
|||
}
|
||||
|
||||
sealed class MigrationMangaEvent {
|
||||
object FailedFetchingFavorites : MigrationMangaEvent()
|
||||
data object FailedFetchingFavorites : MigrationMangaEvent()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.manga.migration.search
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaMigrateSearchScreenDialogScreenModel(
|
||||
val mangaId: Long,
|
||||
getManga: GetManga = Injekt.get(),
|
||||
) : StateScreenModel<MangaMigrateSearchScreenDialogScreenModel.State>(State()) {
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
val manga = getManga.await(mangaId)!!
|
||||
|
||||
mutableState.update {
|
||||
it.copy(manga = manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update {
|
||||
it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val dialog: Dialog? = null,
|
||||
)
|
||||
|
||||
sealed class Dialog {
|
||||
data class Migrate(val manga: Manga) : Dialog()
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import eu.kanade.presentation.browse.manga.MigrateMangaSearchScreen
|
|||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.MangaScreen
|
||||
|
||||
// TODO: this should probably be merged with GlobalSearchScreen somehow to dedupe logic
|
||||
class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
||||
|
||||
@Composable
|
||||
|
@ -20,28 +19,32 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
|||
val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
val dialogScreenModel = rememberScreenModel { MangaMigrateSearchScreenDialogScreenModel(mangaId = mangaId) }
|
||||
val dialogState by dialogScreenModel.state.collectAsState()
|
||||
|
||||
MigrateMangaSearchScreen(
|
||||
state = state,
|
||||
fromSourceId = dialogState.manga?.source,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
onSearch = { screenModel.search() },
|
||||
getManga = { screenModel.getManga(it) },
|
||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||
onToggleResults = screenModel::toggleFilterResults,
|
||||
onClickSource = {
|
||||
navigator.push(MangaSourceSearchScreen(state.manga!!, it.id, state.searchQuery))
|
||||
navigator.push(MangaSourceSearchScreen(dialogState.manga!!, it.id, state.searchQuery))
|
||||
},
|
||||
onClickItem = { screenModel.setDialog(MigrateSearchScreenModel.Dialog.Migrate(it)) },
|
||||
onClickItem = { dialogScreenModel.setDialog(MangaMigrateSearchScreenDialogScreenModel.Dialog.Migrate(it)) },
|
||||
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||
)
|
||||
|
||||
when (val dialog = state.dialog) {
|
||||
is MigrateSearchScreenModel.Dialog.Migrate -> {
|
||||
when (val dialog = dialogState.dialog) {
|
||||
is MangaMigrateSearchScreenDialogScreenModel.Dialog.Migrate -> {
|
||||
MigrateMangaDialog(
|
||||
oldManga = state.manga!!,
|
||||
oldManga = dialogState.manga!!,
|
||||
newManga = dialog.manga,
|
||||
screenModel = rememberScreenModel { MigrateMangaDialogScreenModel() },
|
||||
onDismissRequest = { screenModel.setDialog(null) },
|
||||
onDismissRequest = { dialogScreenModel.setDialog(null) },
|
||||
onClickTitle = {
|
||||
navigator.push(MangaScreen(dialog.manga.id, true))
|
||||
},
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.manga.migration.search
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSourceFilter
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
|
@ -17,81 +14,32 @@ class MigrateSearchScreenModel(
|
|||
val mangaId: Long,
|
||||
initialExtensionFilter: String = "",
|
||||
getManga: GetManga = Injekt.get(),
|
||||
) : MangaSearchScreenModel<MigrateSearchScreenModel.State>(State()) {
|
||||
) : MangaSearchScreenModel() {
|
||||
|
||||
init {
|
||||
extensionFilter = initialExtensionFilter
|
||||
coroutineScope.launch {
|
||||
val manga = getManga.await(mangaId)!!
|
||||
|
||||
mutableState.update {
|
||||
it.copy(manga = manga, searchQuery = manga.title)
|
||||
it.copy(
|
||||
fromSourceId = manga.source,
|
||||
searchQuery = manga.title,
|
||||
)
|
||||
}
|
||||
|
||||
search(manga.title)
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
return super.getEnabledSources()
|
||||
.filter { mutableState.value.sourceFilter != MangaSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
.filter { state.value.sourceFilter != MangaSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.id != state.value.manga!!.source },
|
||||
{ it.id != state.value.fromSourceId },
|
||||
{ "${it.id}" !in pinnedSources },
|
||||
{ "${it.name.lowercase()} (${it.lang})" },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSearchQuery(query: String?) {
|
||||
mutableState.update {
|
||||
it.copy(searchQuery = query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateItems(items: Map<CatalogueSource, MangaSearchItemResult>) {
|
||||
mutableState.update {
|
||||
it.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItems(): Map<CatalogueSource, MangaSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
|
||||
override fun setSourceFilter(filter: MangaSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
override fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update {
|
||||
it.copy(dialog = dialog)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val dialog: Dialog? = null,
|
||||
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: MangaSourceFilter = MangaSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, MangaSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is MangaSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
data class Migrate(val manga: Manga) : Dialog()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ class MigrateSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object FailedFetchingSourcesWithCount : Event()
|
||||
data object FailedFetchingSourcesWithCount : Event()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ class MangaSourcesScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object FailedFetchingSources : Event()
|
||||
data object FailedFetchingSources : Event()
|
||||
}
|
||||
|
||||
data class Dialog(val source: Source)
|
||||
|
|
|
@ -352,8 +352,8 @@ class BrowseMangaSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Listing(open val query: String?, open val filters: FilterList) {
|
||||
object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
||||
object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
||||
data object Popular : Listing(query = GetRemoteManga.QUERY_POPULAR, filters = FilterList())
|
||||
data object Latest : Listing(query = GetRemoteManga.QUERY_LATEST, filters = FilterList())
|
||||
data class Search(override val query: String?, override val filters: FilterList) : Listing(query = query, filters = filters)
|
||||
|
||||
companion object {
|
||||
|
@ -368,7 +368,7 @@ class BrowseMangaSourceScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object Filter : Dialog()
|
||||
data object Filter : Dialog()
|
||||
data class RemoveManga(val manga: Manga) : Dialog()
|
||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
|
||||
data class ChangeMangaCategory(
|
||||
|
|
|
@ -59,7 +59,7 @@ class GlobalMangaSearchScreen(
|
|||
state = state,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
onSearch = { screenModel.search() },
|
||||
getManga = { screenModel.getManga(it) },
|
||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
||||
onToggleResults = screenModel::toggleFilterResults,
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class GlobalMangaSearchScreenModel(
|
||||
initialQuery: String = "",
|
||||
initialExtensionFilter: String? = null,
|
||||
) : MangaSearchScreenModel<GlobalMangaSearchScreenModel.State>(
|
||||
) : MangaSearchScreenModel(
|
||||
State(
|
||||
searchQuery = initialQuery,
|
||||
),
|
||||
|
@ -17,50 +15,16 @@ class GlobalMangaSearchScreenModel(
|
|||
init {
|
||||
extensionFilter = initialExtensionFilter
|
||||
if (initialQuery.isNotBlank() || !initialExtensionFilter.isNullOrBlank()) {
|
||||
search(initialQuery)
|
||||
if (extensionFilter != null) {
|
||||
// we're going to use custom extension filter instead
|
||||
setSourceFilter(MangaSourceFilter.All)
|
||||
}
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
return super.getEnabledSources()
|
||||
.filter { mutableState.value.sourceFilter != MangaSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
}
|
||||
|
||||
override fun updateSearchQuery(query: String?) {
|
||||
mutableState.update {
|
||||
it.copy(searchQuery = query)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateItems(items: Map<CatalogueSource, MangaSearchItemResult>) {
|
||||
mutableState.update {
|
||||
it.copy(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItems(): Map<CatalogueSource, MangaSearchItemResult> {
|
||||
return mutableState.value.items
|
||||
}
|
||||
|
||||
override fun setSourceFilter(filter: MangaSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
}
|
||||
|
||||
override fun toggleFilterResults() {
|
||||
mutableState.update {
|
||||
it.copy(onlyShowHasResults = !it.onlyShowHasResults)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: MangaSourceFilter = MangaSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, MangaSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is MangaSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
.filter { state.value.sourceFilter != MangaSourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.produceState
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.entries.manga.model.toDomainManga
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
|
@ -16,6 +15,8 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
|
@ -27,25 +28,27 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
abstract class MangaSearchScreenModel<T>(
|
||||
initialState: T,
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
abstract class MangaSearchScreenModel(
|
||||
initialState: State = State(),
|
||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: MangaSourceManager = Injekt.get(),
|
||||
private val extensionManager: MangaExtensionManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
) : StateScreenModel<T>(initialState) {
|
||||
) : StateScreenModel<MangaSearchScreenModel.State>(initialState) {
|
||||
|
||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||
private var searchJob: Job? = null
|
||||
|
||||
protected var query: String? = null
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
private val sources by lazy { getSelectedSources() }
|
||||
private val enabledLanguages = sourcePreferences.enabledLanguages().get()
|
||||
private val disabledSources = sourcePreferences.disabledMangaSources().get()
|
||||
protected val pinnedSources = sourcePreferences.pinnedMangaSources().get()
|
||||
|
||||
private var lastQuery: String? = null
|
||||
private var lastSourceFilter: MangaSourceFilter? = null
|
||||
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
private val sortComparator = { map: Map<CatalogueSource, MangaSearchItemResult> ->
|
||||
compareBy<CatalogueSource>(
|
||||
{ (map[it] as? MangaSearchItemResult.Success)?.isEmpty ?: true },
|
||||
|
@ -55,7 +58,7 @@ abstract class MangaSearchScreenModel<T>(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun getManga(initialManga: Manga): State<Manga> {
|
||||
fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> {
|
||||
return produceState(initialValue = initialManga) {
|
||||
getManga.subscribe(initialManga.url, initialManga.source)
|
||||
.filterNotNull()
|
||||
|
@ -66,10 +69,6 @@ abstract class MangaSearchScreenModel<T>(
|
|||
}
|
||||
|
||||
open fun getEnabledSources(): List<CatalogueSource> {
|
||||
val enabledLanguages = sourcePreferences.enabledLanguages().get()
|
||||
val disabledSources = sourcePreferences.disabledMangaSources().get()
|
||||
val pinnedSources = sourcePreferences.pinnedMangaSources().get()
|
||||
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { it.lang in enabledLanguages && "${it.id}" !in disabledSources }
|
||||
.sortedWith(
|
||||
|
@ -95,57 +94,91 @@ abstract class MangaSearchScreenModel<T>(
|
|||
.filter { it in enabledSources }
|
||||
}
|
||||
|
||||
abstract fun updateSearchQuery(query: String?)
|
||||
|
||||
abstract fun updateItems(items: Map<CatalogueSource, MangaSearchItemResult>)
|
||||
|
||||
abstract fun getItems(): Map<CatalogueSource, MangaSearchItemResult>
|
||||
|
||||
private fun getAndUpdateItems(function: (Map<CatalogueSource, MangaSearchItemResult>) -> Map<CatalogueSource, MangaSearchItemResult>) {
|
||||
updateItems(function(getItems()))
|
||||
fun updateSearchQuery(query: String?) {
|
||||
mutableState.update { it.copy(searchQuery = query) }
|
||||
}
|
||||
|
||||
abstract fun setSourceFilter(filter: MangaSourceFilter)
|
||||
fun setSourceFilter(filter: MangaSourceFilter) {
|
||||
mutableState.update { it.copy(sourceFilter = filter) }
|
||||
search()
|
||||
}
|
||||
|
||||
abstract fun toggleFilterResults()
|
||||
fun toggleFilterResults() {
|
||||
mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) }
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
if (this.query == query) return
|
||||
fun search() {
|
||||
val query = state.value.searchQuery
|
||||
val sourceFilter = state.value.sourceFilter
|
||||
|
||||
this.query = query
|
||||
if (query.isNullOrBlank()) return
|
||||
val sameQuery = this.lastQuery == query
|
||||
if (sameQuery && this.lastSourceFilter == sourceFilter) return
|
||||
|
||||
this.lastQuery = query
|
||||
this.lastSourceFilter = sourceFilter
|
||||
|
||||
searchJob?.cancel()
|
||||
val initialItems = getSelectedSources().associateWith { MangaSearchItemResult.Loading }
|
||||
updateItems(initialItems)
|
||||
val sources = getSelectedSources()
|
||||
|
||||
// Reuse previous results if possible
|
||||
if (sameQuery) {
|
||||
val existingResults = state.value.items
|
||||
updateItems(sources.associateWith { existingResults[it] ?: MangaSearchItemResult.Loading })
|
||||
} else {
|
||||
updateItems(sources.associateWith { MangaSearchItemResult.Loading })
|
||||
}
|
||||
searchJob = ioCoroutineScope.launch {
|
||||
sources
|
||||
.map { source ->
|
||||
async {
|
||||
try {
|
||||
val page = withContext(coroutineDispatcher) {
|
||||
source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
|
||||
}
|
||||
sources.map { source ->
|
||||
async {
|
||||
if (state.value.items[source] !is MangaSearchItemResult.Loading) {
|
||||
return@async
|
||||
}
|
||||
try {
|
||||
val page = withContext(coroutineDispatcher) {
|
||||
source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
|
||||
}
|
||||
|
||||
val titles = page.mangas.map {
|
||||
networkToLocalManga.await(it.toDomainManga(source.id))
|
||||
}
|
||||
val titles = page.mangas.map {
|
||||
networkToLocalManga.await(it.toDomainManga(source.id))
|
||||
}
|
||||
|
||||
getAndUpdateItems { items ->
|
||||
val mutableMap = items.toMutableMap()
|
||||
mutableMap[source] = MangaSearchItemResult.Success(titles)
|
||||
mutableMap.toSortedMap(sortComparator(mutableMap))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
getAndUpdateItems { items ->
|
||||
val mutableMap = items.toMutableMap()
|
||||
mutableMap[source] = MangaSearchItemResult.Error(e)
|
||||
mutableMap.toSortedMap(sortComparator(mutableMap))
|
||||
}
|
||||
if (isActive) {
|
||||
updateItem(source, MangaSearchItemResult.Success(titles))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (isActive) {
|
||||
updateItem(source, MangaSearchItemResult.Error(e))
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItems(items: Map<CatalogueSource, MangaSearchItemResult>) {
|
||||
mutableState.update { it.copy(items = items.toSortedMap(sortComparator(items))) }
|
||||
}
|
||||
|
||||
private fun updateItem(source: CatalogueSource, result: MangaSearchItemResult) {
|
||||
val mutableItems = state.value.items.toMutableMap()
|
||||
mutableItems[source] = result
|
||||
updateItems(mutableItems)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val fromSourceId: Long? = null,
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: MangaSourceFilter = MangaSourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, MangaSearchItemResult> = emptyMap(),
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is MangaSearchItemResult.Loading }
|
||||
val total: Int = items.size
|
||||
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||
}
|
||||
}
|
||||
|
||||
enum class MangaSourceFilter {
|
||||
|
@ -154,7 +187,7 @@ enum class MangaSourceFilter {
|
|||
}
|
||||
|
||||
sealed class MangaSearchItemResult {
|
||||
object Loading : MangaSearchItemResult()
|
||||
data object Loading : MangaSearchItemResult()
|
||||
|
||||
data class Error(
|
||||
val throwable: Throwable,
|
||||
|
|
|
@ -131,20 +131,20 @@ class AnimeCategoryScreenModel(
|
|||
}
|
||||
|
||||
sealed class AnimeCategoryDialog {
|
||||
object Create : AnimeCategoryDialog()
|
||||
data object Create : AnimeCategoryDialog()
|
||||
data class Rename(val category: Category) : AnimeCategoryDialog()
|
||||
data class Delete(val category: Category) : AnimeCategoryDialog()
|
||||
}
|
||||
|
||||
sealed class AnimeCategoryEvent {
|
||||
sealed class LocalizedMessage(@StringRes val stringRes: Int) : AnimeCategoryEvent()
|
||||
object InternalError : LocalizedMessage(R.string.internal_error)
|
||||
data object InternalError : LocalizedMessage(R.string.internal_error)
|
||||
}
|
||||
|
||||
sealed class AnimeCategoryScreenState {
|
||||
|
||||
@Immutable
|
||||
object Loading : AnimeCategoryScreenState()
|
||||
data object Loading : AnimeCategoryScreenState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
|
|
|
@ -131,20 +131,20 @@ class MangaCategoryScreenModel(
|
|||
}
|
||||
|
||||
sealed class MangaCategoryDialog {
|
||||
object Create : MangaCategoryDialog()
|
||||
data object Create : MangaCategoryDialog()
|
||||
data class Rename(val category: Category) : MangaCategoryDialog()
|
||||
data class Delete(val category: Category) : MangaCategoryDialog()
|
||||
}
|
||||
|
||||
sealed class MangaCategoryEvent {
|
||||
sealed class LocalizedMessage(@StringRes val stringRes: Int) : MangaCategoryEvent()
|
||||
object InternalError : LocalizedMessage(R.string.internal_error)
|
||||
data object InternalError : LocalizedMessage(R.string.internal_error)
|
||||
}
|
||||
|
||||
sealed class MangaCategoryScreenState {
|
||||
|
||||
@Immutable
|
||||
object Loading : MangaCategoryScreenState()
|
||||
data object Loading : MangaCategoryScreenState()
|
||||
|
||||
@Immutable
|
||||
data class Success(
|
||||
|
|
|
@ -877,10 +877,10 @@ class AnimeInfoScreenModel(
|
|||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
range = selectedIndex + 1 ..< selectedPositions[0]
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
range = (selectedPositions[1] + 1) ..< selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
|
@ -976,10 +976,10 @@ class AnimeInfoScreenModel(
|
|||
data class DeleteEpisodes(val episodes: List<Episode>) : Dialog()
|
||||
data class DuplicateAnime(val anime: Anime, val duplicate: Anime) : Dialog()
|
||||
data class ShowQualities(val episode: Episode, val anime: Anime, val source: AnimeSource) : Dialog()
|
||||
object ChangeAnimeSkipIntro : Dialog()
|
||||
object SettingsSheet : Dialog()
|
||||
object TrackSheet : Dialog()
|
||||
object FullCover : Dialog()
|
||||
data object ChangeAnimeSkipIntro : Dialog()
|
||||
data object SettingsSheet : Dialog()
|
||||
data object TrackSheet : Dialog()
|
||||
data object FullCover : Dialog()
|
||||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
|
|
|
@ -867,10 +867,10 @@ class MangaInfoScreenModel(
|
|||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
range = selectedIndex + 1 ..< selectedPositions[0]
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
range = (selectedPositions[1] + 1) ..< selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
|
@ -959,9 +959,9 @@ class MangaInfoScreenModel(
|
|||
data class ChangeCategory(val manga: Manga, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
||||
data class DeleteChapters(val chapters: List<Chapter>) : Dialog()
|
||||
data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog()
|
||||
object SettingsSheet : Dialog()
|
||||
object TrackSheet : Dialog()
|
||||
object FullCover : Dialog()
|
||||
data object SettingsSheet : Dialog()
|
||||
data object TrackSheet : Dialog()
|
||||
data object FullCover : Dialog()
|
||||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
|
|
|
@ -120,14 +120,14 @@ class AnimeHistoryScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object DeleteAll : Dialog()
|
||||
data object DeleteAll : Dialog()
|
||||
data class Delete(val history: AnimeHistoryWithRelations) : Dialog()
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
data class OpenEpisode(val episode: Episode?) : Event()
|
||||
object InternalError : Event()
|
||||
object HistoryCleared : Event()
|
||||
data object InternalError : Event()
|
||||
data object HistoryCleared : Event()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -120,14 +120,14 @@ class MangaHistoryScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object DeleteAll : Dialog()
|
||||
data object DeleteAll : Dialog()
|
||||
data class Delete(val history: MangaHistoryWithRelations) : Dialog()
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
data class OpenChapter(val chapter: Chapter?) : Event()
|
||||
object InternalError : Event()
|
||||
object HistoryCleared : Event()
|
||||
data object InternalError : Event()
|
||||
data object HistoryCleared : Event()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -339,8 +339,8 @@ object HomeScreen : Screen() {
|
|||
sealed class Tab {
|
||||
data class Animelib(val animeIdToOpen: Long? = null) : Tab()
|
||||
data class Library(val mangaIdToOpen: Long? = null) : Tab()
|
||||
object Updates : Tab()
|
||||
object History : Tab()
|
||||
data object Updates : Tab()
|
||||
data object History : Tab()
|
||||
data class Browse(val toExtensions: Boolean = false) : Tab()
|
||||
data class More(val toDownloads: Boolean) : Tab()
|
||||
}
|
||||
|
|
|
@ -665,7 +665,7 @@ class AnimeLibraryScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object SettingsSheet : Dialog()
|
||||
data object SettingsSheet : Dialog()
|
||||
data class ChangeCategory(val anime: List<Anime>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
||||
data class DeleteAnime(val anime: List<Anime>) : Dialog()
|
||||
}
|
||||
|
|
|
@ -659,7 +659,7 @@ class MangaLibraryScreenModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object SettingsSheet : Dialog()
|
||||
data object SettingsSheet : Dialog()
|
||||
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
||||
data class DeleteManga(val manga: List<Manga>) : Dialog()
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ private class MoreScreenModel(
|
|||
}
|
||||
|
||||
sealed class DownloadQueueState {
|
||||
object Stopped : DownloadQueueState()
|
||||
data object Stopped : DownloadQueueState()
|
||||
data class Paused(val pending: Int) : DownloadQueueState()
|
||||
data class Downloading(val pending: Int) : DownloadQueueState()
|
||||
}
|
||||
|
|
|
@ -488,7 +488,7 @@ class ReaderActivity : BaseActivity() {
|
|||
|
||||
setOnClickListener {
|
||||
popupMenu(
|
||||
items = ReadingModeType.values().map { it.flagValue to it.stringRes },
|
||||
items = ReadingModeType.entries.map { it.flagValue to it.stringRes },
|
||||
selectedItemId = viewModel.getMangaReadingMode(resolveDefault = false),
|
||||
) {
|
||||
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||
|
@ -541,7 +541,7 @@ class ReaderActivity : BaseActivity() {
|
|||
|
||||
setOnClickListener {
|
||||
popupMenu(
|
||||
items = OrientationType.values().map { it.flagValue to it.stringRes },
|
||||
items = OrientationType.entries.map { it.flagValue to it.stringRes },
|
||||
selectedItemId = viewModel.manga?.orientationType?.toInt()
|
||||
?: readerPreferences.defaultOrientationType().get(),
|
||||
) {
|
||||
|
|
|
@ -908,13 +908,13 @@ class ReaderViewModel(
|
|||
}
|
||||
|
||||
sealed class Dialog {
|
||||
object Loading : Dialog()
|
||||
object Settings : Dialog()
|
||||
data object Loading : Dialog()
|
||||
data object Settings : Dialog()
|
||||
data class PageActions(val page: ReaderPage) : Dialog()
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
object ReloadViewerChapters : Event()
|
||||
data object ReloadViewerChapters : Event()
|
||||
data class SetOrientation(val orientation: Int) : Event()
|
||||
data class SetCoverResult(val result: SetAsCoverResult) : Event()
|
||||
|
||||
|
|
|
@ -40,9 +40,9 @@ data class ReaderChapter(val chapter: Chapter) {
|
|||
}
|
||||
|
||||
sealed class State {
|
||||
object Wait : State()
|
||||
object Loading : State()
|
||||
class Error(val error: Throwable) : State()
|
||||
class Loaded(val pages: List<ReaderPage>) : State()
|
||||
data object Wait : State()
|
||||
data object Loading : State()
|
||||
data class Error(val error: Throwable) : State()
|
||||
data class Loaded(val pages: List<ReaderPage>) : State()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,6 @@ enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val str
|
|||
companion object {
|
||||
const val MASK = 0x00000038
|
||||
|
||||
fun fromPreference(preference: Int?): OrientationType = values().find { it.flagValue == preference } ?: DEFAULT
|
||||
fun fromPreference(preference: Int?): OrientationType = entries.find { it.flagValue == preference } ?: DEFAULT
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D
|
|||
companion object {
|
||||
const val MASK = 0x00000007
|
||||
|
||||
fun fromPreference(preference: Int?): ReadingModeType = values().find { it.flagValue == preference } ?: DEFAULT
|
||||
fun fromPreference(preference: Int?): ReadingModeType = entries.find { it.flagValue == preference } ?: DEFAULT
|
||||
|
||||
fun isPagerType(preference: Int): Boolean {
|
||||
val mode = fromPreference(preference)
|
||||
|
|
|
@ -10,11 +10,11 @@ import eu.kanade.tachiyomi.util.lang.invert
|
|||
abstract class ViewerNavigation {
|
||||
|
||||
sealed class NavigationRegion(@StringRes val nameRes: Int, val colorRes: Int) {
|
||||
object MENU : NavigationRegion(R.string.action_menu, R.color.navigation_menu)
|
||||
object PREV : NavigationRegion(R.string.nav_zone_prev, R.color.navigation_prev)
|
||||
object NEXT : NavigationRegion(R.string.nav_zone_next, R.color.navigation_next)
|
||||
object LEFT : NavigationRegion(R.string.nav_zone_left, R.color.navigation_left)
|
||||
object RIGHT : NavigationRegion(R.string.nav_zone_right, R.color.navigation_right)
|
||||
data object MENU : NavigationRegion(R.string.action_menu, R.color.navigation_menu)
|
||||
data object PREV : NavigationRegion(R.string.nav_zone_prev, R.color.navigation_prev)
|
||||
data object NEXT : NavigationRegion(R.string.nav_zone_next, R.color.navigation_next)
|
||||
data object LEFT : NavigationRegion(R.string.nav_zone_left, R.color.navigation_left)
|
||||
data object RIGHT : NavigationRegion(R.string.nav_zone_right, R.color.navigation_right)
|
||||
}
|
||||
|
||||
data class Region(
|
||||
|
|
|
@ -307,10 +307,10 @@ class AnimeUpdatesScreenModel(
|
|||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
range = selectedIndex + 1 ..< selectedPositions[0]
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
range = (selectedPositions[1] + 1) ..< selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
|
@ -384,7 +384,7 @@ class AnimeUpdatesScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object InternalError : Event()
|
||||
data object InternalError : Event()
|
||||
data class LibraryUpdateTriggered(val started: Boolean) : Event()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -295,10 +295,10 @@ class MangaUpdatesScreenModel(
|
|||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
range = selectedIndex + 1 ..< selectedPositions[0]
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
range = (selectedPositions[1] + 1) ..< selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
|
@ -371,7 +371,7 @@ class MangaUpdatesScreenModel(
|
|||
}
|
||||
|
||||
sealed class Event {
|
||||
object InternalError : Event()
|
||||
data object InternalError : Event()
|
||||
data class LibraryUpdateTriggered(val started: Boolean) : Event()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ object GLUtil {
|
|||
var maximumTextureSize = 0
|
||||
|
||||
// Iterate through all the configurations to located the maximum texture size
|
||||
for (i in 0 until totalConfigurations[0]) {
|
||||
for (i in 0 ..< totalConfigurations[0]) {
|
||||
// Only need to check for width since opengl textures are always squared
|
||||
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize)
|
||||
|
||||
|
|
|
@ -150,12 +150,12 @@ enum class ComicInfoPublishingStatus(
|
|||
|
||||
companion object {
|
||||
fun toComicInfoValue(value: Long): String {
|
||||
return values().firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
|
||||
return entries.firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
|
||||
?: UNKNOWN.comicInfoValue
|
||||
}
|
||||
|
||||
fun toSMangaValue(value: String?): Int {
|
||||
return values().firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
|
||||
return entries.firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
|
||||
?: UNKNOWN.sMangaModelValue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,7 +330,7 @@ object ImageUtil {
|
|||
}
|
||||
|
||||
return buildList {
|
||||
val range = 0 until partCount
|
||||
val range = 0 ..< partCount
|
||||
for (index in range) {
|
||||
// Only continue if the list is empty or there is image remaining
|
||||
if (isNotEmpty() && imageHeight <= last().bottomOffset) break
|
||||
|
@ -440,7 +440,7 @@ object ImageUtil {
|
|||
var blackStreak = false
|
||||
var whiteStreak = false
|
||||
val notOffset = x == left || x == right
|
||||
inner@ for ((index, y) in (0 until image.height step image.height / 25).withIndex()) {
|
||||
inner@ for ((index, y) in (0 ..< image.height step image.height / 25).withIndex()) {
|
||||
val pixel = image[x, y]
|
||||
val pixelOff = image[x + (if (x < image.width / 2) -offsetX else offsetX), y]
|
||||
if (pixel.isWhite()) {
|
||||
|
|
|
@ -21,10 +21,8 @@ val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
|
|||
}
|
||||
|
||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Long> {
|
||||
private val enumValues by lazy { UpdateStrategy.values() }
|
||||
|
||||
override fun decode(databaseValue: Long): UpdateStrategy =
|
||||
enumValues.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
|
||||
UpdateStrategy.entries.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
|
||||
|
||||
override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class CreateAnimeCategoryWithName(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class DeleteAnimeCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ class ReorderAnimeCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object Unchanged : Result()
|
||||
data object Success : Result()
|
||||
data object Unchanged : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class UpdateAnimeCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class Error(val error: Exception) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class CreateMangaCategoryWithName(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class DeleteMangaCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ class ReorderMangaCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object Unchanged : Result()
|
||||
data object Success : Result()
|
||||
data object Unchanged : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class UpdateMangaCategory(
|
|||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data object Success : Result()
|
||||
data class Error(val error: Exception) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,15 +24,15 @@ data class AnimeLibrarySort(
|
|||
|
||||
override val mask: Long = 0b00111100L
|
||||
|
||||
object Alphabetical : Type(0b00000000)
|
||||
object LastSeen : Type(0b00000100)
|
||||
object LastUpdate : Type(0b00001000)
|
||||
object UnseenCount : Type(0b00001100)
|
||||
object TotalEpisodes : Type(0b00010000)
|
||||
object LatestEpisode : Type(0b00010100)
|
||||
object EpisodeFetchDate : Type(0b00011000)
|
||||
object DateAdded : Type(0b00011100)
|
||||
object AiringTime : Type(0b00100000)
|
||||
data object Alphabetical : Type(0b00000000)
|
||||
data object LastSeen : Type(0b00000100)
|
||||
data object LastUpdate : Type(0b00001000)
|
||||
data object UnseenCount : Type(0b00001100)
|
||||
data object TotalEpisodes : Type(0b00010000)
|
||||
data object LatestEpisode : Type(0b00010100)
|
||||
data object EpisodeFetchDate : Type(0b00011000)
|
||||
data object DateAdded : Type(0b00011100)
|
||||
data object AiringTime : Type(0b00100000)
|
||||
|
||||
companion object {
|
||||
fun valueOf(flag: Long): Type {
|
||||
|
@ -47,8 +47,8 @@ data class AnimeLibrarySort(
|
|||
|
||||
override val mask: Long = 0b01000000L
|
||||
|
||||
object Ascending : Direction(0b01000000)
|
||||
object Descending : Direction(0b00000000)
|
||||
data object Ascending : Direction(0b01000000)
|
||||
data object Descending : Direction(0b00000000)
|
||||
|
||||
companion object {
|
||||
fun valueOf(flag: Long): Direction {
|
||||
|
|
|
@ -24,14 +24,14 @@ data class MangaLibrarySort(
|
|||
|
||||
override val mask: Long = 0b00111100L
|
||||
|
||||
object Alphabetical : Type(0b00000000)
|
||||
object LastRead : Type(0b00000100)
|
||||
object LastUpdate : Type(0b00001000)
|
||||
object UnreadCount : Type(0b00001100)
|
||||
object TotalChapters : Type(0b00010000)
|
||||
object LatestChapter : Type(0b00010100)
|
||||
object ChapterFetchDate : Type(0b00011000)
|
||||
object DateAdded : Type(0b00011100)
|
||||
data object Alphabetical : Type(0b00000000)
|
||||
data object LastRead : Type(0b00000100)
|
||||
data object LastUpdate : Type(0b00001000)
|
||||
data object UnreadCount : Type(0b00001100)
|
||||
data object TotalChapters : Type(0b00010000)
|
||||
data object LatestChapter : Type(0b00010100)
|
||||
data object ChapterFetchDate : Type(0b00011000)
|
||||
data object DateAdded : Type(0b00011100)
|
||||
|
||||
companion object {
|
||||
fun valueOf(flag: Long): Type {
|
||||
|
@ -46,8 +46,8 @@ data class MangaLibrarySort(
|
|||
|
||||
override val mask: Long = 0b01000000L
|
||||
|
||||
object Ascending : Direction(0b01000000)
|
||||
object Descending : Direction(0b00000000)
|
||||
data object Ascending : Direction(0b01000000)
|
||||
data object Descending : Direction(0b00000000)
|
||||
|
||||
companion object {
|
||||
fun valueOf(flag: Long): Direction {
|
||||
|
|
|
@ -2,10 +2,10 @@ package tachiyomi.domain.library.model
|
|||
|
||||
sealed class LibraryDisplayMode {
|
||||
|
||||
object CompactGrid : LibraryDisplayMode()
|
||||
object ComfortableGrid : LibraryDisplayMode()
|
||||
object List : LibraryDisplayMode()
|
||||
object CoverOnlyGrid : LibraryDisplayMode()
|
||||
data object CompactGrid : LibraryDisplayMode()
|
||||
data object ComfortableGrid : LibraryDisplayMode()
|
||||
data object List : LibraryDisplayMode()
|
||||
data object CoverOnlyGrid : LibraryDisplayMode()
|
||||
|
||||
object Serializer {
|
||||
fun deserialize(serialized: String): LibraryDisplayMode {
|
||||
|
|
|
@ -72,8 +72,8 @@ class GetApplicationRelease(
|
|||
)
|
||||
|
||||
sealed class Result {
|
||||
class NewUpdate(val release: Release) : Result()
|
||||
object NoNewUpdate : Result()
|
||||
object ThirdPartyInstallation : Result()
|
||||
data class NewUpdate(val release: Release) : Result()
|
||||
data object NoNewUpdate : Result()
|
||||
data object ThirdPartyInstallation : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package tachiyomi.domain.source.manga.model
|
||||
|
||||
sealed class Pin(val code: Int) {
|
||||
object Unpinned : Pin(0b00)
|
||||
object Pinned : Pin(0b01)
|
||||
object Actual : Pin(0b10)
|
||||
data object Unpinned : Pin(0b00)
|
||||
data object Pinned : Pin(0b01)
|
||||
data object Actual : Pin(0b10)
|
||||
}
|
||||
|
||||
inline fun Pins(builder: Pins.PinsBuilder.() -> Unit = {}): Pins {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
compiler = "1.4.8"
|
||||
compiler = "1.5.0"
|
||||
compose-bom = "2023.07.00-alpha01"
|
||||
accompanist = "0.31.5-beta"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
kotlin_version = "1.8.22"
|
||||
kotlin_version = "1.9.0"
|
||||
serialization_version = "1.5.1"
|
||||
xml_serialization_version = "0.86.1"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
aboutlib_version = "10.8.2"
|
||||
aboutlib_version = "10.8.3"
|
||||
okhttp_version = "5.0.0-alpha.11"
|
||||
shizuku_version = "12.2.0"
|
||||
sqlite = "2.3.1"
|
||||
|
@ -29,7 +29,7 @@ jsoup = "org.jsoup:jsoup:1.16.1"
|
|||
|
||||
disklrucache = "com.jakewharton:disklrucache:2.0.2"
|
||||
unifile = "com.github.tachiyomiorg:unifile:17bec43"
|
||||
junrar = "com.github.junrar:junrar:7.5.4"
|
||||
junrar = "com.github.junrar:junrar:7.5.5"
|
||||
|
||||
sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" }
|
||||
sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" }
|
||||
|
@ -45,7 +45,7 @@ coil-gif = { module = "io.coil-kt:coil-gif" }
|
|||
coil-compose = { module = "io.coil-kt:coil-compose" }
|
||||
|
||||
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:c8e2650"
|
||||
image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45"
|
||||
image-decoder = "com.github.tachiyomiorg:image-decoder:16eda64574"
|
||||
|
||||
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
||||
|
||||
|
@ -64,7 +64,7 @@ swipe = "me.saket.swipe:swipe:1.2.0"
|
|||
|
||||
logcat = "com.squareup.logcat:logcat:0.1"
|
||||
|
||||
acra-http = "ch.acra:acra-http:5.10.1"
|
||||
acra-http = "ch.acra:acra-http:5.11.0"
|
||||
|
||||
aboutLibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlib_version" }
|
||||
aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" }
|
||||
|
|
|
@ -211,7 +211,7 @@ private fun rememberColumnWidthSums(
|
|||
gridWidth,
|
||||
horizontalArrangement.spacing.roundToPx(),
|
||||
).toMutableList().apply {
|
||||
for (i in 1 until size) {
|
||||
for (i in 1 ..< size) {
|
||||
this[i] += this[i - 1]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ fun UpdatesMangaWidget(data: List<Pair<Long, Bitmap?>>?) {
|
|||
} else if (data.isEmpty()) {
|
||||
Text(text = stringResource(R.string.information_no_recent))
|
||||
} else {
|
||||
(0 until rowCount).forEach { i ->
|
||||
val coverRow = (0 until columnCount).mapNotNull { j ->
|
||||
(0 ..< rowCount).forEach { i ->
|
||||
val coverRow = (0 ..< columnCount).mapNotNull { j ->
|
||||
data.getOrNull(j + (i * columnCount))
|
||||
}
|
||||
if (coverRow.isNotEmpty()) {
|
||||
|
|
Loading…
Reference in a new issue