Skip to content

Commit

Permalink
Merge pull request #554 from Orange-OpenSource/implement-m3-large-top…
Browse files Browse the repository at this point in the history
…-app-bar

118 - ODS Component - app bar top large
  • Loading branch information
florentmaitre authored Jul 12, 2023
2 parents 529cb23 + e353a1b commit a9fce05
Show file tree
Hide file tree
Showing 31 changed files with 926 additions and 483 deletions.
2 changes: 2 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ dependencies {
implementation(project(":lib-xml"))
implementation(project(":theme-innovation-cup"))

implementation(Dependencies.composeMaterial3)
implementation(Dependencies.coreKtx)
implementation(Dependencies.kotlinReflect)
implementation(Dependencies.appCompat)
implementation(Dependencies.material)
implementation(Dependencies.composeUi)
implementation(Dependencies.lifecycleViewModelKtx)
implementation(Dependencies.composeMaterial)
implementation(Dependencies.composeUiToolingPreview)
implementation(Dependencies.lifecycleRuntimeKtx)
implementation(Dependencies.activityCompose)
Expand Down
14 changes: 4 additions & 10 deletions app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,23 @@ fun NavGraphBuilder.addBottomNavigationGraph(navigateToElement: (String, Long?,
val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder()
.prependAction(TopAppBarConfiguration.Action.Search)
.build()
with(LocalMainTopAppBarManager.current) {
updateTopAppBar(topAppBarConfiguration)
clearTopAppBarTabs()
}
LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration)
GuidelinesScreen(onGuidelineClick = { route -> navigateToElement(route, null, from) })
}
composable(BottomNavigationSections.Components.route) { from ->
val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder()
.prependAction(TopAppBarConfiguration.Action.Search)
.build()
with(LocalMainTopAppBarManager.current) {
updateTopAppBar(topAppBarConfiguration)
clearTopAppBarTabs()
}
LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration)
ComponentsScreen(onComponentClick = { id -> navigateToElement(MainDestinations.ComponentDetailRoute, id, from) })
}
composable(BottomNavigationSections.Modules.route) {
LocalMainTopAppBarManager.current.reset()
LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration)
ModulesScreen()
}
composable(BottomNavigationSections.About.route) { from ->
val context = LocalContext.current
LocalMainTopAppBarManager.current.reset()
LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration)
AboutScreen(onAboutItemClick = { id ->
val aboutItem = aboutItems.firstOrNull { it.id == id }
if (aboutItem is UrlAboutItem) {
Expand Down
167 changes: 68 additions & 99 deletions app/src/main/java/com/orange/ods/app/ui/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,23 @@ package com.orange.ods.app.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
Expand All @@ -55,20 +49,18 @@ import com.orange.ods.app.ui.guidelines.addGuidelinesGraph
import com.orange.ods.app.ui.search.SearchScreen
import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled
import com.orange.ods.app.ui.utilities.extension.isOrange
import com.orange.ods.compose.component.list.OdsListItem
import com.orange.ods.compose.component.list.OdsRadioButtonTrailing
import com.orange.ods.compose.text.OdsTextH6
import com.orange.ods.compose.theme.OdsTheme
import com.orange.ods.theme.OdsThemeConfigurationContract
import com.orange.ods.utilities.extension.orElse
import com.orange.ods.xml.theme.OdsXml
import com.orange.ods.xml.utilities.extension.xml

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(themeConfigurations: Set<OdsThemeConfigurationContract>, mainViewModel: MainViewModel = viewModel()) {
val isSystemInDarkTheme = isSystemInDarkTheme()
val mainState = rememberMainState(
themeState = rememberMainThemeState(
themeState = rememberThemeState(
currentThemeConfiguration = rememberSaveable {
mutableStateOf(
getCurrentThemeConfiguration(
Expand All @@ -94,36 +86,54 @@ fun MainScreen(themeConfigurations: Set<OdsThemeConfigurationContract>, mainView
CompositionLocalProvider(
LocalConfiguration provides configuration,
LocalMainTopAppBarManager provides mainState.topAppBarState,
LocalMainThemeManager provides mainState.themeState,
LocalThemeManager provides mainState.themeState,
LocalOdsGuideline provides mainState.themeState.currentThemeConfiguration.guideline,
LocalRecipes provides mainViewModel.recipes,
LocalCategories provides mainViewModel.categories,
LocalUiFramework provides mainState.uiFramework
) {
var changeThemeDialogVisible by remember { mutableStateOf(false) }

OdsTheme(
themeConfiguration = mainState.themeState.currentThemeConfiguration,
darkThemeEnabled = configuration.isDarkModeEnabled
) {
val topBarScrollBehavior: TopAppBarScrollBehavior?
val modifier: Modifier

val showTabs by remember {
derivedStateOf { mainState.topAppBarState.tabsState.hasTabs }
}
val displayLargeTopAppBar by remember {
derivedStateOf { mainState.topAppBarState.isLarge }
}

if (displayLargeTopAppBar) {
topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection }
modifier = Modifier.nestedScroll(nestedScrollConnection)
} else {
topBarScrollBehavior = null
modifier = Modifier
}

Scaffold(
modifier = modifier,
backgroundColor = OdsTheme.colors.background,
topBar = {
Surface(elevation = AppBarDefaults.TopAppBarElevation) {
Surface(elevation = if (isSystemInDarkTheme()) 0.dp else AppBarDefaults.TopAppBarElevation) {
Column {
SystemBarsColorSideEffect()
MainTopAppBar(
titleRes = mainState.topAppBarState.titleRes.value,
shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar,
state = mainState.topAppBarState,
topAppBarState = mainState.topAppBarState,
upPress = mainState::upPress,
onChangeThemeActionClick = { changeThemeDialogVisible = true },
onSearchActionClick = {
mainState.navController.navigate(MainDestinations.SearchRoute)
}
},
scrollBehavior = topBarScrollBehavior
)
// Display tabs in the top bar if needed
MainTabs(mainTabsState = mainState.topAppBarState.tabsState)
if (showTabs) {
MainTabs(mainTabsState = mainState.topAppBarState.tabsState)
}
}
}
},
Expand All @@ -141,21 +151,13 @@ fun MainScreen(themeConfigurations: Set<OdsThemeConfigurationContract>, mainView
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(mainState.navController, startDestination = MainDestinations.HomeRoute) {
mainNavGraph(navigateToElement = mainState::navigateToElement, searchedText = mainState.topAppBarState.searchedText)
}
}

if (changeThemeDialogVisible) {
ChangeThemeDialog(
themeState = mainState.themeState,
dismissDialog = {
changeThemeDialogVisible = false
},
onThemeSelected = {
mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name)
}
NavHost(
navController = mainState.navController, startDestination = MainDestinations.HomeRoute, modifier = Modifier.padding(innerPadding)
) {
mainNavGraph(
navigateToElement = mainState::navigateToElement,
upPress = mainState::upPress,
searchedText = mainState.topAppBarState.searchedText
)
}
}
Expand All @@ -170,38 +172,6 @@ private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConf
.orElse { themeConfigurations.first() }
}

@Composable
private fun ChangeThemeDialog(themeState: MainThemeState, dismissDialog: () -> Unit, onThemeSelected: () -> Unit) {
val selectedRadio = rememberSaveable { mutableStateOf(themeState.currentThemeConfiguration.name) }

Dialog(onDismissRequest = dismissDialog) {
Column(modifier = Modifier.background(OdsTheme.colors.surface)) {
OdsTextH6(
text = stringResource(R.string.top_app_bar_action_change_theme_desc),
modifier = Modifier
.padding(top = dimensionResource(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s))
.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin))
)
themeState.themeConfigurations.forEach { themeConfiguration ->
OdsListItem(
text = themeConfiguration.name,
trailing = OdsRadioButtonTrailing(
selectedRadio = selectedRadio,
currentRadio = themeConfiguration.name,
onClick = {
if (themeConfiguration != themeState.currentThemeConfiguration) {
themeState.currentThemeConfiguration = themeConfiguration
onThemeSelected()
}
dismissDialog()
}
)
)
}
}
}
}

@Composable
private fun SystemBarsColorSideEffect() {
val systemUiController = rememberSystemUiController()
Expand All @@ -218,31 +188,33 @@ private fun SystemBarsColorSideEffect() {
private fun MainTabs(mainTabsState: MainTabsState) {
with(mainTabsState) {
pagerState?.let { pagerState ->
if (hasTabs) {
// Do not use tabs directly because this is a SnapshotStateList
// Thus its value can be modified and can lead to crashes if it becomes empty
val tabs = tabs.toList()
if (scrollableTabs.value) {
ScrollableTabRow(
tabs = tabs,
pagerState = pagerState,
tabIconType = tabIconType.value,
tabTextEnabled = tabTextEnabled.value
)
} else {
FixedTabRow(
tabs = tabs,
pagerState = pagerState,
tabIconType = tabIconType.value,
tabTextEnabled = tabTextEnabled.value
)
}
// Do not use tabs directly because this is a SnapshotStateList
// Thus its value can be modified and can lead to crashes if it becomes empty
val tabs = tabs.toList()
if (scrollableTabs.value) {
ScrollableTabRow(
tabs = tabs,
pagerState = pagerState,
tabIconType = tabIconType.value,
tabTextEnabled = tabTextEnabled.value
)
} else {
FixedTabRow(
tabs = tabs,
pagerState = pagerState,
tabIconType = tabIconType.value,
tabTextEnabled = tabTextEnabled.value
)
}
}
}
}

private fun NavGraphBuilder.mainNavGraph(navigateToElement: (String, Long?, NavBackStackEntry) -> Unit, searchedText: MutableState<TextFieldValue>) {
private fun NavGraphBuilder.mainNavGraph(
navigateToElement: (String, Long?, NavBackStackEntry) -> Unit,
upPress: () -> Unit,
searchedText: MutableState<TextFieldValue>
) {
navigation(
route = MainDestinations.HomeRoute,
startDestination = BottomNavigationSections.Guidelines.route
Expand All @@ -251,16 +223,13 @@ private fun NavGraphBuilder.mainNavGraph(navigateToElement: (String, Long?, NavB
}

addGuidelinesGraph()
addComponentsGraph(navigateToElement)
addComponentsGraph(navigateToElement, upPress)
addAboutGraph()

composable(
route = MainDestinations.SearchRoute
) { from ->
with(LocalMainTopAppBarManager.current) {
clearTopAppBarTabs()
updateTopAppBarTitle(R.string.navigation_item_search)
}
LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_search)
SearchScreen(
searchedText,
onResultItemClick = { route, id -> navigateToElement(route, id, from) }
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/com/orange/ods/app/ui/MainState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object MainDestinations {

const val ComponentDetailRoute = "component"
const val ComponentIdKey = "componentId"
const val ComponentVariantRoute = "component/variant"
const val ComponentVariantDemoRoute = "component/variant"
const val ComponentVariantIdKey = "componentVariantId"
const val ComponentDemoRoute = "component/demo"

Expand All @@ -47,7 +47,7 @@ object MainDestinations {

@Composable
fun rememberMainState(
themeState: MainThemeState,
themeState: ThemeState,
navController: NavHostController = rememberNavController(),
topAppBarState: MainTopAppBarState = rememberMainTopAppBarState(),
uiFramework: MutableState<UiFramework> = rememberSaveable { mutableStateOf(UiFramework.Compose) }
Expand All @@ -57,7 +57,7 @@ fun rememberMainState(
}

class MainState(
val themeState: MainThemeState,
val themeState: ThemeState,
val navController: NavHostController,
val topAppBarState: MainTopAppBarState,
val uiFramework: MutableState<UiFramework>
Expand All @@ -84,6 +84,10 @@ class MainState(
get() = navController.currentDestination?.route

fun upPress() {
with(topAppBarState) {
updateTopAppBar(MainTopAppBarState.DefaultConfiguration)
clearTopAppBarTabs()
}
navController.navigateUp()
}

Expand Down
Loading

0 comments on commit a9fce05

Please sign in to comment.