Last commit merged: 7a4680603d
This commit is contained in:
LuftVerbot 2023-11-12 15:57:35 +01:00
parent 4709cbedba
commit 645746aa43
94 changed files with 499 additions and 511 deletions

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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 ->

View file

@ -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,

View file

@ -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,

View file

@ -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>()
}

View file

@ -64,6 +64,7 @@ private fun AnimeItem(
Box(modifier = Modifier.width(96.dp)) {
EntryComfortableGridItem(
title = title,
titleMaxLines = 3,
coverData = cover,
coverBadgeStart = {
InLibraryBadge(enabled = isFavorite)

View file

@ -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

View file

@ -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,

View file

@ -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 ->

View file

@ -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,

View file

@ -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>()
}

View file

@ -64,6 +64,7 @@ private fun MangaItem(
Box(modifier = Modifier.width(96.dp)) {
EntryComfortableGridItem(
title = title,
titleMaxLines = 3,
coverData = cover,
coverBadgeStart = {
InLibraryBadge(enabled = isFavorite)

View file

@ -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

View file

@ -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)

View file

@ -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,
)

View file

@ -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)

View file

@ -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(

View file

@ -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)),

View file

@ -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(),

View file

@ -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(),

View file

@ -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(

View file

@ -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(

View file

@ -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) {

View file

@ -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 }
}

View file

@ -161,7 +161,7 @@ sealed class Location {
}
}
object Cache : Location()
data object Cache : Location()
fun directory(context: Context): File {
return when (this) {

View file

@ -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)

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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(

View file

@ -163,7 +163,7 @@ class AnimeExtensionDetailsScreenModel(
}
sealed class AnimeExtensionDetailsEvent {
object Uninstalled : AnimeExtensionDetailsEvent()
data object Uninstalled : AnimeExtensionDetailsEvent()
}
@Immutable

View file

@ -54,7 +54,7 @@ class MigrationAnimeScreenModel(
}
sealed class MigrationAnimeEvent {
object FailedFetchingFavorites : MigrationAnimeEvent()
data object FailedFetchingFavorites : MigrationAnimeEvent()
}
@Immutable

View file

@ -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()
}
}

View file

@ -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))
},

View file

@ -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()
}
}

View file

@ -77,7 +77,7 @@ class MigrateAnimeSourceScreenModel(
}
sealed class Event {
object FailedFetchingSourcesWithCount : Event()
data object FailedFetchingSourcesWithCount : Event()
}
}

View file

@ -100,7 +100,7 @@ class AnimeSourcesScreenModel(
}
sealed class Event {
object FailedFetchingSources : Event()
data object FailedFetchingSources : Event()
}
data class Dialog(val source: AnimeSource)

View file

@ -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(

View file

@ -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,31 +94,46 @@ 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 ->
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()
@ -129,21 +143,41 @@ abstract class AnimeSearchScreenModel<T>(
networkToLocalAnime.await(it.toDomainAnime(source.id))
}
getAndUpdateItems { items ->
val mutableMap = items.toMutableMap()
mutableMap[source] = AnimeSearchItemResult.Success(titles)
mutableMap.toSortedMap(sortComparator(mutableMap))
if (isActive) {
updateItem(source, AnimeSearchItemResult.Success(titles))
}
} catch (e: Exception) {
getAndUpdateItems { items ->
val mutableMap = items.toMutableMap()
mutableMap[source] = AnimeSearchItemResult.Error(e)
mutableMap.toSortedMap(sortComparator(mutableMap))
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) }
}
}
@ -153,7 +187,7 @@ enum class AnimeSourceFilter {
}
sealed class AnimeSearchItemResult {
object Loading : AnimeSearchItemResult()
data object Loading : AnimeSearchItemResult()
data class Error(
val throwable: Throwable,

View file

@ -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,

View file

@ -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 }
}
}

View file

@ -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(

View file

@ -163,7 +163,7 @@ class MangaExtensionDetailsScreenModel(
}
sealed class MangaExtensionDetailsEvent {
object Uninstalled : MangaExtensionDetailsEvent()
data object Uninstalled : MangaExtensionDetailsEvent()
}
@Immutable

View file

@ -54,7 +54,7 @@ class MigrationMangaScreenModel(
}
sealed class MigrationMangaEvent {
object FailedFetchingFavorites : MigrationMangaEvent()
data object FailedFetchingFavorites : MigrationMangaEvent()
}
@Immutable

View file

@ -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()
}
}

View file

@ -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))
},

View file

@ -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()
}
}

View file

@ -77,7 +77,7 @@ class MigrateSourceScreenModel(
}
sealed class Event {
object FailedFetchingSourcesWithCount : Event()
data object FailedFetchingSourcesWithCount : Event()
}
}

View file

@ -124,7 +124,7 @@ class MangaSourcesScreenModel(
}
sealed class Event {
object FailedFetchingSources : Event()
data object FailedFetchingSources : Event()
}
data class Dialog(val source: Source)

View file

@ -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(

View file

@ -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,

View file

@ -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 }
}
}

View file

@ -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,32 +94,46 @@ 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 ->
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()
@ -130,21 +143,41 @@ abstract class MangaSearchScreenModel<T>(
networkToLocalManga.await(it.toDomainManga(source.id))
}
getAndUpdateItems { items ->
val mutableMap = items.toMutableMap()
mutableMap[source] = MangaSearchItemResult.Success(titles)
mutableMap.toSortedMap(sortComparator(mutableMap))
if (isActive) {
updateItem(source, MangaSearchItemResult.Success(titles))
}
} catch (e: Exception) {
getAndUpdateItems { items ->
val mutableMap = items.toMutableMap()
mutableMap[source] = MangaSearchItemResult.Error(e)
mutableMap.toSortedMap(sortComparator(mutableMap))
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) }
}
}
@ -154,7 +187,7 @@ enum class MangaSourceFilter {
}
sealed class MangaSearchItemResult {
object Loading : MangaSearchItemResult()
data object Loading : MangaSearchItemResult()
data class Error(
val throwable: Throwable,

View file

@ -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(

View file

@ -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(

View file

@ -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() {

View file

@ -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() {

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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(),
) {

View file

@ -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()

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -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)

View file

@ -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(

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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
}
}

View file

@ -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()) {

View file

@ -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()
}

View file

@ -39,7 +39,7 @@ class CreateAnimeCategoryWithName(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -36,7 +36,7 @@ class DeleteAnimeCategory(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -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()
}

View file

@ -18,7 +18,7 @@ class UpdateAnimeCategory(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class Error(val error: Exception) : Result()
}
}

View file

@ -39,7 +39,7 @@ class CreateMangaCategoryWithName(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -36,7 +36,7 @@ class DeleteMangaCategory(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class InternalError(val error: Throwable) : Result()
}
}

View file

@ -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()
}

View file

@ -18,7 +18,7 @@ class UpdateMangaCategory(
}
sealed class Result {
object Success : Result()
data object Success : Result()
data class Error(val error: Exception) : Result()
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -1,5 +1,5 @@
[versions]
compiler = "1.4.8"
compiler = "1.5.0"
compose-bom = "2023.07.00-alpha01"
accompanist = "0.31.5-beta"

View file

@ -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"

View file

@ -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" }

View file

@ -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]
}
}

View file

@ -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()) {