diff --git a/app/src/main/java/com/merttoptas/composebase/domain/viewstate/characters/CharactersViewState.kt b/app/src/main/java/com/merttoptas/composebase/domain/viewstate/characters/CharactersViewState.kt index 5af14dc..cebcb97 100644 --- a/app/src/main/java/com/merttoptas/composebase/domain/viewstate/characters/CharactersViewState.kt +++ b/app/src/main/java/com/merttoptas/composebase/domain/viewstate/characters/CharactersViewState.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.Flow @Stable data class CharactersViewState( val isLoading: Boolean = false, + val isRefreshing: Boolean = false, val pagedData: Flow>? = null, val data: List? = null, ) : IViewState \ No newline at end of file diff --git a/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersScreen.kt b/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersScreen.kt index ff63f99..01ad9ce 100644 --- a/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersScreen.kt +++ b/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersScreen.kt @@ -3,11 +3,18 @@ package com.merttoptas.composebase.features.screen.characters import android.content.res.Configuration import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -46,7 +53,10 @@ fun CharactersScreen( }, content = { Content( + modifier = Modifier + .padding(it), isLoading = viewState.isLoading, + isRefreshing = viewState.isRefreshing, pagedData = viewState.pagedData, onTriggerEvent = { viewModel.onTriggerEvent(it) @@ -60,9 +70,12 @@ fun CharactersScreen( ) } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun Content( + modifier: Modifier = Modifier, isLoading: Boolean = false, + isRefreshing: Boolean = false, pagedData: Flow>? = null, onTriggerEvent: (CharactersViewEvent) -> Unit, clickDetail: (CharacterDto?) -> Unit @@ -72,12 +85,20 @@ private fun Content( pagingItems = rememberFlowWithLifecycle(it).collectAsLazyPagingItems() } + val pullRefreshState = rememberPullRefreshState( + refreshing = isRefreshing, + onRefresh = { onTriggerEvent(CharactersViewEvent.Refresh) } + ) + Box( - modifier = Modifier + modifier = modifier .fillMaxSize() - .padding(horizontal = 15.dp), + .pullRefresh(pullRefreshState) + .padding(horizontal = 15.dp) + .semantics { testTag = "characters_pull_refresh_container" }, ) { LazyColumn( + modifier = Modifier.semantics { testTag = "characters_lazy_column" }, contentPadding = PaddingValues(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(10.dp) ) { @@ -107,9 +128,17 @@ private fun Content( } } - } } + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier + .align(Alignment.TopCenter) + .semantics { testTag = "pull_refresh_indicator" }, + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) } } diff --git a/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersViewModel.kt b/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersViewModel.kt index cbde991..482a104 100644 --- a/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersViewModel.kt +++ b/app/src/main/java/com/merttoptas/composebase/features/screen/characters/CharactersViewModel.kt @@ -27,16 +27,33 @@ class CharactersViewModel @Inject constructor( private val config = PagingConfig(pageSize = 20) init { - getAllCharacters() + getAllCharacters(isRefreshing = false) } - private fun getAllCharacters() { + private fun getAllCharacters(isRefreshing: Boolean = false) { viewModelScope.launch { - setState { currentState.copy(isLoading = true) } - val params = GetCharactersUseCase.Params(config, hashMapOf()) - val pagedFlow = getCharactersUseCase(params).cachedIn(scope = viewModelScope) - delay(1000) - setState { currentState.copy(isLoading = false, pagedData = pagedFlow) } + try { + if (isRefreshing) { + setState { currentState.copy(isRefreshing = true) } + } else { + setState { currentState.copy(isLoading = true) } + } + val params = GetCharactersUseCase.Params(config, hashMapOf()) + val pagedFlow = getCharactersUseCase(params).cachedIn(scope = viewModelScope) + delay(1000) + if (isRefreshing) { + setState { currentState.copy(isRefreshing = false, pagedData = pagedFlow) } + } else { + setState { currentState.copy(isLoading = false, pagedData = pagedFlow) } + } + } catch (e: Exception) { + // Handle error: reset loading/refreshing state to false + if (isRefreshing) { + setState { currentState.copy(isRefreshing = false) } + } else { + setState { currentState.copy(isLoading = false) } + } + } } } @@ -51,11 +68,13 @@ class CharactersViewModel @Inject constructor( viewModelScope.launch { when (event) { is CharactersViewEvent.UpdateFavorite -> updateFavorite(event.dto) + is CharactersViewEvent.Refresh -> getAllCharacters(isRefreshing = true) } } } } sealed class CharactersViewEvent : IViewEvent { + object Refresh : CharactersViewEvent() class UpdateFavorite(val dto: CharacterDto) : CharactersViewEvent() } \ No newline at end of file