Skip to content

Commit

Permalink
Merge pull request #78 from Infomaniak/tablet-utils
Browse files Browse the repository at this point in the history
core: Pooling all adaptive tablet logic
  • Loading branch information
KevinBoulongne authored Sep 25, 2024
2 parents 3addd91 + 7161412 commit bf1a073
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaf
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowHeightSizeClass
import com.infomaniak.swisstransfer.ui.theme.LocalWindowAdaptiveInfo
import com.infomaniak.swisstransfer.ui.utils.isWindowLarge

/**
* A composable function that sets up a List-Detail interface using a three-pane scaffold navigator.
Expand All @@ -47,16 +48,13 @@ import androidx.window.core.layout.WindowHeightSizeClass
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun <T> TwoPaneScaffold(
windowAdaptiveInfo: WindowAdaptiveInfo,
listPane: @Composable ThreePaneScaffoldNavigator<T>.() -> Unit,
detailPane: @Composable ThreePaneScaffoldNavigator<T>.() -> Unit,
modifier: Modifier = Modifier,
) {
val windowAdaptiveInfo = LocalWindowAdaptiveInfo.current
val paneScaffoldDirective = calculatePaneScaffoldDirective(windowAdaptiveInfo)
val maxHorizontalPartitions = when (windowAdaptiveInfo.windowSizeClass.windowHeightSizeClass) {
WindowHeightSizeClass.COMPACT -> 1
else -> paneScaffoldDirective.maxHorizontalPartitions
}
val maxHorizontalPartitions = if (windowAdaptiveInfo.isWindowLarge()) 2 else 1
val navigator = rememberListDetailPaneScaffoldNavigator<T>(
scaffoldDirective = paneScaffoldDirective.copy(
maxHorizontalPartitions = maxHorizontalPartitions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
Expand All @@ -38,7 +37,6 @@ import com.infomaniak.swisstransfer.ui.screen.main.transferdetails.TransferDetai
fun MainNavHost(
navController: NavHostController,
currentDestination: MainNavigation,
windowAdaptiveInfo: WindowAdaptiveInfo,
) {
NavHost(
navController = navController,
Expand All @@ -57,7 +55,7 @@ fun MainNavHost(
TransferDetailsScreen(transferId = transferDetails.transferId)
}
composable<SettingsDestination> {
SettingsScreenWrapper(windowAdaptiveInfo)
SettingsScreenWrapper()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package com.infomaniak.swisstransfer.ui.screen.main

import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
Expand All @@ -35,7 +34,6 @@ import com.infomaniak.swisstransfer.ui.utils.PreviewTablet
@Composable
fun MainScreen() {
val navController = rememberNavController()
val windowAdaptiveInfo = currentWindowAdaptiveInfo()

val navBackStackEntry by navController.currentBackStackEntryAsState()

Expand All @@ -46,9 +44,8 @@ fun MainScreen() {
MainScaffold(
navController = navController,
currentDestination = currentDestination,
windowAdaptiveInfo = windowAdaptiveInfo,
tabletTopAppBar = { BrandTobAppBar() },
content = { MainNavHost(navController, currentDestination, windowAdaptiveInfo) },
content = { MainNavHost(navController, currentDestination) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
Expand All @@ -30,25 +29,22 @@ import androidx.navigation.NavHostController
import com.infomaniak.swisstransfer.ui.components.BrandTobAppBar
import com.infomaniak.swisstransfer.ui.navigation.MainNavigation
import com.infomaniak.swisstransfer.ui.navigation.NavigationItem
import com.infomaniak.swisstransfer.ui.theme.LocalWindowAdaptiveInfo
import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme
import com.infomaniak.swisstransfer.ui.utils.PreviewMobile
import com.infomaniak.swisstransfer.ui.utils.PreviewTablet

val LocalNavType = staticCompositionLocalOf { NavigationSuiteType.None }
import com.infomaniak.swisstransfer.ui.utils.isWindowLarge
import com.infomaniak.swisstransfer.ui.utils.isWindowSmall

@Composable
fun MainScaffold(
navController: NavHostController,
currentDestination: MainNavigation,
windowAdaptiveInfo: WindowAdaptiveInfo,
tabletTopAppBar: @Composable () -> Unit = {},
content: @Composable () -> Unit = {},
) {
val navType by rememberNavType(currentDestination, windowAdaptiveInfo)

CompositionLocalProvider(LocalNavType provides navType) {
MainScaffold(navType, currentDestination, navController::navigateToSelectedItem, tabletTopAppBar, content)
}
val navType by rememberNavType(currentDestination)
MainScaffold(navType, currentDestination, navController::navigateToSelectedItem, tabletTopAppBar, content)
}

@Composable
Expand All @@ -59,10 +55,12 @@ private fun MainScaffold(
tabletTopAppBar: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
val windowAdaptiveInfo = LocalWindowAdaptiveInfo.current

Column {
if (navType == NavigationSuiteType.NavigationRail) tabletTopAppBar()
if (windowAdaptiveInfo.isWindowLarge()) tabletTopAppBar()
AppNavigationSuiteScaffold(navType, NavigationItem.entries, currentDestination, navigateToSelectedItem) {
if (navType == NavigationSuiteType.NavigationBar) {
if (windowAdaptiveInfo.isWindowSmall()) {
Column {
Box(modifier = Modifier.weight(1.0f)) { content() }
HorizontalDivider()
Expand All @@ -77,7 +75,7 @@ private fun MainScaffold(
@Composable
private fun rememberNavType(
currentDestination: MainNavigation,
windowAdaptiveInfo: WindowAdaptiveInfo,
windowAdaptiveInfo: WindowAdaptiveInfo = LocalWindowAdaptiveInfo.current,
): State<NavigationSuiteType> {

val showNavigation by remember(currentDestination) {
Expand All @@ -89,14 +87,18 @@ private fun rememberNavType(
return remember(showNavigation, windowAdaptiveInfo) {
derivedStateOf {
if (showNavigation) {
NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(windowAdaptiveInfo)
calculateFromAdaptiveInfo(windowAdaptiveInfo)
} else {
NavigationSuiteType.None
}
}
}
}

private fun calculateFromAdaptiveInfo(windowAdaptiveInfo: WindowAdaptiveInfo): NavigationSuiteType {
return if (windowAdaptiveInfo.isWindowLarge()) NavigationSuiteType.NavigationRail else NavigationSuiteType.NavigationBar
}

private fun NavHostController.navigateToSelectedItem(destination: MainNavigation) {
destination.enableTransition = false
navigate(destination) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ package com.infomaniak.swisstransfer.ui.screen.main.components

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import com.infomaniak.swisstransfer.ui.theme.LocalWindowAdaptiveInfo
import com.infomaniak.swisstransfer.ui.utils.isWindowSmall

@Composable
fun PhoneTopAppBarScaffold(
phoneTopAppBar: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
val windowAdaptiveInfo = LocalWindowAdaptiveInfo.current

Scaffold(
topBar = { if (LocalNavType.current == NavigationSuiteType.NavigationBar) phoneTopAppBar() },
topBar = { if (windowAdaptiveInfo.isWindowSmall()) phoneTopAppBar() },
floatingActionButton = floatingActionButton,
) { contentPadding ->
val paddingValues = if (LocalNavType.current == NavigationSuiteType.NavigationBar) {
val paddingValues = if (windowAdaptiveInfo.isWindowSmall()) {
contentPadding
} else {
PaddingValues(all = 0.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.infomaniak.swisstransfer.ui.screen.main.sent

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
Expand All @@ -28,29 +28,29 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.infomaniak.swisstransfer.ui.components.NewTransferFab
import com.infomaniak.swisstransfer.ui.components.NewTransferFabType
import com.infomaniak.swisstransfer.ui.screen.main.components.BrandTobAppBarScaffold
import com.infomaniak.swisstransfer.ui.screen.main.components.LocalNavType
import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme
import com.infomaniak.swisstransfer.ui.utils.PreviewMobile
import com.infomaniak.swisstransfer.ui.utils.PreviewTablet
import com.infomaniak.swisstransfer.ui.utils.isWindowSmall

@Composable
fun SentScreen(
navigateToDetails: (transferId: Int) -> Unit,
sentViewModel: SentViewModel = hiltViewModel<SentViewModel>(),
) {
val transfers by sentViewModel.transfers.collectAsStateWithLifecycle()
val navType = LocalNavType.current

SentScreen(navType, transfers)
SentScreen(transfers)
}

@Composable
private fun SentScreen(navType: NavigationSuiteType, transfers: List<Any>?) {
private fun SentScreen(transfers: List<Any>?) {

if (transfers == null) return
val windowAdaptiveInfo = currentWindowAdaptiveInfo()

BrandTobAppBarScaffold(
floatingActionButton = {
if (navType == NavigationSuiteType.NavigationBar && transfers.isNotEmpty()) {
if (windowAdaptiveInfo.isWindowSmall() && transfers.isNotEmpty()) {
NewTransferFab(newTransferFabType = NewTransferFabType.BOTTOM_BAR)
}
},
Expand All @@ -68,27 +68,19 @@ private fun SentScreen(navType: NavigationSuiteType, transfers: List<Any>?) {
@PreviewMobile
@Composable
private fun SentScreenMobilePreview() {
val navType = LocalNavType.current
SwissTransferTheme {
Surface {
SentScreen(
navType = navType,
transfers = emptyList(),
)
SentScreen(transfers = emptyList())
}
}
}

@PreviewTablet
@Composable
private fun SentScreenTabletPreview() {
val navType = LocalNavType.current
SwissTransferTheme {
Surface {
SentScreen(
navType = navType,
transfers = emptyList(),
)
SentScreen(transfers = emptyList())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ import com.infomaniak.swisstransfer.ui.utils.*
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun SettingsScreenWrapper(
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
settingsViewModel: SettingsViewModel = hiltViewModel<SettingsViewModel>(),
) {
val appSettings by settingsViewModel.appSettingsFlow.collectAsStateWithLifecycle(null)
appSettings?.let { safeAppSettings ->
TwoPaneScaffold<SettingsOptionScreens>(
windowAdaptiveInfo = windowAdaptiveInfo,
listPane = { ListPane(this, safeAppSettings) },
detailPane = { DetailPane(safeAppSettings, settingsViewModel, navigator = this) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color

val LocalCustomTypography = staticCompositionLocalOf { Typography }
val LocalCustomColorScheme: ProvidableCompositionLocal<CustomColorScheme> = staticCompositionLocalOf { CustomColorScheme() }
val LocalWindowAdaptiveInfo = staticCompositionLocalOf<WindowAdaptiveInfo> { error("No WindowAdaptiveInfo provided") }

@Composable
fun SwissTransferTheme(
Expand All @@ -37,6 +40,7 @@ fun SwissTransferTheme(
LocalCustomTypography provides Typography,
LocalTextStyle provides Typography.bodyRegular,
LocalCustomColorScheme provides customColors,
LocalWindowAdaptiveInfo provides currentWindowAdaptiveInfo(),
) {
MaterialTheme(
colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.swisstransfer.ui.utils

import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowWidthSizeClass

/**
* Determines if the current window is classified as a large window suitable for tablet devices.
*
* This is typically used to adapt the UI, such as displaying a list-detail layout or using a [NavigationRail]
* vs a [NavigationBar] for navigation.
*
* @return `true` if the window is large (tablet), `false` otherwise.
*/
fun WindowAdaptiveInfo.isWindowLarge(): Boolean = with(windowSizeClass) {
return windowWidthSizeClass == WindowWidthSizeClass.EXPANDED && windowHeightSizeClass != WindowHeightSizeClass.COMPACT
}

/**
* Determines if the current window is classified as a small window suitable for mobile devices.
*
* This is typically used to adapt the UI, such as displaying a list-detail layout or using a [NavigationRail]
* vs a [NavigationBar] for navigation.
*
* @return `true` if the window is small (mobile), `false` otherwise.
*/
fun WindowAdaptiveInfo.isWindowSmall(): Boolean = !isWindowLarge()

0 comments on commit bf1a073

Please sign in to comment.