Skip to content

Commit

Permalink
Add StoriesMarketPager
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-ramotar committed May 4, 2024
1 parent 8514305 commit 2248312
Show file tree
Hide file tree
Showing 19 changed files with 377 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface MarketPager<Id : Comparable<Id>> {
fun <Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any, A : Market.Action, D : Market.Dispatcher<A>> from(
coroutineDispatcher: CoroutineDispatcher,
marketDispatcher: D,
actionFactory: (state: StoreX.Paging.State<Id, K, V, E>) -> A,
actionFactory: (pagingState: StoreX.Paging.State<Id, K, V, E>) -> A,
pagerBuilder: PagerBuilder<Id, K, V, E>.() -> Pager<Id, K, V, E>
): MarketPager<Id> {
return RealMarketPager(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@ package org.mobilenativefoundation.market

import org.mobilenativefoundation.storex.paging.Identifiable

interface StatefulMarket<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any, S : StatefulMarket.State<Id, K, V, E>> :
Market<S> {
interface StatefulMarket<S : StatefulMarket.State> : Market<S> {

interface State<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> : Market.State {
val subStates: Map<String, SubState<Id, K, V, E>>
interface State : Market.State {
val subStates: Map<String, SubState>
}

sealed interface SubState<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>
sealed interface SubState

data class NormalizedState<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
val allIds: List<Id>,
val byId: Map<Id, ItemState<Id, K, V, E>>
) : SubState<Id, K, V, E>
data class NormalizedState<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(
val allIds: List<Id> = emptyList(),
val byId: Map<Id, ItemState<Id, V, E>> = emptyMap()
) : SubState


sealed interface ItemState<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> {
data object Initial : ItemState<Nothing, Nothing, Nothing, Nothing>
data object Loading : ItemState<Nothing, Nothing, Nothing, Nothing>
data class Error<E : Any>(val error: E) : ItemState<Nothing, Nothing, Nothing, E>
sealed interface ItemState<Id : Comparable<Id>, V : Identifiable<Id>, E : Any> {
data object Initial : ItemState<Nothing, Nothing, Nothing>
data object Loading : ItemState<Nothing, Nothing, Nothing>
data class Error<E : Any>(val error: E) : ItemState<Nothing, Nothing, E>
data class Data<Id : Comparable<Id>, V : Identifiable<Id>>(
val value: V,
val status: Status,
val lastModified: Long,
val lastRefreshed: Long,
) : SubState<Id, Nothing, V, Nothing> {
) : SubState {
enum class Status {
Idle,
Downloading,
Expand All @@ -36,40 +35,42 @@ interface StatefulMarket<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E :
}


sealed interface PagingState<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> : SubState<Id, K, V, E> {
sealed interface PagingState<Id : Comparable<Id>, V : Identifiable<Id>, E : Any> : SubState {
val anchorPosition: Id?
val prefetchPosition: Id?

data class Initial<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
data class Initial<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
) : PagingState<Id, K, V, E>
) : PagingState<Id, V, E>

data class Loading<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
data class Loading<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
) : PagingState<Id, K, V, E>
) : PagingState<Id, V, E>

data class Error<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
data class Error<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(
val value: E,
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
) : PagingState<Id, K, V, E>
) : PagingState<Id, V, E>

data class Data<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
data class Data<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(
val allIds: List<Id>,
override val anchorPosition: Id?,
override val prefetchPosition: Id?,
val lastModified: Long,
val lastRefreshed: Long,
val status: Status
) : PagingState<Id, K, V, E> {
enum class Status {
Idle,
LoadingMore,
ErrorLoadingMore,
Refreshing
val status: Status<E>
) : PagingState<Id, V, E> {

sealed interface Status<out E : Any> {
data object Idle : Status<Nothing>
data class ErrorLoadingMore<E : Any>(val error: E) : Status<E>
data object LoadingMore : Status<Nothing>
data object Refreshing : Status<Nothing>
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package org.mobilenativefoundation.storex.paging
interface AggregatingStrategy<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> {
fun aggregate(
pagingBuffer: PagingBuffer<Id, K, V, E>,
anchorPosition: K?,
prefetchPosition: K?
anchorPosition: Id?,
prefetchPosition: Id?
): StoreX.Paging.Items<Id, V>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package org.mobilenativefoundation.storex.paging
* Implementing a custom [FetchingStrategy] allows you to define your own logic for when to fetch more data.
* For example, you can fetch more data when the user scrolls near the end of the currently loaded data, or when a certain number of items are remaining in the buffer.
*/
interface FetchingStrategy<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> {
fun interface FetchingStrategy<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> {

/**
* Determines whether to fetch more data based on the current state of the pager.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,16 @@ class PagerBuilder<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
pageStore: Store<K, PagingSource.LoadResult.Data<Id, K, V, E>>,
throwableConverter: (Throwable) -> E,
messageConverter: (String) -> E,
builder: PagingSourceBuilder<Id, K, V, E>.() -> PagingSourceBuilder<Id, K, V, E>
itemStore: Store<Id, V>,
) =
apply {
this.pagingSource = builder(
PagingSourceBuilder(
dispatcher,
pageStore,
throwableConverter,
messageConverter
)
).build()
this.pagingSource = PagingSourceBuilder(
dispatcher,
pageStore,
throwableConverter,
messageConverter
).itemStore(itemStore)
.build()
}

fun pagingSource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ interface PagingBuffer<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : A

fun getAll(): List<StoreX.Paging.Data.Page<Id, K, V>>

fun getAllItems(): List<StoreX.Paging.Data.Item<Id, V>>

fun isEmpty(): Boolean

fun indexOf(key: K): Int
fun indexOf(id: Id): Int

fun getItemsInRange(
anchorPosition: K,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ object StoreX {
}

sealed interface State<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any> {
val anchorPosition: K?
val prefetchPosition: K?
val anchorPosition: Id?
val prefetchPosition: Id?
val pagingBuffer: PagingBuffer<Id, K, V, E>

data class Initial<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
override val prefetchPosition: K?,
override val anchorPosition: K?,
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
override val pagingBuffer: PagingBuffer<Id, K, V, E>
) : State<Id, K, V, E>

data class Loading<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
override val prefetchPosition: K?,
override val anchorPosition: K?,
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
override val pagingBuffer: PagingBuffer<Id, K, V, E>
) : State<Id, K, V, E>

data class Error<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
val value: E,
override val prefetchPosition: K?,
override val anchorPosition: K?,
override val prefetchPosition: Id?,
override val anchorPosition: Id?,
override val pagingBuffer: PagingBuffer<Id, K, V, E>
) : State<Id, K, V, E>

Expand All @@ -61,23 +61,23 @@ object StoreX {

data class Idle<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
override val pagingBuffer: PagingBuffer<Id, K, V, E>,
override val anchorPosition: K?,
override val prefetchPosition: K?,
override val anchorPosition: Id?,
override val prefetchPosition: Id?,
private val created: Long = Clock.System.now().epochSeconds,
) : Data<Id, K, V, E>

data class LoadingMore<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
override val pagingBuffer: PagingBuffer<Id, K, V, E>,
override val anchorPosition: K?,
override val prefetchPosition: K?,
override val anchorPosition: Id?,
override val prefetchPosition: Id?,
) : Data<Id, K, V, E>


data class ErrorLoadingMore<Id : Comparable<Id>, K : Any, V : Identifiable<Id>, E : Any>(
override val pagingBuffer: PagingBuffer<Id, K, V, E>,
val error: E,
override val anchorPosition: K?,
override val prefetchPosition: K?,
override val anchorPosition: Id?,
override val prefetchPosition: Id?,
) : Data<Id, K, V, E>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class RealAggregatingStrategy<Id : Comparable<Id>, K : Any, V : Identifiable<Id>

override fun aggregate(
pagingBuffer: PagingBuffer<Id, K, V, E>,
anchorPosition: K?,
prefetchPosition: K?
anchorPosition: Id?,
prefetchPosition: Id?
): StoreX.Paging.Items<Id, V> {
if (pagingBuffer.isEmpty()) return StoreX.Paging.Items(emptyList())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ class RealMutablePagingBuffer<Id : Comparable<Id>, K : Any, V : Identifiable<Id>
page: PagingSource.LoadResult.Data<Id, K, V, E>
) {
val index = computeIndex(params.key)
pages[index] = StoreX.Paging.Data.Page(
items = page.items.map { it.value.id },

val newItemIds = page.items.map { it.value.id }
val newPage = StoreX.Paging.Data.Page<Id, K, V>(
items = newItemIds,
key = page.key,
nextKey = page.nextKey,
itemsBefore = page.itemsBefore,
itemsAfter = page.itemsAfter,
origin = page.origin,
extras = page.extras
)

pages[index] = newPage
paramsToIndex[params] = index
keyToIndex[params.key] = index
tail = (tail + 1) % maxSize
Expand Down Expand Up @@ -90,7 +94,29 @@ class RealMutablePagingBuffer<Id : Comparable<Id>, K : Any, V : Identifiable<Id>
return pages
}

override fun getAllItems(): List<StoreX.Paging.Data.Item<Id, V>> {
return items.values.toList()
}

override fun isEmpty(): Boolean = size == 0
override fun indexOf(id: Id): Int {
if (id !in items) return -1

var index = head
var count = 0
while (count < size) {
val page = pages[index]
if (page != null) {
val itemIndex = page.items.indexOf(id)
if (itemIndex != -1) {
return count * maxSize + itemIndex
}
}
index = (index + 1) % maxSize
count++
}
return -1
}

override fun indexOf(key: K): Int {
return keyToIndex[key] ?: -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class RealPagingSourceController<Id : Comparable<Id>, K : Any, V : Identifiable<
is StoreX.Paging.State.Data -> {
StoreX.Paging.State.LoadingMore(
pagingBuffer = prevState.pagingBuffer,
anchorPosition = params.key,
anchorPosition = prevState.anchorPosition, // TODO
prefetchPosition = prevState.prefetchPosition
)
}
Expand All @@ -34,7 +34,7 @@ class RealPagingSourceController<Id : Comparable<Id>, K : Any, V : Identifiable<
is StoreX.Paging.State.Error -> {
StoreX.Paging.State.Loading(
pagingBuffer = prevState.pagingBuffer,
anchorPosition = params.key,
anchorPosition = prevState.anchorPosition, // TODO
prefetchPosition = prevState.prefetchPosition
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
package monster.scoop.xplat.common.market

import kotlinx.datetime.LocalDateTime
import monster.scoop.xplat.domain.story.api.StoriesError
import monster.scoop.xplat.domain.story.api.Story
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.StatefulMarket
import org.mobilenativefoundation.market.StatefulMarket.PagingState.Data.Status

sealed interface ScoopAction : Market.Action
typealias StoryState = StatefulMarket.ItemState<Int, Story, StoriesError>

sealed interface ScoopAction : Market.Action {
sealed interface Stories : ScoopAction {
data class SetStories(val stories: List<Story>) : Stories
data class PushStories(val stories: List<Story>) : Stories

data class SetStory(val storyId: Int, val storyState: StoryState) : Stories

sealed interface Paging : Stories {
data class SetInitial(val prefetchPosition: Int?, val anchorPosition: Int?) : Stories
data class SetLoading(val prefetchPosition: Int?, val anchorPosition: Int?) : Stories
data class SetError(val error: StoriesError, val prefetchPosition: Int?, val anchorPosition: Int?) : Stories

data class SetData(
val stories: List<Story>,
val anchorPosition: Int?,
val prefetchPosition: Int?,
val lastModified: LocalDateTime,
val lastRefreshed: LocalDateTime,
val status: Status<StoriesError>
) : Paging

data class UpdateData(
val allIds: List<Int>? = null,
val anchorPosition: Int? = null,
val prefetchPosition: Int? = null,
val lastModified: LocalDateTime? = null,
val lastRefreshed: LocalDateTime? = null,
val status: Status<StoriesError>? = null
) : Paging
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package monster.scoop.xplat.common.market

import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.StatefulMarket

typealias ScoopMarket = Market<ScoopState>

typealias ScoopMarket = StatefulMarket<ScoopState>
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package monster.scoop.xplat.common.market

import monster.scoop.xplat.domain.story.api.StoriesState
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.StatefulMarket

data class ScoopState(
val stories: StoriesState = StoriesState()
) : Market.State
override val subStates: Map<String, StatefulMarket.SubState> = subStates()
) : StatefulMarket.State

private fun subStates(): Map<String, StatefulMarket.SubState> = buildMap {
"stories" to StoriesState()
}






Loading

0 comments on commit 2248312

Please sign in to comment.