From 7ff2c82b2407a07aad0a2e8cbdf2d58b81857f6d Mon Sep 17 00:00:00 2001 From: Eric Emery <58563293+br-Emery@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:59:07 -0600 Subject: [PATCH] ASAA-159 - Move NavWrapper from projects to Launchpad Compose (#4) * ASAA-159 - Move NavWrapper from projects to Launchpad Compose * ASAA-159 - Run KtLint * ASAA-159 - Fixed KtLint to not remove trailing commas --- gradle/libs.versions.toml | 14 +- kmp-launchpad-compose/build.gradle.kts | 4 + .../navigation/NavigationWrapperExample.kt | 40 ++++++ .../compose/navigation/.editorconfig | 13 ++ .../navigation/AndroidNavigationWrapper.kt | 101 ++++++++++++++ .../navigation/util/CalculateDimensions.kt | 17 +++ .../util/CalculateWindowWidthSize.kt | 17 +++ .../navigation/util/DevicePostureUtils.kt | 60 +++++++++ .../navigation/util/WindowStateUtils.kt | 18 +++ .../widget/listdetail/AnimatedListDetail.kt | 2 +- .../compose/example/navigation/ExampleApp.kt | 40 ++++++ .../navigation/NavigationWrapperExample.kt | 33 +++++ .../compose/navigation/NavigationWrapper.kt | 109 +++++++++++++++ .../components/LaunchpadBottomAppBar.kt | 24 ++++ .../components/LaunchpadDrawerContent.kt | 10 ++ .../components/LaunchpadNavigationRail.kt | 13 ++ .../navigation/utils/GenerateNavItems.kt | 10 ++ .../navigation/utils/NavigationItem.kt | 119 +++++++++++++++++ .../navigation/utils/NavigationType.kt | 19 +++ .../navigation/utils/WindowStateUtils.kt | 125 ++++++++++++++++++ .../navigation/utils/WindowWidthSizeClass.kt | 50 +++++++ .../navigation/CalculateWindowWidthSize.kt | 10 ++ 22 files changed, 843 insertions(+), 5 deletions(-) create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/.editorconfig create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/AndroidNavigationWrapper.kt create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateDimensions.kt create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateWindowWidthSize.kt create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/DevicePostureUtils.kt create mode 100644 kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/WindowStateUtils.kt rename kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/{launchpadcompose => launchpad/compose}/widget/listdetail/AnimatedListDetail.kt (98%) create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/ExampleApp.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/NavigationWrapper.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadBottomAppBar.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadDrawerContent.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadNavigationRail.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/GenerateNavItems.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationItem.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationType.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowStateUtils.kt create mode 100644 kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowWidthSizeClass.kt create mode 100644 kmp-launchpad-compose/src/iosMain/kotlin/com/bottlerocketstudios/launchpad/compose/util/navigation/CalculateWindowWidthSize.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb7439a..83813c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,23 @@ [versions] -agp = "8.2.0" +agp = "8.2.1" +androidx-window = "1.2.0" +compose-plugin = "1.5.11" kotlin = "1.9.21" kt-lint-gradle = "11.6.1" -compose-plugin = "1.5.11" -navigation-compose = "2.7.5" +navigation-compose = "2.7.6" +precompose = "1.5.10" + compile-sdk = "34" min-sdk = "24" -launchpad-compose = "0.0.1" +launchpad-compose = "0.0.2" [libraries] androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } +androidx-window = { group = "androidx.window", name = "window", version.ref = "androidx-window" } +compose-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +precompose = { group = "moe.tlaster", name = "precompose", version.ref = "precompose" } [plugins] androidLibrary = { id = "com.android.library", version.ref = "agp" } diff --git a/kmp-launchpad-compose/build.gradle.kts b/kmp-launchpad-compose/build.gradle.kts index 85ae4c4..261a78b 100644 --- a/kmp-launchpad-compose/build.gradle.kts +++ b/kmp-launchpad-compose/build.gradle.kts @@ -36,13 +36,17 @@ kotlin { androidMain.dependencies { implementation(compose.material3) implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.window) + implementation(libs.compose.material3.window.size) } commonMain.dependencies { + api(libs.precompose) implementation(compose.animation) @OptIn(ExperimentalComposeLibrary::class) implementation(compose.components.resources) implementation(compose.foundation) implementation(compose.material3) + implementation(compose.materialIconsExtended) implementation(compose.runtime) implementation(compose.ui) } diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt new file mode 100644 index 0000000..d47ba25 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt @@ -0,0 +1,40 @@ +package com.bottlerocketstudios.launchpad.compose.example.navigation + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.collectAsState +import com.bottlerocketstudios.launchpad.compose.navigation.NavigationWrapper +import com.bottlerocketstudios.launchpad.compose.navigation.util.createDevicePostureFlow +import com.bottlerocketstudios.launchpad.compose.navigation.util.getWindowWidthSize +import moe.tlaster.precompose.PreComposeApp + +class MainActivityExample : ComponentActivity() { + + // Create a flow that emits the current device posture. + private val devicePostureFlow = createDevicePostureFlow() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + // Collects the device posture flow and stores it in a state variable. + val devicePosture = devicePostureFlow.collectAsState() + + PreComposeApp { + // In this example, the NavWrapper for the Precompose Navigation library is being used. + // The app param will extend the wrappers navigation component and bottom bar allowing both to be used in App Composable. + NavigationWrapper( + widthSize = getWindowWidthSize(this), + devicePosture = devicePosture.value, + navigationItems = exampleNavigationItems + ) { navigator, bottomBar -> + ExampleApp( + widthSize = getWindowWidthSize(this), + navigator = navigator, + bottomBar = bottomBar + ) + } + } + } + } +} diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/.editorconfig b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/.editorconfig new file mode 100644 index 0000000..abe7841 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/.editorconfig @@ -0,0 +1,13 @@ +[*.{kt,kts}] +# More info about .editorconfig at https://github.com/pinterest/ktlint#custom-editorconfig-properties +# possible values: number (e.g. 2) OR unset (no quotes - makes ktlint ignore indentation completely) +indent_size=4 +# true (recommended) / false +insert_final_newline=true +# possible values: number (e.g. 120) (package name, imports & comments are ignored) OR off (no quotes) +# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide) +# that is just not long enough IMO - using 200 for now +max_line_length=200 +# Makes git commit history look good and appending items to lists easier +ij_kotlin_allow_trailing_comma=true +ij_kotlin_allow_trailing_comma_on_call_site=true \ No newline at end of file diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/AndroidNavigationWrapper.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/AndroidNavigationWrapper.kt new file mode 100644 index 0000000..2698ba9 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/AndroidNavigationWrapper.kt @@ -0,0 +1,101 @@ +package com.bottlerocketstudios.launchpad.compose.navigation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.PermanentDrawerSheet +import androidx.compose.material3.PermanentNavigationDrawer +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadBottomAppBar +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadDrawerContent +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadNavigationRail +import com.bottlerocketstudios.launchpad.compose.navigation.utils.DevicePosture +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationType +import com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass +import com.bottlerocketstudios.launchpad.compose.navigation.utils.generateNavItems +import com.bottlerocketstudios.launchpad.compose.navigation.utils.toNavigationType + +/** + * A composable function that wraps the Androidx Compose Navigation component. + * * + * @param widthSize The current window width size class. + * @param devicePosture The current device posture. + * @param navigationItems The list of navigation items, these will be the items that show up on the navigation rail or bottom navigation bar. + * For an example of this please see [exampleNavigationItems] and the [NavigationItem] class. + * @param app The function that renders the app content. + */ +@Composable +fun AndroidNavigationWrapper( + widthSize: WindowWidthSizeClass, + devicePosture: DevicePosture, + navigationItems: List, + app: @Composable (navHostController: NavHostController?, bottomBar: (@Composable () -> Unit)) -> Unit, +) { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val navController = rememberNavController() + val currentBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = currentBackStackEntry?.destination?.route + val navItems = remember { + generateNavItems(navigationItems) { + navController.navigate(it.route) + } + } + val navigationType = remember(widthSize, devicePosture) { + derivedStateOf { + widthSize.toNavigationType(devicePosture) + } + } + + when (navigationType.value) { + NavigationType.PERMANENT_NAVIGATION_DRAWER -> PermanentNavigationDrawer( + drawerContent = { PermanentDrawerSheet { LaunchpadDrawerContent(navItems) { it == currentRoute } } }, + ) { app(navController) { } } + + NavigationType.MODAL_NAVIGATION -> ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { ModalDrawerSheet { LaunchpadDrawerContent(navItems) { it == currentRoute } } }, + ) { app(navController) { } } + + else -> Row { + AnimatedVisibility( + visible = navigationType.value == NavigationType.NAVIGATION_RAIL, + enter = slideInVertically(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + exit = slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + ) { + LaunchpadNavigationRail(navItems) { it == currentRoute } + } + + Column( + modifier = Modifier + .fillMaxSize(), + ) { + app(navController) { + AnimatedVisibility( + visible = navigationType.value == NavigationType.BOTTOM_NAVIGATION, + enter = slideInVertically(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + exit = slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + ) { + LaunchpadBottomAppBar(navItems) { it == currentRoute } + } + } + } + } + } +} diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateDimensions.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateDimensions.kt new file mode 100644 index 0000000..47522d0 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateDimensions.kt @@ -0,0 +1,17 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.util + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import com.bottlerocketstudios.launchpad.compose.resources.Dimensions +import com.bottlerocketstudios.launchpad.compose.resources.SMALL_SCREEN_WIDTH_DP +import com.bottlerocketstudios.launchpad.compose.resources.smallDimensions +import com.bottlerocketstudios.launchpad.compose.resources.sw360Dimensions + +/** + * Returns the dimensions of the screen. + * + * If the screen width is less than or equal to 360dp, the small dimensions are returned. + * Otherwise, the sw360 dimensions are returned. + */ +@Composable +fun getDimensions(): Dimensions = if (LocalConfiguration.current.screenWidthDp <= SMALL_SCREEN_WIDTH_DP) smallDimensions else sw360Dimensions diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateWindowWidthSize.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateWindowWidthSize.kt new file mode 100644 index 0000000..4527d41 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/CalculateWindowWidthSize.kt @@ -0,0 +1,17 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.util + +import android.app.Activity +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass + +/** + * Gets the window width size class of the given activity. + * + * @param activity The activity to get the window width size class for. + * @return The window width size class of the activity. + */ +@Composable +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +fun getWindowWidthSize(activity: Activity): WindowWidthSizeClass = calculateWindowSizeClass(activity).widthSizeClass.toLocalModel() diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/DevicePostureUtils.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/DevicePostureUtils.kt new file mode 100644 index 0000000..50651e2 --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/DevicePostureUtils.kt @@ -0,0 +1,60 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.util + +import androidx.activity.ComponentActivity +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import androidx.window.layout.WindowInfoTracker +import com.bottlerocketstudios.launchpad.compose.navigation.utils.DevicePosture +import com.bottlerocketstudios.launchpad.compose.navigation.utils.FoldingFeature +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Creates a flow that emits the current device posture. + * + * The flow emits the following values: + * + * * [DevicePosture.NormalPosture] when the device is not in a book or separating posture. + * * [DevicePosture.BookPosture] when the device is in a book posture. + * * [DevicePosture.Separating] when the device is in a separating posture. + * + * The flow is lifecycle-aware and will stop emitting values when the activity is paused. + * + * @return The flow that emits the current device posture. + */ +fun ComponentActivity.createDevicePostureFlow() = WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this) + .flowWithLifecycle(this.lifecycle) + .map { layoutInfo -> + val foldingFeature = + layoutInfo.displayFeatures + .filterIsInstance() + .firstOrNull() + when { + isBookPosture(foldingFeature) -> + DevicePosture.BookPosture(foldingFeature.bounds) + + isSeparating(foldingFeature) -> + DevicePosture.Separating(foldingFeature.bounds, foldingFeature.orientation) + + else -> DevicePosture.NormalPosture + } + } + .stateIn( + scope = lifecycleScope, + started = SharingStarted.Eagerly, + initialValue = DevicePosture.NormalPosture + ) + +/** + * Converts an Android [WindowWidthSizeClass] to a Launchpad [WindowWidthSizeClass]. + * + * @return The converted [WindowWidthSizeClass]. + */ +internal fun WindowWidthSizeClass.toLocalModel(): com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass = when (this) { + WindowWidthSizeClass.Compact -> com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass.Compact + WindowWidthSizeClass.Medium -> com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass.Medium + WindowWidthSizeClass.Expanded -> com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass.Expanded + else -> com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass.Compact +} diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/WindowStateUtils.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/WindowStateUtils.kt new file mode 100644 index 0000000..45ced8e --- /dev/null +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/util/WindowStateUtils.kt @@ -0,0 +1,18 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.util + +import com.bottlerocketstudios.launchpad.compose.navigation.utils.FoldingFeature +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +internal fun isBookPosture(foldFeature: FoldingFeature?): Boolean { + contract { returns(true) implies (foldFeature != null) } + return foldFeature?.state == FoldingFeature.State.HALF_OPENED && + foldFeature.orientation == FoldingFeature.Orientation.VERTICAL +} + +@OptIn(ExperimentalContracts::class) +internal fun isSeparating(foldFeature: FoldingFeature?): Boolean { + contract { returns(true) implies (foldFeature != null) } + return foldFeature?.state == FoldingFeature.State.FLAT && foldFeature.isSeparating +} diff --git a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpadcompose/widget/listdetail/AnimatedListDetail.kt b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/widget/listdetail/AnimatedListDetail.kt similarity index 98% rename from kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpadcompose/widget/listdetail/AnimatedListDetail.kt rename to kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/widget/listdetail/AnimatedListDetail.kt index ebf2648..f3a97b4 100644 --- a/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpadcompose/widget/listdetail/AnimatedListDetail.kt +++ b/kmp-launchpad-compose/src/androidMain/kotlin/com/bottlerocketstudios/launchpad/compose/widget/listdetail/AnimatedListDetail.kt @@ -1,4 +1,4 @@ -package com.bottlerocketstudios.launchpadcompose.widget.listdetail +package com.bottlerocketstudios.launchpad.compose.widget.listdetail import androidx.activity.compose.BackHandler import androidx.compose.animation.ExperimentalAnimationApi diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/ExampleApp.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/ExampleApp.kt new file mode 100644 index 0000000..8f7b790 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/ExampleApp.kt @@ -0,0 +1,40 @@ +package com.bottlerocketstudios.launchpad.compose.example.navigation + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass +import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.transition.NavTransition + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExampleApp( + widthSize: WindowWidthSizeClass, + navigator: Navigator?, + bottomBar: @Composable () -> Unit +) { + Scaffold( + topBar = { TopAppBar(title = { Text("Example App") }) }, + bottomBar = bottomBar + ) { + navigator?.let { navController -> + NavHost( + // Assign the navigator to the NavHost + navigator = navController, + // Navigation transition for the scenes in this NavHost, this is optional + navTransition = NavTransition(), + // The start destination + initialRoute = "/home", + modifier = Modifier.padding(it) + ) { + // Navgraph goes here + } + } + } +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt new file mode 100644 index 0000000..1508cd6 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/example/navigation/NavigationWrapperExample.kt @@ -0,0 +1,33 @@ +package com.bottlerocketstudios.launchpad.compose.example.navigation + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.VerifiedUser +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.VerifiedUser +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem + +val exampleNavigationItems = listOf( + NavigationItem( + route = "/home", + selectedIcon = { Icon(imageVector = Icons.Filled.Home, contentDescription = "") }, + unselectedIcon = { Icon(imageVector = Icons.Outlined.Home, contentDescription = "") }, + label = { Text("Home") }, + ), + NavigationItem( + route = "/settings", + selectedIcon = { Icon(imageVector = Icons.Filled.Settings, contentDescription = "") }, + unselectedIcon = { Icon(imageVector = Icons.Outlined.Settings, contentDescription = "") }, + label = { Text("Settings") }, + ), + NavigationItem( + route = "/profile", + selectedIcon = { Icon(imageVector = Icons.Filled.VerifiedUser, contentDescription = "") }, + unselectedIcon = { Icon(imageVector = Icons.Outlined.VerifiedUser, contentDescription = "") }, + label = { Text("Profile") }, + ) +) diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/NavigationWrapper.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/NavigationWrapper.kt new file mode 100644 index 0000000..3bd6afc --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/NavigationWrapper.kt @@ -0,0 +1,109 @@ +package com.bottlerocketstudios.launchpad.compose.navigation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.PermanentDrawerSheet +import androidx.compose.material3.PermanentNavigationDrawer +import androidx.compose.material3.rememberDrawerState +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.Modifier +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadBottomAppBar +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadDrawerContent +import com.bottlerocketstudios.launchpad.compose.navigation.components.LaunchpadNavigationRail +import com.bottlerocketstudios.launchpad.compose.navigation.utils.DevicePosture +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationType +import com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass +import com.bottlerocketstudios.launchpad.compose.navigation.utils.generateNavItems +import com.bottlerocketstudios.launchpad.compose.navigation.utils.toNavigationType +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.rememberNavigator + +/** + * A composable function that wraps the Precompose navigation component. + * + * Using this Navigation Wrapper requires the Precompose library on your project. + * + * @param widthSize The current window width size class. + * @param devicePosture The current device posture. + * @param navigationItems The list of navigation items, these will be the items that show up on the navigation rail or bottom navigation bar. + * For an example of this please see [exampleNavigationItems] and the [NavigationItem] class. + * @param app The function that renders the app content. + */ +@Composable +fun NavigationWrapper( + widthSize: WindowWidthSizeClass, + devicePosture: DevicePosture, + navigationItems: List, + app: @Composable (navigator: Navigator?, bottomBar: (@Composable () -> Unit)) -> Unit +) { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val navigator = rememberNavigator() + val currentBackStackEntry by navigator.currentEntry.collectAsState(null) + val currentRoute = currentBackStackEntry?.route + val navItems = remember { + generateNavItems(navigationItems) { + navigator.navigate(it.route) + } + } + val navigationType = remember(widthSize, devicePosture) { + derivedStateOf { + widthSize.toNavigationType(devicePosture) + } + } + + when (navigationType.value) { + NavigationType.PERMANENT_NAVIGATION_DRAWER -> + PermanentNavigationDrawer( + drawerContent = { + PermanentDrawerSheet() { LaunchpadDrawerContent(navItems) { it == currentRoute?.route } } + } + ) { app(navigator) { } } + + NavigationType.MODAL_NAVIGATION -> + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { ModalDrawerSheet { LaunchpadDrawerContent(navItems) { it == currentRoute?.route } } } + ) { app(navigator) { } } + + else -> Row { + AnimatedVisibility( + visible = navigationType.value == NavigationType.NAVIGATION_RAIL, + enter = slideInVertically(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + exit = slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)) + ) { + LaunchpadNavigationRail(navItems) { + it == currentRoute?.route + } + } + + Column( + modifier = Modifier + .fillMaxSize() + ) { + app(navigator) { + AnimatedVisibility( + visible = navigationType.value == NavigationType.BOTTOM_NAVIGATION, + enter = slideInVertically(animationSpec = spring(stiffness = Spring.StiffnessHigh)), + exit = slideOutHorizontally(animationSpec = spring(stiffness = Spring.StiffnessHigh)) + ) { + LaunchpadBottomAppBar(navItems) { it == currentRoute?.route } + } + } + } + } + } +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadBottomAppBar.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadBottomAppBar.kt new file mode 100644 index 0000000..1a7d1d7 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadBottomAppBar.kt @@ -0,0 +1,24 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.NavigationBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem +import com.bottlerocketstudios.launchpad.compose.navigation.utils.ToNavigationBarItems + +@Composable +fun LaunchpadBottomAppBar( + navItems: List, + isSelected: (String) -> Boolean +) { + NavigationBar( + Modifier + .fillMaxWidth() + .height(72.dp) + ) { + ToNavigationBarItems(navItems, isSelected) + } +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadDrawerContent.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadDrawerContent.kt new file mode 100644 index 0000000..83ee882 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadDrawerContent.kt @@ -0,0 +1,10 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.components + +import androidx.compose.runtime.Composable +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem +import com.bottlerocketstudios.launchpad.compose.navigation.utils.toNavigationDrawerItems + +@Composable +fun LaunchpadDrawerContent(navItems: List, isSelected: (String) -> Boolean) { + navItems.toNavigationDrawerItems(isSelected) +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadNavigationRail.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadNavigationRail.kt new file mode 100644 index 0000000..f4bb94f --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/components/LaunchpadNavigationRail.kt @@ -0,0 +1,13 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.components + +import androidx.compose.material3.NavigationRail +import androidx.compose.runtime.Composable +import com.bottlerocketstudios.launchpad.compose.navigation.utils.NavigationItem +import com.bottlerocketstudios.launchpad.compose.navigation.utils.toNavigationRailItems + +@Composable +fun LaunchpadNavigationRail(navItems: List, isSelected: (String) -> Boolean) { + NavigationRail { + navItems.toNavigationRailItems(isSelected) + } +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/GenerateNavItems.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/GenerateNavItems.kt new file mode 100644 index 0000000..184d63b --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/GenerateNavItems.kt @@ -0,0 +1,10 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.utils + +/** + * Generates a list of navigation items with the provided onClick callback. + * + * @param items The list of navigation items. + * @param onClick The callback to be called when an item is clicked. + * @return A list of navigation items with the provided onClick callback. + */ +internal fun generateNavItems(items: List, onClick: (NavigationItem) -> Unit) = items.map { it.copy(onClick = onClick) } diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationItem.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationItem.kt new file mode 100644 index 0000000..3537dfb --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationItem.kt @@ -0,0 +1,119 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.utils + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.NavigationRailItem +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** + * A data class representing a navigation item. + * + * @property route The route to navigate to when the item is clicked. + * @property onClick The callback to be called when the item is clicked. + * @property selectedIcon The icon to be displayed when the item is selected. + * @property unselectedIcon The icon to be displayed when the item is not selected. + * @property label The label to be displayed for the item. + * @property modifier The modifier to be applied to the item. + * @property enabled Whether the item is enabled. + */ +data class NavigationItem( + val route: String, + val selectedIcon: @Composable () -> Unit, + val unselectedIcon: @Composable () -> Unit, + val label: @Composable () -> Unit, + val modifier: Modifier = Modifier, + val enabled: Boolean = true, + val onClick: (NavigationItem) -> Unit = {}, +) + +/** + * Converts a [NavigationItem] to a [NavigationDrawerItem]. + * + * @param selected Whether the item is selected. + * @return The converted [NavigationDrawerItem]. + */ +@Composable +internal fun NavigationItem.toNavigationDrawerItem(selected: Boolean = false) = + NavigationDrawerItem( + label = label, + selected = selected, + onClick = { onClick(this) }, + icon = if (selected) selectedIcon else unselectedIcon, + modifier = modifier + ) + +/** + * Converts a list of [NavigationItem]s to a list of [NavigationDrawerItem]s. + * + * @param isSelected A function that determines whether a navigation item is selected. + */ +@Composable +internal fun List.toNavigationDrawerItems(isSelected: (String) -> Boolean) = + forEach { navigationItem -> + navigationItem.toNavigationDrawerItem(selected = isSelected(navigationItem.route)) + } + +/** + * Converts a [NavigationItem] to a [NavigationRailItem]. + * + * @param selected Whether the item is selected. + * @return A [NavigationRailItem] that represents the [NavigationItem]. + */ +@Composable +internal fun NavigationItem.toNavigationRailItem(selected: Boolean = false) = NavigationRailItem( + selected = selected, + onClick = { onClick(this) }, + icon = if (selected) selectedIcon else unselectedIcon, + label = label, + modifier = modifier, + enabled = enabled +) + +/** + * Converts a list of [NavigationItem]s to a list of [NavigationRailItem]s. + * + * @param isSelected A function that determines whether a [NavigationItem] is selected. + */ +@Composable +internal fun List.toNavigationRailItems(isSelected: (String) -> Boolean) = + forEach { navigationItem -> + navigationItem.toNavigationRailItem(selected = isSelected(navigationItem.route)) + } + +/** + * A composable function that converts a [NavigationItem] into a [NavigationBarItem]. + * + * @param item The navigation item to convert. + * @param selected Whether the navigation item is selected. + */ +@Composable +internal fun RowScope.toNavigationBarItem(item: NavigationItem, selected: Boolean = false) { + with(item) { + NavigationBarItem( + selected = selected, + onClick = { onClick(this) }, + icon = if (selected) selectedIcon else unselectedIcon, + label = label, + modifier = modifier, + enabled = enabled + ) + } +} + +/** + * Composes a row of navigation items. + * + * @param items The list of navigation items. + * @param isSelected A function that returns true if the given route is selected. + */ +@Composable +internal fun RowScope.ToNavigationBarItems(items: List, isSelected: (String) -> Boolean) { + items.forEach { navigationItem -> + toNavigationBarItem( + item = navigationItem, + selected = isSelected(navigationItem.route) + ) + } +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationType.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationType.kt new file mode 100644 index 0000000..ed220c1 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/NavigationType.kt @@ -0,0 +1,19 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.utils + +enum class NavigationType { + BOTTOM_NAVIGATION, NAVIGATION_RAIL, PERMANENT_NAVIGATION_DRAWER, MODAL_NAVIGATION +} + +/** + * Maps a [WindowWidthSizeClass] to a [NavigationType] based on the given [DevicePosture]. + * + * @param devicePosture The current device posture. + * @return The corresponding [NavigationType]. + */ +internal fun WindowWidthSizeClass.toNavigationType(devicePosture: DevicePosture) = when (this) { + WindowWidthSizeClass.Compact -> NavigationType.BOTTOM_NAVIGATION + WindowWidthSizeClass.Medium -> NavigationType.NAVIGATION_RAIL + WindowWidthSizeClass.Expanded -> + if (devicePosture is DevicePosture.BookPosture) NavigationType.NAVIGATION_RAIL else NavigationType.PERMANENT_NAVIGATION_DRAWER + else -> NavigationType.BOTTOM_NAVIGATION +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowStateUtils.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowStateUtils.kt new file mode 100644 index 0000000..8da963a --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowStateUtils.kt @@ -0,0 +1,125 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.utils + +/** + * Information about the posture of the device + */ +sealed interface DevicePosture { + object NormalPosture : DevicePosture + + data class BookPosture( + val hingePosition: Rect + ) : DevicePosture + + data class Separating( + val hingePosition: Rect, + var orientation: FoldingFeature.Orientation + ) : DevicePosture +} + +data class Rect( + val bottom: Int, + val left: Int, + val right: Int, + val top: Int +) + +interface DisplayFeature { + val bounds: Rect +} + +interface FoldingFeature : DisplayFeature { + + /** + * Represents how the hinge might occlude content. + */ + class OcclusionType private constructor(private val description: String) { + + override fun toString(): String { + return description + } + } + + /** + * Represents the axis for which the [FoldingFeature] runs parallel to. + */ + class Orientation private constructor(private val description: String) { + + override fun toString(): String { + return description + } + + companion object { + + /** + * The height of the [FoldingFeature] is greater than or equal to the width. + */ + val VERTICAL: Orientation = Orientation("VERTICAL") + + /** + * The width of the [FoldingFeature] is greater than the height. + */ + val HORIZONTAL: Orientation = Orientation("HORIZONTAL") + } + } + + /** + * Represents the [State] of the [FoldingFeature]. + */ + class State private constructor(private val description: String) { + + override fun toString(): String { + return description + } + + companion object { + /** + * The foldable device is completely open, the screen space that is presented to the + * user is flat. See the + * [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) + * section in the official documentation for visual samples and references. + */ + val FLAT: State = State("FLAT") + + /** + * The foldable device's hinge is in an intermediate position between opened and closed + * state, there is a non-flat angle between parts of the flexible screen or between + * physical screen panels. See the + * [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) + * section in the official documentation for visual samples and references. + */ + val HALF_OPENED: State = State("HALF_OPENED") + } + } + + /** + * Calculates if a [FoldingFeature] should be thought of as splitting the window into + * multiple physical areas that can be seen by users as logically separate. Display panels + * connected by a hinge are always separated. Folds on flexible screens should be treated as + * separating when they are not [FoldingFeature.State.FLAT]. + * + * Apps may use this to determine if content should lay out around the [FoldingFeature]. + * Developers should consider the placement of interactive elements. Similar to the case of + * [FoldingFeature.OcclusionType.FULL], when a feature is separating then consider laying + * out the controls around the [FoldingFeature]. + * + * An example use case is to determine if the UI should be split into two logical areas. A + * media app where there is some auxiliary content, such as comments or description of a video, + * may need to adapt the layout. The media can be put on one side of the [FoldingFeature] and + * the auxiliary content can be placed on the other side. + * + * @return `true` if the feature splits the display into two areas, `false` + * otherwise. + */ + val isSeparating: Boolean + + /** + * Returns [FoldingFeature.Orientation.HORIZONTAL] if the width is greater than the + * height, [FoldingFeature.Orientation.VERTICAL] otherwise. + */ + val orientation: Orientation + + /** + * Returns the [FoldingFeature.State] for the [FoldingFeature] + */ + val state: State +} diff --git a/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowWidthSizeClass.kt b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowWidthSizeClass.kt new file mode 100644 index 0000000..3086897 --- /dev/null +++ b/kmp-launchpad-compose/src/commonMain/kotlin/com/bottlerocketstudios/launchpad/compose/navigation/utils/WindowWidthSizeClass.kt @@ -0,0 +1,50 @@ +package com.bottlerocketstudios.launchpad.compose.navigation.utils + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +class WindowWidthSizeClass private constructor(private val value: Int) : Comparable { + + override fun compareTo(other: WindowWidthSizeClass) = value.compareTo(other.value) + + override fun toString(): String { + return "WindowWidthSizeClass." + when (this) { + Compact -> "Compact" + Medium -> "Medium" + Expanded -> "Expanded" + else -> "" + } + } + + companion object { + /** Represents the majority of phones in portrait. */ + val Compact = WindowWidthSizeClass(0) + + /** + * Represents the majority of tablets in portrait and large unfolded inner displays in + * portrait. + */ + val Medium = WindowWidthSizeClass(1) + + /** + * Represents the majority of tablets in landscape and large unfolded inner displays in + * landscape. + */ + val Expanded = WindowWidthSizeClass(2) + + /** Calculates the [WindowWidthSizeClass] for a given [width] */ + internal fun fromWidth(width: Dp): WindowWidthSizeClass { + require(width >= 0.dp) { "Width must not be negative" } + return when { + width < 600.dp -> Compact + width < 840.dp -> Medium + else -> Expanded + } + } + } +} + +enum class WindowOrientation { + Portrait, + Landscape +} diff --git a/kmp-launchpad-compose/src/iosMain/kotlin/com/bottlerocketstudios/launchpad/compose/util/navigation/CalculateWindowWidthSize.kt b/kmp-launchpad-compose/src/iosMain/kotlin/com/bottlerocketstudios/launchpad/compose/util/navigation/CalculateWindowWidthSize.kt new file mode 100644 index 0000000..c702f1b --- /dev/null +++ b/kmp-launchpad-compose/src/iosMain/kotlin/com/bottlerocketstudios/launchpad/compose/util/navigation/CalculateWindowWidthSize.kt @@ -0,0 +1,10 @@ +package com.bottlerocketstudios.launchpad.compose.util.navigation + +import com.bottlerocketstudios.launchpad.compose.navigation.utils.WindowWidthSizeClass +import platform.UIKit.UIWindow + +fun getWindowWidthSize(uiWindow: UIWindow): WindowWidthSizeClass { + // TODO - Logic to get window width size class on iOS + return WindowWidthSizeClass.Compact // for iphone +// return WindowWidthSizeClass.Expanded // for ipad +}