Skip to content

Commit

Permalink
Merge pull request #237 from joreilly/circuit_updates
Browse files Browse the repository at this point in the history
Circuit updates
  • Loading branch information
joreilly authored Oct 5, 2024
2 parents 489c9c8 + b384d5c commit 9b84d75
Show file tree
Hide file tree
Showing 20 changed files with 264 additions and 274 deletions.
2 changes: 1 addition & 1 deletion androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test:runner:1.6.2")

implementation(project(":common"))
implementation(projects.common)
}


Expand Down
8 changes: 8 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ kotlin {
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.components.resources)
implementation(libs.compose.adaptive)
implementation(libs.compose.adaptive.layout)
}

androidMain.dependencies {
implementation(libs.ktor.client.android)
// workaround for https://youtrack.jetbrains.com/issue/CMP-5959/Invalid-redirect-in-window-core#focus=Comments-27-10365630.0-0
implementation("androidx.window:window-core:1.3.0")
}

appleMain.dependencies {
Expand Down Expand Up @@ -135,3 +139,7 @@ room {
schemaDirectory("$projectDir/schemas")
}


configurations.configureEach {
exclude("androidx.window.core", "window-core")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,12 @@ import android.app.Application
import android.content.Context
import androidx.room.Room
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import com.slack.circuit.foundation.Circuit
import dev.johnoreilly.common.database.AppDatabase
import dev.johnoreilly.common.database.dbFileName
import dev.johnoreilly.common.screens.CountryListPresenter
import dev.johnoreilly.common.screens.CountryListScreen
import dev.johnoreilly.common.screens.NetworkListPresenter
import dev.johnoreilly.common.screens.NetworkListScreen
import dev.johnoreilly.common.screens.StationListPresenter
import dev.johnoreilly.common.screens.StationListScreen
import dev.johnoreilly.common.ui.BikeShareContent
import dev.johnoreilly.common.ui.CountryListUi
import dev.johnoreilly.common.ui.NetworkListUi
import dev.johnoreilly.common.ui.StationListUI
import io.ktor.client.engine.android.Android
import kotlinx.coroutines.Dispatchers
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides


@Component
Expand All @@ -29,16 +18,6 @@ abstract class AndroidApplicationComponent(val application: Application): Shared

abstract val bikeShareContent: BikeShareContent

@Provides
fun provideCircuit(): Circuit = Circuit.Builder()
.addPresenterFactory(CountryListPresenter.Factory(repository))
.addPresenterFactory(NetworkListPresenter.Factory(repository))
.addPresenterFactory(StationListPresenter.Factory(repository))
.addUi<CountryListScreen, CountryListScreen.State> { state, modifier -> CountryListUi(state, modifier) }
.addUi<NetworkListScreen, NetworkListScreen.State> { state, modifier -> NetworkListUi(state, modifier) }
.addUi<StationListScreen, StationListScreen.State> { state, modifier -> StationListUI(state, modifier) }
.build()

override fun getHttpClientEngine() = Android.create()

override fun getRoomDatabase() = createRoomDatabase(application)
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dev.johnoreilly.common.countrylist

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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 dev.johnoreilly.common.screens.CountryListScreen
import dev.johnoreilly.common.screens.NetworkListScreen
import dev.johnoreilly.common.getCountryName
import dev.johnoreilly.common.repository.CityBikesRepository
import dev.johnoreilly.common.viewmodel.Country
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject


@Inject
class CountryListPresenterFactory(
private val presenterFactory: (Navigator) -> CountryListPresenter,
) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
CountryListScreen -> presenterFactory(navigator)
else -> null
}
}
}

