mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 16:41:51 +01:00
Use Stable interface for Browse screens (#7544)
This commit is contained in:
parent
383f7089c4
commit
018ca71336
@ -1,5 +1,8 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@ -32,7 +35,6 @@ import androidx.compose.material3.Switch
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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_ALPHA
|
||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
import eu.kanade.presentation.components.PreferenceRow
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
@ -66,19 +69,15 @@ fun ExtensionDetailsScreen(
|
|||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
presenter: ExtensionDetailsPresenter,
|
presenter: ExtensionDetailsPresenter,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: () -> Unit,
|
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
when {
|
||||||
|
presenter.isLoading -> LoadingScreen()
|
||||||
|
presenter.extension == null -> EmptyScreen(textResource = R.string.empty_screen)
|
||||||
|
else -> {
|
||||||
|
val context = LocalContext.current
|
||||||
val extension = presenter.extension
|
val extension = presenter.extension
|
||||||
|
|
||||||
if (extension == null) {
|
|
||||||
EmptyScreen(textResource = R.string.empty_screen)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val sources by presenter.sourcesState.collectAsState()
|
|
||||||
|
|
||||||
var showNsfwWarning by remember { mutableStateOf(false) }
|
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
@ -100,7 +99,12 @@ fun ExtensionDetailsScreen(
|
|||||||
DetailsHeader(
|
DetailsHeader(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
onClickUninstall = onClickUninstall,
|
onClickUninstall = onClickUninstall,
|
||||||
onClickAppInfo = onClickAppInfo,
|
onClickAppInfo = {
|
||||||
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
|
context.startActivity(this)
|
||||||
|
}
|
||||||
|
},
|
||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
@ -108,7 +112,7 @@ fun ExtensionDetailsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
items(
|
items(
|
||||||
items = sources,
|
items = presenter.sources,
|
||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
@ -127,6 +131,8 @@ fun ExtensionDetailsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WarningBanner(@StringRes textRes: Int) {
|
private fun WarningBanner(@StringRes textRes: Int) {
|
||||||
|
@ -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())
|
||||||
|
}
|
@ -5,10 +5,8 @@ import androidx.compose.foundation.layout.asPaddingValues
|
|||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
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.presentation.components.PreferenceRow
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
|
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.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionFilterScreen(
|
fun ExtensionFilterScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
presenter: ExtensionFilterPresenter,
|
presenter: ExtensionFilterPresenter,
|
||||||
onClickLang: (String) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
val context = LocalContext.current
|
||||||
|
when {
|
||||||
when (state) {
|
presenter.isLoading -> LoadingScreen()
|
||||||
is ExtensionFilterState.Loading -> LoadingScreen()
|
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen)
|
||||||
is ExtensionFilterState.Error -> Text(text = (state as ExtensionFilterState.Error).error.message!!)
|
else -> {
|
||||||
is ExtensionFilterState.Success ->
|
|
||||||
SourceFilterContent(
|
SourceFilterContent(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
items = (state as ExtensionFilterState.Success).models,
|
state = presenter,
|
||||||
onClickLang = onClickLang,
|
onClickLang = {
|
||||||
|
presenter.toggleLanguage(it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
presenter.events.collectLatest {
|
||||||
|
when (it) {
|
||||||
|
ExtensionFilterPresenter.Event.FailedFetchingLanguages -> {
|
||||||
|
context.toast(R.string.internal_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceFilterContent(
|
fun SourceFilterContent(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
items: List<FilterUiModel>,
|
state: ExtensionFilterState,
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (items.isEmpty()) {
|
|
||||||
EmptyScreen(textResource = R.string.empty_screen)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = items,
|
items = state.items,
|
||||||
) { model ->
|
) { model ->
|
||||||
ExtensionFilterItem(
|
ExtensionFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
@ -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() }
|
||||||
|
}
|
@ -23,7 +23,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -40,7 +39,9 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
|
|||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
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.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
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.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
@ -69,19 +69,18 @@ fun ExtensionScreen(
|
|||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onLaunched: () -> Unit,
|
onLaunched: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
|
||||||
val isRefreshing = presenter.isRefreshing
|
|
||||||
|
|
||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
state = rememberSwipeRefreshState(isRefreshing),
|
state = rememberSwipeRefreshState(presenter.isRefreshing),
|
||||||
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
|
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
) {
|
) {
|
||||||
when (state) {
|
when {
|
||||||
is ExtensionState.Initialized -> {
|
presenter.isLoading -> LoadingScreen()
|
||||||
|
presenter.isEmpty -> EmptyScreen(R.string.empty_screen)
|
||||||
|
else -> {
|
||||||
ExtensionContent(
|
ExtensionContent(
|
||||||
items = (state as ExtensionState.Initialized).list,
|
state = presenter,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
onInstallExtension = onInstallExtension,
|
onInstallExtension = onInstallExtension,
|
||||||
@ -93,14 +92,13 @@ fun ExtensionScreen(
|
|||||||
onLaunched = onLaunched,
|
onLaunched = onLaunched,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ExtensionState.Uninitialized -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionContent(
|
fun ExtensionContent(
|
||||||
items: List<ExtensionUiModel>,
|
state: ExtensionsState,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
@ -117,7 +115,7 @@ fun ExtensionContent(
|
|||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = items,
|
items = state.items,
|
||||||
key = {
|
key = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is ExtensionUiModel.Header.Resource -> it.textRes
|
is ExtensionUiModel.Header.Resource -> it.textRes
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -4,61 +4,66 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaPresenter
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter.Event
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateMangaScreen(
|
fun MigrateMangaScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
presenter: MigrationMangaPresenter,
|
presenter: MigrateMangaPresenter,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
val context = LocalContext.current
|
||||||
|
when {
|
||||||
when (state) {
|
presenter.isLoading -> LoadingScreen()
|
||||||
MigrateMangaState.Loading -> LoadingScreen()
|
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen)
|
||||||
is MigrateMangaState.Error -> Text(text = (state as MigrateMangaState.Error).error.message!!)
|
else -> {
|
||||||
is MigrateMangaState.Success -> {
|
|
||||||
MigrateMangaContent(
|
MigrateMangaContent(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
list = (state as MigrateMangaState.Success).list,
|
state = presenter,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onClickCover = onClickCover,
|
onClickCover = onClickCover,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
presenter.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
Event.FailedFetchingFavorites -> {
|
||||||
|
context.toast(R.string.internal_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateMangaContent(
|
fun MigrateMangaContent(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
list: List<Manga>,
|
state: MigrateMangaState,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (list.isEmpty()) {
|
|
||||||
EmptyScreen(textResource = R.string.empty_screen)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||||
) {
|
) {
|
||||||
items(list) { manga ->
|
items(state.items) { manga ->
|
||||||
MigrateMangaItem(
|
MigrateMangaItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -11,12 +11,12 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
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.plus
|
||||||
import eu.kanade.presentation.util.topPaddingValues
|
import eu.kanade.presentation.util.topPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
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.ui.browse.migration.sources.MigrationSourcesPresenter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
presenter: MigrationSourcesPresenter,
|
presenter: MigrationSourcesPresenter,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
val context = LocalContext.current
|
||||||
when (state) {
|
when {
|
||||||
is MigrateSourceState.Loading -> LoadingScreen()
|
presenter.isLoading -> LoadingScreen()
|
||||||
is MigrateSourceState.Error -> Text(text = (state as MigrateSourceState.Error).error.message!!)
|
presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
|
||||||
is MigrateSourceState.Success ->
|
else ->
|
||||||
MigrateSourceList(
|
MigrateSourceList(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
list = (state as MigrateSourceState.Success).sources,
|
list = presenter.items,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = { source ->
|
||||||
|
val sourceId = source.id.toString()
|
||||||
|
context.copyToClipboard(sourceId, sourceId)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,11 +66,6 @@ fun MigrateSourceList(
|
|||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (list.isEmpty()) {
|
|
||||||
EmptyScreen(textResource = R.string.information_empty_library)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -6,9 +6,8 @@ import androidx.compose.foundation.layout.navigationBars
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
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.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
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.ui.browse.source.SourcesFilterPresenter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesFilterScreen(
|
fun SourcesFilterScreen(
|
||||||
@ -33,39 +33,43 @@ fun SourcesFilterScreen(
|
|||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
val context = LocalContext.current
|
||||||
|
when {
|
||||||
when (state) {
|
presenter.isLoading -> LoadingScreen()
|
||||||
is SourceFilterState.Loading -> LoadingScreen()
|
presenter.isEmpty -> EmptyScreen(textResource = R.string.source_filter_empty_screen)
|
||||||
is SourceFilterState.Error -> Text(text = (state as SourceFilterState.Error).error.message!!)
|
else -> {
|
||||||
is SourceFilterState.Success ->
|
|
||||||
SourcesFilterContent(
|
SourcesFilterContent(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
items = (state as SourceFilterState.Success).models,
|
state = presenter,
|
||||||
onClickLang = onClickLang,
|
onClickLang = onClickLang,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
presenter.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
SourcesFilterPresenter.Event.FailedFetchingLanguages -> {
|
||||||
|
context.toast(R.string.internal_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesFilterContent(
|
fun SourcesFilterContent(
|
||||||
nestedScrollInterop: NestedScrollConnection,
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
items: List<FilterUiModel>,
|
state: SourcesFilterState,
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (items.isEmpty()) {
|
|
||||||
EmptyScreen(textResource = R.string.source_filter_empty_screen)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = items,
|
items = state.items,
|
||||||
contentType = {
|
contentType = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is FilterUiModel.Header -> "header"
|
is FilterUiModel.Header -> "header"
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -19,10 +19,8 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
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.presentation.util.topPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
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
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter.Dialog
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(
|
fun SourcesScreen(
|
||||||
@ -55,14 +55,14 @@ fun SourcesScreen(
|
|||||||
onClickLatest: (Source) -> Unit,
|
onClickLatest: (Source) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by presenter.state.collectAsState()
|
val context = LocalContext.current
|
||||||
|
when {
|
||||||
when (state) {
|
presenter.isLoading -> LoadingScreen()
|
||||||
is SourceState.Loading -> LoadingScreen()
|
presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen)
|
||||||
is SourceState.Error -> Text(text = (state as SourceState.Error).error.message!!)
|
else -> {
|
||||||
is SourceState.Success -> SourceList(
|
SourceList(
|
||||||
nestedScrollConnection = nestedScrollInterop,
|
nestedScrollConnection = nestedScrollInterop,
|
||||||
list = (state as SourceState.Success).uiModels,
|
state = presenter,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onClickDisable = onClickDisable,
|
onClickDisable = onClickDisable,
|
||||||
onClickLatest = onClickLatest,
|
onClickLatest = onClickLatest,
|
||||||
@ -70,29 +70,32 @@ fun SourcesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
presenter.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
SourcesPresenter.Event.FailedFetchingSources -> {
|
||||||
|
context.toast(R.string.internal_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceList(
|
fun SourceList(
|
||||||
nestedScrollConnection: NestedScrollConnection,
|
nestedScrollConnection: NestedScrollConnection,
|
||||||
list: List<SourceUiModel>,
|
state: SourcesState,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onClickDisable: (Source) -> Unit,
|
onClickDisable: (Source) -> Unit,
|
||||||
onClickLatest: (Source) -> Unit,
|
onClickLatest: (Source) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (list.isEmpty()) {
|
|
||||||
EmptyScreen(textResource = R.string.source_empty_screen)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceState by remember { mutableStateOf<Source?>(null) }
|
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = list,
|
items = state.items,
|
||||||
contentType = {
|
contentType = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is SourceUiModel.Header -> "header"
|
is SourceUiModel.Header -> "header"
|
||||||
@ -117,7 +120,7 @@ fun SourceList(
|
|||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = { sourceState = it },
|
onLongClickItem = { state.dialog = Dialog(it) },
|
||||||
onClickLatest = onClickLatest,
|
onClickLatest = onClickLatest,
|
||||||
onClickPin = onClickPin,
|
onClickPin = onClickPin,
|
||||||
)
|
)
|
||||||
@ -125,18 +128,19 @@ fun SourceList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceState != null) {
|
if (state.dialog != null) {
|
||||||
|
val source = state.dialog!!.source
|
||||||
SourceOptionsDialog(
|
SourceOptionsDialog(
|
||||||
source = sourceState!!,
|
source = source,
|
||||||
onClickPin = {
|
onClickPin = {
|
||||||
onClickPin(sourceState!!)
|
onClickPin(source)
|
||||||
sourceState = null
|
state.dialog = null
|
||||||
},
|
},
|
||||||
onClickDisable = {
|
onClickDisable = {
|
||||||
onClickDisable(sourceState!!)
|
onClickDisable(source)
|
||||||
sourceState = null
|
state.dialog = null
|
||||||
},
|
},
|
||||||
onDismiss = { sourceState = null },
|
onDismiss = { state.dialog = null },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -20,6 +20,9 @@ import eu.kanade.tachiyomi.util.preference.plusAssign
|
|||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -63,9 +66,16 @@ class ExtensionManager(
|
|||||||
var installedExtensions = emptyList<Extension.Installed>()
|
var installedExtensions = emptyList<Extension.Installed>()
|
||||||
private set(value) {
|
private set(value) {
|
||||||
field = value
|
field = value
|
||||||
|
installedExtensionsFlow.value = field
|
||||||
installedExtensionsRelay.call(value)
|
installedExtensionsRelay.call(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val installedExtensionsFlow = MutableStateFlow(installedExtensions)
|
||||||
|
|
||||||
|
fun getInstalledExtensionsFlow(): StateFlow<List<Extension.Installed>> {
|
||||||
|
return installedExtensionsFlow.asStateFlow()
|
||||||
|
}
|
||||||
|
|
||||||
fun getAppIconForSource(source: Source): Drawable? {
|
fun getAppIconForSource(source: Source): Drawable? {
|
||||||
return getAppIconForSource(source.id)
|
return getAppIconForSource(source.id)
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,6 @@ class ExtensionFilterController : ComposeController<ExtensionFilterPresenter>()
|
|||||||
ExtensionFilterScreen(
|
ExtensionFilterScreen(
|
||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
presenter = presenter,
|
presenter = presenter,
|
||||||
onClickLang = { language ->
|
|
||||||
presenter.toggleLanguage(language)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,32 +3,37 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.presentation.browse.ExtensionFilterState
|
||||||
|
import eu.kanade.presentation.browse.ExtensionFilterStateImpl
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionFilterPresenter(
|
class ExtensionFilterPresenter(
|
||||||
|
private val state: ExtensionFilterStateImpl = ExtensionFilterState() as ExtensionFilterStateImpl,
|
||||||
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
||||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||||
private val preferences: PreferencesHelper = Injekt.get(),
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
) : BasePresenter<ExtensionFilterController>() {
|
) : BasePresenter<ExtensionFilterController>(), ExtensionFilterState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<ExtensionFilterState> = MutableStateFlow(ExtensionFilterState.Loading)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val state: StateFlow<ExtensionFilterState> = _state.asStateFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getExtensionLanguages.subscribe()
|
getExtensionLanguages.subscribe()
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
_state.value = ExtensionFilterState.Error(exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
_events.send(Event.FailedFetchingLanguages)
|
||||||
}
|
}
|
||||||
.collectLatest(::collectLatestSourceLangMap)
|
.collectLatest(::collectLatestSourceLangMap)
|
||||||
}
|
}
|
||||||
@ -36,19 +41,17 @@ class ExtensionFilterPresenter(
|
|||||||
|
|
||||||
private fun collectLatestSourceLangMap(extLangs: List<String>) {
|
private fun collectLatestSourceLangMap(extLangs: List<String>) {
|
||||||
val enabledLanguages = preferences.enabledLanguages().get()
|
val enabledLanguages = preferences.enabledLanguages().get()
|
||||||
val uiModels = extLangs.map {
|
state.items = extLangs.map {
|
||||||
FilterUiModel(it, it in enabledLanguages)
|
FilterUiModel(it, it in enabledLanguages)
|
||||||
}
|
}
|
||||||
_state.value = ExtensionFilterState.Success(uiModels)
|
state.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleLanguage(language: String) {
|
fun toggleLanguage(language: String) {
|
||||||
toggleLanguage.await(language)
|
toggleLanguage.await(language)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class ExtensionFilterState {
|
sealed class Event {
|
||||||
object Loading : ExtensionFilterState()
|
object FailedFetchingLanguages : Event()
|
||||||
data class Error(val error: Throwable) : ExtensionFilterState()
|
}
|
||||||
data class Success(val models: List<FilterUiModel>) : ExtensionFilterState()
|
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionUpdates
|
import eu.kanade.domain.extension.interactor.GetExtensionUpdates
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensions
|
import eu.kanade.domain.extension.interactor.GetExtensions
|
||||||
|
import eu.kanade.presentation.browse.ExtensionState
|
||||||
|
import eu.kanade.presentation.browse.ExtensionsState
|
||||||
|
import eu.kanade.presentation.browse.ExtensionsStateImpl
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
@ -17,8 +17,6 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@ -27,20 +25,16 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionsPresenter(
|
class ExtensionsPresenter(
|
||||||
|
private val state: ExtensionsStateImpl = ExtensionState() as ExtensionsStateImpl,
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
private val getExtensionUpdates: GetExtensionUpdates = Injekt.get(),
|
private val getExtensionUpdates: GetExtensionUpdates = Injekt.get(),
|
||||||
private val getExtensions: GetExtensions = Injekt.get(),
|
private val getExtensions: GetExtensions = Injekt.get(),
|
||||||
) : BasePresenter<ExtensionsController>() {
|
) : BasePresenter<ExtensionsController>(), ExtensionsState by state {
|
||||||
|
|
||||||
private val _query: MutableStateFlow<String> = MutableStateFlow("")
|
private val _query: MutableStateFlow<String> = MutableStateFlow("")
|
||||||
|
|
||||||
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
private var _currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
||||||
|
|
||||||
private val _state: MutableStateFlow<ExtensionState> = MutableStateFlow(ExtensionState.Uninitialized)
|
|
||||||
val state: StateFlow<ExtensionState> = _state.asStateFlow()
|
|
||||||
|
|
||||||
var isRefreshing: Boolean by mutableStateOf(true)
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
@ -86,8 +80,6 @@ class ExtensionsPresenter(
|
|||||||
getExtensionUpdates.subscribe(),
|
getExtensionUpdates.subscribe(),
|
||||||
_currentDownloads,
|
_currentDownloads,
|
||||||
) { query, (installed, untrusted, available), updates, downloads ->
|
) { query, (installed, untrusted, available), updates, downloads ->
|
||||||
isRefreshing = false
|
|
||||||
|
|
||||||
val languagesWithExtensions = available
|
val languagesWithExtensions = available
|
||||||
.filter(queryFilter(query))
|
.filter(queryFilter(query))
|
||||||
.groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) }
|
.groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) }
|
||||||
@ -121,7 +113,9 @@ class ExtensionsPresenter(
|
|||||||
|
|
||||||
items
|
items
|
||||||
}.collectLatest {
|
}.collectLatest {
|
||||||
_state.value = ExtensionState.Initialized(it)
|
state.isRefreshing = false
|
||||||
|
state.isLoading = false
|
||||||
|
state.items = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,9 +128,9 @@ class ExtensionsPresenter(
|
|||||||
|
|
||||||
fun updateAllExtensions() {
|
fun updateAllExtensions() {
|
||||||
launchIO {
|
launchIO {
|
||||||
val state = _state.value
|
if (state.isEmpty) return@launchIO
|
||||||
if (state !is ExtensionState.Initialized) return@launchIO
|
val items = state.items
|
||||||
state.list.mapNotNull {
|
items.mapNotNull {
|
||||||
if (it !is ExtensionUiModel.Item) return@mapNotNull null
|
if (it !is ExtensionUiModel.Item) return@mapNotNull null
|
||||||
if (it.extension !is Extension.Installed) return@mapNotNull null
|
if (it.extension !is Extension.Installed) return@mapNotNull null
|
||||||
if (it.extension.hasUpdate.not()) return@mapNotNull null
|
if (it.extension.hasUpdate.not()) return@mapNotNull null
|
||||||
@ -189,7 +183,7 @@ class ExtensionsPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findAvailableExtensions() {
|
fun findAvailableExtensions() {
|
||||||
isRefreshing = true
|
state.isRefreshing = true
|
||||||
extensionManager.findAvailableExtensions()
|
extensionManager.findAvailableExtensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,8 +211,3 @@ sealed interface ExtensionUiModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ExtensionState {
|
|
||||||
object Uninitialized : ExtensionState()
|
|
||||||
data class Initialized(val list: List<ExtensionUiModel>) : ExtensionState()
|
|
||||||
}
|
|
||||||
|
@ -43,7 +43,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
nestedScrollInterop = nestedScrollInterop,
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
presenter = presenter,
|
presenter = presenter,
|
||||||
onClickUninstall = { presenter.uninstallExtension() },
|
onClickUninstall = { presenter.uninstallExtension() },
|
||||||
onClickAppInfo = { presenter.openInSettings() },
|
|
||||||
onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
|
onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
|
||||||
onClickSource = { presenter.toggleSource(it) },
|
onClickSource = { presenter.toggleSource(it) },
|
||||||
)
|
)
|
||||||
|
@ -1,48 +1,52 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension.details
|
package eu.kanade.tachiyomi.ui.browse.extension.details
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
|
import eu.kanade.presentation.browse.ExtensionDetailsState
|
||||||
|
import eu.kanade.presentation.browse.ExtensionDetailsStateImpl
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import kotlinx.coroutines.flow.take
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionDetailsPresenter(
|
class ExtensionDetailsPresenter(
|
||||||
private val pkgName: String,
|
private val pkgName: String,
|
||||||
|
private val state: ExtensionDetailsStateImpl = ExtensionDetailsState() as ExtensionDetailsStateImpl,
|
||||||
private val context: Application = Injekt.get(),
|
private val context: Application = Injekt.get(),
|
||||||
private val getExtensionSources: GetExtensionSources = Injekt.get(),
|
private val getExtensionSources: GetExtensionSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
) : BasePresenter<ExtensionDetailsController>() {
|
) : BasePresenter<ExtensionDetailsController>(), ExtensionDetailsState by state {
|
||||||
|
|
||||||
val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName }
|
|
||||||
|
|
||||||
private val _state: MutableStateFlow<List<ExtensionSourceItem>> = MutableStateFlow(emptyList())
|
|
||||||
val sourcesState: StateFlow<List<ExtensionSourceItem>> = _state.asStateFlow()
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
val extension = extension ?: return
|
presenterScope.launchIO {
|
||||||
|
extensionManager.getInstalledExtensionsFlow()
|
||||||
|
.map { it.firstOrNull { it.pkgName == pkgName } }
|
||||||
|
.collectLatest {
|
||||||
|
state.extension = it
|
||||||
|
fetchExtensionSources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bindToUninstalledExtension()
|
bindToUninstalledExtension()
|
||||||
|
}
|
||||||
|
|
||||||
presenterScope.launchIO {
|
private fun CoroutineScope.fetchExtensionSources() {
|
||||||
getExtensionSources.subscribe(extension)
|
launchIO {
|
||||||
|
getExtensionSources.subscribe(extension!!)
|
||||||
.map {
|
.map {
|
||||||
it.sortedWith(
|
it.sortedWith(
|
||||||
compareBy(
|
compareBy(
|
||||||
@ -51,20 +55,24 @@ class ExtensionDetailsPresenter(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.collectLatest { _state.value = it }
|
.collectLatest {
|
||||||
|
state.isLoading = false
|
||||||
|
state.sources = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindToUninstalledExtension() {
|
private fun bindToUninstalledExtension() {
|
||||||
extensionManager.getInstalledExtensionsObservable()
|
presenterScope.launchIO {
|
||||||
.skip(1)
|
extensionManager.getInstalledExtensionsFlow()
|
||||||
|
.drop(1)
|
||||||
.filter { extensions -> extensions.none { it.pkgName == pkgName } }
|
.filter { extensions -> extensions.none { it.pkgName == pkgName } }
|
||||||
.map { }
|
.map { }
|
||||||
.take(1)
|
.take(1)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.collectLatest {
|
||||||
.subscribeFirst({ view, _ ->
|
view?.onExtensionUninstalled()
|
||||||
view.onExtensionUninstalled()
|
}
|
||||||
},)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstallExtension() {
|
fun uninstallExtension() {
|
||||||
@ -72,13 +80,6 @@ class ExtensionDetailsPresenter(
|
|||||||
extensionManager.uninstallExtension(extension.pkgName)
|
extensionManager.uninstallExtension(extension.pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openInSettings() {
|
|
||||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
||||||
data = Uri.fromParts("package", pkgName, null)
|
|
||||||
}
|
|
||||||
view?.startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleSource(sourceId: Long) {
|
fun toggleSource(sourceId: Long) {
|
||||||
toggleSource.await(sourceId)
|
toggleSource.await(sourceId)
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,29 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.presentation.browse.MigrateMangaState
|
||||||
|
import eu.kanade.presentation.browse.MigrateMangaStateImpl
|
||||||
|
import eu.kanade.presentation.browse.MigrationMangaState
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrationMangaPresenter(
|
class MigrateMangaPresenter(
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
|
private val state: MigrateMangaStateImpl = MigrationMangaState() as MigrateMangaStateImpl,
|
||||||
private val getFavorites: GetFavorites = Injekt.get(),
|
private val getFavorites: GetFavorites = Injekt.get(),
|
||||||
) : BasePresenter<MigrationMangaController>() {
|
) : BasePresenter<MigrationMangaController>(), MigrateMangaState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<MigrateMangaState> = MutableStateFlow(MigrateMangaState.Loading)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val state: StateFlow<MigrateMangaState> = _state.asStateFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -28,20 +32,20 @@ class MigrationMangaPresenter(
|
|||||||
getFavorites
|
getFavorites
|
||||||
.subscribe(sourceId)
|
.subscribe(sourceId)
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
_state.value = MigrateMangaState.Error(exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
_events.send(Event.FailedFetchingFavorites)
|
||||||
}
|
}
|
||||||
.map { list ->
|
.map { list ->
|
||||||
list.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title })
|
list.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title })
|
||||||
}
|
}
|
||||||
.collectLatest { sortedList ->
|
.collectLatest { sortedList ->
|
||||||
_state.value = MigrateMangaState.Success(sortedList)
|
state.isLoading = false
|
||||||
}
|
state.items = sortedList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class MigrateMangaState {
|
sealed class Event {
|
||||||
object Loading : MigrateMangaState()
|
object FailedFetchingFavorites : Event()
|
||||||
data class Error(val error: Throwable) : MigrateMangaState()
|
}
|
||||||
data class Success(val list: List<Manga>) : MigrateMangaState()
|
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
|
||||||
class MigrationMangaController : ComposeController<MigrationMangaPresenter> {
|
class MigrationMangaController : ComposeController<MigrateMangaPresenter> {
|
||||||
|
|
||||||
constructor(sourceId: Long, sourceName: String?) : super(
|
constructor(sourceId: Long, sourceName: String?) : super(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@ -30,7 +30,7 @@ class MigrationMangaController : ComposeController<MigrationMangaPresenter> {
|
|||||||
|
|
||||||
override fun getTitle(): String? = sourceName
|
override fun getTitle(): String? = sourceName
|
||||||
|
|
||||||
override fun createPresenter(): MigrationMangaPresenter = MigrationMangaPresenter(sourceId)
|
override fun createPresenter(): MigrateMangaPresenter = MigrateMangaPresenter(sourceId)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||||
|
@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
|
|
||||||
class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>() {
|
class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>() {
|
||||||
@ -34,10 +33,6 @@ class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onLongClickItem = { source ->
|
|
||||||
val sourceId = source.id.toString()
|
|
||||||
activity?.copyToClipboard(sourceId, sourceId)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,24 +3,27 @@ package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.model.Source
|
import eu.kanade.presentation.browse.MigrateSourceState
|
||||||
|
import eu.kanade.presentation.browse.MigrateSourceStateImpl
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrationSourcesPresenter(
|
class MigrationSourcesPresenter(
|
||||||
|
private val state: MigrateSourceStateImpl = MigrateSourceState() as MigrateSourceStateImpl,
|
||||||
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
||||||
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
||||||
) : BasePresenter<MigrationSourcesController>() {
|
) : BasePresenter<MigrationSourcesController>(), MigrateSourceState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<MigrateSourceState> = MutableStateFlow(MigrateSourceState.Loading)
|
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
||||||
val state: StateFlow<MigrateSourceState> = _state.asStateFlow()
|
val channel = _channel.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -28,10 +31,12 @@ class MigrationSourcesPresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getSourcesWithFavoriteCount.subscribe()
|
getSourcesWithFavoriteCount.subscribe()
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
_state.value = MigrateSourceState.Error(exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
_channel.send(Event.FailedFetchingSourcesWithCount)
|
||||||
}
|
}
|
||||||
.collectLatest { sources ->
|
.collectLatest { sources ->
|
||||||
_state.value = MigrateSourceState.Success(sources)
|
state.items = sources
|
||||||
|
state.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,10 +48,8 @@ class MigrationSourcesPresenter(
|
|||||||
fun setTotalSorting(isAscending: Boolean) {
|
fun setTotalSorting(isAscending: Boolean) {
|
||||||
setMigrateSorting.await(SetMigrateSorting.Mode.TOTAL, isAscending)
|
setMigrateSorting.await(SetMigrateSorting.Mode.TOTAL, isAscending)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class MigrateSourceState {
|
sealed class Event {
|
||||||
object Loading : MigrateSourceState()
|
object FailedFetchingSourcesWithCount : Event()
|
||||||
data class Error(val error: Throwable) : MigrateSourceState()
|
}
|
||||||
data class Success(val sources: List<Pair<Source, Long>>) : MigrateSourceState()
|
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,30 @@ import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
|||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.presentation.browse.SourcesFilterState
|
||||||
|
import eu.kanade.presentation.browse.SourcesFilterStateImpl
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class SourcesFilterPresenter(
|
class SourcesFilterPresenter(
|
||||||
|
private val state: SourcesFilterStateImpl = SourcesFilterState() as SourcesFilterStateImpl,
|
||||||
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
|
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||||
private val preferences: PreferencesHelper = Injekt.get(),
|
private val preferences: PreferencesHelper = Injekt.get(),
|
||||||
) : BasePresenter<SourceFilterController>() {
|
) : BasePresenter<SourceFilterController>(), SourcesFilterState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<SourceFilterState> = MutableStateFlow(SourceFilterState.Loading)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val state: StateFlow<SourceFilterState> = _state.asStateFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -32,14 +36,15 @@ class SourcesFilterPresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getLanguagesWithSources.subscribe()
|
getLanguagesWithSources.subscribe()
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
_state.value = SourceFilterState.Error(exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
_events.send(Event.FailedFetchingLanguages)
|
||||||
}
|
}
|
||||||
.collectLatest(::collectLatestSourceLangMap)
|
.collectLatest(::collectLatestSourceLangMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectLatestSourceLangMap(sourceLangMap: Map<String, List<Source>>) {
|
private fun collectLatestSourceLangMap(sourceLangMap: Map<String, List<Source>>) {
|
||||||
val uiModels = sourceLangMap.flatMap {
|
state.items = sourceLangMap.flatMap {
|
||||||
val isLangEnabled = it.key in preferences.enabledLanguages().get()
|
val isLangEnabled = it.key in preferences.enabledLanguages().get()
|
||||||
val header = listOf(FilterUiModel.Header(it.key, isLangEnabled))
|
val header = listOf(FilterUiModel.Header(it.key, isLangEnabled))
|
||||||
|
|
||||||
@ -51,7 +56,7 @@ class SourcesFilterPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_state.value = SourceFilterState.Success(uiModels)
|
state.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSource(source: Source) {
|
fun toggleSource(source: Source) {
|
||||||
@ -61,10 +66,8 @@ class SourcesFilterPresenter(
|
|||||||
fun toggleLanguage(language: String) {
|
fun toggleLanguage(language: String) {
|
||||||
toggleLanguage.await(language)
|
toggleLanguage.await(language)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class SourceFilterState {
|
sealed class Event {
|
||||||
object Loading : SourceFilterState()
|
object FailedFetchingLanguages : Event()
|
||||||
data class Error(val error: Throwable) : SourceFilterState()
|
}
|
||||||
data class Success(val models: List<FilterUiModel>) : SourceFilterState()
|
|
||||||
}
|
}
|
||||||
|
@ -7,32 +7,37 @@ import eu.kanade.domain.source.interactor.ToggleSourcePin
|
|||||||
import eu.kanade.domain.source.model.Pin
|
import eu.kanade.domain.source.model.Pin
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.presentation.browse.SourceUiModel
|
import eu.kanade.presentation.browse.SourceUiModel
|
||||||
|
import eu.kanade.presentation.browse.SourcesState
|
||||||
|
import eu.kanade.presentation.browse.SourcesStateImpl
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
|
|
||||||
class SourcesPresenter(
|
class SourcesPresenter(
|
||||||
|
private val state: SourcesStateImpl = SourcesState() as SourcesStateImpl,
|
||||||
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
||||||
) : BasePresenter<SourcesController>() {
|
) : BasePresenter<SourcesController>(), SourcesState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<SourceState> = MutableStateFlow(SourceState.Loading)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val state: StateFlow<SourceState> = _state.asStateFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getEnabledSources.subscribe()
|
getEnabledSources.subscribe()
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
_state.value = SourceState.Error(exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
|
_events.send(Event.FailedFetchingSources)
|
||||||
}
|
}
|
||||||
.collectLatest(::collectLatestSources)
|
.collectLatest(::collectLatestSources)
|
||||||
}
|
}
|
||||||
@ -67,7 +72,8 @@ class SourcesPresenter(
|
|||||||
}.toTypedArray(),
|
}.toTypedArray(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_state.value = SourceState.Success(uiModels)
|
state.isLoading = false
|
||||||
|
state.items = uiModels
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSource(source: Source) {
|
fun toggleSource(source: Source) {
|
||||||
@ -78,14 +84,14 @@ class SourcesPresenter(
|
|||||||
toggleSourcePin.await(source)
|
toggleSourcePin.await(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class Event {
|
||||||
|
object FailedFetchingSources : Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Dialog(val source: Source)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PINNED_KEY = "pinned"
|
const val PINNED_KEY = "pinned"
|
||||||
const val LAST_USED_KEY = "last_used"
|
const val LAST_USED_KEY = "last_used"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SourceState {
|
|
||||||
object Loading : SourceState()
|
|
||||||
data class Error(val error: Throwable) : SourceState()
|
|
||||||
data class Success(val uiModels: List<SourceUiModel>) : SourceState()
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user