Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add WarehouseFactory
Browse files Browse the repository at this point in the history
matt-ramotar committed May 3, 2024
1 parent 3b2f61f commit dd6458c
Showing 21 changed files with 398 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.mobilenativefoundation.market.warehouse

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.warehouse.impl.RealWarehouse


interface Warehouse<S : Warehouse.State, A : Warehouse.Action> {
@@ -21,26 +22,16 @@ interface Warehouse<S : Warehouse.State, A : Warehouse.Action> {
fun interface Selector<S : State, D : Any> {
fun select(state: S): D
}
}

class RealWarehouse<S : Warehouse.State, A : Warehouse.Action, MS : Market.State, M : Market<MS>>(
coroutineDispatcher: CoroutineDispatcher,
private val market: M,
private val selector: Market.Selector<MS, S>,
private val actionHandler: (A, MS) -> Unit
) : Warehouse<S, A> {

private val coroutineScope = CoroutineScope(coroutineDispatcher)

override val state: StateFlow<S> = market.state.map {
selector.select(it)
}.stateIn(coroutineScope, SharingStarted.Eagerly, selector.select(market.state.value))


override fun <D : Any> subscribe(selector: Warehouse.Selector<S, D>): Flow<D> =
state.map { selector.select(it) }

override fun dispatch(action: A) {
actionHandler(action, market.state.value)
companion object {
fun <S : State, A : Action, MS : Market.State, M : Market<MS>> from(
coroutineDispatcher: CoroutineDispatcher,
market: M,
selector: Market.Selector<MS, S>,
actionHandler: (A, MS) -> Unit
): Warehouse<S, A> {
return RealWarehouse(coroutineDispatcher, market, selector, actionHandler)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.mobilenativefoundation.market.warehouse

import kotlinx.coroutines.CoroutineDispatcher
import org.mobilenativefoundation.market.Market

class WarehouseFactory<S : Warehouse.State, A : Warehouse.Action, MS : Market.State, M : Market<MS>>(
private val market: M,
) {
fun create(
actionHandler: (A, MS) -> Unit,
selector: Market.Selector<MS, S>,
coroutineDispatcher: CoroutineDispatcher
): Warehouse<S, A> {
return Warehouse.from(
coroutineDispatcher, market, selector, actionHandler
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.mobilenativefoundation.market.warehouse.impl

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.market.warehouse.Warehouse


internal class RealWarehouse<S : Warehouse.State, A : Warehouse.Action, MS : Market.State, M : Market<MS>>(
coroutineDispatcher: CoroutineDispatcher,
private val market: M,
private val selector: Market.Selector<MS, S>,
private val actionHandler: (A, MS) -> Unit
) : Warehouse<S, A> {

private val coroutineScope = CoroutineScope(coroutineDispatcher)

override val state: StateFlow<S> = market.state.map {
selector.select(it)
}.stateIn(coroutineScope, SharingStarted.Eagerly, selector.select(market.state.value))


override fun <D : Any> subscribe(selector: Warehouse.Selector<S, D>): Flow<D> =
state.map { selector.select(it) }

override fun dispatch(action: A) {
actionHandler(action, market.state.value)
}
}
5 changes: 5 additions & 0 deletions experimental/sample/scoop/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -43,6 +43,11 @@ dependencies {
implementation(libs.ktor.negotiation)

implementation(libs.compose.webview.multiplatform)

implementation(project(":experimental:market"))
implementation(project(":experimental:market:warehouse"))
implementation(project(":experimental:sample:scoop:xplat:foundation:di"))
implementation(project(":experimental:sample:scoop:xplat:common:market"))
}

ksp {
Original file line number Diff line number Diff line change
@@ -3,20 +3,33 @@ package monster.scoop.android.app
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.ExperimentalMaterial3Api
import coil3.annotation.ExperimentalCoilApi
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.foundation.CircuitCompositionLocals
import me.tatarka.inject.annotations.Inject
import monster.scoop.android.app.di.CoreComponent
import monster.scoop.android.app.di.create


@OptIn(ExperimentalCoilApi::class, ExperimentalMaterial3Api::class)
@Inject
class MainActivity : ComponentActivity() {

private val coreComponent: CoreComponent by lazy {
CoreComponent.create()
}

private val circuit: Circuit by lazy {
Circuit.Builder()
.addPresenterFactory(coreComponent.presenterFactory)
.addUiFactory(coreComponent.uiFactory)
.build()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
CircuitCompositionLocals(circuit) {
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package monster.scoop.android.app.circuit

import com.slack.circuit.runtime.CircuitContext
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.foundation.di.UserScope

@Inject
@UserScope
class ScoopPresenterFactory : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
else -> null
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package monster.scoop.android.app.circuit

import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.foundation.di.UserScope

@Inject
@UserScope
class ScoopScreenFactory : ScreenFactory {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package monster.scoop.android.app.circuit

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.foundation.di.UserScope

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

interface ScreenFactory

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

import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.ui.Ui
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.xplat.foundation.di.UserScope

@UserScope
@Component
abstract class CoreComponent {

abstract val presenterFactory: Presenter.Factory
abstract val screenFactory: ScreenFactory
abstract val uiFactory: Ui.Factory

@Provides
fun bindPresenterFactory(impl: ScoopPresenterFactory): Presenter.Factory = impl

@Provides
fun bindUiFactory(impl: ScoopUiFactory): Ui.Factory = impl

@Provides
fun bindScreenFactory(impl: ScoopScreenFactory): ScreenFactory = impl

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

import monster.scoop.xplat.common.market.ScoopState
import org.mobilenativefoundation.market.MutableMarket


typealias MutableScoopMarket = MutableMarket<ScoopState>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package monster.scoop.android.app.market

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.common.market.ScoopState

@Inject
class RealMutableScoopMarket : MutableScoopMarket {
private val _state = MutableStateFlow(ScoopState())

override val state: StateFlow<ScoopState> = _state.asStateFlow()

override fun updateState(nextState: ScoopState) {
_state.value = nextState
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package monster.scoop.android.app.market

import me.tatarka.inject.annotations.Inject
import monster.scoop.xplat.common.market.ScoopAction
import monster.scoop.xplat.common.market.ScoopDispatcher

@Inject
class RealScoopDispatcher(
private val mutableMarket: MutableScoopMarket,
private val rootReducer: RootReducer
) : ScoopDispatcher {
override fun dispatch(action: ScoopAction) {
val nextState = rootReducer.reduce(mutableMarket.state.value, action)
mutableMarket.updateState(nextState)
}
}





Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package monster.scoop.android.app.market

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

typealias RootReducer = Market.Reducer<ScoopState, ScoopAction>
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package monster.scoop.android.app.theme


import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import monster.scoop.android.app.R

@Composable
fun ScoopTheme(
content: @Composable () -> Unit
) {
val darkColorScheme = darkColorScheme(
primary = Color(0xFFFFA726),
onPrimary = Color.Black,
secondary = Color(0xFF6D4C41),
onSecondary = Color.White,
background = Color(0xFF010101),
onBackground = Color.White,
surface = Color(0xFF25262B),
onSurface = Color.White,
error = Color(0xFFCF6679),
onError = Color.White
)

MaterialTheme(
colorScheme = darkColorScheme,
content = content,
typography = ScoopTypography
)
}

val scoopFontFamily = FontFamily(
Font(R.font.roboto_regular),
Font(R.font.roboto_medium, FontWeight.Medium),
Font(R.font.roboto_thin, FontWeight.Thin),
Font(R.font.roboto_bold, FontWeight.Bold),
Font(R.font.roboto_italic, FontWeight.Normal, FontStyle.Italic)
)

val ScoopTypography = Typography(
displayLarge = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp
),
displayMedium = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 45.sp,
lineHeight = 52.sp
),
displaySmall = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 36.sp,
lineHeight = 44.sp
),
headlineLarge = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Thin,
fontSize = 32.sp,
lineHeight = 40.sp
),
headlineMedium = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Thin,
fontSize = 28.sp,
lineHeight = 36.sp
),
headlineSmall = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Thin,
fontSize = 24.sp,
lineHeight = 32.sp
),
titleLarge = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp
),
titleMedium = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
lineHeight = 24.sp
),
titleSmall = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp
),
bodyLarge = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp
),
bodyMedium = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp
),
bodySmall = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 16.sp
),
labelLarge = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
labelMedium = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontFamily = scoopFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package monster.scoop.xplat.common.market

import org.mobilenativefoundation.market.Market

sealed interface ScoopAction : Market.Action
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package monster.scoop.xplat.common.market

import org.mobilenativefoundation.market.Market

typealias ScoopDispatcher = Market.Dispatcher<ScoopAction>
Original file line number Diff line number Diff line change
@@ -2,4 +2,4 @@ package monster.scoop.xplat.common.market

import org.mobilenativefoundation.market.Market

typealias ScoopMarket = Market<ScoopMarketState>
typealias ScoopMarket = Market<ScoopState>
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ package monster.scoop.xplat.common.market
import monster.scoop.xplat.domain.story.api.StoriesState
import org.mobilenativefoundation.market.Market

data class ScoopMarketState(
val stories: StoriesState
data class ScoopState(
val stories: StoriesState = StoriesState()
) : Market.State


Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package monster.scoop.xplat.common.market

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.mobilenativefoundation.market.Market
import org.mobilenativefoundation.store.store5.Store
import org.mobilenativefoundation.store.store5.impl.extensions.fresh


fun <A : Market.Action, D : Market.Dispatcher<A>, K : Any, V : Any> Store<K, V>.contribute(
key: K,
marketDispatcher: D,
coroutineDispatcher: CoroutineDispatcher,
actionFactory: (V) -> A
) {

val coroutineScope = CoroutineScope(coroutineDispatcher)
coroutineScope.launch {
val response = fresh(key)
val marketAction = actionFactory(response)
marketDispatcher.dispatch(marketAction)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package monster.scoop.xplat.domain.story.api

data class StoriesState(
val byId: Map<Int, Story>,
val allIds: List<Int>
val byId: Map<Int, Story> = mapOf(),
val allIds: List<Int> = listOf()
)

0 comments on commit dd6458c

Please sign in to comment.