-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #693 from Orange-OpenSource/develop
Release 0.17.0
- Loading branch information
Showing
342 changed files
with
6,405 additions
and
4,158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* | ||
* Copyright 2021 Orange | ||
* | ||
* Use of this source code is governed by an MIT-style | ||
* license that can be found in the LICENSE file or at | ||
* https://opensource.org/licenses/MIT. | ||
* / | ||
*/ | ||
|
||
package com.orange.ods.app.ui | ||
|
||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.TopAppBarScrollBehavior | ||
import androidx.compose.runtime.* | ||
import com.orange.ods.compose.component.appbar.top.OdsLargeTopAppBarInternal | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarInternal | ||
|
||
/** | ||
* Displays the unique top app bar of the application which can be regular or large. | ||
*/ | ||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
fun AppBar( | ||
appBarState: AppBarState, | ||
upPress: () -> Unit, | ||
scrollBehavior: TopAppBarScrollBehavior? | ||
) { | ||
with(appBarState) { | ||
if (isLarge) { | ||
OdsLargeTopAppBarInternal( | ||
title = title, | ||
navigationIcon = getNavigationIcon(upPress), | ||
actions = actions, | ||
overflowMenuActions = overflowMenuActions, | ||
scrollBehavior = if (hasScrollBehavior) scrollBehavior else null | ||
) | ||
} else { | ||
OdsTopAppBarInternal( | ||
title = title, | ||
navigationIcon = getNavigationIcon(upPress), | ||
actions = actions, | ||
overflowMenuActions = overflowMenuActions, | ||
elevated = false // elevation is managed in MainScreen cause of tabs | ||
) | ||
} | ||
} | ||
} |
116 changes: 116 additions & 0 deletions
116
app/src/main/java/com/orange/ods/app/ui/AppBarActions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* | ||
* Copyright 2021 Orange | ||
* | ||
* Use of this source code is governed by an MIT-style | ||
* license that can be found in the LICENSE file or at | ||
* https://opensource.org/licenses/MIT. | ||
* / | ||
*/ | ||
|
||
package com.orange.ods.app.ui | ||
|
||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.focus.FocusRequester | ||
import androidx.compose.ui.focus.focusRequester | ||
import androidx.compose.ui.platform.LocalConfiguration | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.input.TextFieldValue | ||
import com.orange.ods.app.R | ||
import com.orange.ods.app.ui.AppBarAction.Companion.defaultActions | ||
import com.orange.ods.app.ui.utilities.UiString | ||
import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuActionItem | ||
import com.orange.ods.compose.component.content.OdsComponentContent | ||
import com.orange.ods.compose.component.textfield.search.OdsSearchTextField | ||
|
||
enum class AppBarAction { | ||
Search, ChangeTheme, ChangeMode; | ||
|
||
companion object { | ||
val defaultActions = listOf(ChangeTheme, ChangeMode) | ||
} | ||
|
||
@Composable | ||
fun getOdsTopAppBarAction(onActionClick: (AppBarAction) -> Unit) = when (this) { | ||
ChangeTheme -> getChangeThemeAction(onActionClick) | ||
ChangeMode -> getChangeModeAction(onActionClick) | ||
Search -> getSearchAction(onActionClick) | ||
} | ||
} | ||
|
||
data class AppBarOverflowMenuAction( | ||
val title: UiString, | ||
val onClick: () -> Unit | ||
) { | ||
@Composable | ||
fun getOdsTopAppBarOverflowMenuAction() = OdsTopAppBarOverflowMenuActionItem( | ||
text = title.asString(), | ||
onClick = onClick | ||
) | ||
} | ||
|
||
@Composable | ||
fun getDefaultActions(onActionClick: (AppBarAction) -> Unit): List<OdsTopAppBarActionButton> = | ||
defaultActions.map { it.getOdsTopAppBarAction(onActionClick = onActionClick) } | ||
|
||
@Composable | ||
fun getHomeActions(onActionClick: (AppBarAction) -> Unit): List<OdsTopAppBarActionButton> = | ||
listOf(getSearchAction(onActionClick)) + getDefaultActions(onActionClick = onActionClick) | ||
|
||
@Composable | ||
fun getSearchFieldAction(onTextChange: (TextFieldValue) -> Unit): OdsComponentContent<Nothing> { | ||
return object : OdsComponentContent<Nothing>() { | ||
|
||
@Composable | ||
override fun Content(modifier: Modifier) { | ||
val focusRequester = remember { FocusRequester() } | ||
OdsSearchTextField( | ||
value = LocalAppBarManager.current.searchedText, | ||
onValueChange = onTextChange, | ||
placeholder = stringResource(id = R.string.search_text_field_hint), | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.focusRequester(focusRequester) | ||
) | ||
LaunchedEffect(Unit) { | ||
focusRequester.requestFocus() | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun getSearchAction(onClick: (AppBarAction) -> Unit) = OdsTopAppBarActionButton( | ||
onClick = { onClick(AppBarAction.Search) }, | ||
painter = painterResource(id = R.drawable.ic_search), | ||
contentDescription = stringResource(id = R.string.search_content_description) | ||
) | ||
|
||
@Composable | ||
private fun getChangeThemeAction(onClick: (AppBarAction) -> Unit) = OdsTopAppBarActionButton( | ||
onClick = { onClick(AppBarAction.ChangeTheme) }, | ||
painter = painterResource(id = R.drawable.ic_palette), | ||
contentDescription = stringResource(id = R.string.top_app_bar_action_change_mode_to_dark_desc) | ||
) | ||
|
||
@Composable | ||
private fun getChangeModeAction(onClick: (AppBarAction) -> Unit): OdsTopAppBarActionButton { | ||
val configuration = LocalConfiguration.current | ||
|
||
val painterRes = if (configuration.isDarkModeEnabled) R.drawable.ic_ui_light_mode else R.drawable.ic_ui_dark_mode | ||
val iconDesc = | ||
if (configuration.isDarkModeEnabled) R.string.top_app_bar_action_change_mode_to_light_desc else R.string.top_app_bar_action_change_mode_to_dark_desc | ||
|
||
return OdsTopAppBarActionButton( | ||
onClick = { onClick(AppBarAction.ChangeMode) }, | ||
painter = painterResource(id = painterRes), | ||
contentDescription = stringResource(id = iconDesc) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
/* | ||
* | ||
* Copyright 2021 Orange | ||
* | ||
* Use of this source code is governed by an MIT-style | ||
* license that can be found in the LICENSE file or at | ||
* https://opensource.org/licenses/MIT. | ||
* / | ||
*/ | ||
|
||
package com.orange.ods.app.ui | ||
|
||
import android.os.Bundle | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.filled.ArrowBack | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.MutableState | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.staticCompositionLocalOf | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.input.TextFieldValue | ||
import androidx.navigation.NavBackStackEntry | ||
import androidx.navigation.NavController | ||
import androidx.navigation.compose.currentBackStackEntryAsState | ||
import com.orange.ods.app.R | ||
import com.orange.ods.app.domain.recipes.LocalRecipes | ||
import com.orange.ods.app.ui.AppBarAction.Companion.defaultActions | ||
import com.orange.ods.app.ui.AppBarState.Companion.CustomDefaultConfiguration | ||
import com.orange.ods.app.ui.components.appbars.top.TopAppBarCustomizationState | ||
import com.orange.ods.app.ui.components.utilities.clickOnElement | ||
import com.orange.ods.app.ui.utilities.NavigationItem | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarNavigationIcon | ||
import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuActionItem | ||
import com.orange.ods.compose.component.content.OdsComponentContent | ||
import com.orange.ods.extension.orElse | ||
import kotlin.math.max | ||
|
||
val LocalAppBarManager = staticCompositionLocalOf<AppBarManager> { error("CompositionLocal AppBarManager not present") } | ||
|
||
interface AppBarManager { | ||
val searchedText: TextFieldValue | ||
|
||
fun setCustomAppBar(appBarConfiguration: AppBarConfiguration) | ||
|
||
fun updateAppBarTabs(tabsConfiguration: TabsConfiguration) | ||
fun clearAppBarTabs() | ||
} | ||
|
||
/** | ||
* AppBar state source of truth. | ||
* | ||
* The app bar is managed according to the [Screen] displayed except when its type is [ScreenType.WithCustomizableTopAppBar]. In this case, the app bar is | ||
* displayed according to the provided [AppBarConfiguration]. | ||
*/ | ||
class AppBarState( | ||
private val navController: NavController, | ||
private val searchText: MutableState<TextFieldValue>, | ||
private val customAppBarConfiguration: MutableState<AppBarConfiguration>, | ||
val tabsState: AppBarTabsState | ||
) : AppBarManager { | ||
|
||
companion object { | ||
val CustomDefaultConfiguration = AppBarConfiguration( | ||
isLarge = false, | ||
largeTitleRes = R.string.empty, | ||
scrollBehavior = TopAppBarCustomizationState.ScrollBehavior.Collapsible, | ||
isNavigationIconEnabled = true, | ||
actionCount = defaultActions.size, | ||
isOverflowMenuEnabled = false | ||
) | ||
} | ||
|
||
private val currentBackStackEntry: NavBackStackEntry? | ||
@Composable get() = navController.currentBackStackEntryAsState().value | ||
|
||
private val currentScreenRoute: String? | ||
@Composable get() = currentBackStackEntry?.destination?.route | ||
|
||
private val currentScreenRouteArgs: Bundle? | ||
@Composable get() = currentBackStackEntry?.arguments | ||
|
||
private val currentScreen: Screen? | ||
@Composable get() = currentScreenRoute?.let { getScreen(it, currentScreenRouteArgs) } | ||
|
||
private val isCustom: Boolean | ||
@Composable get() = currentScreen?.hasCustomAppBar.orElse { false } | ||
|
||
private val showNavigationIcon: Boolean | ||
@Composable get() = (isCustom && customAppBarConfiguration.value.isNavigationIconEnabled) | ||
|| (!isCustom && currentScreen?.isHome == false) | ||
|
||
val isLarge: Boolean | ||
@Composable get() = currentScreen?.isLargeAppBar == true | ||
|
||
val title: String | ||
@Composable get() = if (isCustom) { | ||
stringResource(id = customAppBarConfiguration.value.largeTitleRes) | ||
} else { | ||
currentScreen?.title?.asString().orEmpty() | ||
} | ||
|
||
val actions: List<OdsComponentContent<Nothing>> | ||
@Composable get() { | ||
val screenAppBarActions = currentScreen?.getAppBarActions { searchText.value = it }.orEmpty() | ||
return if (isCustom) { | ||
val context = LocalContext.current | ||
val customActionCount = max(0, customAppBarConfiguration.value.actionCount - AppBarAction.defaultActions.size) | ||
val customActions = NavigationItem.values() | ||
.take(customActionCount) | ||
.map { | ||
val contentDescription = stringResource(id = it.textResId) | ||
OdsTopAppBarActionButton(painter = painterResource(id = it.iconResId), contentDescription = contentDescription) { | ||
clickOnElement(context, contentDescription) | ||
} | ||
} | ||
screenAppBarActions.take(customAppBarConfiguration.value.actionCount) + customActions | ||
} else { | ||
screenAppBarActions | ||
} | ||
} | ||
|
||
val overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem> | ||
@Composable get() = if (isCustom && customAppBarConfiguration.value.isOverflowMenuEnabled) { | ||
val context = LocalContext.current | ||
LocalRecipes.current.map { recipe -> | ||
OdsTopAppBarOverflowMenuActionItem( | ||
text = recipe.title, | ||
onClick = { clickOnElement(context, recipe.title) } | ||
) | ||
} | ||
} else { | ||
emptyList() | ||
} | ||
|
||
val hasScrollBehavior: Boolean | ||
get() = customAppBarConfiguration.value.scrollBehavior != TopAppBarCustomizationState.ScrollBehavior.None | ||
|
||
@Composable | ||
fun getNavigationIcon(upPress: () -> Unit) = if (showNavigationIcon) { | ||
OdsTopAppBarNavigationIcon(Icons.Filled.ArrowBack, stringResource(id = R.string.top_app_bar_back_icon_desc), upPress) | ||
} else { | ||
null | ||
} | ||
|
||
// ---------------------------------------------------------- | ||
// AppBarManager implementation | ||
// ---------------------------------------------------------- | ||
|
||
override val searchedText: TextFieldValue | ||
get() = searchText.value | ||
|
||
override fun setCustomAppBar(appBarConfiguration: AppBarConfiguration) { | ||
customAppBarConfiguration.value = appBarConfiguration | ||
} | ||
|
||
override fun updateAppBarTabs(tabsConfiguration: TabsConfiguration) { | ||
tabsState.updateAppBarTabs(tabsConfiguration) | ||
} | ||
|
||
override fun clearAppBarTabs() { | ||
tabsState.clearAppBarTabs() | ||
} | ||
|
||
} | ||
|
||
@Composable | ||
fun rememberAppBarState( | ||
navController: NavController, | ||
searchedText: MutableState<TextFieldValue> = remember { mutableStateOf(TextFieldValue("")) }, | ||
customAppBarConfiguration: MutableState<AppBarConfiguration> = remember { mutableStateOf(CustomDefaultConfiguration) }, | ||
tabsState: AppBarTabsState = rememberAppBarTabsState() | ||
) = remember(navController, searchedText, customAppBarConfiguration, tabsState) { | ||
AppBarState(navController, searchedText, customAppBarConfiguration, tabsState) | ||
} | ||
|
||
data class AppBarConfiguration constructor( | ||
val isLarge: Boolean, | ||
val largeTitleRes: Int, | ||
val scrollBehavior: TopAppBarCustomizationState.ScrollBehavior, | ||
val isNavigationIconEnabled: Boolean, | ||
val actionCount: Int, | ||
val isOverflowMenuEnabled: Boolean | ||
) |
Oops, something went wrong.