Use Stable interface for Browse screens (#7544)

This commit is contained in:
Andreas
2022-07-16 20:44:37 +02:00
committed by GitHub
parent 383f7089c4
commit 018ca71336
26 changed files with 505 additions and 307 deletions

View File

@@ -1,5 +1,8 @@
package eu.kanade.presentation.browse
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import android.util.DisplayMetrics
import androidx.annotation.StringRes
import androidx.compose.foundation.background
@@ -32,7 +35,6 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -51,6 +53,7 @@ import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.DIVIDER_ALPHA
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.util.horizontalPadding
@@ -66,65 +69,68 @@ fun ExtensionDetailsScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: ExtensionDetailsPresenter,
onClickUninstall: () -> Unit,
onClickAppInfo: () -> Unit,
onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickSource: (sourceId: Long) -> Unit,
) {
val extension = presenter.extension
when {
presenter.isLoading -> LoadingScreen()
presenter.extension == null -> EmptyScreen(textResource = R.string.empty_screen)
else -> {
val context = LocalContext.current
val extension = presenter.extension
var showNsfwWarning by remember { mutableStateOf(false) }
if (extension == null) {
EmptyScreen(textResource = R.string.empty_screen)
return
}
val sources by presenter.sourcesState.collectAsState()
var showNsfwWarning by remember { mutableStateOf(false) }
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
when {
extension.isUnofficial ->
item {
WarningBanner(R.string.unofficial_extension_message)
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
when {
extension.isUnofficial ->
item {
WarningBanner(R.string.unofficial_extension_message)
}
extension.isObsolete ->
item {
WarningBanner(R.string.obsolete_extension_message)
}
}
extension.isObsolete ->
item {
WarningBanner(R.string.obsolete_extension_message)
DetailsHeader(
extension = extension,
onClickUninstall = onClickUninstall,
onClickAppInfo = {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", extension.pkgName, null)
context.startActivity(this)
}
},
onClickAgeRating = {
showNsfwWarning = true
},
)
}
}
item {
DetailsHeader(
extension = extension,
onClickUninstall = onClickUninstall,
onClickAppInfo = onClickAppInfo,
onClickAgeRating = {
showNsfwWarning = true
},
)
items(
items = presenter.sources,
key = { it.source.id },
) { source ->
SourceSwitchPreference(
modifier = Modifier.animateItemPlacement(),
source = source,
onClickSourcePreferences = onClickSourcePreferences,
onClickSource = onClickSource,
)
}
}
if (showNsfwWarning) {
NsfwWarningDialog(
onClickConfirm = {
showNsfwWarning = false
},
)
}
}
items(
items = sources,
key = { it.source.id },
) { source ->
SourceSwitchPreference(
modifier = Modifier.animateItemPlacement(),
source = source,
onClickSourcePreferences = onClickSourcePreferences,
onClickSource = onClickSource,
)
}
}
if (showNsfwWarning) {
NsfwWarningDialog(
onClickConfirm = {
showNsfwWarning = false
},
)
}
}

View File

@@ -0,0 +1,25 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
@Stable
interface ExtensionDetailsState {
val isLoading: Boolean
val extension: Extension.Installed?
val sources: List<ExtensionSourceItem>
}
fun ExtensionDetailsState(): ExtensionDetailsState {
return ExtensionDetailsStateImpl()
}
class ExtensionDetailsStateImpl : ExtensionDetailsState {
override var isLoading: Boolean by mutableStateOf(true)
override var extension: Extension.Installed? by mutableStateOf(null)
override var sources: List<ExtensionSourceItem> by mutableStateOf(emptyList())
}

View File

