diff --git a/core/core/src/commonMain/kotlin/com/stslex/core/core/paging/PagingCoreData.kt b/core/core/src/commonMain/kotlin/com/stslex/core/core/paging/PagingCoreData.kt index 2ce49903..35ba56eb 100644 --- a/core/core/src/commonMain/kotlin/com/stslex/core/core/paging/PagingCoreData.kt +++ b/core/core/src/commonMain/kotlin/com/stslex/core/core/paging/PagingCoreData.kt @@ -11,5 +11,6 @@ interface PagingCoreData { const val DEFAULT_PAGE_SIZE = 15 const val DEFAULT_PAGE = 0 + const val DEFAULT_PAGE_OFFSET = 0.5f } } diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingConfig.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingConfig.kt new file mode 100644 index 00000000..b111c64d --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingConfig.kt @@ -0,0 +1,19 @@ +package com.stslex.core.ui.base.paging + +import androidx.compose.runtime.Stable +import com.stslex.core.core.paging.PagingCoreData + +@Stable +data class PagingConfig( + val pageSize: Int, + val pageOffset: Float = PagingCoreData.DEFAULT_PAGE_OFFSET +) { + + companion object { + + val DEFAULT = PagingConfig( + pageSize = PagingCoreData.DEFAULT_PAGE_SIZE, + pageOffset = PagingCoreData.DEFAULT_PAGE_OFFSET + ) + } +} \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingState.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingState.kt index a0574a58..edd9ee4b 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingState.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingState.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.Stable import com.stslex.core.core.asyncMap import com.stslex.core.core.paging.PagingCoreData import com.stslex.core.core.paging.PagingCoreData.Companion.DEFAULT_PAGE -import com.stslex.core.core.paging.PagingCoreData.Companion.DEFAULT_PAGE_SIZE import com.stslex.core.core.paging.PagingCoreItem import com.stslex.core.core.paging.PagingResponse import kotlinx.collections.immutable.ImmutableList @@ -23,10 +22,10 @@ data class PagingState( companion object { fun default( - pageSize: Int = DEFAULT_PAGE_SIZE, + pagingConfig: PagingConfig, ) = PagingState( page = DEFAULT_PAGE, - pageSize = pageSize, + pageSize = pagingConfig.pageSize, total = 0, hasMore = true, result = persistentListOf(), diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingUiState.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingUiState.kt new file mode 100644 index 00000000..58847eb2 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/paging/PagingUiState.kt @@ -0,0 +1,36 @@ +package com.stslex.core.ui.base.paging + +import androidx.compose.runtime.Stable +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlin.math.roundToInt + +@Stable +data class PagingUiState( + val items: ImmutableList, + val hasMore: Boolean, + val total: Int, + val config: PagingConfig +) { + + val pageOffset: Int = (config.pageSize * config.pageOffset).roundToInt() + + companion object { + + fun default(config: PagingConfig) = PagingUiState( + items = persistentListOf(), + total = 0, + hasMore = true, + config = config + ) + } +} + +fun PagingState.toUi( + pagingConfig: PagingConfig +): PagingUiState = PagingUiState( + items = result, + hasMore = hasMore, + total = total, + config = pagingConfig +) \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactory.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactory.kt index 4abedc98..c3eaffe2 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactory.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactory.kt @@ -1,9 +1,9 @@ package com.stslex.core.ui.pager.pager import com.stslex.core.core.coroutine.AppCoroutineScope -import com.stslex.core.core.paging.PagingCoreData import com.stslex.core.core.paging.PagingCoreItem import com.stslex.core.core.paging.PagingResponse +import com.stslex.core.ui.base.paging.PagingConfig import com.stslex.core.ui.base.paging.PagingItem import com.stslex.core.ui.pager.utils.PagingMapper @@ -13,6 +13,6 @@ interface StorePagerFactory { scope: AppCoroutineScope, request: suspend (page: Int, pageSize: Int) -> PagingResponse, mapper: PagingMapper, - pageSize: Int = PagingCoreData.DEFAULT_PAGE_SIZE, + config: PagingConfig ): StorePager } \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactoryImpl.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactoryImpl.kt index 7f7ecd92..9081b999 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactoryImpl.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerFactoryImpl.kt @@ -3,6 +3,7 @@ package com.stslex.core.ui.pager.pager import com.stslex.core.core.coroutine.AppCoroutineScope import com.stslex.core.core.paging.PagingCoreItem import com.stslex.core.core.paging.PagingResponse +import com.stslex.core.ui.base.paging.PagingConfig import com.stslex.core.ui.base.paging.PagingItem import com.stslex.core.ui.pager.utils.PagingMapper import com.stslex.core.ui.pager.utils.PagingWorkerImpl @@ -13,13 +14,13 @@ class StorePagerFactoryImpl : StorePagerFactory { scope: AppCoroutineScope, request: suspend (page: Int, pageSize: Int) -> PagingResponse, mapper: PagingMapper, - pageSize: Int + config: PagingConfig ): StorePager { return StorePagerImpl( pagingWorker = PagingWorkerImpl(scope = scope), request = request, mapper = mapper, - pageSize = pageSize + pagingConfig = config ) } } \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerImpl.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerImpl.kt index 06527706..e3ad71d0 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerImpl.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/pager/pager/StorePagerImpl.kt @@ -1,10 +1,10 @@ package com.stslex.core.ui.pager.pager -import com.stslex.core.core.paging.PagingCoreData import com.stslex.core.core.paging.PagingCoreData.Companion.DEFAULT_PAGE import com.stslex.core.core.paging.PagingCoreItem import com.stslex.core.core.paging.PagingResponse import com.stslex.core.ui.base.mapToAppError +import com.stslex.core.ui.base.paging.PagingConfig import com.stslex.core.ui.base.paging.PagingItem import com.stslex.core.ui.base.paging.PagingState import com.stslex.core.ui.base.paging.pagingMap @@ -24,10 +24,10 @@ class StorePagerImpl( private val pagingWorker: PagingWorker, private val request: suspend (page: Int, pageSize: Int) -> PagingResponse, private val mapper: PagingMapper, - pageSize: Int = PagingCoreData.DEFAULT_PAGE_SIZE, + pagingConfig: PagingConfig, ) : StorePager { - private val _state = MutableStateFlow>(PagingState.default(pageSize)) + private val _state = MutableStateFlow>(PagingState.default(pagingConfig)) override val state = _state.asStateFlow() private val _loadState = MutableStateFlow(PagerLoadState.Initial) diff --git a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/FavouriteScreen.kt b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/FavouriteScreen.kt index 368dc166..223d7098 100644 --- a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/FavouriteScreen.kt +++ b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/FavouriteScreen.kt @@ -25,7 +25,7 @@ internal fun FavouriteScreen( when (state.screen) { is FavouriteScreenState.Content -> FavouriteScreenContent( state = state.screen, - items = state.pagingState.result, + items = state.paging.items, query = state.query, isLoading = state.isLoading, onItemClick = { uuid -> diff --git a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/content/FavouriteScreenContentItem.kt b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/content/FavouriteScreenContentItem.kt index e9d7f933..68813b34 100644 --- a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/content/FavouriteScreenContentItem.kt +++ b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/components/content/FavouriteScreenContentItem.kt @@ -7,7 +7,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material3.Card -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -18,7 +17,6 @@ import androidx.compose.ui.text.style.TextAlign import com.stslex.core.ui.theme.AppDimension import com.stslex.feature.favourite.ui.model.FavouriteModel -@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun FavouriteScreenContentItem( item: FavouriteModel, diff --git a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStore.kt b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStore.kt index ed4e3fee..619c9b7b 100644 --- a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStore.kt +++ b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStore.kt @@ -1,7 +1,8 @@ package com.stslex.feature.favourite.ui.store import androidx.compose.runtime.Stable -import com.stslex.core.ui.base.paging.PagingState +import com.stslex.core.ui.base.paging.PagingConfig +import com.stslex.core.ui.base.paging.PagingUiState import com.stslex.core.ui.mvi.Store import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.feature.favourite.ui.model.FavouriteModel @@ -15,7 +16,7 @@ interface FavouriteStore : Store { data class State( val uuid: String, val query: String, - val pagingState: PagingState, + val paging: PagingUiState, val screen: FavouriteScreenState, val isLoading: Boolean ) : Store.State { @@ -25,7 +26,7 @@ interface FavouriteStore : Store { val INITIAL = State( uuid = "", query = "", - pagingState = PagingState.default(), + paging = PagingUiState.default(PagingConfig.DEFAULT), screen = FavouriteScreenState.Shimmer, isLoading = true ) diff --git a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStoreImpl.kt b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStoreImpl.kt index 30effeaa..fecf6884 100644 --- a/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStoreImpl.kt +++ b/feature/favourite/src/commonMain/kotlin/com/stslex/feature/favourite/ui/store/FavouriteStoreImpl.kt @@ -2,6 +2,7 @@ package com.stslex.feature.favourite.ui.store import com.stslex.core.core.AppDispatcher import com.stslex.core.ui.base.mapToAppError +import com.stslex.core.ui.base.paging.toUi import com.stslex.core.ui.mvi.BaseStore import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.core.ui.pager.pager.StorePager @@ -44,7 +45,8 @@ class FavouriteStoreImpl( ) }, scope = scope, - mapper = { it.toUI() } + mapper = { it.toUI() }, + config = state.value.paging.config ) override fun process(action: Action) { @@ -69,7 +71,7 @@ class FavouriteStoreImpl( pager.state.launch { pagerState -> updateState { currentState -> currentState.copy( - pagingState = pagerState + paging = pagerState.toUi(currentState.paging.config) ) } } @@ -116,20 +118,20 @@ class FavouriteStoreImpl( private fun actionLikeClick(action: Action.LikeClick) { if (likeJob?.isActive == true) return - val items = state.value.pagingState.result.toMutableList() + val items = state.value.paging.items.toMutableList() val itemIndex = items .indexOfFirst { it.uuid == action.uuid } .takeIf { it != -1 } ?: return - val item = state.value.pagingState.result.getOrNull(itemIndex) ?: return + val item = state.value.paging.items.getOrNull(itemIndex) ?: return val newItem = item.copy(isFavourite = item.isFavourite.not()) items[itemIndex] = newItem updateState { state -> state.copy( - pagingState = state.pagingState.copy( - result = items.toImmutableList() + paging = state.paging.copy( + items = items.toImmutableList() ) ) } diff --git a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/FollowerScreen.kt b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/FollowerScreen.kt index 01dfb5fe..23e5daeb 100644 --- a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/FollowerScreen.kt +++ b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/FollowerScreen.kt @@ -48,9 +48,9 @@ internal fun FollowerScreen( when (state.screen) { is FollowerScreenState.Content -> { LazyColumn { - items(state.pagingState.result.size) { index -> + items(state.paging.items.size) { index -> Text( - "test: ${state.pagingState.result[index].username}" + "test: ${state.paging.items[index].username}" ) } } diff --git a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStore.kt b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStore.kt index 86e9db10..497e2b91 100644 --- a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStore.kt +++ b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStore.kt @@ -1,7 +1,8 @@ package com.stslex.feature.follower.ui.store import androidx.compose.runtime.Stable -import com.stslex.core.ui.base.paging.PagingState +import com.stslex.core.ui.base.paging.PagingConfig +import com.stslex.core.ui.base.paging.PagingUiState import com.stslex.core.ui.mvi.Store import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.feature.follower.navigation.FollowerScreenArgs @@ -15,18 +16,16 @@ interface FollowerStore : Store { @Stable data class State( val type: FollowerScreenArgs, - val pagingState: PagingState, + val paging: PagingUiState, val screen: FollowerScreenState, val query: String ) : Store.State { companion object { - const val DEFAULT_PAGE = -1 - val INITIAL = State( type = FollowerScreenArgs.Follower(""), - pagingState = PagingState.default(), + paging = PagingUiState.default(PagingConfig.DEFAULT), screen = FollowerScreenState.Shimmer, query = "" ) diff --git a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStoreImpl.kt b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStoreImpl.kt index 5b1be04e..04ec44a9 100644 --- a/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStoreImpl.kt +++ b/feature/follower/src/commonMain/kotlin/com.stslex.feature.follower/ui/store/FollowerStoreImpl.kt @@ -2,6 +2,7 @@ package com.stslex.feature.follower.ui.store import com.stslex.core.core.AppDispatcher import com.stslex.core.ui.base.mapToAppError +import com.stslex.core.ui.base.paging.toUi import com.stslex.core.ui.mvi.BaseStore import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.core.ui.pager.pager.StorePager @@ -50,7 +51,8 @@ class FollowerStoreImpl( } }, scope = scope, - mapper = { it.toUi() } + mapper = { it.toUi() }, + config = state.value.paging.config ) override fun process(action: Action) { @@ -72,7 +74,7 @@ class FollowerStoreImpl( pager.state.launch { pagerState -> updateState { currentState -> currentState.copy( - pagingState = pagerState + paging = pagerState.toUi(currentState.paging.config) ) } } diff --git a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/MatchScreen.kt b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/MatchScreen.kt index 61139b8c..18c88e65 100644 --- a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/MatchScreen.kt +++ b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/MatchScreen.kt @@ -69,7 +69,7 @@ private fun MatchScreen( ) { when (val screen = state.screen) { is MatchScreenState.Content -> MatchScreenContent( - state = state.pagingState, + state = state.paging, screen = screen, onAction = onAction ) diff --git a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/components/MatchScreenContent.kt b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/components/MatchScreenContent.kt index af3a24c5..d9483379 100644 --- a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/components/MatchScreenContent.kt +++ b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/components/MatchScreenContent.kt @@ -22,19 +22,20 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.stslex.core.ui.base.DotsPrintAnimation -import com.stslex.core.ui.base.paging.PagingState +import com.stslex.core.ui.base.paging.PagingUiState import com.stslex.core.ui.base.shimmerLoadingAnimation import com.stslex.core.ui.theme.AppDimension import com.stslex.feature.match.ui.model.MatchUiModel import com.stslex.feature.match.ui.store.MatchScreenState import com.stslex.feature.match.ui.store.MatchStore.Action import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @OptIn(ExperimentalMaterialApi::class) @Composable internal fun MatchScreenContent( - state: PagingState, + state: PagingUiState, screen: MatchScreenState.Content, onAction: (Action) -> Unit, modifier: Modifier = Modifier, @@ -45,19 +46,19 @@ internal fun MatchScreenContent( ) val listState = rememberLazyListState() - LaunchedEffect(listState, state.result.size, state.pageSize) { + LaunchedEffect(listState, state) { snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } .filterNotNull() + .filter { index -> + state.hasMore && + index >= (state.items.size - state.pageOffset) && + screen is MatchScreenState.Content.Data + } .distinctUntilChanged() - .collect { index -> - if ( - screen != MatchScreenState.Content.Append && - index >= (state.result.size - state.pageSize * 0.5f) - ) { - onAction(Action.LoadMore) - } + .collect { + onAction(Action.LoadMore) } } @@ -74,12 +75,12 @@ internal fun MatchScreenContent( state = listState, ) { items( - count = state.result.size, + count = state.items.size, key = { index -> - state.result[index].uuid + state.items[index].uuid }, ) { index -> - state.result.getOrNull(index)?.let { item -> + state.items.getOrNull(index)?.let { item -> MatchItem( item = item, onItemClicked = { matchUuid -> diff --git a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStore.kt b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStore.kt index 8bd9c342..a297dfc3 100644 --- a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStore.kt +++ b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStore.kt @@ -1,7 +1,8 @@ package com.stslex.feature.match.ui.store import androidx.compose.runtime.Stable -import com.stslex.core.ui.base.paging.PagingState +import com.stslex.core.ui.base.paging.PagingConfig +import com.stslex.core.ui.base.paging.PagingUiState import com.stslex.core.ui.mvi.Store import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.core.ui.navigation.args.MatchScreenArgs @@ -18,14 +19,14 @@ interface MatchStore : Store { val uuid: String, val isSelf: Boolean, val query: String, - val pagingState: PagingState + val paging: PagingUiState ) : Store.State { companion object { val INITIAL = State( screen = MatchScreenState.Shimmer, - pagingState = PagingState.default(), + paging = PagingUiState.default(PagingConfig.DEFAULT), uuid = "", isSelf = false, query = "" diff --git a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStoreImpl.kt b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStoreImpl.kt index dc4eb864..c242f140 100644 --- a/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStoreImpl.kt +++ b/feature/match/src/commonMain/kotlin/com/stslex/feature/match/ui/store/MatchStoreImpl.kt @@ -4,6 +4,7 @@ import com.stslex.core.core.AppDispatcher import com.stslex.core.core.Logger import com.stslex.core.database.store.UserStore import com.stslex.core.ui.base.mapToAppError +import com.stslex.core.ui.base.paging.toUi import com.stslex.core.ui.mvi.BaseStore import com.stslex.core.ui.mvi.Store.Event.Snackbar import com.stslex.core.ui.pager.pager.StorePager @@ -20,7 +21,6 @@ import com.stslex.feature.match.ui.store.MatchStore.State import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -// todo refactor pager state for UI https://github.com/stslex/Wizard/issues/35 class MatchStoreImpl( appDispatcher: AppDispatcher, router: MatchRouter, @@ -43,7 +43,8 @@ class MatchStoreImpl( ) }, scope = scope, - mapper = { it.toUi() } + mapper = { it.toUi() }, + config = state.value.paging.config ) override fun process(action: Action) { @@ -64,7 +65,7 @@ class MatchStoreImpl( pager.state.launch { pagerState -> updateState { currentState -> currentState.copy( - pagingState = pagerState + paging = pagerState.toUi(currentState.paging.config) ) } }