From 204c9e0ac68c185f3fbb038446eb669868df6618 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 11:41:36 +0200 Subject: [PATCH 01/18] Refactor profiles settings --- .idea/copyright/profiles_settings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index 426357d0e..65104e8ae 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,3 +1,3 @@ - - \ No newline at end of file + + From 9d405febbcf6dd1f00954a893780042827a6e50e Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 11:50:23 +0200 Subject: [PATCH 02/18] refactor: Rename NavigationArgs to NavigationDestination --- ...gationArgs.kt => NavigationDestination.kt} | 42 +++++++++++++------ .../ui/navigation/NavigationItem.kt | 8 ++-- 2 files changed, 34 insertions(+), 16 deletions(-) rename app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/{NavigationArgs.kt => NavigationDestination.kt} (50%) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationArgs.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt similarity index 50% rename from app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationArgs.kt rename to app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt index 1a14cd8d0..88aff4607 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationArgs.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt @@ -18,31 +18,47 @@ package com.infomaniak.swisstransfer.ui.navigation +import kotlinx.serialization.Serializable + /** * Sealed class representing the navigation arguments for the main navigation flow. */ -sealed class MainNavigation private constructor() : NavigationArgs() { - data object Sent : MainNavigation() - data object Received : MainNavigation() - data class TransferDetails(val transferId: Int) : MainNavigation() +@Serializable +sealed class MainNavigation : NavigationDestination() { + @Serializable + data object SentDestination : MainNavigation() + @Serializable + data object ReceivedDestination : MainNavigation() + @Serializable + data class TransferDetailsDestination(val transferId: Int) : MainNavigation() - data object Settings : MainNavigation() + @Serializable + data object SettingsDestination : MainNavigation() } /** * Sealed class representing the navigation arguments for the new transfer flow. */ -sealed class NewTransferNavigation private constructor() : NavigationArgs() { - data object ImportFiles : NewTransferNavigation() - data object TransferType : NewTransferNavigation() - data object TransferOptions : NewTransferNavigation() - data object ValidateUserEmail : NewTransferNavigation() +@Serializable +sealed class NewTransferNavigation : NavigationDestination() { + @Serializable + data object ImportFilesDestination : NewTransferNavigation() + @Serializable + data object TransferTypeDestination : NewTransferNavigation() + @Serializable + data object TransferOptionsDestination : NewTransferNavigation() + @Serializable + data object ValidateUserEmailDestination : NewTransferNavigation() - data object UploadProgress : NewTransferNavigation() - data object UploadSuccess : NewTransferNavigation() + @Serializable + data object UploadProgressDestination : NewTransferNavigation() + @Serializable + data object UploadSuccessDestination : NewTransferNavigation() } /** * Sealed class representing navigation arguments with a title resource. */ -sealed class NavigationArgs +@Serializable +sealed class NavigationDestination { +} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt index 11bd8ba1f..459bb6bd4 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt @@ -27,6 +27,7 @@ import androidx.compose.material3.BottomAppBar import androidx.compose.material3.NavigationRail import androidx.compose.ui.graphics.vector.ImageVector import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.* /** * Enum class representing the different destinations in the app's [BottomAppBar] or [NavigationRail]. @@ -39,8 +40,9 @@ enum class NavigationItem( @StringRes val label: Int, val icon: ImageVector, @StringRes val contentDescription: Int, + val destination: MainNavigation, ) { - SENT(R.string.appName, Icons.AutoMirrored.Filled.Send, R.string.appName), - RECEIVED(R.string.appName, Icons.Default.Star, R.string.appName), - SETTINGS(R.string.appName, Icons.Default.Settings, R.string.appName), + SENT(R.string.appName, Icons.AutoMirrored.Filled.Send, R.string.appName, SentDestination), + RECEIVED(R.string.appName, Icons.Default.Star, R.string.appName, ReceivedDestination), + SETTINGS(R.string.appName, Icons.Default.Settings, R.string.appName, SettingsDestination), } From b7944ea1c944f49529318473ac6754e36db27f4b Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 11:52:59 +0200 Subject: [PATCH 03/18] core: Find a NavigationDestination from back stack entry --- .../ui/navigation/NavigationDestination.kt | 40 +++++++++++++++++++ gradle/libs.versions.toml | 2 + 2 files changed, 42 insertions(+) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt index 88aff4607..b174617fe 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt @@ -18,7 +18,10 @@ package com.infomaniak.swisstransfer.ui.navigation +import android.os.Bundle +import androidx.navigation.NavBackStackEntry import kotlinx.serialization.Serializable +import kotlin.reflect.KClass /** * Sealed class representing the navigation arguments for the main navigation flow. @@ -34,6 +37,12 @@ sealed class MainNavigation : NavigationDestination() { @Serializable data object SettingsDestination : MainNavigation() + + companion object { + fun fromRoute(backStackEntry: NavBackStackEntry?, startDestination: MainNavigation): MainNavigation { + return fromRoute(MainNavigation::class, backStackEntry, startDestination) + } + } } /** @@ -54,6 +63,12 @@ sealed class NewTransferNavigation : NavigationDestination() { data object UploadProgressDestination : NewTransferNavigation() @Serializable data object UploadSuccessDestination : NewTransferNavigation() + + companion object { + fun fromRoute(backStackEntry: NavBackStackEntry?, startDestination: NewTransferNavigation): NewTransferNavigation { + return fromRoute(NewTransferNavigation::class, backStackEntry, startDestination) + } + } } /** @@ -61,4 +76,29 @@ sealed class NewTransferNavigation : NavigationDestination() { */ @Serializable sealed class NavigationDestination { + companion object { + fun fromRoute( + kClass: KClass, + backStackEntry: NavBackStackEntry?, + startDestination: T, + ): T { + if (backStackEntry == null) return startDestination + val route = backStackEntry.destination.route ?: "" + val args = backStackEntry.arguments + val subclass = kClass.sealedSubclasses.firstOrNull { + route.contains(it.qualifiedName.toString()) + } + return subclass?.let { createInstance(it, args) } ?: startDestination + } + + private fun createInstance(kClass: KClass, bundle: Bundle?): T? { + val primaryConstructor = kClass.constructors.firstOrNull() + return primaryConstructor?.let { + val args = it.parameters.associateWith { parameter -> + bundle?.get(parameter.name) + } + it.callBy(args) + } ?: kClass.objectInstance + } + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad24acfa5..69ee46762 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ junitVersion = "1.2.1" kotlin = "2.0.0" lifecycleRuntimeKtx = "2.8.3" navigation = "2.8.0-beta05" +serialization = "1.7.1" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } @@ -19,6 +20,7 @@ compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } # Tests androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } From d2ada6abff31cd02e18ff3598427e30d5dc1b629 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 11:53:57 +0200 Subject: [PATCH 04/18] Add kotlinx serialization --- app/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4cf4ecc38..b5b1cf41d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,8 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.compose.compiler) + id("kotlin-parcelize") + kotlin("plugin.serialization") version libs.versions.kotlin } val sharedMinSdk: Int by rootProject.extra @@ -52,6 +54,7 @@ android { } dependencies { + implementation(kotlin("reflect")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) @@ -62,11 +65,15 @@ dependencies { implementation(libs.compose.ui) implementation(libs.compose.ui.graphics) implementation(libs.compose.material3) + implementation(libs.compose.material3.adaptative.navigation) implementation(libs.navigation.compose) // Compose preview tools implementation(libs.compose.ui.tooling.preview) debugImplementation(libs.compose.ui.tooling) + // Others + implementation(libs.kotlinx.serialization) + // Test testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) From 5f9bdb645461dbc755b89658b922c2848a002dfe Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 11:59:29 +0200 Subject: [PATCH 05/18] core: Add MainNavHost --- .../ui/screen/main/MainNavHost.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt new file mode 100644 index 000000000..718b641f5 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt @@ -0,0 +1,48 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.infomaniak.swisstransfer.ui.screen.main + +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.* + +@Composable +fun MainNavHost(navController: NavHostController) { + NavHost(navController, startDestination = SentDestination, modifier = Modifier.safeDrawingPadding()) { + composable { + Text("Sent") + } + composable { + Text("Received") + } + composable { + val transferDetails: TransferDetailsDestination = it.toRoute() + Text("TransferDetails for transfer ${transferDetails.transferId}") + } + composable { + Text("Settings") + } + } +} From bbeb840ddc573fbe6851806505a692c553190ace Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 12:00:54 +0200 Subject: [PATCH 06/18] core: Add adaptative MainScaffold --- .../ui/screen/main/MainScaffold.kt | 104 ++++++++++++++++++ gradle/libs.versions.toml | 2 + 2 files changed, 106 insertions(+) create mode 100644 app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt new file mode 100644 index 000000000..62889f0c0 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -0,0 +1,104 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.infomaniak.swisstransfer.ui.screen.main + +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType +import androidx.compose.runtime.* +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation +import com.infomaniak.swisstransfer.ui.navigation.NavigationItem + +@Composable +fun MainScaffold( + navController: NavHostController, + currentDestination: MainNavigation, + content: @Composable () -> Unit = {}, +) { + val adaptiveInfo by rememberUpdatedState(currentWindowAdaptiveInfo()) + + val showBottomBar by remember(currentDestination) { + derivedStateOf { + NavigationItem.entries.any { it.destination == currentDestination } + } + } + + val navType by remember(showBottomBar, adaptiveInfo) { + derivedStateOf { + NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) + } + } + + MainScaffold( + navType = navType, + currentDestination = currentDestination, + navController = navController, + content = content, + ) +} + +@Composable +private fun MainScaffold( + navType: NavigationSuiteType, + currentDestination: MainNavigation, + navController: NavHostController, + content: @Composable () -> Unit, +) { + NavigationSuiteScaffold( + navigationSuiteItems = { + NavigationItem.entries.forEach { navigationItem -> + item( + icon = { + Icon(navigationItem.icon, stringResource(navigationItem.contentDescription)) + }, + label = { + if (navType == NavigationSuiteType.NavigationBar) { + Text(stringResource(navigationItem.label)) + } + }, + selected = currentDestination == navigationItem.destination, + onClick = { navController.navigateToSelectedItem(navigationItem.destination) } + ) + } + }, + layoutType = navType, + content = content, + ) +} + +private fun NavHostController.navigateToSelectedItem(destination: MainNavigation) { + navigate(destination) { + // Pop up to the start destination of the graph to + // avoid building up a large stack of destinations + // on the back stack as users select items + popUpTo(this@navigateToSelectedItem.graph.findStartDestination().id) { + saveState = true + } + // Avoid multiple copies of the same destination when re-selecting the same item + launchSingleTop = true + // Restore state when re-selecting a previously selected item + restoreState = true + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 69ee46762..e23215d4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ junit = "4.13.2" junitVersion = "1.2.1" kotlin = "2.0.0" lifecycleRuntimeKtx = "2.8.3" +material3Beta = "1.3.0-beta04" navigation = "2.8.0-beta05" serialization = "1.7.1" @@ -16,6 +17,7 @@ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } compose-material3 = { group = "androidx.compose.material3", name = "material3" } +compose-material3-adaptative-navigation = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "material3Beta" } compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } From 4a00a1b28af8a4f80aa4affa3cf9885a86d0329c Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 12:02:12 +0200 Subject: [PATCH 07/18] Add main navigation in MainScreen --- .../infomaniak/swisstransfer/ui/MainScreen.kt | 27 ------- .../ui/screen/main/MainScreen.kt | 75 +++++++++++++++++++ 2 files changed, 75 insertions(+), 27 deletions(-) delete mode 100644 app/src/main/java/com/infomaniak/swisstransfer/ui/MainScreen.kt create mode 100644 app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainScreen.kt deleted file mode 100644 index 9d531e8fa..000000000 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainScreen.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Infomaniak SwissTransfer - Android - * Copyright (C) 2024 Infomaniak Network SA - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.infomaniak.swisstransfer.ui - -import androidx.compose.runtime.Composable -import androidx.navigation.compose.rememberNavController - -@Composable -fun MainScreen() { - val navController = rememberNavController() -} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt new file mode 100644 index 000000000..1ed88c30e --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -0,0 +1,75 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.infomaniak.swisstransfer.ui.screen.main + +import android.content.res.Configuration +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.SentDestination +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme + +@Composable +fun MainScreen() { + val navController = rememberNavController() + + val navBackStackEntry by navController.currentBackStackEntryAsState() + + val currentDestination by remember(navBackStackEntry) { + derivedStateOf { + Log.e("sisi", ">MainScreen: current destination recreated ${navBackStackEntry?.destination?.route}") + MainNavigation.fromRoute(navBackStackEntry, SentDestination) + } + } + + MainScaffold(navController, currentDestination) { + MainNavHost(navController) + } +} + +@Preview(name = "LightMode") +@Preview(name = "DarkMode", uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) +@Composable +private fun MainScreenPreview() { + SwissTransferTheme { + MainScreen() + } +} + +@Preview(device = "spec:parent=pixel_8_pro,orientation=landscape") +@Composable +private fun MainScreenPortraitPreview() { + SwissTransferTheme { + MainScreen() + } +} + +@Preview(device = "spec:width=1280dp,height=800dp,dpi=240,orientation=landscape") +@Composable +private fun MainScreenTabletPreview() { + SwissTransferTheme { + MainScreen() + } +} From 98e4008ad753ac51b910f615f048eb6e7684e240 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 12:02:54 +0200 Subject: [PATCH 08/18] Update MainActivity with MainScreen --- .../swisstransfer/ui/MainActivity.kt | 32 ++----------------- .../ui/screen/main/MainScreen.kt | 2 -- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt index ee1ec89df..fe719500a 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt @@ -23,16 +23,9 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import com.infomaniak.swisstransfer.ui.screen.main.MainScreen import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme class MainActivity : ComponentActivity() { @@ -42,35 +35,16 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { SwissTransferTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding) - ) - } + MainScreen() } } } } -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Column { - Text( - text = "Hello $name!", - modifier = modifier, - style = SwissTransferTheme.typography.h1 - ) - } -} - -@Preview(showBackground = true) @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) @Composable fun GreetingPreview() { SwissTransferTheme { - Surface(color = MaterialTheme.colorScheme.background, modifier = Modifier.fillMaxSize()) { - Greeting("Android") - } + MainScreen() } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt index 1ed88c30e..d4c4c8a06 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -19,7 +19,6 @@ package com.infomaniak.swisstransfer.ui.screen.main import android.content.res.Configuration -import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -39,7 +38,6 @@ fun MainScreen() { val currentDestination by remember(navBackStackEntry) { derivedStateOf { - Log.e("sisi", ">MainScreen: current destination recreated ${navBackStackEntry?.destination?.route}") MainNavigation.fromRoute(navBackStackEntry, SentDestination) } } From 2cae47074d195bb2738c84eb9a1a143740dc1f2a Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 14:56:29 +0200 Subject: [PATCH 09/18] Remove parcelize --- app/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b5b1cf41d..3f4d11f3a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.compose.compiler) - id("kotlin-parcelize") kotlin("plugin.serialization") version libs.versions.kotlin } From 5458cb0a3da6cc3cfaaae20cac3a4b925e71c824 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Wed, 24 Jul 2024 16:17:11 +0200 Subject: [PATCH 10/18] Add toDestination extension --- .../ui/navigation/NavigationDestination.kt | 36 ++++++++----------- .../ui/screen/main/MainScreen.kt | 3 +- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt index b174617fe..454c337dc 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt @@ -37,12 +37,6 @@ sealed class MainNavigation : NavigationDestination() { @Serializable data object SettingsDestination : MainNavigation() - - companion object { - fun fromRoute(backStackEntry: NavBackStackEntry?, startDestination: MainNavigation): MainNavigation { - return fromRoute(MainNavigation::class, backStackEntry, startDestination) - } - } } /** @@ -63,12 +57,6 @@ sealed class NewTransferNavigation : NavigationDestination() { data object UploadProgressDestination : NewTransferNavigation() @Serializable data object UploadSuccessDestination : NewTransferNavigation() - - companion object { - fun fromRoute(backStackEntry: NavBackStackEntry?, startDestination: NewTransferNavigation): NewTransferNavigation { - return fromRoute(NewTransferNavigation::class, backStackEntry, startDestination) - } - } } /** @@ -77,18 +65,22 @@ sealed class NewTransferNavigation : NavigationDestination() { @Serializable sealed class NavigationDestination { companion object { - fun fromRoute( - kClass: KClass, - backStackEntry: NavBackStackEntry?, - startDestination: T, - ): T { - if (backStackEntry == null) return startDestination - val route = backStackEntry.destination.route ?: "" - val args = backStackEntry.arguments - val subclass = kClass.sealedSubclasses.firstOrNull { + inline fun NavBackStackEntry.toDestination(): T? { + return toDestination(T::class, backStackEntry = this) + } + + fun toDestination(kClass: KClass, backStackEntry: NavBackStackEntry?): T? { + fun kClassFromRoute(route: String) = kClass.sealedSubclasses.firstOrNull { route.contains(it.qualifiedName.toString()) } - return subclass?.let { createInstance(it, args) } ?: startDestination + + if (backStackEntry == null) return null + + val route = backStackEntry.destination.route ?: "" + val args = backStackEntry.arguments + val subclass = kClassFromRoute(route) ?: return null + + return createInstance(subclass, args) } private fun createInstance(kClass: KClass, bundle: Bundle?): T? { diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt index d4c4c8a06..892ba22ed 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -28,6 +28,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.infomaniak.swisstransfer.ui.navigation.MainNavigation import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.SentDestination +import com.infomaniak.swisstransfer.ui.navigation.NavigationDestination.Companion.toDestination import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme @Composable @@ -38,7 +39,7 @@ fun MainScreen() { val currentDestination by remember(navBackStackEntry) { derivedStateOf { - MainNavigation.fromRoute(navBackStackEntry, SentDestination) + navBackStackEntry?.toDestination() ?: SentDestination } } From cc6d592ca8d0af5aa27da6f6526d2cfda3b8aa57 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Wed, 24 Jul 2024 16:45:57 +0200 Subject: [PATCH 11/18] Add missing code --- .../swisstransfer/ui/screen/main/MainScaffold.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt index 62889f0c0..97a57b88a 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -47,16 +47,13 @@ fun MainScaffold( val navType by remember(showBottomBar, adaptiveInfo) { derivedStateOf { - NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) + NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo).let { + if (it == NavigationSuiteType.NavigationBar && !showBottomBar) NavigationSuiteType.None else it + } } } - MainScaffold( - navType = navType, - currentDestination = currentDestination, - navController = navController, - content = content, - ) + MainScaffold(navType, currentDestination, navController, content) } @Composable From 584c6ede8b46cca6d6fd8bec7753c9c6ef84acee Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 09:19:59 +0200 Subject: [PATCH 12/18] Use a global startDestination --- .../swisstransfer/ui/navigation/NavigationDestination.kt | 2 ++ .../infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt | 4 ++-- .../infomaniak/swisstransfer/ui/screen/main/MainScreen.kt | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt index 454c337dc..bff19d956 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt @@ -64,7 +64,9 @@ sealed class NewTransferNavigation : NavigationDestination() { */ @Serializable sealed class NavigationDestination { + companion object { + inline fun NavBackStackEntry.toDestination(): T? { return toDestination(T::class, backStackEntry = this) } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt index 718b641f5..63606120c 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainNavHost.kt @@ -29,8 +29,8 @@ import androidx.navigation.toRoute import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.* @Composable -fun MainNavHost(navController: NavHostController) { - NavHost(navController, startDestination = SentDestination, modifier = Modifier.safeDrawingPadding()) { +fun MainNavHost(navController: NavHostController, startDestination: SentDestination) { + NavHost(navController, startDestination, modifier = Modifier.safeDrawingPadding()) { composable { Text("Sent") } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt index 892ba22ed..294e84500 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -34,17 +34,18 @@ import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme @Composable fun MainScreen() { val navController = rememberNavController() + val startDestination = SentDestination val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination by remember(navBackStackEntry) { derivedStateOf { - navBackStackEntry?.toDestination() ?: SentDestination + navBackStackEntry?.toDestination() ?: startDestination } } MainScaffold(navController, currentDestination) { - MainNavHost(navController) + MainNavHost(navController, startDestination) } } From 6cad343440422b6ce4302ca1abb4a7b2781f3e2b Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 09:22:01 +0200 Subject: [PATCH 13/18] Show or hide all different navigations if needed --- .../swisstransfer/ui/screen/main/MainScaffold.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt index 97a57b88a..8d840e49b 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -39,16 +39,19 @@ fun MainScaffold( ) { val adaptiveInfo by rememberUpdatedState(currentWindowAdaptiveInfo()) - val showBottomBar by remember(currentDestination) { + val showNavigation by remember(currentDestination) { derivedStateOf { - NavigationItem.entries.any { it.destination == currentDestination } + if (currentDestination == MainNavigation.SettingsDestination) false else + NavigationItem.entries.any { it.destination == currentDestination } } } - val navType by remember(showBottomBar, adaptiveInfo) { + val navType by remember(showNavigation, adaptiveInfo) { derivedStateOf { - NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo).let { - if (it == NavigationSuiteType.NavigationBar && !showBottomBar) NavigationSuiteType.None else it + if (showNavigation) { + NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo) + } else { + NavigationSuiteType.None } } } From c6baf7ad0ca5873897bd49bf32fe3e05fc5b7d1f Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 09:59:01 +0200 Subject: [PATCH 14/18] Update tablet preview --- .../com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt index 294e84500..eb92ddd2d 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -66,7 +66,7 @@ private fun MainScreenPortraitPreview() { } } -@Preview(device = "spec:width=1280dp,height=800dp,dpi=240,orientation=landscape") +@Preview(device = "spec:id=reference_tablet,shape=Normal,width=1280,height=800,unit=dp,dpi=240") @Composable private fun MainScreenTabletPreview() { SwissTransferTheme { From 29ed23488f347dbf0b15f6ec61f2ad009511a895 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 10:27:49 +0200 Subject: [PATCH 15/18] Remove the contentDescription from navigationItem --- .../swisstransfer/ui/navigation/NavigationItem.kt | 8 +++----- .../swisstransfer/ui/screen/main/MainScaffold.kt | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt index 459bb6bd4..e3b516772 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationItem.kt @@ -34,15 +34,13 @@ import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.* * * @property label The resource ID of the string label for the destination. * @property icon The icon to be displayed for the destination. - * @property contentDescription The resource ID of the content description for accessibility. */ enum class NavigationItem( @StringRes val label: Int, val icon: ImageVector, - @StringRes val contentDescription: Int, val destination: MainNavigation, ) { - SENT(R.string.appName, Icons.AutoMirrored.Filled.Send, R.string.appName, SentDestination), - RECEIVED(R.string.appName, Icons.Default.Star, R.string.appName, ReceivedDestination), - SETTINGS(R.string.appName, Icons.Default.Settings, R.string.appName, SettingsDestination), + SENT(R.string.appName, Icons.AutoMirrored.Filled.Send, SentDestination), + RECEIVED(R.string.appName, Icons.Default.Star, ReceivedDestination), + SETTINGS(R.string.appName, Icons.Default.Settings, SettingsDestination), } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt index 8d840e49b..a3f0cf790 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -71,7 +71,7 @@ private fun MainScaffold( NavigationItem.entries.forEach { navigationItem -> item( icon = { - Icon(navigationItem.icon, stringResource(navigationItem.contentDescription)) + Icon(navigationItem.icon, stringResource(navigationItem.label)) }, label = { if (navType == NavigationSuiteType.NavigationBar) { @@ -90,8 +90,7 @@ private fun MainScaffold( private fun NavHostController.navigateToSelectedItem(destination: MainNavigation) { navigate(destination) { - // Pop up to the start destination of the graph to - // avoid building up a large stack of destinations + // Pop up to the start destination of the graph to avoid building up a large stack of destinations // on the back stack as users select items popUpTo(this@navigateToSelectedItem.graph.findStartDestination().id) { saveState = true From e987791a9fef7d3c0049dd9dc82bbe77ec07ccc9 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 10:37:58 +0200 Subject: [PATCH 16/18] Only set the contentDescription on NavigationRail --- .../infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt index a3f0cf790..c0f901ebc 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -71,7 +71,11 @@ private fun MainScaffold( NavigationItem.entries.forEach { navigationItem -> item( icon = { - Icon(navigationItem.icon, stringResource(navigationItem.label)) + val contentDescription = when (navType) { + NavigationSuiteType.NavigationRail -> stringResource(navigationItem.label) + else -> "" + } + Icon(navigationItem.icon, contentDescription) }, label = { if (navType == NavigationSuiteType.NavigationBar) { From 2a6d2d303252a61361ce76808943e27712dca430 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 10:47:01 +0200 Subject: [PATCH 17/18] refactor: Create a sub compose function for navigation icon and label --- .../ui/screen/main/MainScaffold.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt index c0f901ebc..0143e3f34 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScaffold.kt @@ -66,22 +66,14 @@ private fun MainScaffold( navController: NavHostController, content: @Composable () -> Unit, ) { + val isNavigationBar = navType == NavigationSuiteType.NavigationBar + NavigationSuiteScaffold( navigationSuiteItems = { NavigationItem.entries.forEach { navigationItem -> item( - icon = { - val contentDescription = when (navType) { - NavigationSuiteType.NavigationRail -> stringResource(navigationItem.label) - else -> "" - } - Icon(navigationItem.icon, contentDescription) - }, - label = { - if (navType == NavigationSuiteType.NavigationBar) { - Text(stringResource(navigationItem.label)) - } - }, + icon = { NavigationIcon(isNavigationBar, navigationItem) }, + label = { NavigationLabel(isNavigationBar, navigationItem) }, selected = currentDestination == navigationItem.destination, onClick = { navController.navigateToSelectedItem(navigationItem.destination) } ) @@ -92,6 +84,19 @@ private fun MainScaffold( ) } +@Composable +private fun NavigationIcon(isNavigationBar: Boolean, navigationItem: NavigationItem) { + val contentDescription = if (isNavigationBar) null else stringResource(navigationItem.label) + Icon(navigationItem.icon, contentDescription) +} + +@Composable +private fun NavigationLabel(isNavigationBar: Boolean, navigationItem: NavigationItem) { + if (isNavigationBar) { + Text(stringResource(navigationItem.label)) + } +} + private fun NavHostController.navigateToSelectedItem(destination: MainNavigation) { navigate(destination) { // Pop up to the start destination of the graph to avoid building up a large stack of destinations From 52de92f5456a10d8a873ef1adbf48e79ff5621e9 Mon Sep 17 00:00:00 2001 From: Abdourahamane Boinaidi Date: Thu, 25 Jul 2024 10:53:57 +0200 Subject: [PATCH 18/18] Remove useless preview --- .../com/infomaniak/swisstransfer/ui/MainActivity.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt index fe719500a..a9e42cbb2 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/MainActivity.kt @@ -18,13 +18,10 @@ package com.infomaniak.swisstransfer.ui -import android.content.res.Configuration import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview import com.infomaniak.swisstransfer.ui.screen.main.MainScreen import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme @@ -40,11 +37,3 @@ class MainActivity : ComponentActivity() { } } } - -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) -@Composable -fun GreetingPreview() { - SwissTransferTheme { - MainScreen() - } -}