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 package yokai.presentation.extension.repo
import androidx.compose.runtime.Composable 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 import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
class ExtensionRepoController() : class ExtensionRepoController(private val repoUrl: String? = null) : BaseComposeController() {
BaseComposeController() {
private var repoUrl: String? = null
constructor(repoUrl: String) : this() {
this.repoUrl = repoUrl
}
@Composable @Composable
override fun ScreenContent() { override fun ScreenContent() {
ExtensionRepoScreen( Navigator(
screen = ExtensionRepoScreen(
title = "Extension Repos", title = "Extension Repos",
repoUrl = repoUrl, 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.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp 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 dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.util.compose.LocalBackPress import eu.kanade.tachiyomi.util.compose.LocalBackPress
import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.LocalDialogHostState
@ -43,20 +43,23 @@ import yokai.presentation.component.EmptyScreen
import yokai.presentation.component.ToolTipButton import yokai.presentation.component.ToolTipButton
import yokai.presentation.extension.repo.component.ExtensionRepoInput import yokai.presentation.extension.repo.component.ExtensionRepoInput
import yokai.presentation.extension.repo.component.ExtensionRepoItem import yokai.presentation.extension.repo.component.ExtensionRepoItem
import yokai.util.Screen
import android.R as AR import android.R as AR
class ExtensionRepoScreen(
private val title: String,
private var repoUrl: String? = null,
): Screen() {
@Composable @Composable
fun ExtensionRepoScreen( override fun Content() {
title: String,
viewModel: ExtensionRepoViewModel = viewModel(),
repoUrl: String? = null,
) {
val onBackPress = LocalBackPress.currentOrThrow val onBackPress = LocalBackPress.currentOrThrow
val context = LocalContext.current val context = LocalContext.current
val alertDialog = LocalDialogHostState.currentOrThrow 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("") } var inputText by remember { mutableStateOf("") }
val listState = rememberLazyListState() val listState = rememberLazyListState()
@ -74,14 +77,14 @@ fun ExtensionRepoScreen(
icon = Icons.Outlined.Refresh, icon = Icons.Outlined.Refresh,
buttonClicked = { buttonClicked = {
context.toast("Refreshing...") // TODO: Should be loading animation instead context.toast("Refreshing...") // TODO: Should be loading animation instead
viewModel.refreshRepos() screenModel.refreshRepos()
}, },
) )
}, },
) { innerPadding -> ) { 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() alertDialog.value?.invoke()
@ -96,7 +99,7 @@ fun ExtensionRepoScreen(
inputText = inputText, inputText = inputText,
inputHint = stringResource(MR.strings.label_add_repo), inputHint = stringResource(MR.strings.label_add_repo),
onInputChange = { inputText = it }, onInputChange = { inputText = it },
onAddClick = { viewModel.addRepo(it) }, onAddClick = { screenModel.addRepo(it) },
) )
} }
@ -117,7 +120,7 @@ fun ExtensionRepoScreen(
ExtensionRepoItem( ExtensionRepoItem(
extensionRepo = repo, extensionRepo = repo,
onDeleteClick = { repoToDelete -> onDeleteClick = { repoToDelete ->
scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, viewModel) } scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, screenModel) }
}, },
) )
} }
@ -126,11 +129,14 @@ fun ExtensionRepoScreen(
} }
LaunchedEffect(repoUrl) { LaunchedEffect(repoUrl) {
repoUrl?.let { viewModel.addRepo(repoUrl) } repoUrl?.let {
screenModel.addRepo(repoUrl!!)
repoUrl = null
}
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.event.collectLatest { event -> screenModel.event.collectLatest { event ->
when (event) { when (event) {
is ExtensionRepoEvent.NoOp -> {} is ExtensionRepoEvent.NoOp -> {}
is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes) is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes)
@ -141,7 +147,7 @@ fun ExtensionRepoScreen(
alertDialog.awaitExtensionRepoReplacePrompt( alertDialog.awaitExtensionRepoReplacePrompt(
oldRepo = event.dialog.oldRepo, oldRepo = event.dialog.oldRepo,
newRepo = event.dialog.newRepo, newRepo = event.dialog.newRepo,
onMigrate = { viewModel.replaceRepo(event.dialog.newRepo) }, onMigrate = { screenModel.replaceRepo(event.dialog.newRepo) },
) )
} }
} }
@ -151,7 +157,7 @@ fun ExtensionRepoScreen(
} }
} }
suspend fun DialogHostState.awaitExtensionRepoReplacePrompt( private suspend fun DialogHostState.awaitExtensionRepoReplacePrompt(
oldRepo: ExtensionRepo, oldRepo: ExtensionRepo,
newRepo: ExtensionRepo, newRepo: ExtensionRepo,
onMigrate: () -> Unit, onMigrate: () -> Unit,
@ -182,9 +188,9 @@ suspend fun DialogHostState.awaitExtensionRepoReplacePrompt(
) )
} }
suspend fun DialogHostState.awaitExtensionRepoDeletePrompt( private suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
repoToDelete: String, repoToDelete: String,
viewModel: ExtensionRepoViewModel, screenModel: ExtensionRepoScreenModel,
): Unit = dialog { cont -> ): Unit = dialog { cont ->
AlertDialog( AlertDialog(
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
@ -208,7 +214,7 @@ suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
viewModel.deleteRepo(repoToDelete) screenModel.deleteRepo(repoToDelete)
cont.cancel() cont.cancel()
} }
) { ) {
@ -230,3 +236,4 @@ suspend fun DialogHostState.awaitExtensionRepoDeletePrompt(
}, },
) )
} }
}

View file

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