diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt index b712cf3..3bcac45 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope @@ -25,13 +24,12 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.PreferencesScreenDestination import com.ramcosta.composedestinations.generated.destinations.SaveBookmarkScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import kotlinx.coroutines.launch import org.yrovas.linklater.show import org.yrovas.linklater.ui.common.AppBar -import org.yrovas.linklater.ui.component.BookmarkRow import org.yrovas.linklater.ui.common.Frame import org.yrovas.linklater.ui.common.Icon import org.yrovas.linklater.ui.common.RefreshIcon +import org.yrovas.linklater.ui.component.BookmarkRow import org.yrovas.linklater.ui.state.HomeScreenState import org.yrovas.linklater.ui.state.HomeScreenState.Effect import org.yrovas.linklater.ui.state.HomeScreenState.Event @@ -50,18 +48,14 @@ fun HomeScreen( val bookmarkCount by state.bookmarkCount.collectAsState() val listState = rememberLazyListState() - LaunchedEffect(true) { - scope.launch { - state.effect.collect { effect -> - when (effect) { - is Effect.RefreshError -> { - snackState.show(effect.error) - } + state.subscribeEffects(scope) { effect -> + when (effect) { + is Effect.RefreshError -> { + snackState.show(effect.error) + } - Effect.RefreshOk -> { - listState.animateScrollToItem(0) - } - } + Effect.RefreshOk -> { + listState.animateScrollToItem(0) } } } diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkActivityScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkActivityScreen.kt index fbd555c..d272230 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkActivityScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkActivityScreen.kt @@ -23,8 +23,8 @@ fun SaveBookmarkActivityScreen( nav: DestinationsNavigator, saveBookmarkScreenState: () -> SaveBookmarkScreenState, snackState: SnackbarHostState, - context: Context = LocalContext.current, ) { + val context: Context = LocalContext.current val state = viewModel { saveBookmarkScreenState() } LaunchedEffect(true) { val url = (context as SaveBookmarkActivity).extractURL() diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt index dbd018d..d394bdf 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource @@ -78,7 +77,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import kotlinx.coroutines.launch import org.yrovas.linklater.readClipboard import org.yrovas.linklater.show import org.yrovas.linklater.ui.activity.launch @@ -98,8 +96,8 @@ import kotlin.math.round fun SaveBookmarkScreen( nav: DestinationsNavigator, snackState: SnackbarHostState, - context: Context = LocalContext.current, state: () -> SaveBookmarkScreenState, + context: Context = LocalContext.current, back: () -> Unit = { nav.popBackStack() }, onSubmitSuccess: suspend () -> Unit = { context.launch { @@ -109,29 +107,22 @@ fun SaveBookmarkScreen( }, ) { @Suppress("NAME_SHADOWING") val state = viewModel { state() } - val isSubmitting by state.isSubmitting.collectAsState() val scope = rememberCoroutineScope() - LaunchedEffect(true) { - scope.launch { - state.effect.collect { effect -> - when (effect) { - Effect.SubmitSuccess -> onSubmitSuccess() - is Effect.SubmitError -> { - snackState.show(effect.error) - } - is Effect.InvalidBookmark -> { - snackState.showSnackbar(effect.message) - } - } + var showTagRow by remember { mutableStateOf(false) } + + state.subscribeEffects(scope) { effect -> + when (effect) { + Effect.SubmitSuccess -> onSubmitSuccess() + is Effect.SubmitError -> { + snackState.show(effect.error) } - } - } - var showTagRow by remember { mutableStateOf(false) } - val onTagFocus = { b: Boolean -> - showTagRow = b + is Effect.InvalidBookmark -> { + snackState.showSnackbar(effect.message) + } + } } Frame( @@ -159,7 +150,9 @@ fun SaveBookmarkScreen( ) { CircularProgressIndicator() } else { - SaveBookmarkFields(state = state, onTagFocus = onTagFocus) + SaveBookmarkFields(state = state, onTagFocus = { + showTagRow = it + }) } } } diff --git a/app/src/main/java/org/yrovas/linklater/ui/state/ScreenState.kt b/app/src/main/java/org/yrovas/linklater/ui/state/ScreenState.kt index 4aba8e3..328698b 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/state/ScreenState.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/state/ScreenState.kt @@ -1,12 +1,21 @@ package org.yrovas.linklater.ui.state +import android.annotation.SuppressLint +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModel +import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext interface ScreenEvent @@ -15,9 +24,9 @@ interface ScreenEffect abstract class ScreenState : ViewModel() { private val _event: MutableSharedFlow = MutableSharedFlow() - protected val event = _event.asSharedFlow() + private val event = _event.asSharedFlow() private val _effect: Channel = Channel() - val effect = _effect.receiveAsFlow() // consumeAsFlow? + private val effect = _effect.receiveAsFlow() // consumeAsFlow? private fun subscribeEvents() { viewModelScope.launch { @@ -42,4 +51,35 @@ abstract class ScreenState : init { subscribeEvents() } + + @SuppressLint("ComposableNaming") + @Composable + fun subscribeEffects(onEffect: (Effect) -> Unit) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(effect, lifecycleOwner.lifecycle) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { + effect.collect(onEffect) + } + } + } + } + + @SuppressLint("ComposableNaming") + @Composable + fun subscribeEffects( + scope: CoroutineScope, + onEffect: suspend (Effect) -> Unit, + ) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(effect, lifecycleOwner.lifecycle) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { + effect.collect { + scope.launch { onEffect(it) } + } + } + } + } + } }