浏览代码

Use immutable collections in more places

arkon 1 年之前
父节点
当前提交
336221a972
共有 35 个文件被更改,包括 253 次插入153 次删除
  1. 1 0
      app/build.gradle.kts
  2. 3 2
      app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
  3. 2 2
      app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
  4. 2 1
      app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt
  5. 9 6
      app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt
  6. 5 6
      app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt
  7. 5 1
      app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt
  8. 4 2
      app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt
  9. 2 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt
  10. 9 6
      app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt
  11. 7 3
      app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt
  12. 3 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt
  13. 4 2
      app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt
  14. 3 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt
  15. 10 4
      app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt
  16. 7 3
      app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt
  17. 7 3
      app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt
  18. 3 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt
  19. 9 4
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionFilterScreenModel.kt
  20. 8 5
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt
  21. 10 5
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreenModel.kt
  22. 5 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceScreenModel.kt
  23. 14 9
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesScreenModel.kt
  24. 27 8
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt
  25. 6 2
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt
  26. 23 20
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
  27. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt
  28. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt
  29. 38 35
      app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt
  30. 4 2
      app/src/main/java/eu/kanade/test/DummyTracker.kt
  31. 4 5
      core/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt
  32. 2 0
      presentation-core/build.gradle.kts
  33. 4 2
      presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt
  34. 4 3
      presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt
  35. 5 4
      presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt

+ 1 - 0
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)

+ 3 - 2
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,

+ 2 - 2
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,

+ 2 - 1
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,

+ 9 - 6
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,

+ 5 - 6
app/src/main/java/eu/kanade/presentation/more/settings/PreferenceScreen.kt

@@ -85,12 +85,11 @@ fun PreferenceScreen(
 private fun List<Preference>.findHighlightedIndex(highlightKey: String): Int {
     return flatMap {
         if (it is Preference.PreferenceGroup) {
-            mutableListOf<String?>()
-                .apply {
-                    add(null) // Header
-                    addAll(it.preferenceItems.map { groupItem -> groupItem.title })
-                    add(null) // Spacer
-                }
+            buildList<String?> {
+                add(null) // Header
+                addAll(it.preferenceItems.map { groupItem -> groupItem.title })
+                add(null) // Spacer
+            }
         } else {
             listOf(it.title)
         }

+ 5 - 1
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<CreateBackupScreenModel
 
     @Immutable
     data class State(
-        val flags: Set<Int> = BackupChoices.keys,
+        val flags: PersistentSet<Int> = BackupChoices.keys.toPersistentSet(),
     )
 }
 

+ 4 - 2
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<String>,
+    selections: ImmutableList<String>,
     onConfirm: () -> Unit,
     onDismissRequest: () -> Unit,
 ) {

+ 2 - 1
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<String>
+    fun getScoreList(): ImmutableList<String>
 
     // TODO: Store all scores as 10 point in the future maybe?
     fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double

+ 9 - 6
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<String> {
+    override fun getScoreList(): ImmutableList<String> {
         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")
         }
     }

+ 7 - 3
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<String> {
-        return IntRange(0, 10).map(Int::toString)
-    }
+    override fun getScoreList(): ImmutableList<String> = 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()
     }
 }

+ 3 - 1
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<String> = emptyList()
+    override fun getScoreList(): ImmutableList<String> = persistentListOf()
 
     override fun displayScore(track: Track): String = ""
 

