From 336221a972187c8e35d733d2f9d1d1a99e7b6f1b Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 11 Nov 2023 22:43:50 -0500 Subject: [PATCH] Use immutable collections in more places --- app/build.gradle.kts | 1 + .../presentation/browse/BrowseSourceScreen.kt | 5 +- .../presentation/browse/SourcesScreen.kt | 4 +- .../presentation/components/EmptyScreen.kt | 3 +- .../manga/components/MangaDialogs.kt | 15 ++-- .../more/settings/PreferenceScreen.kt | 11 ++- .../screen/data/CreateBackupScreen.kt | 6 +- .../track/TrackInfoDialogSelector.kt | 6 +- .../eu/kanade/tachiyomi/data/track/Tracker.kt | 3 +- .../tachiyomi/data/track/anilist/Anilist.kt | 15 ++-- .../tachiyomi/data/track/bangumi/Bangumi.kt | 10 ++- .../tachiyomi/data/track/kavita/Kavita.kt | 4 +- .../tachiyomi/data/track/kitsu/Kitsu.kt | 6 +- .../tachiyomi/data/track/komga/Komga.kt | 4 +- .../data/track/mangaupdates/MangaUpdates.kt | 14 ++-- .../data/track/myanimelist/MyAnimeList.kt | 10 ++- .../data/track/shikimori/Shikimori.kt | 10 ++- .../tachiyomi/data/track/suwayomi/Suwayomi.kt | 4 +- .../extension/ExtensionFilterScreenModel.kt | 13 ++-- .../details/ExtensionDetailsScreenModel.kt | 13 ++-- .../manga/MigrateMangaScreenModel.kt | 15 ++-- .../sources/MigrateSourceScreenModel.kt | 7 +- .../ui/browse/source/SourcesScreenModel.kt | 23 +++--- .../source/globalsearch/SearchScreenModel.kt | 35 ++++++--- .../ui/category/CategoryScreenModel.kt | 8 ++- .../ui/library/LibraryScreenModel.kt | 43 +++++------ .../kanade/tachiyomi/ui/library/LibraryTab.kt | 3 +- .../ui/manga/track/TrackInfoDialog.kt | 3 +- .../ui/updates/UpdatesScreenModel.kt | 71 ++++++++++--------- .../main/java/eu/kanade/test/DummyTracker.kt | 6 +- .../kanade/tachiyomi/util/storage/EpubFile.kt | 9 ++- presentation-core/build.gradle.kts | 2 + .../presentation/core/components/Badges.kt | 6 +- .../core/components/WheelPicker.kt | 7 +- .../presentation/core/screens/EmptyScreen.kt | 9 +-- 35 files changed, 252 insertions(+), 152 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13b7d3caa8..5db3ca582e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -175,6 +175,7 @@ dependencies { implementation(libs.bundles.sqlite) implementation(kotlinx.reflect) + implementation(kotlinx.immutables) implementation(platform(kotlinx.coroutines.bom)) implementation(kotlinx.bundles.coroutines) diff --git a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt index 833e4bf791..6d01f85b8b 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt @@ -24,6 +24,7 @@ import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.util.formattedMessage import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.Source +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.StateFlow import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.manga.model.Manga @@ -76,7 +77,7 @@ fun BrowseSourceContent( modifier = Modifier.padding(contentPadding), message = getErrorMessage(errorState), actions = if (source is LocalSource) { - listOf( + persistentListOf( EmptyScreenAction( stringResId = R.string.local_source_help_guide, icon = Icons.Outlined.HelpOutline, @@ -84,7 +85,7 @@ fun BrowseSourceContent( ), ) } else { - listOf( + persistentListOf( EmptyScreenAction( stringResId = R.string.action_retry, icon = Icons.Outlined.Refresh, diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index d6a8c71863..377bcb0317 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -94,8 +94,8 @@ fun SourcesScreen( @Composable private fun SourceHeader( - modifier: Modifier = Modifier, language: String, + modifier: Modifier = Modifier, ) { val context = LocalContext.current Text( @@ -108,11 +108,11 @@ private fun SourceHeader( @Composable private fun SourceItem( - modifier: Modifier = Modifier, source: Source, onClickItem: (Source, Listing) -> Unit, onLongClickItem: (Source) -> Unit, onClickPin: (Source) -> Unit, + modifier: Modifier = Modifier, ) { BaseSourceItem( modifier = modifier, diff --git a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt b/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt index def969a393..cc2a56ec71 100644 --- a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R +import kotlinx.collections.immutable.persistentListOf import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction @@ -30,7 +31,7 @@ private fun WithActionPreview() { Surface { EmptyScreen( textResource = R.string.empty_screen, - actions = listOf( + actions = persistentListOf( EmptyScreenAction( stringResId = R.string.action_retry, icon = Icons.Outlined.Refresh, diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt index 1fd1dd0210..30817579c0 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import eu.kanade.tachiyomi.R +import kotlinx.collections.immutable.toImmutableList import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.presentation.core.components.WheelTextPicker @@ -67,13 +68,15 @@ fun SetIntervalDialog( contentAlignment = Alignment.Center, ) { val size = DpSize(width = maxWidth / 2, height = 128.dp) - val items = (0..FetchInterval.MAX_INTERVAL).map { - if (it == 0) { - stringResource(R.string.label_default) - } else { - it.toString() + val items = (0..FetchInterval.MAX_INTERVAL) + .map { + if (it == 0) { + stringResource(R.string.label_default) + } else { + it.toString() + } } - } + .toImmutableList() WheelTextPicker( items = items, size = size, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt index 2367190246..26b4086b86 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt @@ -85,12 +85,11 @@ fun PreferenceScreen( private fun List.findHighlightedIndex(highlightKey: String): Int { return flatMap { if (it is Preference.PreferenceGroup) { - mutableListOf() - .apply { - add(null) // Header - addAll(it.preferenceItems.map { groupItem -> groupItem.title }) - add(null) // Spacer - } + buildList { + add(null) // Header + addAll(it.preferenceItems.map { groupItem -> groupItem.title }) + add(null) // Spacer + } } else { listOf(it.title) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt index 571a7bda86..b1a5d9502f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt @@ -36,6 +36,10 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.PersistentSet +import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.plus +import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.flow.update import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.material.Scaffold @@ -154,7 +158,7 @@ private class CreateBackupScreenModel : StateScreenModel = BackupChoices.keys, + val flags: PersistentSet = BackupChoices.keys.toPersistentSet(), ) } diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index a4faba194e..2f4ecf11d4 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.WheelNumberPicker import tachiyomi.presentation.core.components.WheelTextPicker @@ -102,7 +104,7 @@ fun TrackChapterSelector( title = stringResource(R.string.chapters), content = { WheelNumberPicker( - items = range.toList(), + items = range.toImmutableList(), modifier = Modifier.align(Alignment.Center), startIndex = selection, onSelectionChanged = { onSelectionChange(it) }, @@ -117,7 +119,7 @@ fun TrackChapterSelector( fun TrackScoreSelector( selection: String, onSelectionChange: (String) -> Unit, - selections: List, + selections: ImmutableList, onConfirm: () -> Unit, onDismissRequest: () -> Unit, ) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt index af51d7b80f..732b25cac7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt @@ -6,6 +6,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList import okhttp3.OkHttpClient interface Tracker { @@ -36,7 +37,7 @@ interface Tracker { fun getCompletionStatus(): Int - fun getScoreList(): List + fun getScoreList(): ImmutableList // TODO: Store all scores as 10 point in the future maybe? fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 19227efea4..6fef751d60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy @@ -74,18 +77,18 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List { + override fun getScoreList(): ImmutableList { return when (scorePreference.get()) { // 10 point - POINT_10 -> IntRange(0, 10).map(Int::toString) + POINT_10 -> IntRange(0, 10).map(Int::toString).toImmutableList() // 100 point - POINT_100 -> IntRange(0, 100).map(Int::toString) + POINT_100 -> IntRange(0, 100).map(Int::toString).toImmutableList() // 5 stars - POINT_5 -> IntRange(0, 5).map { "$it ★" } + POINT_5 -> IntRange(0, 5).map { "$it ★" }.toImmutableList() // Smiley - POINT_3 -> listOf("-", "😦", "😐", "😊") + POINT_3 -> persistentListOf("-", "😦", "😐", "😊") // 10 point decimal - POINT_10_DECIMAL -> IntRange(0, 100).map { (it / 10f).toString() } + POINT_10_DECIMAL -> IntRange(0, 100).map { (it / 10f).toString() }.toImmutableList() else -> throw Exception("Unknown score type") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index ca4ddbc668..8434b3db63 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy @@ -18,9 +20,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { private val api by lazy { BangumiApi(id, client, interceptor) } - override fun getScoreList(): List { - return IntRange(0, 10).map(Int::toString) - } + override fun getScoreList(): ImmutableList = SCORE_LIST override fun displayScore(track: Track): String { return track.score.toInt().toString() @@ -141,5 +141,9 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { const val ON_HOLD = 4 const val DROPPED = 5 const val PLAN_TO_READ = 1 + + private val SCORE_LIST = IntRange(0, 10) + .map(Int::toString) + .toImmutableList() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt index e9de559255..bee854afde 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt @@ -10,6 +10,8 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.sourcePreferences +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.injectLazy @@ -51,7 +53,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List = emptyList() + override fun getScoreList(): ImmutableList = persistentListOf() override fun displayScore(track: Track): String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index 7c1fd34ffa..5763b735eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy @@ -54,9 +56,9 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List { + override fun getScoreList(): ImmutableList { val df = DecimalFormat("0.#") - return listOf("0") + IntRange(2, 20).map { df.format(it / 2f) } + return (listOf("0") + IntRange(2, 20).map { df.format(it / 2f) }).toImmutableList() } override fun indexToScore(index: Int): Float { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt index a2071c7859..5c9d3e6d28 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt @@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.source.Source +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import okhttp3.Dns import okhttp3.OkHttpClient import tachiyomi.domain.manga.model.Manga @@ -48,7 +50,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List = emptyList() + override fun getScoreList(): ImmutableList = persistentListOf() override fun displayScore(track: Track): String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index 14095f4206..86826bb073 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker { @@ -18,6 +20,12 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker const val COMPLETE_LIST = 2 const val UNFINISHED_LIST = 3 const val ON_HOLD_LIST = 4 + + private val SCORE_LIST = ( + (0..9) + .flatMap { i -> (0..9).map { j -> "$i.$j" } } + listOf("10.0") + ) + .toImmutableList() } private val interceptor by lazy { MangaUpdatesInterceptor(this) } @@ -48,11 +56,9 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker override fun getCompletionStatus(): Int = COMPLETE_LIST - private val _scoreList = (0..9).flatMap { i -> (0..9).map { j -> "$i.$j" } } + listOf("10.0") + override fun getScoreList(): ImmutableList = SCORE_LIST - override fun getScoreList(): List = _scoreList - - override fun indexToScore(index: Int): Float = _scoreList[index].toFloat() + override fun indexToScore(index: Int): Float = SCORE_LIST[index].toFloat() override fun displayScore(track: Track): String = track.score.toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index d7141099a9..bce0e1d0ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy @@ -23,6 +25,10 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { private const val SEARCH_ID_PREFIX = "id:" private const val SEARCH_LIST_PREFIX = "my:" + + private val SCORE_LIST = IntRange(0, 10) + .map(Int::toString) + .toImmutableList() } private val json: Json by injectLazy() @@ -57,9 +63,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List { - return IntRange(0, 10).map(Int::toString) - } + override fun getScoreList(): ImmutableList = SCORE_LIST override fun displayScore(track: Track): String { return track.score.toInt().toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 2f39a95048..6a589511c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy @@ -20,6 +22,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { const val DROPPED = 4 const val PLAN_TO_READ = 5 const val REREADING = 6 + + private val SCORE_LIST = IntRange(0, 10) + .map(Int::toString) + .toImmutableList() } private val json: Json by injectLazy() @@ -28,9 +34,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { private val api by lazy { ShikimoriApi(id, client, interceptor) } - override fun getScoreList(): List { - return IntRange(0, 10).map(Int::toString) - } + override fun getScoreList(): ImmutableList = SCORE_LIST override fun displayScore(track: Track): String { return track.score.toInt().toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt index 4f36709295..6e4539ff70 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt @@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.source.Source +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import tachiyomi.domain.manga.model.Manga as DomainManga import tachiyomi.domain.track.model.Track as DomainTrack @@ -41,7 +43,7 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker { override fun getCompletionStatus(): Int = COMPLETED - override fun getScoreList(): List = emptyList() + override fun getScoreList(): ImmutableList = persistentListOf() override fun displayScore(track: Track): String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterScreenModel.kt index c50a3d9e9b..acfeafa042 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterScreenModel.kt @@ -6,6 +6,11 @@ import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.extension.interactor.GetExtensionLanguages import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.service.SourcePreferences +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -41,8 +46,8 @@ class ExtensionFilterScreenModel( .collectLatest { (extensionLanguages, enabledLanguages) -> mutableState.update { ExtensionFilterState.Success( - languages = extensionLanguages, - enabledLanguages = enabledLanguages, + languages = extensionLanguages.toImmutableList(), + enabledLanguages = enabledLanguages.toImmutableSet(), ) } } @@ -65,8 +70,8 @@ sealed interface ExtensionFilterState { @Immutable data class Success( - val languages: List, - val enabledLanguages: Set = emptySet(), + val languages: ImmutableList, + val enabledLanguages: ImmutableSet = persistentSetOf(), ) : ExtensionFilterState { val isEmpty: Boolean diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt index a3d9b2432b..a0ed5495b9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt @@ -12,6 +12,9 @@ import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.system.LocaleHelper +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -75,10 +78,10 @@ class ExtensionDetailsScreenModel( } .catch { throwable -> logcat(LogPriority.ERROR, throwable) - mutableState.update { it.copy(_sources = emptyList()) } + mutableState.update { it.copy(_sources = persistentListOf()) } } .collectLatest { sources -> - mutableState.update { it.copy(_sources = sources) } + mutableState.update { it.copy(_sources = sources.toImmutableList()) } } } } @@ -164,11 +167,11 @@ class ExtensionDetailsScreenModel( @Immutable data class State( val extension: Extension.Installed? = null, - private val _sources: List? = null, + private val _sources: ImmutableList? = null, ) { - val sources: List - get() = _sources.orEmpty() + val sources: ImmutableList + get() = _sources ?: persistentListOf() val isLoading: Boolean get() = extension == null || _sources == null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreenModel.kt index 2eb4085d6c..e5d886f850 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreenModel.kt @@ -4,6 +4,9 @@ import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.tachiyomi.source.Source +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -40,11 +43,13 @@ class MigrateMangaScreenModel( logcat(LogPriority.ERROR, it) _events.send(MigrationMangaEvent.FailedFetchingFavorites) mutableState.update { state -> - state.copy(titleList = emptyList()) + state.copy(titleList = persistentListOf()) } } .map { manga -> - manga.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }) + manga + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }) + .toImmutableList() } .collectLatest { list -> mutableState.update { it.copy(titleList = list) } @@ -55,11 +60,11 @@ class MigrateMangaScreenModel( @Immutable data class State( val source: Source? = null, - private val titleList: List? = null, + private val titleList: ImmutableList? = null, ) { - val titles: List - get() = titleList.orEmpty() + val titles: ImmutableList + get() = titleList ?: persistentListOf() val isLoading: Boolean get() = source == null || titleList == null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt index 2fd7e1c273..f7659b3e00 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt @@ -6,6 +6,9 @@ import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.service.SourcePreferences +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest @@ -40,7 +43,7 @@ class MigrateSourceScreenModel( mutableState.update { it.copy( isLoading = false, - items = sources, + items = sources.toImmutableList(), ) } } @@ -80,7 +83,7 @@ class MigrateSourceScreenModel( @Immutable data class State( val isLoading: Boolean = true, - val items: List> = emptyList(), + val items: ImmutableList> = persistentListOf(), val sortingMode: SetMigrateSorting.Mode = SetMigrateSorting.Mode.ALPHABETICAL, val sortingDirection: SetMigrateSorting.Direction = SetMigrateSorting.Direction.ASCENDING, ) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt index 1435eea3cc..ce98bf6298 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt @@ -7,6 +7,9 @@ import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSourcePin import eu.kanade.presentation.browse.SourceUiModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest @@ -65,14 +68,16 @@ class SourcesScreenModel( state.copy( isLoading = false, - items = byLang.flatMap { - listOf( - SourceUiModel.Header(it.key), - *it.value.map { source -> - SourceUiModel.Item(source) - }.toTypedArray(), - ) - }, + items = byLang + .flatMap { + listOf( + SourceUiModel.Header(it.key), + *it.value.map { source -> + SourceUiModel.Item(source) + }.toTypedArray(), + ) + } + .toImmutableList(), ) } } @@ -103,7 +108,7 @@ class SourcesScreenModel( data class State( val dialog: Dialog? = null, val isLoading: Boolean = true, - val items: List = emptyList(), + val items: ImmutableList = persistentListOf(), ) { val isEmpty = items.isEmpty() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 276af54ee2..3f7438b7cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -9,6 +9,10 @@ import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.source.CatalogueSource +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentMap import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async @@ -125,9 +129,17 @@ abstract class SearchScreenModel( // Reuse previous results if possible if (sameQuery) { val existingResults = state.value.items - updateItems(sources.associateWith { existingResults[it] ?: SearchItemResult.Loading }) + updateItems( + sources + .associateWith { existingResults[it] ?: SearchItemResult.Loading } + .toPersistentMap(), + ) } else { - updateItems(sources.associateWith { SearchItemResult.Loading }) + updateItems( + sources + .associateWith { SearchItemResult.Loading } + .toPersistentMap(), + ) } searchJob = ioCoroutineScope.launch { @@ -160,14 +172,21 @@ abstract class SearchScreenModel( } } - private fun updateItems(items: Map) { - mutableState.update { it.copy(items = items.toSortedMap(sortComparator(items))) } + private fun updateItems(items: PersistentMap) { + mutableState.update { + it.copy( + items = items + .toSortedMap(sortComparator(items)) + .toPersistentMap(), + ) + } } private fun updateItem(source: CatalogueSource, result: SearchItemResult) { - val mutableItems = state.value.items.toMutableMap() - mutableItems[source] = result - updateItems(mutableItems) + val newItems = state.value.items.mutate { + it[source] = result + } + updateItems(newItems) } @Immutable @@ -176,7 +195,7 @@ abstract class SearchScreenModel( val searchQuery: String? = null, val sourceFilter: SourceFilter = SourceFilter.PinnedOnly, val onlyShowHasResults: Boolean = false, - val items: Map = emptyMap(), + val items: PersistentMap = persistentMapOf(), ) { val progress: Int = items.count { it.value !is SearchItemResult.Loading } val total: Int = items.size diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt index a4a4fe3098..33b7ea1483 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.tachiyomi.R +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow @@ -36,7 +38,9 @@ class CategoryScreenModel( .collectLatest { categories -> mutableState.update { CategoryScreenState.Success( - categories = categories.filterNot(Category::isSystemCategory), + categories = categories + .filterNot(Category::isSystemCategory) + .toImmutableList(), ) } } @@ -135,7 +139,7 @@ sealed interface CategoryScreenState { @Immutable data class Success( - val categories: List, + val categories: ImmutableList, val dialog: CategoryDialog? = null, ) : CategoryScreenState { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index c33b6cd121..72fa1cc6ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -28,6 +28,9 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.removeCovers +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine @@ -562,16 +565,16 @@ class LibraryScreenModel( } fun clearSelection() { - mutableState.update { it.copy(selection = emptyList()) } + mutableState.update { it.copy(selection = persistentListOf()) } } fun toggleSelection(manga: LibraryManga) { mutableState.update { state -> - val newSelection = state.selection.toMutableList().apply { - if (fastAny { it.id == manga.id }) { - removeAll { it.id == manga.id } + val newSelection = state.selection.mutate { list -> + if (list.fastAny { it.id == manga.id }) { + list.removeAll { it.id == manga.id } } else { - add(manga) + list.add(manga) } } state.copy(selection = newSelection) @@ -584,11 +587,11 @@ class LibraryScreenModel( */ fun toggleRangeSelection(manga: LibraryManga) { mutableState.update { state -> - val newSelection = state.selection.toMutableList().apply { - val lastSelected = lastOrNull() + val newSelection = state.selection.mutate { list -> + val lastSelected = list.lastOrNull() if (lastSelected?.category != manga.category) { - add(manga) - return@apply + list.add(manga) + return@mutate } val items = state.getLibraryItemsByCategoryId(manga.category) @@ -596,17 +599,17 @@ class LibraryScreenModel( val lastMangaIndex = items.indexOf(lastSelected) val curMangaIndex = items.indexOf(manga) - val selectedIds = fastMap { it.id } + val selectedIds = list.fastMap { it.id } val selectionRange = when { lastMangaIndex < curMangaIndex -> IntRange(lastMangaIndex, curMangaIndex) curMangaIndex < lastMangaIndex -> IntRange(curMangaIndex, lastMangaIndex) // We shouldn't reach this point - else -> return@apply + else -> return@mutate } val newSelections = selectionRange.mapNotNull { index -> items[index].takeUnless { it.id in selectedIds } } - addAll(newSelections) + list.addAll(newSelections) } state.copy(selection = newSelection) } @@ -614,14 +617,14 @@ class LibraryScreenModel( fun selectAll(index: Int) { mutableState.update { state -> - val newSelection = state.selection.toMutableList().apply { + val newSelection = state.selection.mutate { list -> val categoryId = state.categories.getOrNull(index)?.id ?: -1 - val selectedIds = fastMap { it.id } + val selectedIds = list.fastMap { it.id } state.getLibraryItemsByCategoryId(categoryId) ?.fastMapNotNull { item -> item.libraryManga.takeUnless { it.id in selectedIds } } - ?.let { addAll(it) } + ?.let { list.addAll(it) } } state.copy(selection = newSelection) } @@ -629,14 +632,14 @@ class LibraryScreenModel( fun invertSelection(index: Int) { mutableState.update { state -> - val newSelection = state.selection.toMutableList().apply { + val newSelection = state.selection.mutate { list -> val categoryId = state.categories[index].id val items = state.getLibraryItemsByCategoryId(categoryId)?.fastMap { it.libraryManga }.orEmpty() - val selectedIds = fastMap { it.id } + val selectedIds = list.fastMap { it.id } val (toRemove, toAdd) = items.fastPartition { it.id in selectedIds } val toRemoveIds = toRemove.fastMap { it.id } - removeAll { it.id in toRemoveIds } - addAll(toAdd) + list.removeAll { it.id in toRemoveIds } + list.addAll(toAdd) } state.copy(selection = newSelection) } @@ -703,7 +706,7 @@ class LibraryScreenModel( val isLoading: Boolean = true, val library: LibraryMap = emptyMap(), val searchQuery: String? = null, - val selection: List = emptyList(), + val selection: PersistentList = persistentListOf(), val hasActiveFilters: Boolean = false, val showCategoryTabs: Boolean = false, val showMangaCount: Boolean = false, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index aff6a2fddf..12e8f49747 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow @@ -154,7 +155,7 @@ object LibraryTab : Tab { EmptyScreen( textResource = R.string.information_empty_library, modifier = Modifier.padding(contentPadding), - actions = listOf( + actions = persistentListOf( EmptyScreenAction( stringResId = R.string.getting_started_guide, icon = Icons.Outlined.HelpOutline, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt index 6eb8a0d746..3f33b96516 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt @@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged @@ -398,7 +399,7 @@ private data class TrackScoreSelectorScreen( private val tracker: Tracker, ) : StateScreenModel(State(tracker.displayScore(track.toDbTrack()))) { - fun getSelections(): List { + fun getSelections(): ImmutableList { return tracker.getScoreList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index 5dcd140b33..d4a340ff32 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -21,6 +21,10 @@ import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toRelativeString +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch @@ -106,27 +110,29 @@ class UpdatesScreenModel( } } - private fun List.toUpdateItems(): List { - return this.map { update -> - val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId) - val downloaded = downloadManager.isChapterDownloaded( - update.chapterName, - update.scanlator, - update.mangaTitle, - update.sourceId, - ) - val downloadState = when { - activeDownload != null -> activeDownload.status - downloaded -> Download.State.DOWNLOADED - else -> Download.State.NOT_DOWNLOADED + private fun List.toUpdateItems(): PersistentList { + return this + .map { update -> + val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId) + val downloaded = downloadManager.isChapterDownloaded( + update.chapterName, + update.scanlator, + update.mangaTitle, + update.sourceId, + ) + val downloadState = when { + activeDownload != null -> activeDownload.status + downloaded -> Download.State.DOWNLOADED + else -> Download.State.NOT_DOWNLOADED + } + UpdatesItem( + update = update, + downloadStateProvider = { downloadState }, + downloadProgressProvider = { activeDownload?.progress ?: 0 }, + selected = update.chapterId in selectedChapterIds, + ) } - UpdatesItem( - update = update, - downloadStateProvider = { downloadState }, - downloadProgressProvider = { activeDownload?.progress ?: 0 }, - selected = update.chapterId in selectedChapterIds, - ) - } + .toPersistentList() } fun updateLibrary(): Boolean { @@ -144,17 +150,14 @@ class UpdatesScreenModel( */ private fun updateDownloadState(download: Download) { mutableState.update { state -> - val newItems = state.items.toMutableList().apply { - val modifiedIndex = indexOfFirst { it.update.chapterId == download.chapter.id } - if (modifiedIndex < 0) return@apply + val newItems = state.items.mutate { list -> + val modifiedIndex = list.indexOfFirst { it.update.chapterId == download.chapter.id } + if (modifiedIndex < 0) return@mutate - val item = get(modifiedIndex) - set( - modifiedIndex, - item.copy( - downloadStateProvider = { download.status }, - downloadProgressProvider = { download.progress }, - ), + val item = list[modifiedIndex] + list[modifiedIndex] = item.copy( + downloadStateProvider = { download.status }, + downloadProgressProvider = { download.progress }, ) } state.copy(items = newItems) @@ -330,7 +333,7 @@ class UpdatesScreenModel( } } } - state.copy(items = newItems) + state.copy(items = newItems.toPersistentList()) } } @@ -340,7 +343,7 @@ class UpdatesScreenModel( selectedChapterIds.addOrRemove(it.update.chapterId, selected) it.copy(selected = selected) } - state.copy(items = newItems) + state.copy(items = newItems.toPersistentList()) } selectedPositions[0] = -1 @@ -353,7 +356,7 @@ class UpdatesScreenModel( selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected) it.copy(selected = !it.selected) } - state.copy(items = newItems) + state.copy(items = newItems.toPersistentList()) } selectedPositions[0] = -1 selectedPositions[1] = -1 @@ -370,7 +373,7 @@ class UpdatesScreenModel( @Immutable data class State( val isLoading: Boolean = true, - val items: List = emptyList(), + val items: PersistentList = persistentListOf(), val dialog: Dialog? = null, ) { val selected = items.filter { it.selected } diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt index c9e53f9f57..2e20ebb4dd 100644 --- a/app/src/main/java/eu/kanade/test/DummyTracker.kt +++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt @@ -4,6 +4,8 @@ import android.graphics.Color import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import okhttp3.OkHttpClient import tachiyomi.domain.track.model.Track @@ -18,7 +20,7 @@ data class DummyTracker( val valReadingStatus: Int = 1, val valRereadingStatus: Int = 1, val valCompletionStatus: Int = 2, - val valScoreList: List = (0..10).map(Int::toString), + val valScoreList: ImmutableList = (0..10).map(Int::toString).toImmutableList(), val val10PointScore: Double = 5.4, val valSearchResults: List = listOf(), ) : Tracker { @@ -48,7 +50,7 @@ data class DummyTracker( override fun getCompletionStatus(): Int = valCompletionStatus - override fun getScoreList(): List = valScoreList + override fun getScoreList(): ImmutableList = valScoreList override fun get10PointScore(track: Track): Double = val10PointScore diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt index 8838a199c8..a00ee69e75 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -80,7 +80,7 @@ class EpubFile(file: File) : Closeable { /** * Returns all the pages from the epub. */ - fun getPagesFromDocument(document: Document): List { + private fun getPagesFromDocument(document: Document): List { val pages = document.select("manifest > item") .filter { node -> "application/xhtml+xml" == node.attr("media-type") } .associateBy { it.attr("id") } @@ -102,10 +102,9 @@ class EpubFile(file: File) : Closeable { val imageBasePath = getParentDirectory(entryPath) document.allElements.forEach { - if (it.tagName() == "img") { - result.add(resolveZipPath(imageBasePath, it.attr("src"))) - } else if (it.tagName() == "image") { - result.add(resolveZipPath(imageBasePath, it.attr("xlink:href"))) + when (it.tagName()) { + "img" -> result.add(resolveZipPath(imageBasePath, it.attr("src"))) + "image" -> result.add(resolveZipPath(imageBasePath, it.attr("xlink:href"))) } } } diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index b578de9130..4727dbed8d 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -36,6 +36,8 @@ dependencies { implementation(compose.ui.tooling.preview) implementation(compose.ui.util) lintChecks(compose.lintchecks) + + implementation(kotlinx.immutables) } tasks { diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt index f2a004ba2b..ee2a05dac0 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt @@ -36,13 +36,14 @@ fun BadgeGroup( @Composable fun Badge( text: String, + modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.secondary, textColor: Color = MaterialTheme.colorScheme.onSecondary, shape: Shape = RectangleShape, ) { Text( text = text, - modifier = Modifier + modifier = modifier .clip(shape) .background(color) .padding(horizontal = 3.dp, vertical = 1.dp), @@ -56,6 +57,7 @@ fun Badge( @Composable fun Badge( imageVector: ImageVector, + modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.secondary, iconColor: Color = MaterialTheme.colorScheme.onSecondary, shape: Shape = RectangleShape, @@ -86,7 +88,7 @@ fun Badge( Text( text = text, inlineContent = inlineContent, - modifier = Modifier + modifier = modifier .clip(shape) .background(color) .padding(horizontal = 3.dp, vertical = 1.dp), diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt index d13b96a7b5..636e1ebacf 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop @@ -54,7 +55,7 @@ import kotlin.math.absoluteValue @Composable fun WheelNumberPicker( - items: List, + items: ImmutableList, modifier: Modifier = Modifier, startIndex: Int = 0, size: DpSize = DpSize(128.dp, 128.dp), @@ -78,7 +79,7 @@ fun WheelNumberPicker( @Composable fun WheelTextPicker( - items: List, + items: ImmutableList, modifier: Modifier = Modifier, startIndex: Int = 0, size: DpSize = DpSize(128.dp, 128.dp), @@ -101,7 +102,7 @@ fun WheelTextPicker( @Composable private fun WheelPicker( - items: List, + items: ImmutableList, modifier: Modifier = Modifier, startIndex: Int = 0, size: DpSize = DpSize(128.dp, 128.dp), diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt index 23eaa436cb..f7de6a9316 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach +import kotlinx.collections.immutable.ImmutableList import tachiyomi.presentation.core.components.ActionButton import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.secondaryItemAlpha @@ -38,7 +39,7 @@ data class EmptyScreenAction( fun EmptyScreen( @StringRes textResource: Int, modifier: Modifier = Modifier, - actions: List? = null, + actions: ImmutableList? = null, ) { EmptyScreen( message = stringResource(textResource), @@ -51,7 +52,7 @@ fun EmptyScreen( fun EmptyScreen( message: String, modifier: Modifier = Modifier, - actions: List? = null, + actions: ImmutableList? = null, ) { val face = remember { getRandomErrorFace() } Column( @@ -98,7 +99,7 @@ fun EmptyScreen( } } -private val ERROR_FACES = listOf( +private val ErrorFaces = listOf( "(・o・;)", "Σ(ಠ_ಠ)", "ಥ_ಥ", @@ -108,5 +109,5 @@ private val ERROR_FACES = listOf( ) private fun getRandomErrorFace(): String { - return ERROR_FACES[Random.nextInt(ERROR_FACES.size)] + return ErrorFaces[Random.nextInt(ErrorFaces.size)] }