Skip to content

Commit

Permalink
Home feed!
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-ramotar committed May 6, 2024
1 parent fa0c42c commit 8ba33b5
Show file tree
Hide file tree
Showing 25 changed files with 556 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ interface StatefulMarket<S : StatefulMarket.State> : Market<S> {
) : SubState


sealed interface ItemState<Id : Comparable<Id>, out V : Identifiable<Id>,out E : Any> {
data class Initial<Id: Comparable<Id>, V: Identifiable<Id>, E : Any>(val id: Id) : ItemState<Id, V, E>
data class Loading<Id: Comparable<Id>, V: Identifiable<Id>, E : Any>(val id: Id) : ItemState<Id, V, E>
data class Error<Id: Comparable<Id>, V: Identifiable<Id>, E : Any>(val error: E) : ItemState<Id, V, E>
sealed interface ItemState<Id : Comparable<Id>, out V : Identifiable<Id>, out E : Any> {
data class Initial<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(val id: Id) : ItemState<Id, V, E>
data class Loading<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(val id: Id) : ItemState<Id, V, E>
data class Error<Id : Comparable<Id>, V : Identifiable<Id>, E : Any>(val error: E) : ItemState<Id, V, E>
data class Data<Id : Comparable<Id>, V : Identifiable<Id>>(
val value: V,
val status: Status,
Expand Down Expand Up @@ -73,4 +73,9 @@ interface StatefulMarket<S : StatefulMarket.State> : Market<S> {

}
}
}
}

fun <Id : Comparable<Id>, V : Identifiable<Id>, E : Any> EmptySubState() = StatefulMarket.NormalizedState<Id, V, E>(
allIds = emptyList(),
byId = emptyMap()
)
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,23 @@ class RealMutablePagingBuffer<Id : Comparable<Id>, K : Any, V : Identifiable<Id>
}

override fun minDistanceBetween(a: Id, b: Id): Int {
val positionA = positionOf(a)
require(positionA > -1) { "Not found: $a" }
var positionA = positionOf(a)

// We can't simply require `positionA` to be > -1, because it is not required to be in the paging buffer.
// For example, a first load with a default `anchorPosition` of 1 but IDs start in the 100s.

if (positionA == -1) {
val headPage = pages[head]
val firstItemHeadPage = headPage?.items?.first()

val positionFirstItem = firstItemHeadPage?.let {
positionOf(it)
}

positionFirstItem?.let { positionA = it }
}

require(positionA > -1) { "$a not found, head also not found" }

var positionB = positionOf(b)

Expand Down
3 changes: 3 additions & 0 deletions experimental/sample/scoop/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ dependencies {
implementation(project(":experimental:market"))
implementation(project(":experimental:market:warehouse"))
implementation(project(":experimental:sample:scoop:xplat:foundation:di"))
implementation(project(":experimental:sample:scoop:xplat:foundation:networking:impl"))
implementation(project(":experimental:sample:scoop:xplat:common:market"))
implementation(project(":experimental:sample:scoop:xplat:domain:story:impl"))
implementation(project(":experimental:sample:scoop:xplat:feat:homeTab:impl"))
}

ksp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ package monster.scoop.android.app
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.foundation.CircuitCompositionLocals
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.slack.circuit.backstack.rememberSaveableBackStack
import com.slack.circuit.foundation.*
import me.tatarka.inject.annotations.Inject
import monster.scoop.android.app.di.CoreComponent
import monster.scoop.android.app.di.create
import monster.scoop.android.app.theme.ScoopTheme


@Inject
Expand All @@ -28,7 +35,24 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)

