Skip to content

Commit

Permalink
Initial attempt to use Jetpack Navigation in shared KMP code
Browse files Browse the repository at this point in the history
  • Loading branch information
joreilly committed Jul 5, 2024
1 parent 26ade4c commit ad17379
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 6 deletions.
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ kotlin {
implementation(libs.voyager)

implementation(libs.kmpObservableViewModel)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)

implementation(libs.koalaplot)
implementation(libs.treemap.chart)
Expand Down
220 changes: 218 additions & 2 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,236 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import cafe.adriel.voyager.navigator.Navigator
import dev.johnoreilly.climatetrace.di.commonModule
import dev.johnoreilly.climatetrace.remote.Country
import dev.johnoreilly.climatetrace.ui.ClimateTraceScreen
import dev.johnoreilly.climatetrace.ui.CountryAssetEmissionsInfoTreeMapChart
import dev.johnoreilly.climatetrace.ui.CountryListView
import dev.johnoreilly.climatetrace.ui.SectorEmissionsPieChart
import dev.johnoreilly.climatetrace.ui.YearSelector
import dev.johnoreilly.climatetrace.ui.toPercent
import dev.johnoreilly.climatetrace.viewmodel.CountryDetailsUIState
import dev.johnoreilly.climatetrace.viewmodel.CountryDetailsViewModel
import dev.johnoreilly.climatetrace.viewmodel.CountryListUIState
import dev.johnoreilly.climatetrace.viewmodel.CountryListViewModel
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.KoinApplication
import org.koin.compose.koinInject


@Preview
@Composable
fun App() {
fun AppVoyagerNav() {
KoinApplication(application = {
modules(commonModule())
}) {
MaterialTheme {
Navigator(screen = ClimateTraceScreen())
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppJetpackBav() {
KoinApplication(application = {
modules(commonModule())
}) {
MaterialTheme {
val navController = rememberNavController()

NavHost(
navController = navController,
startDestination = "countryList",
) {

composable(route = "countryList") {
CountryListScreenJetpackNav { country ->
navController.navigate(country)
}
}
composable<Country> { backStackEntry ->
val country: Country = backStackEntry.toRoute()
CountryInfoDetailedViewJetpackNav(country, popBack = { navController.popBackStack() })
}
}
}
}
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CountryListScreenJetpackNav(countrySelected: (country: Country) -> Unit) {
val viewModel = koinInject<CountryListViewModel>()
val viewState by viewModel.viewState.collectAsState()

Scaffold(
topBar = {
CenterAlignedTopAppBar(title = {
Text("ClimateTraceKMP")
}
)
}
) {
Column(Modifier.padding(it)) {
when (val state = viewState) {
is CountryListUIState.Loading -> {
Column(
modifier = Modifier.fillMaxSize().fillMaxHeight()
.wrapContentSize(Alignment.Center)
) {
CircularProgressIndicator()
}
}

is CountryListUIState.Error -> {}
is CountryListUIState.Success -> {
CountryListView(state.countryList, null, countrySelected)
}
}
}
}
}


@Composable
fun CountryInfoDetailedViewJetpackNav(
country: Country,
popBack: () -> Unit
) {
val countryDetailsViewModel: CountryDetailsViewModel = koinInject()
val countryDetailsViewState by countryDetailsViewModel.viewState.collectAsState()

LaunchedEffect(country) {
countryDetailsViewModel.setCountry(country)
}

val viewState = countryDetailsViewState
when (viewState) {
CountryDetailsUIState.NoCountrySelected -> {
Column(
modifier = Modifier.fillMaxSize()
.wrapContentSize(Alignment.Center)
) {
Text(text = "No Country Selected.", style = MaterialTheme.typography.titleMedium)
}
}
is CountryDetailsUIState.Loading -> {
Column(
modifier = Modifier.fillMaxSize()
.wrapContentSize(Alignment.Center)
) {
CircularProgressIndicator()
}
}
is CountryDetailsUIState.Error -> { Text("Error") }
is CountryDetailsUIState.Success -> {
CountryInfoDetailedViewSuccessJetpackNav(viewState, popBack) {
countryDetailsViewModel.setYear(it)
}
}
}
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CountryInfoDetailedViewSuccessJetpackNav(viewState: CountryDetailsUIState.Success, popBack: () -> Unit, onYearSelected: (String) -> Unit) {

Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = { Text(viewState.country.name) },
navigationIcon = {
IconButton(onClick = { popBack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
}
)
}
) {

Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {

Text(
text = viewState.country.name,
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center
)

Spacer(modifier = Modifier.size(16.dp))

val year = viewState.year
val countryAssetEmissionsList = viewState.countryAssetEmissionsList
val countryEmissionInfo = viewState.countryEmissionInfo

YearSelector(year, onYearSelected)
countryEmissionInfo?.let {
val co2 = (countryEmissionInfo.emissions.co2 / 1_000_000).toInt()
val percentage =
(countryEmissionInfo.emissions.co2 / countryEmissionInfo.worldEmissions.co2).toPercent(
2
)

Text(text = "co2 = $co2 Million Tonnes ($year)")
Text(text = "rank = ${countryEmissionInfo.rank} ($percentage)")

Spacer(modifier = Modifier.size(16.dp))

val filteredCountryAssetEmissionsList =
countryAssetEmissionsList.filter { it.sector != null }
if (filteredCountryAssetEmissionsList.isNotEmpty()) {
SectorEmissionsPieChart(countryAssetEmissionsList)
Spacer(modifier = Modifier.size(32.dp))
CountryAssetEmissionsInfoTreeMapChart(countryAssetEmissionsList)
} else {
Spacer(modifier = Modifier.size(16.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
"Invalid data",
style = MaterialTheme.typography.titleMedium.copy(color = Color.Red),
textAlign = TextAlign.Center
)
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions composeApp/src/desktopMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import androidx.compose.ui.window.application

fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "ClimateTraceKMP") {
App()
AppJetpackBav()
}
}

@Preview
@Composable
fun AppDesktopPreview() {
App()
AppJetpackBav()
}

2 changes: 1 addition & 1 deletion composeApp/src/wasmJsMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import androidx.compose.ui.window.CanvasBasedWindow

@OptIn(ExperimentalComposeUiApi::class)
fun main() {
CanvasBasedWindow(canvasElementId = "ComposeTarget") { App() }
CanvasBasedWindow(canvasElementId = "ComposeTarget") { AppJetpackBav() }
}
10 changes: 9 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ android-minSdk = "24"
android-targetSdk = "34"
androidx-activityCompose = "1.9.0"
compose = "1.6.8"
compose-plugin = "1.6.11"
compose-plugin = "1.7.0-alpha01"
androidx-navigation = "2.8.0-alpha08"
androidx-lifecycle = "2.8.0"

composeWindowSize = "0.5.0"
harawata-appdirs = "1.2.2"
koalaplot = "0.5.3"
Expand All @@ -30,6 +33,11 @@ molecule = "2.0.0"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }

androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }


compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-window-size = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version.ref = "composeWindowSize" }
Expand Down
8 changes: 8 additions & 0 deletions local.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Apr 17 18:40:20 CEST 2024
sdk.dir=/Users/johnoreilly/Library/Android/sdk

0 comments on commit ad17379

Please sign in to comment.