@@ -5,10 +5,8 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -19,47 +17,52 @@ import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
import eu.kanade.tachiyomi.ui.browse.extension.FilterUiModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun ExtensionFilterScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: ExtensionFilterPresenter,
onClickLang: (String) -> Unit,
) {
val state by presenter.state.collectAsState()
when (state) {
is ExtensionFilterState.Loading -> LoadingScreen()
is ExtensionFilterState.Error -> Text(text = (state as ExtensionFilterState.Error).error.message!!)
is ExtensionFilterState.Success ->
val context = LocalContext.current
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen)
else -> {
SourceFilterContent(
nestedScrollInterop = nestedScrollInterop,
items = (state as ExtensionFilterState.Success).models,
onClickLang = onClickLang,
state = presenter,
onClickLang = {
presenter.toggleLanguage(it)
},
)
}
}
LaunchedEffect(Unit) {
presenter.events.collectLatest {
when (it) {
ExtensionFilterPresenter.Event.FailedFetchingLanguages -> {
context.toast(R.string.internal_error)
}
}
}
}
}
@Composable
fun SourceFilterContent(
nestedScrollInterop: NestedScrollConnection,
items: List<FilterUiModel>,
state: ExtensionFilterState,
onClickLang: (String) -> Unit,
) {
if (items.isEmpty()) {
EmptyScreen(textResource = R.string.empty_screen)
return
}
LazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
items(
items = items,
items = state.items,
) { model ->
ExtensionFilterItem(
modifier = Modifier.animateItemPlacement(),

View File

@@ -0,0 +1,25 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.ui.browse.extension.FilterUiModel
@Stable
interface ExtensionFilterState {
val isLoading: Boolean
val items: List<FilterUiModel>
val isEmpty: Boolean
}
fun ExtensionFilterState(): ExtensionFilterState {
return ExtensionFilterStateImpl()
}
class ExtensionFilterStateImpl : ExtensionFilterState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<FilterUiModel> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View File

@@ -23,7 +23,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -40,7 +39,9 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.kanade.presentation.browse.components.BaseBrowseItem
import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.SwipeRefreshIndicator
import eu.kanade.presentation.theme.header
import eu.kanade.presentation.util.horizontalPadding
@@ -49,7 +50,6 @@ import eu.kanade.presentation.util.topPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionState
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper
@@ -69,19 +69,18 @@ fun ExtensionScreen(
onRefresh: () -> Unit,
onLaunched: () -> Unit,
) {
val state by presenter.state.collectAsState()
val isRefreshing = presenter.isRefreshing
SwipeRefresh(
modifier = Modifier.nestedScroll(nestedScrollInterop),
state = rememberSwipeRefreshState(isRefreshing),
state = rememberSwipeRefreshState(presenter.isRefreshing),
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
onRefresh = onRefresh,
) {
when (state) {
is ExtensionState.Initialized -> {
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(R.string.empty_screen)
else -> {
ExtensionContent(
items = (state as ExtensionState.Initialized).list,
state = presenter,
onLongClickItem = onLongClickItem,
onClickItemCancel = onClickItemCancel,
onInstallExtension = onInstallExtension,
@@ -93,14 +92,13 @@ fun ExtensionScreen(
onLaunched = onLaunched,
)
}
ExtensionState.Uninitialized -> {}
}
}
}
@Composable
fun ExtensionContent(
items: List<ExtensionUiModel>,
state: ExtensionsState,
onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit,
onInstallExtension: (Extension.Available) -> Unit,
@@ -117,7 +115,7 @@ fun ExtensionContent(
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
) {
items(
items = items,
items = state.items,
key = {
when (it) {
is ExtensionUiModel.Header.Resource -> it.textRes

View File

@@ -0,0 +1,25 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
interface ExtensionsState {
val isLoading: Boolean
val isRefreshing: Boolean
val items: List<ExtensionUiModel>
val isEmpty: Boolean
}
fun ExtensionState(): ExtensionsState {
return ExtensionsStateImpl()
}
class ExtensionsStateImpl : ExtensionsState {
override var isLoading: Boolean by mutableStateOf(true)
override var isRefreshing: Boolean by mutableStateOf(false)
override var items: List<ExtensionUiModel> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View File

@@ -4,61 +4,66 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.manga.components.BaseMangaListItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaPresenter
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter.Event
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun MigrateMangaScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: MigrationMangaPresenter,
presenter: MigrateMangaPresenter,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
val state by presenter.state.collectAsState()
when (state) {
MigrateMangaState.Loading -> LoadingScreen()
is MigrateMangaState.Error -> Text(text = (state as MigrateMangaState.Error).error.message!!)
is MigrateMangaState.Success -> {
val context = LocalContext.current
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen)
else -> {
MigrateMangaContent(
nestedScrollInterop = nestedScrollInterop,
list = (state as MigrateMangaState.Success).list,
state = presenter,
onClickItem = onClickItem,
onClickCover = onClickCover,
)
}
}
LaunchedEffect(Unit) {
presenter.events.collectLatest { event ->
when (event) {
Event.FailedFetchingFavorites -> {
context.toast(R.string.internal_error)
}
}
}
}
}
@Composable
fun MigrateMangaContent(
nestedScrollInterop: NestedScrollConnection,
list: List<Manga>,
state: MigrateMangaState,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
if (list.isEmpty()) {
EmptyScreen(textResource = R.string.empty_screen)
return
}
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
items(list) { manga ->
items(state.items) { manga ->
MigrateMangaItem(
manga = manga,
onClickItem = onClickItem,

View File

@@ -0,0 +1,23 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.domain.manga.model.Manga
interface MigrateMangaState {
val isLoading: Boolean
val items: List<Manga>
val isEmpty: Boolean
}
fun MigrationMangaState(): MigrateMangaState {
return MigrateMangaStateImpl()
}
class MigrateMangaStateImpl : MigrateMangaState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<Manga> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View File

@@ -11,12 +11,12 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -32,27 +32,29 @@ import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.copyToClipboard
@Composable
fun MigrateSourceScreen(
nestedScrollInterop: NestedScrollConnection,
presenter: MigrationSourcesPresenter,
onClickItem: (Source) -> Unit,
onLongClickItem: (Source) -> Unit,
) {
val state by presenter.state.collectAsState()
when (state) {
is MigrateSourceState.Loading -> LoadingScreen()
is MigrateSourceState.Error -> Text(text = (state as MigrateSourceState.Error).error.message!!)
is MigrateSourceState.Success ->
val context = LocalContext.current
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
else ->
MigrateSourceList(
nestedScrollInterop = nestedScrollInterop,
list = (state as MigrateSourceState.Success).sources,
list = presenter.items,
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
onLongClickItem = { source ->
val sourceId = source.id.toString()
context.copyToClipboard(sourceId, sourceId)
},
)
}
}
@@ -64,11 +66,6 @@ fun MigrateSourceList(
onClickItem: (Source) -> Unit,
onLongClickItem: (Source) -> Unit,
) {
if (list.isEmpty()) {
EmptyScreen(textResource = R.string.information_empty_library)
return
}
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,

View File

@@ -0,0 +1,23 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.domain.source.model.Source
interface MigrateSourceState {
val isLoading: Boolean
val items: List<Pair<Source, Long>>
val isEmpty: Boolean
}
fun MigrateSourceState(): MigrateSourceState {
return MigrateSourceStateImpl()
}
class MigrateSourceStateImpl : MigrateSourceState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<Pair<Source, Long>> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View File

@@ -6,9 +6,8 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -22,9 +21,10 @@ import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterState
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun SourcesFilterScreen(
@@ -33,39 +33,43 @@ fun SourcesFilterScreen(
onClickLang: (String) -> Unit,
onClickSource: (Source) -> Unit,
) {
val state by presenter.state.collectAsState()
when (state) {
is SourceFilterState.Loading -> LoadingScreen()
is SourceFilterState.Error -> Text(text = (state as SourceFilterState.Error).error.message!!)
is SourceFilterState.Success ->
val context = LocalContext.current
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.source_filter_empty_screen)
else -> {
SourcesFilterContent(
nestedScrollInterop = nestedScrollInterop,
items = (state as SourceFilterState.Success).models,
state = presenter,
onClickLang = onClickLang,
onClickSource = onClickSource,
)
}
}
LaunchedEffect(Unit) {
presenter.events.collectLatest { event ->
when (event) {
SourcesFilterPresenter.Event.FailedFetchingLanguages -> {
context.toast(R.string.internal_error)
}
}
}
}
}
@Composable
fun SourcesFilterContent(
nestedScrollInterop: NestedScrollConnection,
items: List<FilterUiModel>,
state: SourcesFilterState,
onClickLang: (String) -> Unit,
onClickSource: (Source) -> Unit,
) {
if (items.isEmpty()) {
EmptyScreen(textResource = R.string.source_filter_empty_screen)
return
}
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
items(
items = items,
items = state.items,
contentType = {
when (it) {
is FilterUiModel.Header -> "header"

View File

@@ -0,0 +1,23 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
interface SourcesFilterState {
val isLoading: Boolean
val items: List<FilterUiModel>
val isEmpty: Boolean
}
fun SourcesFilterState(): SourcesFilterState {
return SourcesFilterStateImpl()
}
class SourcesFilterStateImpl : SourcesFilterState {
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<FilterUiModel> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}

View File

@@ -19,10 +19,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -42,9 +40,11 @@ import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.SourceState
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter.Dialog
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun SourcesScreen(
@@ -55,44 +55,47 @@ fun SourcesScreen(
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
) {
val state by presenter.state.collectAsState()
when (state) {
is SourceState.Loading -> LoadingScreen()
is SourceState.Error -> Text(text = (state as SourceState.Error).error.message!!)
is SourceState.Success -> SourceList(
nestedScrollConnection = nestedScrollInterop,
list = (state as SourceState.Success).uiModels,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
val context = LocalContext.current
when {
presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
else -> {
SourceList(
nestedScrollConnection = nestedScrollInterop,
state = presenter,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
}
}
LaunchedEffect(Unit) {
presenter.events.collectLatest { event ->
when (event) {
SourcesPresenter.Event.FailedFetchingSources -> {
context.toast(R.string.internal_error)
}
}
}
}
}
@Composable
fun SourceList(
nestedScrollConnection: NestedScrollConnection,
list: List<SourceUiModel>,
state: SourcesState,
onClickItem: (Source) -> Unit,
onClickDisable: (Source) -> Unit,
onClickLatest: (Source) -> Unit,
onClickPin: (Source) -> Unit,
) {
if (list.isEmpty()) {
EmptyScreen(textResource = R.string.source_empty_screen)
return
}
var sourceState by remember { mutableStateOf<Source?>(null) }
ScrollbarLazyColumn(
modifier = Modifier.nestedScroll(nestedScrollConnection),
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
) {
items(
items = list,
items = state.items,
contentType = {
when (it) {
is SourceUiModel.Header -> "header"
@@ -117,7 +120,7 @@ fun SourceList(
modifier = Modifier.animateItemPlacement(),
source = model.source,
onClickItem = onClickItem,
onLongClickItem = { sourceState = it },
onLongClickItem = { state.dialog = Dialog(it) },
onClickLatest = onClickLatest,
onClickPin = onClickPin,
)
@@ -125,18 +128,19 @@ fun SourceList(
}
}
if (sourceState != null) {
if (state.dialog != null) {
val source = state.dialog!!.source
SourceOptionsDialog(
source = sourceState!!,
source = source,
onClickPin = {
onClickPin(sourceState!!)
sourceState = null
onClickPin(source)
state.dialog = null
},
onClickDisable = {
onClickDisable(sourceState!!)
sourceState = null
onClickDisable(source)
state.dialog = null
},
onDismiss = { sourceState = null },
onDismiss = { state.dialog = null },
)
}
}

View File

@@ -0,0 +1,27 @@
package eu.kanade.presentation.browse
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter
@Stable
interface SourcesState {
var dialog: SourcesPresenter.Dialog?
val isLoading: Boolean
val items: List<SourceUiModel>
val isEmpty: Boolean
}
fun SourcesState(): SourcesState {
return SourcesStateImpl()
}
class SourcesStateImpl : SourcesState {
override var dialog: SourcesPresenter.Dialog? by mutableStateOf(null)
override var isLoading: Boolean by mutableStateOf(true)
override var items: List<SourceUiModel> by mutableStateOf(emptyList())
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
}