setContent {

val homeTab = remember { coreComponent.screenFactory.homeTab() }

val backStack = rememberSaveableBackStack(root = homeTab)
val navigator = rememberCircuitNavigator(backStack)

CircuitCompositionLocals(circuit) {
ScoopTheme {
Scaffold { innerPadding ->
NavigableCircuitContent(
navigator,
backStack,
modifier = Modifier.padding(innerPadding).background(MaterialTheme.colorScheme.background),
decoration = NavigatorDefaults.EmptyDecoration
)

}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.screen.Screen
import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.feat.homeTab.api.HomeTab
import monster.scoop.xplat.feat.homeTab.impl.HomeTabPresenter
import monster.scoop.xplat.foundation.di.UserScope

@Inject
@UserScope
class ScoopPresenterFactory : Presenter.Factory {
class ScoopPresenterFactory(
private val homeTabPresenterFactory: (navigator: Navigator) -> HomeTabPresenter
) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
is HomeTab -> homeTabPresenterFactory(navigator)
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package monster.scoop.android.app.circuit

import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.feat.homeTab.api.HomeTab
import monster.scoop.xplat.foundation.di.UserScope

@Inject
@UserScope
class ScoopScreenFactory : ScreenFactory {
class ScoopScreenFactory(
private val homeTab: HomeTab
) : ScreenFactory {
override fun homeTab(): HomeTab = homeTab
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.screen.Screen
import com.slack.circuit.runtime.ui.Ui
import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.feat.homeTab.api.HomeTab
import monster.scoop.xplat.feat.homeTab.impl.HomeTabUi
import monster.scoop.xplat.foundation.di.UserScope

@Inject
@UserScope
class ScoopUiFactory : Ui.Factory {
class ScoopUiFactory(
private val homeTabUi: HomeTabUi
) : Ui.Factory {
override fun create(screen: Screen, context: CircuitContext): Ui<*>? {
return when (screen) {
is HomeTab -> homeTabUi
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
package monster.scoop.android.app.circuit

interface ScreenFactory
import monster.scoop.xplat.feat.homeTab.api.HomeTab

interface ScreenFactory {
fun homeTab(): HomeTab
}

Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
package monster.scoop.android.app.di

import com.apollographql.apollo3.ApolloClient
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.ui.Ui
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
import monster.scoop.android.app.circuit.ScoopPresenterFactory
import monster.scoop.android.app.circuit.ScoopScreenFactory
import monster.scoop.android.app.circuit.ScoopUiFactory
import monster.scoop.android.app.circuit.ScreenFactory
import monster.scoop.android.app.market.MutableScoopMarket
import monster.scoop.android.app.market.RealMutableScoopMarket
import monster.scoop.android.app.market.RealScoopDispatcher
import monster.scoop.android.app.market.RootReducer
import monster.scoop.android.app.market.reducers.storiesReducer
import monster.scoop.xplat.common.market.ScoopDispatcher
import monster.scoop.xplat.domain.story.api.StoriesPager
import monster.scoop.xplat.domain.story.impl.StoriesPagerFactory
import monster.scoop.xplat.feat.homeTab.api.HomeTab
import monster.scoop.xplat.feat.homeTab.impl.HomeTabDataLayer
import monster.scoop.xplat.feat.homeTab.impl.RealHomeTab
import monster.scoop.xplat.feat.homeTab.impl.RealHomeTabDataLayer
import monster.scoop.xplat.foundation.di.Singleton
import monster.scoop.xplat.foundation.di.UserScope
import monster.scoop.xplat.foundation.networking.api.NetworkingClient
import monster.scoop.xplat.foundation.networking.impl.Env
import monster.scoop.xplat.foundation.networking.impl.RealNetworkingClient
import org.mobilenativefoundation.market.combineReducers

@UserScope
@Component
Expand All @@ -27,5 +47,52 @@ abstract class CoreComponent {
@Provides
fun bindScreenFactory(impl: ScoopScreenFactory): ScreenFactory = impl

@Provides
fun provideCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.IO

@Provides
fun provideApolloClient(): ApolloClient = ApolloClient.Builder()
.serverUrl(Env.API_URL)
.addHttpHeader(Env.API_KEY, Env.API_SECRET)
.build()

@Provides
fun bindNetworkingClient(impl: RealNetworkingClient): NetworkingClient = impl

@Provides
@UserScope
@Singleton
fun bindScoopMarket(impl: RealMutableScoopMarket): MutableScoopMarket = impl

@Provides
fun provideRootReducer(): RootReducer {
return combineReducers(
storiesReducer
)
}

@Provides
fun bindScoopDispatcher(impl: RealScoopDispatcher): ScoopDispatcher = impl

@Provides
@UserScope
@Singleton
fun provideStoriesPager(
factory: StoriesPagerFactory
): StoriesPager = factory.create()

@Provides
@UserScope
@Singleton
fun provideHomeTabDataLayer(
coroutineDispatcher: CoroutineDispatcher,
pager: StoriesPager
): HomeTabDataLayer {
return RealHomeTabDataLayer(coroutineDispatcher, pager)
}

@Provides
fun provideHomeTab(): HomeTab = RealHomeTab

companion object
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package monster.scoop.android.app.market.reducers

import monster.scoop.xplat.common.market.ScoopAction
import monster.scoop.xplat.common.market.ScoopState
import org.mobilenativefoundation.market.Market

val storiesReducer : Market.Reducer<ScoopState, ScoopAction> = Market.Reducer {state, action ->
if (action is ScoopAction.Stories) {
when (action) {
is ScoopAction.Stories.NormalizeStory -> state // TODO
is ScoopAction.Stories.Paging.NormalizeAll -> state // TODO
is ScoopAction.Stories.Paging.SetData -> state // TODO
is ScoopAction.Stories.Paging.UpdateData -> state // TODO
is ScoopAction.Stories.PushStories -> state // TODO
is ScoopAction.Stories.Paging.SetError -> state // TODO
is ScoopAction.Stories.Paging.SetInitial -> state // TODO
is ScoopAction.Stories.Paging.SetLoading -> state // TODO
is ScoopAction.Stories.SetStories -> state // TODO
is ScoopAction.Stories.SetStory -> state // TODO
is ScoopAction.Stories.UpdateStoryDataStatus -> state // TODO
}
} else {
state
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,14 @@ import org.mobilenativefoundation.store.store5.Store
import org.mobilenativefoundation.store.store5.StoreBuilder
import org.mobilenativefoundation.storex.paging.*

typealias StoriesPager = Pager<Int, StoriesPagingKey, Story, StoriesError>
typealias StoriesNetworkPager = Pager<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesNetworkPagerBuilder = PagerBuilder<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesNetworkPlaceholderFactory = PlaceholderFactory<Int, NetworkStory>


typealias StoriesPagingState = StoreX.Paging.State<Int, StoriesPagingKey, Story, StoriesError>
typealias StoriesPagerBuilder = PagerBuilder<Int, StoriesPagingKey, Story, StoriesError>
typealias StoriesPlaceholderFactory = PlaceholderFactory<Int, Story>
typealias StoriesPager = Pager<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPagerBuilder = PagerBuilder<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPlaceholderFactory = PlaceholderFactory<Int, NetworkStory>
typealias StoriesKeyFactory = KeyFactory<Int, StoriesPagingKey>
typealias StoriesPagingSourceData = PagingSource.LoadResult.Data<Int, StoriesPagingKey, Story, StoriesError>
typealias StoriesNetworkPagingSourceData = PagingSource.LoadResult.Data<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesNetworkPagingSourceLoadResult = PagingSource.LoadResult<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPageStore = Store<StoriesPagingKey, StoriesPagingSourceData>
typealias StoriesNetworkPageStore = Store<StoriesPagingKey, StoriesNetworkPagingSourceData>
typealias StoriesNetworkPageStoreBuilder = StoreBuilder<StoriesPagingKey, StoriesNetworkPagingSourceData>
typealias StoriesPageStoreBuilder = StoreBuilder<StoriesPagingKey, StoriesPagingSourceData>


typealias StoriesItemStore = Store<Int, Story>
typealias StoriesNetworkItemStore = Store<Int, NetworkStory>

typealias StoriesItemStoreBuilder = StoreBuilder<Int, Story>
typealias StoriesNetworkItemStoreBuilder = StoreBuilder<Int, NetworkStory>
typealias StoriesFetchingStrategy = FetchingStrategy<Int, StoriesPagingKey, Story, StoriesError>
typealias StoriesNetworkFetchingStrategy = FetchingStrategy<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPagingSourceLoadResultData = PagingSource.LoadResult.Data<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPagingSourceLoadResult = PagingSource.LoadResult<Int, StoriesPagingKey, NetworkStory, StoriesError>
typealias StoriesPageStore = Store<StoriesPagingKey, StoriesPagingSourceLoadResultData>
typealias StoriesPageStoreBuilder = StoreBuilder<StoriesPagingKey, StoriesPagingSourceLoadResultData>
typealias StoriesItemStore = Store<Int, NetworkStory>
typealias StoriesItemStoreBuilder = StoreBuilder<Int, NetworkStory>
typealias StoriesFetchingStrategy = FetchingStrategy<Int, StoriesPagingKey, NetworkStory, StoriesError>
Loading

0 comments on commit 8ba33b5

Please sign in to comment.