Use immutable collections in more places

This commit is contained in:
arkon 2023-11-11 22:43:50 -05:00
parent dd998be1e7
commit 336221a972
35 changed files with 252 additions and 152 deletions

View file

@ -175,6 +175,7 @@ dependencies {
implementation(libs.bundles.sqlite) implementation(libs.bundles.sqlite)
implementation(kotlinx.reflect) implementation(kotlinx.reflect)
implementation(kotlinx.immutables)
implementation(platform(kotlinx.coroutines.bom)) implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines) implementation(kotlinx.bundles.coroutines)

View file

@ -24,6 +24,7 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.formattedMessage import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -76,7 +77,7 @@ fun BrowseSourceContent(
modifier = Modifier.padding(contentPadding), modifier = Modifier.padding(contentPadding),
message = getErrorMessage(errorState), message = getErrorMessage(errorState),
actions = if (source is LocalSource) { actions = if (source is LocalSource) {
listOf( persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.local_source_help_guide, stringResId = R.string.local_source_help_guide,
icon = Icons.Outlined.HelpOutline, icon = Icons.Outlined.HelpOutline,
@ -84,7 +85,7 @@ fun BrowseSourceContent(
), ),
) )
} else { } else {
listOf( persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.action_retry, stringResId = R.string.action_retry,
icon = Icons.Outlined.Refresh, icon = Icons.Outlined.Refresh,

View file

@ -94,8 +94,8 @@ fun SourcesScreen(
@Composable @Composable
private fun SourceHeader( private fun SourceHeader(
modifier: Modifier = Modifier,
language: String, language: String,
modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current val context = LocalContext.current
Text( Text(
@ -108,11 +108,11 @@ private fun SourceHeader(
@Composable @Composable
private fun SourceItem( private fun SourceItem(
modifier: Modifier = Modifier,
source: Source, source: Source,
onClickItem: (Source, Listing) -> Unit, onClickItem: (Source, Listing) -> Unit,
onLongClickItem: (Source) -> Unit, onLongClickItem: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BaseSourceItem( BaseSourceItem(
modifier = modifier, modifier = modifier,

View file

@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.EmptyScreenAction import tachiyomi.presentation.core.screens.EmptyScreenAction
@ -30,7 +31,7 @@ private fun WithActionPreview() {
Surface { Surface {
EmptyScreen( EmptyScreen(
textResource = R.string.empty_screen, textResource = R.string.empty_screen,
actions = listOf( actions = persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.action_retry, stringResId = R.string.action_retry,
icon = Icons.Outlined.Refresh, icon = Icons.Outlined.Refresh,

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.toImmutableList
import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.WheelTextPicker
@ -67,13 +68,15 @@ fun SetIntervalDialog(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
val size = DpSize(width = maxWidth / 2, height = 128.dp) val size = DpSize(width = maxWidth / 2, height = 128.dp)
val items = (0..FetchInterval.MAX_INTERVAL).map { val items = (0..FetchInterval.MAX_INTERVAL)
if (it == 0) { .map {
stringResource(R.string.label_default) if (it == 0) {
} else { stringResource(R.string.label_default)
it.toString() } else {
it.toString()
}
} }
} .toImmutableList()
WheelTextPicker( WheelTextPicker(
items = items, items = items,
size = size, size = size,

View file

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

View file

@ -36,6 +36,10 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.toast 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 kotlinx.coroutines.flow.update
import tachiyomi.presentation.core.components.LabeledCheckbox import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -154,7 +158,7 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel
@Immutable @Immutable
data class State( data class State(
val flags: Set<Int> = BackupChoices.keys, val flags: PersistentSet<Int> = BackupChoices.keys.toPersistentSet(),
) )
} }

View file

@ -34,6 +34,8 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R 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.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.WheelNumberPicker import tachiyomi.presentation.core.components.WheelNumberPicker
import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.WheelTextPicker
@ -102,7 +104,7 @@ fun TrackChapterSelector(
title = stringResource(R.string.chapters), title = stringResource(R.string.chapters),
content = { content = {
WheelNumberPicker( WheelNumberPicker(
items = range.toList(), items = range.toImmutableList(),
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
startIndex = selection, startIndex = selection,
onSelectionChanged = { onSelectionChange(it) }, onSelectionChanged = { onSelectionChange(it) },
@ -117,7 +119,7 @@ fun TrackChapterSelector(
fun TrackScoreSelector( fun TrackScoreSelector(
selection: String, selection: String,
onSelectionChange: (String) -> Unit, onSelectionChange: (String) -> Unit,
selections: List<String>, selections: ImmutableList<String>,
onConfirm: () -> Unit, onConfirm: () -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {

View file

@ -6,6 +6,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
interface Tracker { interface Tracker {
@ -36,7 +37,7 @@ interface Tracker {
fun getCompletionStatus(): Int fun getCompletionStatus(): Int
fun getScoreList(): List<String> fun getScoreList(): ImmutableList<String>
// TODO: Store all scores as 10 point in the future maybe? // TODO: Store all scores as 10 point in the future maybe?
fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double

View file

@ -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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -74,18 +77,18 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
override fun getCompletionStatus(): Int = COMPLETED override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> { override fun getScoreList(): ImmutableList<String> {
return when (scorePreference.get()) { return when (scorePreference.get()) {
// 10 point // 10 point
POINT_10 -> IntRange(0, 10).map(Int::toString) POINT_10 -> IntRange(0, 10).map(Int::toString).toImmutableList()
// 100 point // 100 point
POINT_100 -> IntRange(0, 100).map(Int::toString) POINT_100 -> IntRange(0, 100).map(Int::toString).toImmutableList()
// 5 stars // 5 stars
POINT_5 -> IntRange(0, 5).map { "$it" } POINT_5 -> IntRange(0, 5).map { "$it" }.toImmutableList()
// Smiley // Smiley
POINT_3 -> listOf("-", "😦", "😐", "😊") POINT_3 -> persistentListOf("-", "😦", "😐", "😊")
// 10 point decimal // 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") else -> throw Exception("Unknown score type")
} }
} }

View file

@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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) } private val api by lazy { BangumiApi(id, client, interceptor) }
override fun getScoreList(): List<String> { override fun getScoreList(): ImmutableList<String> = SCORE_LIST
return IntRange(0, 10).map(Int::toString)
}
override fun displayScore(track: Track): String { override fun displayScore(track: Track): String {
return track.score.toInt().toString() return track.score.toInt().toString()
@ -141,5 +141,9 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
const val ON_HOLD = 4 const val ON_HOLD = 4
const val DROPPED = 5 const val DROPPED = 5
const val PLAN_TO_READ = 1 const val PLAN_TO_READ = 1
private val SCORE_LIST = IntRange(0, 10)
.map(Int::toString)
.toImmutableList()
} }
} }

View file

@ -10,6 +10,8 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.sourcePreferences 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.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -51,7 +53,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
override fun getCompletionStatus(): Int = COMPLETED override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList() override fun getScoreList(): ImmutableList<String> = persistentListOf()
override fun displayScore(track: Track): String = "" override fun displayScore(track: Track): String = ""

View file

@ -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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -54,9 +56,9 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
override fun getCompletionStatus(): Int = COMPLETED override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> { override fun getScoreList(): ImmutableList<String> {
val df = DecimalFormat("0.#") 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 { override fun indexToScore(index: Int): Float {

View file

@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import okhttp3.Dns import okhttp3.Dns
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import tachiyomi.domain.manga.model.Manga 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 getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList() override fun getScoreList(): ImmutableList<String> = persistentListOf()
override fun displayScore(track: Track): String = "" override fun displayScore(track: Track): String = ""

View file

@ -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.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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 { 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 COMPLETE_LIST = 2
const val UNFINISHED_LIST = 3 const val UNFINISHED_LIST = 3
const val ON_HOLD_LIST = 4 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) } 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 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<String> = SCORE_LIST
override fun getScoreList(): List<String> = _scoreList override fun indexToScore(index: Int): Float = SCORE_LIST[index].toFloat()
override fun indexToScore(index: Int): Float = _scoreList[index].toFloat()
override fun displayScore(track: Track): String = track.score.toString() override fun displayScore(track: Track): String = track.score.toString()

View file

@ -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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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_ID_PREFIX = "id:"
private const val SEARCH_LIST_PREFIX = "my:" private const val SEARCH_LIST_PREFIX = "my:"
private val SCORE_LIST = IntRange(0, 10)
.map(Int::toString)
.toImmutableList()
} }
private val json: Json by injectLazy() 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 getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> { override fun getScoreList(): ImmutableList<String> = SCORE_LIST
return IntRange(0, 10).map(Int::toString)
}
override fun displayScore(track: Track): String { override fun displayScore(track: Track): String {
return track.score.toInt().toString() return track.score.toInt().toString()

View file

@ -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.BaseTracker
import eu.kanade.tachiyomi.data.track.DeletableTracker import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -20,6 +22,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
const val DROPPED = 4 const val DROPPED = 4
const val PLAN_TO_READ = 5 const val PLAN_TO_READ = 5
const val REREADING = 6 const val REREADING = 6
private val SCORE_LIST = IntRange(0, 10)
.map(Int::toString)
.toImmutableList()
} }
private val json: Json by injectLazy() 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) } private val api by lazy { ShikimoriApi(id, client, interceptor) }
override fun getScoreList(): List<String> { override fun getScoreList(): ImmutableList<String> = SCORE_LIST
return IntRange(0, 10).map(Int::toString)
}
override fun displayScore(track: Track): String { override fun displayScore(track: Track): String {
return track.score.toInt().toString() return track.score.toInt().toString()

View file

@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.track.BaseTracker
import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source 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.manga.model.Manga as DomainManga
import tachiyomi.domain.track.model.Track as DomainTrack 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 getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList() override fun getScoreList(): ImmutableList<String> = persistentListOf()
override fun displayScore(track: Track): String = "" override fun displayScore(track: Track): String = ""

View file

@ -6,6 +6,11 @@ import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.extension.interactor.GetExtensionLanguages import eu.kanade.domain.extension.interactor.GetExtensionLanguages
import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.service.SourcePreferences 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.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -41,8 +46,8 @@ class ExtensionFilterScreenModel(
.collectLatest { (extensionLanguages, enabledLanguages) -> .collectLatest { (extensionLanguages, enabledLanguages) ->
mutableState.update { mutableState.update {
ExtensionFilterState.Success( ExtensionFilterState.Success(
languages = extensionLanguages, languages = extensionLanguages.toImmutableList(),
enabledLanguages = enabledLanguages, enabledLanguages = enabledLanguages.toImmutableSet(),
) )
} }
} }
@ -65,8 +70,8 @@ sealed interface ExtensionFilterState {
@Immutable @Immutable
data class Success( data class Success(
val languages: List<String>, val languages: ImmutableList<String>,
val enabledLanguages: Set<String> = emptySet(), val enabledLanguages: ImmutableSet<String> = persistentSetOf(),
) : ExtensionFilterState { ) : ExtensionFilterState {
val isEmpty: Boolean val isEmpty: Boolean

View file

@ -12,6 +12,9 @@ import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.LocaleHelper 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.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -75,10 +78,10 @@ class ExtensionDetailsScreenModel(
} }
.catch { throwable -> .catch { throwable ->
logcat(LogPriority.ERROR, throwable) logcat(LogPriority.ERROR, throwable)
mutableState.update { it.copy(_sources = emptyList()) } mutableState.update { it.copy(_sources = persistentListOf()) }
} }
.collectLatest { sources -> .collectLatest { sources ->
mutableState.update { it.copy(_sources = sources) } mutableState.update { it.copy(_sources = sources.toImmutableList()) }
} }
} }
} }
@ -164,11 +167,11 @@ class ExtensionDetailsScreenModel(
@Immutable @Immutable
data class State( data class State(
val extension: Extension.Installed? = null, val extension: Extension.Installed? = null,
private val _sources: List<ExtensionSourceItem>? = null, private val _sources: ImmutableList<ExtensionSourceItem>? = null,
) { ) {
val sources: List<ExtensionSourceItem> val sources: ImmutableList<ExtensionSourceItem>
get() = _sources.orEmpty() get() = _sources ?: persistentListOf()
val isLoading: Boolean val isLoading: Boolean
get() = extension == null || _sources == null get() = extension == null || _sources == null

View file

@ -4,6 +4,9 @@ import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.tachiyomi.source.Source 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.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -40,11 +43,13 @@ class MigrateMangaScreenModel(
logcat(LogPriority.ERROR, it) logcat(LogPriority.ERROR, it)
_events.send(MigrationMangaEvent.FailedFetchingFavorites) _events.send(MigrationMangaEvent.FailedFetchingFavorites)
mutableState.update { state -> mutableState.update { state ->
state.copy(titleList = emptyList()) state.copy(titleList = persistentListOf())
} }
} }
.map { manga -> .map { manga ->
manga.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }) manga
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title })
.toImmutableList()
} }
.collectLatest { list -> .collectLatest { list ->
mutableState.update { it.copy(titleList = list) } mutableState.update { it.copy(titleList = list) }
@ -55,11 +60,11 @@ class MigrateMangaScreenModel(
@Immutable @Immutable
data class State( data class State(
val source: Source? = null, val source: Source? = null,
private val titleList: List<Manga>? = null, private val titleList: ImmutableList<Manga>? = null,
) { ) {
val titles: List<Manga> val titles: ImmutableList<Manga>
get() = titleList.orEmpty() get() = titleList ?: persistentListOf()
val isLoading: Boolean val isLoading: Boolean
get() = source == null || titleList == null get() = source == null || titleList == null

View file

@ -6,6 +6,9 @@ import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.service.SourcePreferences 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.channels.Channel
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -40,7 +43,7 @@ class MigrateSourceScreenModel(
mutableState.update { mutableState.update {
it.copy( it.copy(
isLoading = false, isLoading = false,
items = sources, items = sources.toImmutableList(),
) )
} }
} }
@ -80,7 +83,7 @@ class MigrateSourceScreenModel(
@Immutable @Immutable
data class State( data class State(
val isLoading: Boolean = true, 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 sortingMode: SetMigrateSorting.Mode = SetMigrateSorting.Mode.ALPHABETICAL,
val sortingDirection: SetMigrateSorting.Direction = SetMigrateSorting.Direction.ASCENDING, val sortingDirection: SetMigrateSorting.Direction = SetMigrateSorting.Direction.ASCENDING,
) { ) {

View file

@ -7,6 +7,9 @@ import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.presentation.browse.SourceUiModel 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.channels.Channel
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -65,14 +68,16 @@ class SourcesScreenModel(
state.copy( state.copy(
isLoading = false, isLoading = false,
items = byLang.flatMap { items = byLang
listOf( .flatMap {
SourceUiModel.Header(it.key), listOf(
*it.value.map { source -> SourceUiModel.Header(it.key),
SourceUiModel.Item(source) *it.value.map { source ->
}.toTypedArray(), SourceUiModel.Item(source)
) }.toTypedArray(),
}, )
}
.toImmutableList(),
) )
} }
} }
@ -103,7 +108,7 @@ class SourcesScreenModel(
data class State( data class State(
val dialog: Dialog? = null, val dialog: Dialog? = null,
val isLoading: Boolean = true, val isLoading: Boolean = true,
val items: List<SourceUiModel> = emptyList(), val items: ImmutableList<SourceUiModel> = persistentListOf(),
) { ) {
val isEmpty = items.isEmpty() val isEmpty = items.isEmpty()
} }

View file

@ -9,6 +9,10 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource 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.Job
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -125,9 +129,17 @@ abstract class SearchScreenModel(
// Reuse previous results if possible // Reuse previous results if possible
if (sameQuery) { if (sameQuery) {
val existingResults = state.value.items val existingResults = state.value.items
updateItems(sources.associateWith { existingResults[it] ?: SearchItemResult.Loading }) updateItems(
sources
.associateWith { existingResults[it] ?: SearchItemResult.Loading }
.toPersistentMap(),
)
} else { } else {
updateItems(sources.associateWith { SearchItemResult.Loading }) updateItems(
sources
.associateWith { SearchItemResult.Loading }
.toPersistentMap(),
)
} }
searchJob = ioCoroutineScope.launch { searchJob = ioCoroutineScope.launch {
@ -160,14 +172,21 @@ abstract class SearchScreenModel(
} }
} }
private fun updateItems(items: Map<CatalogueSource, SearchItemResult>) { private fun updateItems(items: PersistentMap<CatalogueSource, SearchItemResult>) {
mutableState.update { it.copy(items = items.toSortedMap(sortComparator(items))) } mutableState.update {
it.copy(
items = items
.toSortedMap(sortComparator(items))
.toPersistentMap(),
)
}
} }
private fun updateItem(source: CatalogueSource, result: SearchItemResult) { private fun updateItem(source: CatalogueSource, result: SearchItemResult) {
val mutableItems = state.value.items.toMutableMap() val newItems = state.value.items.mutate {
mutableItems[source] = result it[source] = result
updateItems(mutableItems) }
updateItems(newItems)
} }
@Immutable @Immutable
@ -176,7 +195,7 @@ abstract class SearchScreenModel(
val searchQuery: String? = null, val searchQuery: String? = null,
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly, val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
val onlyShowHasResults: Boolean = false, 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 progress: Int = items.count { it.value !is SearchItemResult.Loading }
val total: Int = items.size val total: Int = items.size

View file

@ -5,6 +5,8 @@ import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
@ -36,7 +38,9 @@ class CategoryScreenModel(
.collectLatest { categories -> .collectLatest { categories ->
mutableState.update { mutableState.update {
CategoryScreenState.Success( CategoryScreenState.Success(
categories = categories.filterNot(Category::isSystemCategory), categories = categories
.filterNot(Category::isSystemCategory)
.toImmutableList(),
) )
} }
} }
@ -135,7 +139,7 @@ sealed interface CategoryScreenState {
@Immutable @Immutable
data class Success( data class Success(
val categories: List<Category>, val categories: ImmutableList<Category>,
val dialog: CategoryDialog? = null, val dialog: CategoryDialog? = null,
) : CategoryScreenState { ) : CategoryScreenState {

View file

@ -28,6 +28,9 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.removeCovers 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.Flow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -562,16 +565,16 @@ class LibraryScreenModel(
} }
fun clearSelection() { fun clearSelection() {
mutableState.update { it.copy(selection = emptyList()) } mutableState.update { it.copy(selection = persistentListOf()) }
} }
fun toggleSelection(manga: LibraryManga) { fun toggleSelection(manga: LibraryManga) {
mutableState.update { state -> mutableState.update { state ->
val newSelection = state.selection.toMutableList().apply { val newSelection = state.selection.mutate { list ->
if (fastAny { it.id == manga.id }) { if (list.fastAny { it.id == manga.id }) {
removeAll { it.id == manga.id } list.removeAll { it.id == manga.id }
} else { } else {
add(manga) list.add(manga)
} }
} }
state.copy(selection = newSelection) state.copy(selection = newSelection)
@ -584,11 +587,11 @@ class LibraryScreenModel(
*/ */
fun toggleRangeSelection(manga: LibraryManga) { fun toggleRangeSelection(manga: LibraryManga) {
mutableState.update { state -> mutableState.update { state ->
val newSelection = state.selection.toMutableList().apply { val newSelection = state.selection.mutate { list ->
val lastSelected = lastOrNull() val lastSelected = list.lastOrNull()
if (lastSelected?.category != manga.category) { if (lastSelected?.category != manga.category) {
add(manga) list.add(manga)
return@apply return@mutate
} }
val items = state.getLibraryItemsByCategoryId(manga.category) val items = state.getLibraryItemsByCategoryId(manga.category)
@ -596,17 +599,17 @@ class LibraryScreenModel(
val lastMangaIndex = items.indexOf(lastSelected) val lastMangaIndex = items.indexOf(lastSelected)
val curMangaIndex = items.indexOf(manga) val curMangaIndex = items.indexOf(manga)
val selectedIds = fastMap { it.id } val selectedIds = list.fastMap { it.id }
val selectionRange = when { val selectionRange = when {
lastMangaIndex < curMangaIndex -> IntRange(lastMangaIndex, curMangaIndex) lastMangaIndex < curMangaIndex -> IntRange(lastMangaIndex, curMangaIndex)
curMangaIndex < lastMangaIndex -> IntRange(curMangaIndex, lastMangaIndex) curMangaIndex < lastMangaIndex -> IntRange(curMangaIndex, lastMangaIndex)
// We shouldn't reach this point // We shouldn't reach this point
else -> return@apply else -> return@mutate
} }
val newSelections = selectionRange.mapNotNull { index -> val newSelections = selectionRange.mapNotNull { index ->
items[index].takeUnless { it.id in selectedIds } items[index].takeUnless { it.id in selectedIds }
} }
addAll(newSelections) list.addAll(newSelections)
} }
state.copy(selection = newSelection) state.copy(selection = newSelection)
} }
@ -614,14 +617,14 @@ class LibraryScreenModel(
fun selectAll(index: Int) { fun selectAll(index: Int) {
mutableState.update { state -> mutableState.update { state ->
val newSelection = state.selection.toMutableList().apply { val newSelection = state.selection.mutate { list ->
val categoryId = state.categories.getOrNull(index)?.id ?: -1 val categoryId = state.categories.getOrNull(index)?.id ?: -1
val selectedIds = fastMap { it.id } val selectedIds = list.fastMap { it.id }
state.getLibraryItemsByCategoryId(categoryId) state.getLibraryItemsByCategoryId(categoryId)
?.fastMapNotNull { item -> ?.fastMapNotNull { item ->
item.libraryManga.takeUnless { it.id in selectedIds } item.libraryManga.takeUnless { it.id in selectedIds }
} }
?.let { addAll(it) } ?.let { list.addAll(it) }
} }
state.copy(selection = newSelection) state.copy(selection = newSelection)
} }
@ -629,14 +632,14 @@ class LibraryScreenModel(
fun invertSelection(index: Int) { fun invertSelection(index: Int) {
mutableState.update { state -> mutableState.update { state ->
val newSelection = state.selection.toMutableList().apply { val newSelection = state.selection.mutate { list ->
val categoryId = state.categories[index].id val categoryId = state.categories[index].id
val items = state.getLibraryItemsByCategoryId(categoryId)?.fastMap { it.libraryManga }.orEmpty() 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 (toRemove, toAdd) = items.fastPartition { it.id in selectedIds }
val toRemoveIds = toRemove.fastMap { it.id } val toRemoveIds = toRemove.fastMap { it.id }
removeAll { it.id in toRemoveIds } list.removeAll { it.id in toRemoveIds }
addAll(toAdd) list.addAll(toAdd)
} }
state.copy(selection = newSelection) state.copy(selection = newSelection)
} }
@ -703,7 +706,7 @@ class LibraryScreenModel(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val library: LibraryMap = emptyMap(), val library: LibraryMap = emptyMap(),
val searchQuery: String? = null, val searchQuery: String? = null,
val selection: List<LibraryManga> = emptyList(), val selection: PersistentList<LibraryManga> = persistentListOf(),
val hasActiveFilters: Boolean = false, val hasActiveFilters: Boolean = false,
val showCategoryTabs: Boolean = false, val showCategoryTabs: Boolean = false,
val showMangaCount: Boolean = false, val showMangaCount: Boolean = false,

View file

@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
@ -154,7 +155,7 @@ object LibraryTab : Tab {
EmptyScreen( EmptyScreen(
textResource = R.string.information_empty_library, textResource = R.string.information_empty_library,
modifier = Modifier.padding(contentPadding), modifier = Modifier.padding(contentPadding),
actions = listOf( actions = persistentListOf(
EmptyScreenAction( EmptyScreenAction(
stringResId = R.string.getting_started_guide, stringResId = R.string.getting_started_guide,
icon = Icons.Outlined.HelpOutline, icon = Icons.Outlined.HelpOutline,

View file

@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
@ -398,7 +399,7 @@ private data class TrackScoreSelectorScreen(
private val tracker: Tracker, private val tracker: Tracker,
) : StateScreenModel<Model.State>(State(tracker.displayScore(track.toDbTrack()))) { ) : StateScreenModel<Model.State>(State(tracker.displayScore(track.toDbTrack()))) {
fun getSelections(): List<String> { fun getSelections(): ImmutableList<String> {
return tracker.getScoreList() return tracker.getScoreList()
} }

View file

@ -21,6 +21,10 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toDateKey
import eu.kanade.tachiyomi.util.lang.toRelativeString 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.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -106,27 +110,29 @@ class UpdatesScreenModel(
} }
} }
private fun List<UpdatesWithRelations>.toUpdateItems(): List<UpdatesItem> { private fun List<UpdatesWithRelations>.toUpdateItems(): PersistentList<UpdatesItem> {
return this.map { update -> return this
val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId) .map { update ->
val downloaded = downloadManager.isChapterDownloaded( val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId)
update.chapterName, val downloaded = downloadManager.isChapterDownloaded(
update.scanlator, update.chapterName,
update.mangaTitle, update.scanlator,
update.sourceId, update.mangaTitle,
) update.sourceId,
val downloadState = when { )
activeDownload != null -> activeDownload.status val downloadState = when {
downloaded -> Download.State.DOWNLOADED activeDownload != null -> activeDownload.status
else -> Download.State.NOT_DOWNLOADED downloaded -> Download.State.DOWNLOADED
else -> Download.State.NOT_DOWNLOADED
}
UpdatesItem(
update = update,
downloadStateProvider = { downloadState },
downloadProgressProvider = { activeDownload?.progress ?: 0 },
selected = update.chapterId in selectedChapterIds,
)
} }
UpdatesItem( .toPersistentList()
update = update,
downloadStateProvider = { downloadState },
downloadProgressProvider = { activeDownload?.progress ?: 0 },
selected = update.chapterId in selectedChapterIds,
)
}
} }
fun updateLibrary(): Boolean { fun updateLibrary(): Boolean {
@ -144,17 +150,14 @@ class UpdatesScreenModel(
*/ */
private fun updateDownloadState(download: Download) { private fun updateDownloadState(download: Download) {
mutableState.update { state -> mutableState.update { state ->
val newItems = state.items.toMutableList().apply { val newItems = state.items.mutate { list ->
val modifiedIndex = indexOfFirst { it.update.chapterId == download.chapter.id } val modifiedIndex = list.indexOfFirst { it.update.chapterId == download.chapter.id }
if (modifiedIndex < 0) return@apply if (modifiedIndex < 0) return@mutate
val item = get(modifiedIndex) val item = list[modifiedIndex]
set( list[modifiedIndex] = item.copy(
modifiedIndex, downloadStateProvider = { download.status },
item.copy( downloadProgressProvider = { download.progress },
downloadStateProvider = { download.status },
downloadProgressProvider = { download.progress },
),
) )
} }
state.copy(items = newItems) 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) selectedChapterIds.addOrRemove(it.update.chapterId, selected)
it.copy(selected = selected) it.copy(selected = selected)
} }
state.copy(items = newItems) state.copy(items = newItems.toPersistentList())
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
@ -353,7 +356,7 @@ class UpdatesScreenModel(
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected) selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
it.copy(selected = !it.selected) it.copy(selected = !it.selected)
} }
state.copy(items = newItems) state.copy(items = newItems.toPersistentList())
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
selectedPositions[1] = -1 selectedPositions[1] = -1
@ -370,7 +373,7 @@ class UpdatesScreenModel(
@Immutable @Immutable
data class State( data class State(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val items: List<UpdatesItem> = emptyList(), val items: PersistentList<UpdatesItem> = persistentListOf(),
val dialog: Dialog? = null, val dialog: Dialog? = null,
) { ) {
val selected = items.filter { it.selected } val selected = items.filter { it.selected }

View file

@ -4,6 +4,8 @@ import android.graphics.Color
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
@ -18,7 +20,7 @@ data class DummyTracker(
val valReadingStatus: Int = 1, val valReadingStatus: Int = 1,
val valRereadingStatus: Int = 1, val valRereadingStatus: Int = 1,
val valCompletionStatus: Int = 2, 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 val10PointScore: Double = 5.4,
val valSearchResults: List<TrackSearch> = listOf(), val valSearchResults: List<TrackSearch> = listOf(),
) : Tracker { ) : Tracker {
@ -48,7 +50,7 @@ data class DummyTracker(
override fun getCompletionStatus(): Int = valCompletionStatus override fun getCompletionStatus(): Int = valCompletionStatus
override fun getScoreList(): List<String> = valScoreList override fun getScoreList(): ImmutableList<String> = valScoreList
override fun get10PointScore(track: Track): Double = val10PointScore override fun get10PointScore(track: Track): Double = val10PointScore

View file

@ -80,7 +80,7 @@ class EpubFile(file: File) : Closeable {
/** /**
* Returns all the pages from the epub. * 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") val pages = document.select("manifest > item")
.filter { node -> "application/xhtml+xml" == node.attr("media-type") } .filter { node -> "application/xhtml+xml" == node.attr("media-type") }
.associateBy { it.attr("id") } .associateBy { it.attr("id") }
@ -102,10 +102,9 @@ class EpubFile(file: File) : Closeable {
val imageBasePath = getParentDirectory(entryPath) val imageBasePath = getParentDirectory(entryPath)
document.allElements.forEach { document.allElements.forEach {
if (it.tagName() == "img") { when (it.tagName()) {
result.add(resolveZipPath(imageBasePath, it.attr("src"))) "img" -> result.add(resolveZipPath(imageBasePath, it.attr("src")))
} else if (it.tagName() == "image") { "image" -> result.add(resolveZipPath(imageBasePath, it.attr("xlink:href")))
result.add(resolveZipPath(imageBasePath, it.attr("xlink:href")))
} }
} }
} }

View file

@ -36,6 +36,8 @@ dependencies {
implementation(compose.ui.tooling.preview) implementation(compose.ui.tooling.preview)
implementation(compose.ui.util) implementation(compose.ui.util)
lintChecks(compose.lintchecks) lintChecks(compose.lintchecks)
implementation(kotlinx.immutables)
} }
tasks { tasks {

View file

@ -36,13 +36,14 @@ fun BadgeGroup(
@Composable @Composable
fun Badge( fun Badge(
text: String, text: String,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.secondary, color: Color = MaterialTheme.colorScheme.secondary,
textColor: Color = MaterialTheme.colorScheme.onSecondary, textColor: Color = MaterialTheme.colorScheme.onSecondary,
shape: Shape = RectangleShape, shape: Shape = RectangleShape,
) { ) {
Text( Text(
text = text, text = text,
modifier = Modifier modifier = modifier
.clip(shape) .clip(shape)
.background(color) .background(color)
.padding(horizontal = 3.dp, vertical = 1.dp), .padding(horizontal = 3.dp, vertical = 1.dp),
@ -56,6 +57,7 @@ fun Badge(
@Composable @Composable
fun Badge( fun Badge(
imageVector: ImageVector, imageVector: ImageVector,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.secondary, color: Color = MaterialTheme.colorScheme.secondary,
iconColor: Color = MaterialTheme.colorScheme.onSecondary, iconColor: Color = MaterialTheme.colorScheme.onSecondary,
shape: Shape = RectangleShape, shape: Shape = RectangleShape,
@ -86,7 +88,7 @@ fun Badge(
Text( Text(
text = text, text = text,
inlineContent = inlineContent, inlineContent = inlineContent,
modifier = Modifier modifier = modifier
.clip(shape) .clip(shape)
.background(color) .background(color)
.padding(horizontal = 3.dp, vertical = 1.dp), .padding(horizontal = 3.dp, vertical = 1.dp),

View file

@ -41,6 +41,7 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -54,7 +55,7 @@ import kotlin.math.absoluteValue
@Composable @Composable
fun WheelNumberPicker( fun WheelNumberPicker(
items: List<Number>, items: ImmutableList<Number>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
startIndex: Int = 0, startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp), size: DpSize = DpSize(128.dp, 128.dp),
@ -78,7 +79,7 @@ fun WheelNumberPicker(
@Composable @Composable
fun WheelTextPicker( fun WheelTextPicker(
items: List<String>, items: ImmutableList<String>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
startIndex: Int = 0, startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp), size: DpSize = DpSize(128.dp, 128.dp),
@ -101,7 +102,7 @@ fun WheelTextPicker(
@Composable @Composable
private fun <T> WheelPicker( private fun <T> WheelPicker(
items: List<T>, items: ImmutableList<T>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
startIndex: Int = 0, startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp), size: DpSize = DpSize(128.dp, 128.dp),

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import kotlinx.collections.immutable.ImmutableList
import tachiyomi.presentation.core.components.ActionButton import tachiyomi.presentation.core.components.ActionButton
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
@ -38,7 +39,7 @@ data class EmptyScreenAction(
fun EmptyScreen( fun EmptyScreen(
@StringRes textResource: Int, @StringRes textResource: Int,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
actions: List<EmptyScreenAction>? = null, actions: ImmutableList<EmptyScreenAction>? = null,
) { ) {
EmptyScreen( EmptyScreen(
message = stringResource(textResource), message = stringResource(textResource),
@ -51,7 +52,7 @@ fun EmptyScreen(
fun EmptyScreen( fun EmptyScreen(
message: String, message: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
actions: List<EmptyScreenAction>? = null, actions: ImmutableList<EmptyScreenAction>? = null,
) { ) {
val face = remember { getRandomErrorFace() } val face = remember { getRandomErrorFace() }
Column( Column(
@ -98,7 +99,7 @@ fun EmptyScreen(
} }
} }
private val ERROR_FACES = listOf( private val ErrorFaces = listOf(
"(・o・;)", "(・o・;)",
"Σ(ಠ_ಠ)", "Σ(ಠ_ಠ)",
"ಥ_ಥ", "ಥ_ಥ",
@ -108,5 +109,5 @@ private val ERROR_FACES = listOf(
) )
private fun getRandomErrorFace(): String { private fun getRandomErrorFace(): String {
return ERROR_FACES[Random.nextInt(ERROR_FACES.size)] return ErrorFaces[Random.nextInt(ErrorFaces.size)]
} }