refactor(extension/repo): Use ScreenModel instead of ViewModel

This commit is contained in:
Ahmad Ansori Palembani 2025-01-07 07:56:43 +07:00
parent d655c3e09a
commit d0d322fd67
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
3 changed files with 205 additions and 201 deletions

View file

@ -1,22 +1,22 @@
package yokai.presentation.extension.repo
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.CrossfadeTransition
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
class ExtensionRepoController() :
BaseComposeController() {
private var repoUrl: String? = null
constructor(repoUrl: String) : this() {
this.repoUrl = repoUrl
}
class ExtensionRepoController(private val repoUrl: String? = null) : BaseComposeController() {
@Composable
override fun ScreenContent() {
ExtensionRepoScreen(
Navigator(
screen = ExtensionRepoScreen(
title = "Extension Repos",
repoUrl = repoUrl,
),
content = {
CrossfadeTransition(navigator = it)
},
)
}
}

View file

@ -25,7 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.util.compose.LocalBackPress
import eu.kanade.tachiyomi.util.compose.LocalDialogHostState
@ -43,20 +43,23 @@ import yokai.presentation.component.EmptyScreen
import yokai.presentation.component.ToolTipButton
import yokai.presentation.extension.repo.component.ExtensionRepoInput
import yokai.presentation.extension.repo.component.ExtensionRepoItem
import yokai.util.Screen
import android.R as AR
@Composable
fun ExtensionRepoScreen(
title: String,
viewModel: ExtensionRepoViewModel = viewModel(),
repoUrl: String? = null,
) {
class ExtensionRepoScreen(
private val title: String,
private var repoUrl: String? = null,
): Screen() {
@Composable
override fun Content() {
val onBackPress = LocalBackPress.currentOrThrow
val context = LocalContext.current
val alertDialog = LocalDialogHostState.currentOrThrow
val scope = rememberCoroutineScope()
val repoState by viewModel.repoState.collectAsState()
val scope = rememberCoroutineScope()
val screenModel = rememberScreenModel { ExtensionRepoScreenModel() }
val state by screenModel.state.collectAsState()
var inputText by remember { mutableStateOf("") }
val listState = rememberLazyListState()
@ -74,14 +77,14 @@ fun ExtensionRepoScreen(
icon = Icons.Outlined.Refresh,
buttonClicked = {
context.toast("Refreshing...") // TODO: Should be loading animation instead
viewModel.refreshRepos()
screenModel.refreshRepos()
},
)
},
) { innerPadding ->
if (repoState is ExtensionRepoState.Loading) return@YokaiScaffold
if (state is ExtensionRepoScreenModel.State.Loading) return@YokaiScaffold
val repos = (repoState as ExtensionRepoState.Success).repos
val repos = (state as ExtensionRepoScreenModel.State.Success).repos
alertDialog.value?.invoke()
@ -96,7 +99,7 @@ fun ExtensionRepoScreen(
inputText = inputText,
inputHint = stringResource(MR.strings.label_add_repo),
onInputChange = { inputText = it },
onAddClick = { viewModel.addRepo(it) },
onAddClick = { screenModel.addRepo(it) },
)
}
@ -117,7 +120,7 @@ fun ExtensionRepoScreen(
ExtensionRepoItem(
extensionRepo = repo,
onDeleteClick = { repoToDelete ->
scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, viewModel) }
scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, screenModel) }
},
)
}
@ -126,11 +129,14 @@ fun ExtensionRepoScreen(
}
LaunchedEffect(repoUrl) {
repoUrl?.let { viewModel.addRepo(repoUrl) }
repoUrl?.let {
screenModel.addRepo(repoUrl!!)
repoUrl = null
}
}
LaunchedEffect(Unit) {
viewModel.event.collectLatest { event ->
screenModel.event.collectLatest { event ->
when (event) {
is ExtensionRepoEvent.NoOp -> {}
is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes)
@ -141,7 +147,7 @@ fun ExtensionRepoScreen(
alertDialog.awaitExtensionRepoReplacePrompt(
oldRepo = event.dialog.oldRepo,
newRepo = event.dialog.newRepo,
onMigrate = { viewModel.replaceRepo(event.dialog.newRepo) },
onMigrate = { screenModel.replaceRepo(event.dialog.newRepo) },
)
}
}
@ -149,13 +155,13 @@ fun ExtensionRepoScreen(
}
}
}
}
}
suspend fun DialogHostState.awaitExtensionRepoReplacePrompt(
private suspend fun DialogHostState.awaitExtensionRepoReplacePrompt(
oldRepo: ExtensionRepo,
newRepo: ExtensionRepo,
onMigrate: () -> Unit,
): Unit = dialog { cont ->
): Unit = dialog { cont ->
AlertDialog(
onDismissRequest = { cont.cancel() },
confirmButton = {
@ -180,12 +186,12 @@ suspend fun DialogHostState.awaitExtensionRepoReplacePrompt(
Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name))
},
)
}
}
suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
private suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
repoToDelete: String,
viewModel: ExtensionRepoViewModel,
): Unit = dialog { cont ->
screenModel: ExtensionRepoScreenModel,
): Unit = dialog { cont ->
AlertDialog(
containerColor = MaterialTheme.colorScheme.surface,
title = {
@ -208,7 +214,7 @@ suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
confirmButton = {
TextButton(
onClick = {
viewModel.deleteRepo(repoToDelete)
screenModel.deleteRepo(repoToDelete)
cont.cancel()
}
) {
@ -229,4 +235,5 @@ suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
}
},
)
}
}