+ 4 - 2
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<String> {
+    override fun getScoreList(): ImmutableList<String> {
         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 {

+ 3 - 1
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<String> = emptyList()
+    override fun getScoreList(): ImmutableList<String> = persistentListOf()
 
     override fun displayScore(track: Track): String = ""
 

+ 10 - 4
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(): List<String> = _scoreList
+    override fun getScoreList(): ImmutableList<String> = SCORE_LIST
 
-    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()
 

+ 7 - 3
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<String> {
-        return IntRange(0, 10).map(Int::toString)
-    }
+    override fun getScoreList(): ImmutableList<String> = SCORE_LIST
 
     override fun displayScore(track: Track): String {
         return track.score.toInt().toString()

+ 7 - 3
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<String> {
-        return IntRange(0, 10).map(Int::toString)
-    }
+    override fun getScoreList(): ImmutableList<String> = SCORE_LIST
 
     override fun displayScore(track: Track): String {
         return track.score.toInt().toString()

+ 3 - 1
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<String> = emptyList()
+    override fun getScoreList(): ImmutableList<String> = persistentListOf()
 
     override fun displayScore(track: Track): String = ""
 

+ 9 - 4
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<String>,
-        val enabledLanguages: Set<String> = emptySet(),
+        val languages: ImmutableList<String>,
+        val enabledLanguages: ImmutableSet<String> = persistentSetOf(),
     ) : ExtensionFilterState {
 
         val isEmpty: Boolean

+ 8 - 5
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<ExtensionSourceItem>? = null,
+        private val _sources: ImmutableList<ExtensionSourceItem>? = null,
     ) {
 
-        val sources: List<ExtensionSourceItem>
-            get() = _sources.orEmpty()
+        val sources: ImmutableList<ExtensionSourceItem>
+            get() = _sources ?: persistentListOf()
 
         val isLoading: Boolean
             get() = extension == null || _sources == null

+ 10 - 5
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<Manga>? = null,
+        private val titleList: ImmutableList<Manga>? = null,
     ) {
 
-        val titles: List<Manga>
-            get() = titleList.orEmpty()
+        val titles: ImmutableList<Manga>
+            get() = titleList ?: persistentListOf()
 
         val isLoading: Boolean
             get() = source == null || titleList == null

+ 5 - 2
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<Pair<Source, Long>> = emptyList(),
+        val items: ImmutableList<Pair<Source, Long>> = persistentListOf(),
         val sortingMode: SetMigrateSorting.Mode = SetMigrateSorting.Mode.ALPHABETICAL,
         val sortingDirection: SetMigrateSorting.Direction = SetMigrateSorting.Direction.ASCENDING,
     ) {

+ 14 - 9
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<SourceUiModel> = emptyList(),
+        val items: ImmutableList<SourceUiModel> = persistentListOf(),
     ) {
         val isEmpty = items.isEmpty()
     }

+ 27 - 8
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<CatalogueSource, SearchItemResult>) {
-        mutableState.update { it.copy(items = items.toSortedMap(sortComparator(items))) }
+    private fun updateItems(items: PersistentMap<CatalogueSource, SearchItemResult>) {
+        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<CatalogueSource, SearchItemResult> = emptyMap(),
+        val items: PersistentMap<CatalogueSource, SearchItemResult> = persistentMapOf(),
     ) {
         val progress: Int = items.count { it.value !is SearchItemResult.Loading }
         val total: Int = items.size

+ 6 - 2
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<Category>,
+        val categories: ImmutableList<Category>,
         val dialog: CategoryDialog? = null,
     ) : CategoryScreenState {
 

+ 23 - 20
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<LibraryManga> = emptyList(),
+        val selection: PersistentList<LibraryManga> = persistentListOf(),
         val hasActiveFilters: Boolean = false,
         val showCategoryTabs: Boolean = false,
         val showMangaCount: Boolean = false,

+ 2 - 1
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,

+ 2 - 1
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<Model.State>(State(tracker.displayScore(track.toDbTrack()))) {
 
-        fun getSelections(): List<String> {
+        fun getSelections(): ImmutableList<String> {
             return tracker.getScoreList()
         }
 

+ 38 - 35
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<UpdatesWithRelations>.toUpdateItems(): List<UpdatesItem> {
-        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<UpdatesWithRelations>.toUpdateItems(): PersistentList<UpdatesItem> {
+        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 item = get(modifiedIndex)
-                set(
-                    modifiedIndex,
-                    item.copy(
-                        downloadStateProvider = { download.status },
-                        downloadProgressProvider = { download.progress },
-                    ),
+            val newItems = state.items.mutate { list ->
+                val modifiedIndex = list.indexOfFirst { it.update.chapterId == download.chapter.id }
+                if (modifiedIndex < 0) return@mutate
+
+                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<UpdatesItem> = emptyList(),
+        val items: PersistentList<UpdatesItem> = persistentListOf(),
         val dialog: Dialog? = null,
     ) {
         val selected = items.filter { it.selected }

+ 4 - 2
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<String> = (0..10).map(Int::toString),
+    val valScoreList: ImmutableList<String> = (0..10).map(Int::toString).toImmutableList(),
     val val10PointScore: Double = 5.4,
     val valSearchResults: List<TrackSearch> = listOf(),
 ) : Tracker {
@@ -48,7 +50,7 @@ data class DummyTracker(
 
     override fun getCompletionStatus(): Int = valCompletionStatus
 
-    override fun getScoreList(): List<String> = valScoreList
+    override fun getScoreList(): ImmutableList<String> = valScoreList
 
     override fun get10PointScore(track: Track): Double = val10PointScore
 

+ 4 - 5
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<String> {
+    private fun getPagesFromDocument(document: Document): List<String> {
         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")))
                 }
             }
         }

+ 2 - 0
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 {

+ 4 - 2
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),

+ 4 - 3
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<Number>,
+    items: ImmutableList<Number>,
     modifier: Modifier = Modifier,
     startIndex: Int = 0,
     size: DpSize = DpSize(128.dp, 128.dp),
@@ -78,7 +79,7 @@ fun WheelNumberPicker(
 
 @Composable
 fun WheelTextPicker(
-    items: List<String>,
+    items: ImmutableList<String>,
     modifier: Modifier = Modifier,
     startIndex: Int = 0,
     size: DpSize = DpSize(128.dp, 128.dp),
@@ -101,7 +102,7 @@ fun WheelTextPicker(
 
 @Composable
 private fun <T> WheelPicker(
-    items: List<T>,
+    items: ImmutableList<T>,
     modifier: Modifier = Modifier,
     startIndex: Int = 0,
     size: DpSize = DpSize(128.dp, 128.dp),

+ 5 - 4
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<EmptyScreenAction>? = null,
+    actions: ImmutableList<EmptyScreenAction>? = null,
 ) {
     EmptyScreen(
         message = stringResource(textResource),
@@ -51,7 +52,7 @@ fun EmptyScreen(
 fun EmptyScreen(
     message: String,
     modifier: Modifier = Modifier,
-    actions: List<EmptyScreenAction>? = null,
+    actions: ImmutableList<EmptyScreenAction>? = 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)]
 }