From e3ccd99c5a238d5d01941c722e11a57b3de19efb Mon Sep 17 00:00:00 2001 From: "Victor B." Date: Mon, 13 Jan 2025 21:33:13 +0100 Subject: [PATCH 1/4] initial parcel list work --- app/build.gradle.kts | 3 + .../dev/itsvic/parceltracker/MainActivity.kt | 249 +++++++++--------- .../parceltracker/ParcelListDetailRoute.kt | 70 +++++ gradle/libs.versions.toml | 4 + 4 files changed, 202 insertions(+), 124 deletions(-) create mode 100644 app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 070fd01..7779c1b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -101,6 +101,9 @@ dependencies { implementation(libs.work.runtime) implementation(libs.work.runtime.ktx) implementation(libs.kotlinx.coroutines.guava) + implementation(libs.androidx.adaptive) + implementation(libs.androidx.adaptive.layout) + implementation(libs.androidx.adaptive.navigation) implementation(libs.logging.interceptor) implementation(libs.androidx.browser) diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index 518b8e4..8190bac 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -131,8 +131,8 @@ object HomePage @Serializable object SettingsPage -@Serializable -data class ParcelPage(val parcelDbId: Int) +//@Serializable +//data class ParcelPage(val parcelDbId: Int) @Serializable object AddParcelPage @@ -149,11 +149,11 @@ fun ParcelAppNavigation(parcelToOpen: Int) { val demoMode by context.dataStore.data.map { it[DEMO_MODE] == true }.collectAsState(false) LaunchedEffect(parcelToOpen) { - if (parcelToOpen != -1) { - navController.navigate(route = ParcelPage(parcelToOpen)) { - popUpTo(HomePage) - } - } +// if (parcelToOpen != -1) { +// navController.navigate(route = ParcelPage(parcelToOpen)) { +// popUpTo(HomePage) +// } +// } } val animDuration = 300 @@ -183,17 +183,18 @@ fun ParcelAppNavigation(parcelToOpen: Int) { }, ) { composable { - val parcels = if (demoMode) - derivedStateOf { demoModeParcels } - else - db.parcelDao().getAllWithStatus().collectAsState(initial = emptyList()) - - HomeView( - parcels = parcels.value, - onNavigateToAddParcel = { navController.navigate(route = AddParcelPage) }, - onNavigateToParcel = { navController.navigate(route = ParcelPage(it.id)) }, - onNavigateToSettings = { navController.navigate(route = SettingsPage) }, - ) +// val parcels = if (demoMode) +// derivedStateOf { demoModeParcels } +// else +// db.parcelDao().getAllWithStatus().collectAsState(initial = emptyList()) +// +// HomeView( +// parcels = parcels.value, +// onNavigateToAddParcel = { navController.navigate(route = AddParcelPage) }, +// onNavigateToParcel = { navController.navigate(route = ParcelPage(it.id)) }, +// onNavigateToSettings = { navController.navigate(route = SettingsPage) }, +// ) + ParcelListDetailRoute() } composable { @@ -202,107 +203,107 @@ fun ParcelAppNavigation(parcelToOpen: Int) { ) } - composable { backStackEntry -> - val route: ParcelPage = backStackEntry.toRoute() - val parcelWithStatus: ParcelWithStatus? by if (demoMode) - derivedStateOf { demoModeParcels[route.parcelDbId] } - else - db.parcelDao().getWithStatusById(route.parcelDbId).collectAsState(null) - var apiParcel: APIParcel? by remember { mutableStateOf(null) } - - val dbParcel = parcelWithStatus?.parcel - - LaunchedEffect(parcelWithStatus) { - if (dbParcel != null) { - launch(Dispatchers.IO) { - try { - apiParcel = getParcel( - dbParcel.parcelId, - dbParcel.postalCode, - dbParcel.service - ) - - if (!demoMode) { - // update parcel status - val zone = ZoneId.systemDefault() - val lastChange = - apiParcel!!.history.first().time.atZone(zone).toInstant() - val status = ParcelStatus( - dbParcel.id, - apiParcel!!.currentStatus, - lastChange, - ) - if (parcelWithStatus?.status == null) { - db.parcelStatusDao().insert(status) - } else { - db.parcelStatusDao().update(status) - } - } - } catch (e: IOException) { - Log.w("MainActivity", "Failed fetch: $e") - apiParcel = APIParcel( - dbParcel.parcelId, - listOf( - ParcelHistoryItem( - context.getString(R.string.network_failure_detail), - LocalDateTime.now(), - "" - ) - ), - Status.NetworkFailure - ) - } catch (_: ParcelNonExistentException) { - apiParcel = APIParcel( - dbParcel.parcelId, - listOf( - ParcelHistoryItem( - context.getString(R.string.parcel_doesnt_exist_detail), - LocalDateTime.now(), - "" - ) - ), - Status.NoData - ) - } - } - } - } - - if (apiParcel == null || dbParcel == null) - Box( - modifier = Modifier - .background(color = MaterialTheme.colorScheme.background) - .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator() - } - else - ParcelView( - apiParcel!!, - dbParcel.humanName, - dbParcel.service, - onBackPressed = { navController.popBackStack() }, - onEdit = { navController.navigate(EditParcelPage(dbParcel.id)) }, - onDelete = { - if (demoMode) { - Toast.makeText( - context, - context.getString(R.string.demo_mode_action_block), - Toast.LENGTH_SHORT - ).show() - return@ParcelView - } - - scope.launch(Dispatchers.IO) { - db.parcelDao().delete(dbParcel) - scope.launch { - navController.popBackStack(HomePage, false) - } - } - }, - ) - } +// composable { backStackEntry -> +// val route: ParcelPage = backStackEntry.toRoute() +// val parcelWithStatus: ParcelWithStatus? by if (demoMode) +// derivedStateOf { demoModeParcels[route.parcelDbId] } +// else +// db.parcelDao().getWithStatusById(route.parcelDbId).collectAsState(null) +// var apiParcel: APIParcel? by remember { mutableStateOf(null) } +// +// val dbParcel = parcelWithStatus?.parcel +// +// LaunchedEffect(parcelWithStatus) { +// if (dbParcel != null) { +// launch(Dispatchers.IO) { +// try { +// apiParcel = getParcel( +// dbParcel.parcelId, +// dbParcel.postalCode, +// dbParcel.service +// ) +// +// if (!demoMode) { +// // update parcel status +// val zone = ZoneId.systemDefault() +// val lastChange = +// apiParcel!!.history.first().time.atZone(zone).toInstant() +// val status = ParcelStatus( +// dbParcel.id, +// apiParcel!!.currentStatus, +// lastChange, +// ) +// if (parcelWithStatus?.status == null) { +// db.parcelStatusDao().insert(status) +// } else { +// db.parcelStatusDao().update(status) +// } +// } +// } catch (e: IOException) { +// Log.w("MainActivity", "Failed fetch: $e") +// apiParcel = APIParcel( +// dbParcel.parcelId, +// listOf( +// ParcelHistoryItem( +// context.getString(R.string.network_failure_detail), +// LocalDateTime.now(), +// "" +// ) +// ), +// Status.NetworkFailure +// ) +// } catch (e: ParcelNonExistentException) { +// apiParcel = APIParcel( +// dbParcel.parcelId, +// listOf( +// ParcelHistoryItem( +// context.getString(R.string.parcel_doesnt_exist_detail), +// LocalDateTime.now(), +// "" +// ) +// ), +// Status.NoData +// ) +// } +// } +// } +// } +// +// if (apiParcel == null || dbParcel == null) +// Box( +// modifier = Modifier +// .background(color = MaterialTheme.colorScheme.background) +// .fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// CircularProgressIndicator() +// } +// else +// ParcelView( +// apiParcel!!, +// dbParcel.humanName, +// dbParcel.service, +// onBackPressed = { navController.popBackStack() }, +// onEdit = { navController.navigate(EditParcelPage(dbParcel.id)) }, +// onDelete = { +// if (demoMode) { +// Toast.makeText( +// context, +// context.getString(R.string.demo_mode_action_block), +// Toast.LENGTH_SHORT +// ).show() +// return@ParcelView +// } +// +// scope.launch(Dispatchers.IO) { +// db.parcelDao().delete(dbParcel) +// scope.launch { +// navController.popBackStack(HomePage, false) +// } +// } +// }, +// ) +// } composable { AddEditParcelView( @@ -320,11 +321,11 @@ fun ParcelAppNavigation(parcelToOpen: Int) { scope.launch(Dispatchers.IO) { val id = db.parcelDao().insert(it) - scope.launch { - navController.navigate(route = ParcelPage(id.toInt())) { - popUpTo(HomePage) - } - } +// scope.launch { +// navController.navigate(route = ParcelPage(id.toInt())) { +// popUpTo(HomePage) +// } +// } } }, ) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt new file mode 100644 index 0000000..f27c446 --- /dev/null +++ b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt @@ -0,0 +1,70 @@ +package dev.itsvic.parceltracker + +import android.os.Parcelable +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import dev.itsvic.parceltracker.db.demoModeParcels +import dev.itsvic.parceltracker.ui.views.HomeView +import kotlinx.parcelize.Parcelize + +@Parcelize +class ParcelInfo(val id: Int): Parcelable + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +fun ParcelListDetailRoute( + demoMode: Boolean = true, +) { + val db = ParcelApplication.db + val navigator = rememberListDetailPaneScaffoldNavigator() + val parcels = if (demoMode) + remember { derivedStateOf { demoModeParcels } } + else + db.parcelDao().getAllWithStatus().collectAsState(emptyList()) + + BackHandler(enabled = navigator.canNavigateBack()) { + navigator.navigateBack() + } + + ListDetailPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + AnimatedPane { + HomeView( + parcels = parcels.value, + onNavigateToAddParcel = {}, + onNavigateToSettings = {}, + onNavigateToParcel = { + navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, ParcelInfo(it.id)) + } + ) + } + }, + detailPane = { + AnimatedPane { + navigator.currentDestination?.content?.let { + Scaffold { innerPadding -> + Column(modifier = Modifier.padding(innerPadding)) { + Text("detail pane: ${it.id}") + } + } + } + } + } + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f006e07..6e4bef5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ work = "2.10.0" kotlinxCoroutinesGuava = "1.10.1" material3 = "1.4.0-alpha09" browser = "1.8.0" +adaptive = "1.0.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -57,6 +58,9 @@ work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" } logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" } +androidx-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" } +androidx-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "adaptive" } +androidx-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "adaptive" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 882e4a3401671d70db1508152ae7da0c19f42b84 Mon Sep 17 00:00:00 2001 From: "Victor B." Date: Mon, 13 Jan 2025 21:54:11 +0100 Subject: [PATCH 2/4] more work, actually opens but i doubt this will work with real parcels --- .../parceltracker/ParcelListDetailRoute.kt | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt index f27c446..3ea7c71 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt @@ -2,11 +2,11 @@ package dev.itsvic.parceltracker import android.os.Parcelable import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold @@ -15,14 +15,22 @@ import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaf import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import dev.itsvic.parceltracker.api.Parcel +import dev.itsvic.parceltracker.api.getParcel +import dev.itsvic.parceltracker.db.ParcelWithStatus import dev.itsvic.parceltracker.db.demoModeParcels import dev.itsvic.parceltracker.ui.views.HomeView +import dev.itsvic.parceltracker.ui.views.ParcelView +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import kotlinx.parcelize.Parcelize @Parcelize -class ParcelInfo(val id: Int): Parcelable +class ParcelInfo(val id: Int) : Parcelable @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable @@ -31,11 +39,6 @@ fun ParcelListDetailRoute( ) { val db = ParcelApplication.db val navigator = rememberListDetailPaneScaffoldNavigator() - val parcels = if (demoMode) - remember { derivedStateOf { demoModeParcels } } - else - db.parcelDao().getAllWithStatus().collectAsState(emptyList()) - BackHandler(enabled = navigator.canNavigateBack()) { navigator.navigateBack() } @@ -45,8 +48,13 @@ fun ParcelListDetailRoute( value = navigator.scaffoldValue, listPane = { AnimatedPane { + val parcels: List by if (demoMode) + remember { derivedStateOf { demoModeParcels } } + else + db.parcelDao().getAllWithStatus().collectAsState(emptyList()) + HomeView( - parcels = parcels.value, + parcels = parcels, onNavigateToAddParcel = {}, onNavigateToSettings = {}, onNavigateToParcel = { @@ -55,16 +63,43 @@ fun ParcelListDetailRoute( ) } }, + detailPane = { AnimatedPane { - navigator.currentDestination?.content?.let { - Scaffold { innerPadding -> - Column(modifier = Modifier.padding(innerPadding)) { - Text("detail pane: ${it.id}") + navigator.currentDestination?.content?.let { parcelInfo -> + val parcelWithStatus: ParcelWithStatus? by + if (demoMode) + derivedStateOf { demoModeParcels[parcelInfo.id] } + else + db.parcelDao().getWithStatusById(parcelInfo.id).collectAsState(null) + val dbParcel = parcelWithStatus?.parcel + val apiParcel = dbParcel?.let { getParcelFlow(it).collectAsState(null) } + + if (apiParcel?.value == null) + Box( + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) + { + CircularProgressIndicator() } - } + else + ParcelView( + apiParcel.value!!, + dbParcel.humanName, + dbParcel.service, + onBackPressed = {}, + onEdit = {}, + onDelete = {}, + ) } } } ) } + +fun getParcelFlow(parcel: dev.itsvic.parceltracker.db.Parcel): Flow = flow { + emit(getParcel(parcel.parcelId, parcel.postalCode, parcel.service)) +} From fb1e760dfbb731d283e1323a7bb60ecfaae4b399 Mon Sep 17 00:00:00 2001 From: "Victor B." Date: Wed, 12 Mar 2025 23:17:13 +0100 Subject: [PATCH 3/4] fucking google. and it STILL doesn't work right --- app/build.gradle.kts | 1 + .../dev/itsvic/parceltracker/MainActivity.kt | 23 ++-- .../parceltracker/ParcelListDetailRoute.kt | 102 +++++++++++++++--- .../parceltracker/ui/views/ParcelView.kt | 9 +- gradle/libs.versions.toml | 2 +- 5 files changed, 104 insertions(+), 33 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7779c1b..afe9eff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(libs.plugins.room) kotlin("plugin.serialization") version "2.0.21" + id("kotlin-parcelize") } val versionMajor = 1 diff --git a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt index 8190bac..60c5811 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/MainActivity.kt @@ -31,10 +31,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -43,27 +40,15 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute -import dev.itsvic.parceltracker.api.ParcelHistoryItem -import dev.itsvic.parceltracker.api.ParcelNonExistentException -import dev.itsvic.parceltracker.api.Status -import dev.itsvic.parceltracker.api.getParcel import dev.itsvic.parceltracker.db.Parcel -import dev.itsvic.parceltracker.db.ParcelStatus -import dev.itsvic.parceltracker.db.ParcelWithStatus -import dev.itsvic.parceltracker.api.Parcel as APIParcel import dev.itsvic.parceltracker.db.demoModeParcels import dev.itsvic.parceltracker.ui.theme.ParcelTrackerTheme import dev.itsvic.parceltracker.ui.views.AddEditParcelView -import dev.itsvic.parceltracker.ui.views.HomeView -import dev.itsvic.parceltracker.ui.views.ParcelView import dev.itsvic.parceltracker.ui.views.SettingsView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import okio.IOException -import java.time.LocalDateTime -import java.time.ZoneId class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -194,7 +179,11 @@ fun ParcelAppNavigation(parcelToOpen: Int) { // onNavigateToParcel = { navController.navigate(route = ParcelPage(it.id)) }, // onNavigateToSettings = { navController.navigate(route = SettingsPage) }, // ) - ParcelListDetailRoute() + ParcelListDetailRoute( + demoMode = demoMode, + onNavigateToSettings = { navController.navigate(route = SettingsPage) }, + onNavigateToAddParcel = { navController.navigate(route = AddParcelPage) }, + ) } composable { @@ -320,7 +309,7 @@ fun ParcelAppNavigation(parcelToOpen: Int) { } scope.launch(Dispatchers.IO) { - val id = db.parcelDao().insert(it) +// val id = db.parcelDao().insert(it) // scope.launch { // navController.navigate(route = ParcelPage(id.toInt())) { // popUpTo(HomePage) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt index 3ea7c71..90af421 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt @@ -1,7 +1,16 @@ package dev.itsvic.parceltracker +import android.content.Context import android.os.Parcelable +import android.util.Log import androidx.activity.compose.BackHandler +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -17,9 +26,14 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import dev.itsvic.parceltracker.api.Parcel +import dev.itsvic.parceltracker.api.ParcelHistoryItem +import dev.itsvic.parceltracker.api.ParcelNonExistentException +import dev.itsvic.parceltracker.api.Status import dev.itsvic.parceltracker.api.getParcel import dev.itsvic.parceltracker.db.ParcelWithStatus import dev.itsvic.parceltracker.db.demoModeParcels @@ -27,7 +41,10 @@ import dev.itsvic.parceltracker.ui.views.HomeView import dev.itsvic.parceltracker.ui.views.ParcelView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import okio.IOException +import java.time.LocalDateTime @Parcelize class ParcelInfo(val id: Int) : Parcelable @@ -36,18 +53,30 @@ class ParcelInfo(val id: Int) : Parcelable @Composable fun ParcelListDetailRoute( demoMode: Boolean = true, + onNavigateToSettings: () -> Unit, + onNavigateToAddParcel: () -> Unit, ) { + val context = LocalContext.current val db = ParcelApplication.db val navigator = rememberListDetailPaneScaffoldNavigator() - BackHandler(enabled = navigator.canNavigateBack()) { - navigator.navigateBack() + val scope = rememberCoroutineScope() + BackHandler(navigator.canNavigateBack()) { + scope.launch { + navigator.navigateBack() + } } ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, listPane = { - AnimatedPane { + AnimatedPane( + enterTransition = fadeIn(tween(300)) + scaleIn(tween(500), 0.9f), + exitTransition = slideOutHorizontally( + tween(300), + targetOffsetX = { -it / 4 } + ) + fadeOut(tween(300)) + ) { val parcels: List by if (demoMode) remember { derivedStateOf { demoModeParcels } } else @@ -55,25 +84,36 @@ fun ParcelListDetailRoute( HomeView( parcels = parcels, - onNavigateToAddParcel = {}, - onNavigateToSettings = {}, + onNavigateToAddParcel = onNavigateToAddParcel, + onNavigateToSettings = onNavigateToSettings, onNavigateToParcel = { - navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, ParcelInfo(it.id)) + scope.launch { + navigator.navigateTo( + ListDetailPaneScaffoldRole.Detail, + ParcelInfo(it.id) + ) + } } ) } }, detailPane = { - AnimatedPane { - navigator.currentDestination?.content?.let { parcelInfo -> + AnimatedPane( + enterTransition = slideInHorizontally( + tween(300), + initialOffsetX = { it / 4 } + ) + fadeIn(tween(300)), + exitTransition = fadeOut(tween(300)) + scaleOut(tween(500), 0.9f), + ) { + navigator.currentDestination?.contentKey?.let { parcelInfo -> val parcelWithStatus: ParcelWithStatus? by if (demoMode) derivedStateOf { demoModeParcels[parcelInfo.id] } else db.parcelDao().getWithStatusById(parcelInfo.id).collectAsState(null) val dbParcel = parcelWithStatus?.parcel - val apiParcel = dbParcel?.let { getParcelFlow(it).collectAsState(null) } + val apiParcel = dbParcel?.let { context.getParcelFlow(it).collectAsState(null) } if (apiParcel?.value == null) Box( @@ -90,7 +130,14 @@ fun ParcelListDetailRoute( apiParcel.value!!, dbParcel.humanName, dbParcel.service, - onBackPressed = {}, + canBack = navigator.canNavigateBack(), + onBackPressed = { + if (navigator.canNavigateBack()) { + scope.launch { + navigator.navigateBack() + } + } + }, onEdit = {}, onDelete = {}, ) @@ -100,6 +147,37 @@ fun ParcelListDetailRoute( ) } -fun getParcelFlow(parcel: dev.itsvic.parceltracker.db.Parcel): Flow = flow { - emit(getParcel(parcel.parcelId, parcel.postalCode, parcel.service)) +fun Context.getParcelFlow(parcel: dev.itsvic.parceltracker.db.Parcel): Flow = flow { + try { + emit(getParcel(parcel.parcelId, parcel.postalCode, parcel.service)) + } catch (e: IOException) { + Log.w("ParcelListDetailRoute", "Failed fetch: $e") + emit( + Parcel( + parcel.parcelId, + listOf( + ParcelHistoryItem( + getString(R.string.network_failure_detail), + LocalDateTime.now(), + "" + ) + ), + Status.NetworkFailure + ) + ) + } catch (_: ParcelNonExistentException) { + emit( + Parcel( + parcel.parcelId, + listOf( + ParcelHistoryItem( + getString(R.string.parcel_doesnt_exist_detail), + LocalDateTime.now(), + "" + ) + ), + Status.NoData + ) + ) + } } diff --git a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt index e80d9cc..45169ec 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ui/views/ParcelView.kt @@ -50,6 +50,7 @@ fun ParcelView( parcel: Parcel, humanName: String, service: Service, + canBack: Boolean, onBackPressed: () -> Unit, onEdit: () -> Unit, onDelete: () -> Unit, @@ -64,9 +65,10 @@ fun ParcelView( Text(humanName) }, navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, "Go back") - } + if (canBack) + IconButton(onClick = onBackPressed) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, "Go back") + } }, actions = { IconButton(onClick = { expanded = !expanded }) { @@ -177,6 +179,7 @@ private fun ParcelViewPreview() { parcel, "My precious package", Service.EXAMPLE, + canBack = true, onBackPressed = {}, onEdit = {}, onDelete = {}, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e4bef5..18cafbb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ work = "2.10.0" kotlinxCoroutinesGuava = "1.10.1" material3 = "1.4.0-alpha09" browser = "1.8.0" -adaptive = "1.0.0" +adaptive = "1.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } From c38676c59a705256d6acef4a5e18ecb5105567bb Mon Sep 17 00:00:00 2001 From: "Victor B." Date: Wed, 12 Mar 2025 23:31:46 +0100 Subject: [PATCH 4/4] im not touching this until material adaptive library updates --- .../itsvic/parceltracker/ParcelListDetailRoute.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt index 90af421..22b2d97 100644 --- a/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt +++ b/app/src/main/java/dev/itsvic/parceltracker/ParcelListDetailRoute.kt @@ -3,7 +3,6 @@ package dev.itsvic.parceltracker import android.content.Context import android.os.Parcelable import android.util.Log -import androidx.activity.compose.BackHandler import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -18,8 +17,8 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.layout.AnimatedPane -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -60,15 +59,9 @@ fun ParcelListDetailRoute( val db = ParcelApplication.db val navigator = rememberListDetailPaneScaffoldNavigator() val scope = rememberCoroutineScope() - BackHandler(navigator.canNavigateBack()) { - scope.launch { - navigator.navigateBack() - } - } - ListDetailPaneScaffold( - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, + NavigableListDetailPaneScaffold( + navigator = navigator, listPane = { AnimatedPane( enterTransition = fadeIn(tween(300)) + scaleIn(tween(500), 0.9f), @@ -104,7 +97,6 @@ fun ParcelListDetailRoute( tween(300), initialOffsetX = { it / 4 } ) + fadeIn(tween(300)), - exitTransition = fadeOut(tween(300)) + scaleOut(tween(500), 0.9f), ) { navigator.currentDestination?.contentKey?.let { parcelInfo -> val parcelWithStatus: ParcelWithStatus? by