View file

@ -1,8 +1,8 @@
package yokai.presentation.extension.repo
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.util.system.launchIO
@ -22,8 +22,7 @@ import yokai.domain.extension.repo.interactor.UpdateExtensionRepo
import yokai.domain.extension.repo.model.ExtensionRepo
import yokai.i18n.MR
class ExtensionRepoViewModel :
ViewModel() {
class ExtensionRepoScreenModel : StateScreenModel<ExtensionRepoScreenModel.State>(State.Loading) {
private val extensionManager: ExtensionManager by injectLazy()
@ -33,23 +32,20 @@ class ExtensionRepoViewModel :
private val replaceExtensionRepo: ReplaceExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
private val mutableRepoState: MutableStateFlow<ExtensionRepoState> = MutableStateFlow(ExtensionRepoState.Loading)
val repoState: StateFlow<ExtensionRepoState> = mutableRepoState.asStateFlow()
private val internalEvent: MutableStateFlow<ExtensionRepoEvent> = MutableStateFlow(ExtensionRepoEvent.NoOp)
val event: StateFlow<ExtensionRepoEvent> = internalEvent.asStateFlow()
init {
viewModelScope.launchIO {
screenModelScope.launchIO {
getExtensionRepo.subscribeAll().collectLatest { repos ->
mutableRepoState.update { ExtensionRepoState.Success(repos = repos.toImmutableList()) }
mutableState.update { State.Success(repos = repos.toImmutableList()) }
extensionManager.refreshTrust()
}
}
}
fun addRepo(url: String) {
viewModelScope.launchIO {
screenModelScope.launchIO {
when (val result = createExtensionRepo.await(url)) {
is CreateExtensionRepo.Result.Success -> internalEvent.value = ExtensionRepoEvent.Success
is CreateExtensionRepo.Result.Error -> internalEvent.value = ExtensionRepoEvent.InvalidUrl
@ -63,26 +59,41 @@ class ExtensionRepoViewModel :
}
fun replaceRepo(newRepo: ExtensionRepo) {
viewModelScope.launchIO {
screenModelScope.launchIO {
replaceExtensionRepo.await(newRepo)
}
}
fun refreshRepos() {
val status = repoState.value
val status = state.value
if (status is ExtensionRepoState.Success) {
viewModelScope.launchIO {
if (status is State.Success) {
screenModelScope.launchIO {
updateExtensionRepo.awaitAll()
}
}
}
fun deleteRepo(url: String) {
viewModelScope.launchIO {
screenModelScope.launchIO {
deleteExtensionRepo.await(url)
}
}
sealed interface State {
@Immutable
data object Loading : State
@Immutable
data class Success(
val repos: ImmutableList<ExtensionRepo>,
) : State {
val isEmpty: Boolean
get() = repos.isEmpty()
}
}
}
sealed class RepoDialog {
@ -98,17 +109,3 @@ sealed class ExtensionRepoEvent {
data object Success : ExtensionRepoEvent()
}
sealed class ExtensionRepoState {
@Immutable
data object Loading : ExtensionRepoState()
@Immutable
data class Success(
val repos: ImmutableList<ExtensionRepo>,
) : ExtensionRepoState() {
val isEmpty: Boolean
get() = repos.isEmpty()
}
}