@Inject
class CountryListPresenter(
@Assisted private val navigator: Navigator,
private val cityBikesRepository: CityBikesRepository
) : Presenter<CountryListScreen.State> {
@Composable
override fun present(): CountryListScreen.State {
val groupedNetworkList by cityBikesRepository.groupedNetworkList.collectAsState()
val countryCodeList = groupedNetworkList.keys.toList()
val countryList = countryCodeList.map { countryCode -> Country(countryCode, getCountryName(countryCode)) }
.sortedBy { it.displayName }
return CountryListScreen.State(countryList) { event ->
when (event) {
is CountryListScreen.Event.CountryClicked -> navigator.goTo(NetworkListScreen(event.countryCode))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package dev.johnoreilly.common.ui
package dev.johnoreilly.common.countrylist

import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
@Target(CLASS, FUNCTION, PROPERTY_GETTER)
annotation class Singleton

interface SharedApplicationComponent {
interface SharedApplicationComponent: SharedUiComponent {

val countriesViewModel: CountriesViewModelShared
val networksViewModel: NetworksViewModelShared
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dev.johnoreilly.common.di

import com.slack.circuit.foundation.Circuit
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.ui.Ui
import dev.johnoreilly.common.screens.CountryListUiFactory
import dev.johnoreilly.common.screens.NetworkListUiFactory
import dev.johnoreilly.common.screens.StationListUiFactory
import dev.johnoreilly.common.countrylist.CountryListPresenterFactory
import dev.johnoreilly.common.networklist.NetworkListPresenterFactory
import dev.johnoreilly.common.stationlist.StationListPresenterFactory
import me.tatarka.inject.annotations.IntoSet
import me.tatarka.inject.annotations.Provides

interface SharedUiComponent {
@IntoSet
@Provides
fun bindCountryListPresenterFactory(factory: CountryListPresenterFactory): Presenter.Factory = factory

@IntoSet
@Provides
fun bindCountryListUiFactory(factory: CountryListUiFactory): Ui.Factory = factory

@IntoSet
@Provides
fun bindNetworkListPresenterFactory(factory: NetworkListPresenterFactory): Presenter.Factory = factory

@IntoSet
@Provides
fun bindNetworkListUiFactory(factory: NetworkListUiFactory): Ui.Factory = factory

@IntoSet
@Provides
fun bindStationListPresenterFactory(factory: StationListPresenterFactory): Presenter.Factory = factory

@IntoSet
@Provides
fun bindStationListUiFactory(factory: StationListUiFactory): Ui.Factory = factory

@Provides
fun provideCircuit(
uiFactories: Set<Ui.Factory>,
presenterFactories: Set<Presenter.Factory>
): Circuit = Circuit.Builder()
.addUiFactories(uiFactories)
.addPresenterFactories(presenterFactories)
.build()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dev.johnoreilly.common.networklist

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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 dev.johnoreilly.common.screens.NetworkListScreen
import dev.johnoreilly.common.screens.StationListScreen
import dev.johnoreilly.common.getCountryName
import dev.johnoreilly.common.repository.CityBikesRepository
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject

@Inject
class NetworkListPresenterFactory(
private val presenterFactory: (NetworkListScreen, Navigator) -> NetworkListPresenter
) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
is NetworkListScreen -> presenterFactory(screen, navigator)
else -> null
}
}
}

@Inject
class NetworkListPresenter(
@Assisted private val screen: NetworkListScreen,
@Assisted private val navigator: Navigator,
private val cityBikesRepository: CityBikesRepository
) : Presenter<NetworkListScreen.State> {
@Composable
override fun present(): NetworkListScreen.State {
val groupedNetworkList by cityBikesRepository.groupedNetworkList.collectAsState()
val networkList = groupedNetworkList[screen.countryCode]?.sortedBy { it.city } ?: emptyList()
val oountryName = getCountryName(screen.countryCode)
return NetworkListScreen.State(screen.countryCode, oountryName, networkList) { event ->
when (event) {
is NetworkListScreen.Event.NetworkClicked -> navigator.goTo(StationListScreen(event.networkId))
NetworkListScreen.Event.BackClicked -> navigator.pop()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package dev.johnoreilly.common.ui
package dev.johnoreilly.common.networklist

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
Expand Down
104 changes: 30 additions & 74 deletions common/src/commonMain/kotlin/dev/johnoreilly/common/screens/Screens.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package dev.johnoreilly.common.screens

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.slack.circuit.runtime.CircuitContext
import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.screen.Screen
import dev.johnoreilly.common.getCountryName
import com.slack.circuit.runtime.ui.Ui
import com.slack.circuit.runtime.ui.ui
import dev.johnoreilly.common.model.Network
import dev.johnoreilly.common.remote.Station
import dev.johnoreilly.common.repository.CityBikesRepository
import dev.johnoreilly.common.countrylist.CountryListUi
import dev.johnoreilly.common.networklist.NetworkListUi
import dev.johnoreilly.common.stationlist.StationListUI
import dev.johnoreilly.common.viewmodel.Country
import me.tatarka.inject.annotations.Inject


@Target(AnnotationTarget.CLASS)
Expand Down Expand Up @@ -62,87 +61,44 @@ data class StationListScreen(val networkId: String) : Screen {
}


// Presenters (TODO: where should we put these?)

class CountryListPresenter(
private val navigator: Navigator,
private val cityBikesRepository: CityBikesRepository
) : Presenter<CountryListScreen.State> {
@Composable
override fun present(): CountryListScreen.State {
val groupedNetworkList by cityBikesRepository.groupedNetworkList.collectAsState()
val countryCodeList = groupedNetworkList.keys.toList()
val countryList = countryCodeList.map { countryCode -> Country(countryCode, getCountryName(countryCode)) }
.sortedBy { it.displayName }
return CountryListScreen.State(countryList) { event ->
when (event) {
is CountryListScreen.Event.CountryClicked -> navigator.goTo(NetworkListScreen(event.countryCode))
}
}
}

class Factory(private val repository: CityBikesRepository) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
CountryListScreen -> return CountryListPresenter(navigator, repository)
else -> null
}
}
}
}

// TODO move these somewhere else

class NetworkListPresenter(
private val screen: NetworkListScreen,
private val navigator: Navigator,
private val cityBikesRepository: CityBikesRepository
) : Presenter<NetworkListScreen.State> {
@Composable
override fun present(): NetworkListScreen.State {
val groupedNetworkList by cityBikesRepository.groupedNetworkList.collectAsState()
val countryList = groupedNetworkList[screen.countryCode]?.sortedBy { it.city } ?: emptyList()
val oountryName = getCountryName(screen.countryCode)
return NetworkListScreen.State(screen.countryCode, oountryName, countryList) { event ->
when (event) {
is NetworkListScreen.Event.NetworkClicked -> navigator.goTo(StationListScreen(event.networkId))
NetworkListScreen.Event.BackClicked -> navigator.pop()
}
}
}

class Factory(private val repository: CityBikesRepository) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
is NetworkListScreen -> return NetworkListPresenter(screen, navigator, repository)
else -> null
@Inject
class CountryListUiFactory : Ui.Factory {
override fun create(screen: Screen, context: CircuitContext): Ui<*>? = when (screen) {
is CountryListScreen -> {
ui<CountryListScreen.State> { state, modifier ->
CountryListUi(state, modifier)
}
}
else -> null
}
}


class StationListPresenter(
private val screen: StationListScreen,
private val navigator: Navigator,
private val cityBikesRepository: CityBikesRepository
) : Presenter<StationListScreen.State> {
@Composable
override fun present(): StationListScreen.State {
val stationList by cityBikesRepository.pollNetworkUpdates(screen.networkId).collectAsState(emptyList())
return StationListScreen.State(screen.networkId, stationList) { event ->
when (event) {
StationListScreen.Event.BackClicked -> navigator.pop()
@Inject
class NetworkListUiFactory : Ui.Factory {
override fun create(screen: Screen, context: CircuitContext): Ui<*>? = when (screen) {
is NetworkListScreen -> {
ui<NetworkListScreen.State> { state, modifier ->
NetworkListUi(state, modifier)
}
}
else -> null
}
}

class Factory(private val repository: CityBikesRepository) : Presenter.Factory {
override fun create(screen: Screen, navigator: Navigator, context: CircuitContext): Presenter<*>? {
return when (screen) {
is StationListScreen -> return StationListPresenter(screen, navigator, repository)
else -> null
@Inject
class StationListUiFactory : Ui.Factory {
override fun create(screen: Screen, context: CircuitContext): Ui<*>? = when (screen) {
is StationListScreen -> {
ui<StationListScreen.State> { state, modifier ->
StationListUI(state, modifier)
}
}
else -> null
}
}


Loading

0 comments on commit 9b84d75

Please sign in to comment.