diff --git a/DEVELOP.md b/DEVELOP.md index 50532288f..ea83b8bfa 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -18,7 +18,7 @@ repositories { ```groovy dependencies { - implementation 'com.orange.ods.android:ods-lib:0.16.0' + implementation 'com.orange.ods.android:ods-lib:0.17.0' } ``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4efd288dd..06d69dcdd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,7 +35,7 @@ android { minSdk = Versions.minSdk targetSdk = Versions.targetSdk val versionCodeProperty = project.findTypedProperty("versionCode") - versionCode = versionCodeProperty?.toInt() ?: 8 + versionCode = versionCodeProperty?.toInt() ?: 9 versionName = version.toString() val versionNameSuffixProperty = project.findTypedProperty("versionNameSuffix") versionNameSuffix = versionNameSuffixProperty diff --git a/app/src/main/java/com/orange/ods/app/ui/AppBar.kt b/app/src/main/java/com/orange/ods/app/ui/AppBar.kt new file mode 100644 index 000000000..f198a8c00 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/AppBar.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/AppBarActions.kt b/app/src/main/java/com/orange/ods/app/ui/AppBarActions.kt new file mode 100644 index 000000000..7f01fc4ee --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/AppBarActions.kt @@ -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 = + defaultActions.map { it.getOdsTopAppBarAction(onActionClick = onActionClick) } + +@Composable +fun getHomeActions(onActionClick: (AppBarAction) -> Unit): List = + listOf(getSearchAction(onActionClick)) + getDefaultActions(onActionClick = onActionClick) + +@Composable +fun getSearchFieldAction(onTextChange: (TextFieldValue) -> Unit): OdsComponentContent { + return object : OdsComponentContent() { + + @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) + ) +} diff --git a/app/src/main/java/com/orange/ods/app/ui/AppBarState.kt b/app/src/main/java/com/orange/ods/app/ui/AppBarState.kt new file mode 100644 index 000000000..cae474fc1 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/AppBarState.kt @@ -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 { 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, + private val customAppBarConfiguration: MutableState, + 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> + @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 + @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 = remember { mutableStateOf(TextFieldValue("")) }, + customAppBarConfiguration: MutableState = 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 +) \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/MainTabsState.kt b/app/src/main/java/com/orange/ods/app/ui/AppBarTabsState.kt similarity index 78% rename from app/src/main/java/com/orange/ods/app/ui/MainTabsState.kt rename to app/src/main/java/com/orange/ods/app/ui/AppBarTabsState.kt index 6c3962fd0..bf15d7311 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTabsState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/AppBarTabsState.kt @@ -22,19 +22,11 @@ import com.orange.ods.app.ui.components.tabs.MainTabsCustomizationState import com.orange.ods.app.ui.utilities.NavigationItem import com.orange.ods.app.ui.utilities.rememberSaveableMutableStateListOf -@Composable -fun rememberMainTabsState( - tabs: SnapshotStateList = rememberSaveableMutableStateListOf(), - tabIconType: MutableState = rememberSaveable { mutableStateOf(MainTabsState.DefaultConfiguration.tabIconType) }, - tabTextEnabled: MutableState = rememberSaveable { mutableStateOf(MainTabsState.DefaultConfiguration.tabTextEnabled) }, - scrollableTabs: MutableState = rememberSaveable { mutableStateOf(MainTabsState.DefaultConfiguration.scrollableTabs) } -) = - remember(tabs, tabIconType, tabTextEnabled, scrollableTabs) { - MainTabsState(tabs, tabIconType, tabTextEnabled, scrollableTabs) - } - +/** + * Tabs state source of truth. + */ @OptIn(ExperimentalFoundationApi::class) -class MainTabsState( +class AppBarTabsState( val tabs: SnapshotStateList, val tabIconType: MutableState, val tabTextEnabled: MutableState, @@ -57,11 +49,7 @@ class MainTabsState( val hasTabs: Boolean get() = tabs.isNotEmpty() - // ---------------------------------------------------------- - // Tabs state source of truth - // ---------------------------------------------------------- - - fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) { + fun updateAppBarTabs(tabsConfiguration: TabsConfiguration) { with(tabs) { clear() addAll(tabsConfiguration.tabs) @@ -72,12 +60,22 @@ class MainTabsState( scrollableTabs.value = tabsConfiguration.scrollableTabs } - fun clearTopAppBarTabs() { + fun clearAppBarTabs() { tabs.clear() pagerState = null } } +@Composable +fun rememberAppBarTabsState( + tabs: SnapshotStateList = rememberSaveableMutableStateListOf(), + tabIconType: MutableState = rememberSaveable { mutableStateOf(AppBarTabsState.DefaultConfiguration.tabIconType) }, + tabTextEnabled: MutableState = rememberSaveable { mutableStateOf(AppBarTabsState.DefaultConfiguration.tabTextEnabled) }, + scrollableTabs: MutableState = rememberSaveable { mutableStateOf(AppBarTabsState.DefaultConfiguration.scrollableTabs) } +) = remember(tabs, tabIconType, tabTextEnabled, scrollableTabs) { + AppBarTabsState(tabs, tabIconType, tabTextEnabled, scrollableTabs) +} + @OptIn(ExperimentalFoundationApi::class) data class TabsConfiguration( val scrollableTabs: Boolean, diff --git a/app/src/main/java/com/orange/ods/app/ui/AppNavGraph.kt b/app/src/main/java/com/orange/ods/app/ui/AppNavGraph.kt new file mode 100644 index 000000000..c8ddd4e63 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/AppNavGraph.kt @@ -0,0 +1,46 @@ +/* + * + * 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.navigation.NavBackStackEntry +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.orange.ods.app.ui.about.addAboutGraph +import com.orange.ods.app.ui.components.addComponentsGraph +import com.orange.ods.app.ui.guidelines.addGuidelinesGraph +import com.orange.ods.app.ui.search.SearchScreen + +/** + * Destinations used in the [MainScreen]. + */ +object MainNavigation { + const val SearchRoute = "search" +} + +/** + * Navigation graph of the application. + */ +fun NavGraphBuilder.appNavGraph( + navigateToElement: (String, Long?, NavBackStackEntry) -> Unit, + upPress: () -> Unit +) { + addBottomBarGraph(navigateToElement) + + addGuidelinesGraph() + addComponentsGraph(navigateToElement, upPress) + addAboutGraph() + + composable( + route = MainNavigation.SearchRoute + ) { from -> + SearchScreen(onResultItemClick = { route, id -> navigateToElement(route, id, from) }) + } +} diff --git a/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt b/app/src/main/java/com/orange/ods/app/ui/BottomBar.kt similarity index 66% rename from app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt rename to app/src/main/java/com/orange/ods/app/ui/BottomBar.kt index 24fa5fc52..badef7a94 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt +++ b/app/src/main/java/com/orange/ods/app/ui/BottomBar.kt @@ -20,10 +20,12 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import com.orange.ods.app.R +import com.orange.ods.app.ui.about.AboutNavigation import com.orange.ods.app.ui.about.AboutScreen import com.orange.ods.app.ui.about.UrlAboutItem import com.orange.ods.app.ui.about.aboutItems import com.orange.ods.app.ui.about.id +import com.orange.ods.app.ui.components.ComponentsNavigation import com.orange.ods.app.ui.components.ComponentsScreen import com.orange.ods.app.ui.guidelines.GuidelinesScreen import com.orange.ods.app.ui.modules.ModulesScreen @@ -33,7 +35,7 @@ import com.orange.ods.compose.component.bottomnavigation.OdsBottomNavigationItem import com.orange.ods.compose.component.bottomnavigation.OdsBottomNavigationItemIcon @Composable -fun MainBottomNavigation(items: Array, currentRoute: String, navigateToRoute: (String) -> Unit) { +fun BottomBar(items: Array, currentRoute: String, navigateToRoute: (String) -> Unit) { OdsBottomNavigation( items = items.map { item -> OdsBottomNavigationItem( @@ -46,40 +48,30 @@ fun MainBottomNavigation(items: Array, currentRoute: S ) } -fun NavGraphBuilder.addBottomNavigationGraph(navigateToElement: (String, Long?, NavBackStackEntry) -> Unit) { - composable(BottomNavigationSections.Guidelines.route) { from -> - val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder() - .prependAction(TopAppBarConfiguration.Action.Search) - .build() - LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration) +fun NavGraphBuilder.addBottomBarGraph(navigateToElement: (String, Long?, NavBackStackEntry) -> Unit) { + composable(BottomBarItem.Guidelines.route) { from -> GuidelinesScreen(onGuidelineClick = { route -> navigateToElement(route, null, from) }) } - composable(BottomNavigationSections.Components.route) { from -> - val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder() - .prependAction(TopAppBarConfiguration.Action.Search) - .build() - LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration) - ComponentsScreen(onComponentClick = { id -> navigateToElement(MainDestinations.ComponentDetailRoute, id, from) }) + composable(BottomBarItem.Components.route) { from -> + ComponentsScreen(onComponentClick = { id -> navigateToElement(ComponentsNavigation.ComponentDetailRoute, id, from) }) } - composable(BottomNavigationSections.Modules.route) { - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + composable(BottomBarItem.Modules.route) { ModulesScreen() } - composable(BottomNavigationSections.About.route) { from -> + composable(BottomBarItem.About.route) { from -> val context = LocalContext.current - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) AboutScreen(onAboutItemClick = { id -> val aboutItem = aboutItems.firstOrNull { it.id == id } if (aboutItem is UrlAboutItem) { context.launchUrl(aboutItem.url) } else { - navigateToElement(MainDestinations.AboutItemDetailRoute, id, from) + navigateToElement(AboutNavigation.AboutItemDetailRoute, id, from) } }) } } -enum class BottomNavigationSections( +enum class BottomBarItem( @StringRes val titleRes: Int, @DrawableRes val iconRes: Int, val route: String diff --git a/app/src/main/java/com/orange/ods/app/ui/MainScreen.kt b/app/src/main/java/com/orange/ods/app/ui/MainScreen.kt index ebd43fead..06a663da7 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -29,31 +30,30 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.navigation import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalCategories import com.orange.ods.app.domain.recipes.LocalRecipes -import com.orange.ods.app.ui.about.addAboutGraph -import com.orange.ods.app.ui.components.addComponentsGraph import com.orange.ods.app.ui.components.tabs.FixedTabRow import com.orange.ods.app.ui.components.tabs.ScrollableTabRow -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.OdsListItemTrailingRadioButton +import com.orange.ods.compose.text.OdsTextH6 import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.extension.orElse import com.orange.ods.theme.OdsThemeConfigurationContract import com.orange.ods.xml.theme.OdsXml import com.orange.ods.xml.utilities.extension.xml +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -73,6 +73,7 @@ fun MainScreen(themeConfigurations: Set, mainView themeConfigurations = themeConfigurations.toList() ) ) + // Change isSystemInDarkTheme() value to make switching theme working with custom color val configuration = LocalConfiguration.current.apply { isDarkModeEnabled = mainState.themeState.darkModeEnabled @@ -83,15 +84,18 @@ fun MainScreen(themeConfigurations: Set, mainView uiMode = if (mainState.themeState.darkModeEnabled) OdsXml.UiMode.Dark else OdsXml.UiMode.Light } + var changeThemeDialogVisible by remember { mutableStateOf(false) } + CompositionLocalProvider( LocalConfiguration provides configuration, - LocalMainTopAppBarManager provides mainState.topAppBarState, + LocalAppBarManager provides mainState.appBarState, LocalThemeManager provides mainState.themeState, LocalOdsGuideline provides mainState.themeState.currentThemeConfiguration.guideline, LocalRecipes provides mainViewModel.recipes, LocalCategories provides mainViewModel.categories, LocalUiFramework provides mainState.uiFramework ) { + AppBarActionsHandler(navigate = mainState.navController::navigate, onChangeThemeActionClick = { changeThemeDialogVisible = true }) OdsTheme( themeConfiguration = mainState.themeState.currentThemeConfiguration, darkThemeEnabled = configuration.isDarkModeEnabled @@ -99,14 +103,7 @@ fun MainScreen(themeConfigurations: Set, mainView 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) { + if (mainState.appBarState.isLarge) { topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection } modifier = Modifier.nestedScroll(nestedScrollConnection) @@ -121,17 +118,13 @@ fun MainScreen(themeConfigurations: Set, mainView Surface(elevation = if (isSystemInDarkTheme()) 0.dp else AppBarDefaults.TopAppBarElevation) { Column { SystemBarsColorSideEffect() - MainTopAppBar( - shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar, - topAppBarState = mainState.topAppBarState, + AppBar( + appBarState = mainState.appBarState, upPress = mainState::upPress, - onSearchActionClick = { - mainState.navController.navigate(MainDestinations.SearchRoute) - }, scrollBehavior = topBarScrollBehavior ) - if (showTabs) { - MainTabs(mainTabsState = mainState.topAppBarState.tabsState) + if (mainState.appBarState.tabsState.hasTabs) { + AppBarTabs(appBarTabsState = mainState.appBarState.tabsState) } } } @@ -142,7 +135,7 @@ fun MainScreen(themeConfigurations: Set, mainView enter = fadeIn(), exit = fadeOut() ) { - MainBottomNavigation( + BottomBar( items = mainState.bottomBarItems, currentRoute = mainState.currentRoute!!, navigateToRoute = mainState::navigateToBottomBarRoute @@ -151,12 +144,23 @@ fun MainScreen(themeConfigurations: Set, mainView } ) { innerPadding -> NavHost( - navController = mainState.navController, startDestination = MainDestinations.HomeRoute, modifier = Modifier.padding(innerPadding) + navController = mainState.navController, startDestination = BottomBarItem.Guidelines.route, modifier = Modifier.padding(innerPadding) ) { - mainNavGraph( + appNavGraph( navigateToElement = mainState::navigateToElement, - upPress = mainState::upPress, - searchedText = mainState.topAppBarState.searchedText + upPress = mainState::upPress + ) + } + + if (changeThemeDialogVisible) { + ChangeThemeDialog( + themeManager = mainState.themeState, + dismissDialog = { + changeThemeDialogVisible = false + }, + onThemeSelected = { + mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name) + } ) } } @@ -184,8 +188,8 @@ private fun SystemBarsColorSideEffect() { @OptIn(ExperimentalFoundationApi::class) @Composable -private fun MainTabs(mainTabsState: MainTabsState) { - with(mainTabsState) { +private fun AppBarTabs(appBarTabsState: AppBarTabsState) { + with(appBarTabsState) { pagerState?.let { pagerState -> // 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 @@ -209,29 +213,51 @@ private fun MainTabs(mainTabsState: MainTabsState) { } } -private fun NavGraphBuilder.mainNavGraph( - navigateToElement: (String, Long?, NavBackStackEntry) -> Unit, - upPress: () -> Unit, - searchedText: MutableState -) { - navigation( - route = MainDestinations.HomeRoute, - startDestination = BottomNavigationSections.Guidelines.route - ) { - addBottomNavigationGraph(navigateToElement) - } +@Composable +private fun AppBarActionsHandler(navigate: (String) -> Unit, onChangeThemeActionClick: () -> Unit) { + val configuration = LocalConfiguration.current + val themeManager = LocalThemeManager.current - addGuidelinesGraph() - addComponentsGraph(navigateToElement, upPress) - addAboutGraph() - - composable( - route = MainDestinations.SearchRoute - ) { from -> - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_search) - SearchScreen( - searchedText, - onResultItemClick = { route, id -> navigateToElement(route, id, from) } - ) + // Manage top app bar actions clicked + LaunchedEffect(key1 = Unit) { + Screen.appBarActionClicked.onEach { action -> + when (action) { + AppBarAction.Search -> navigate(MainNavigation.SearchRoute) + AppBarAction.ChangeTheme -> onChangeThemeActionClick() + AppBarAction.ChangeMode -> themeManager.darkModeEnabled = !configuration.isDarkModeEnabled + } + }.launchIn(this) } } + +@Composable +private fun ChangeThemeDialog(themeManager: ThemeManager, dismissDialog: () -> Unit, onThemeSelected: () -> Unit) { + var selectedThemeName by rememberSaveable { mutableStateOf(themeManager.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(com.orange.ods.R.dimen.spacing_m), bottom = dimensionResource(id = com.orange.ods.R.dimen.spacing_s)) + .padding(horizontal = dimensionResource(com.orange.ods.R.dimen.screen_horizontal_margin)) + ) + themeManager.themeConfigurations.forEach { themeConfiguration -> + OdsListItem( + text = themeConfiguration.name, + trailing = OdsListItemTrailingRadioButton( + selectedThemeName == themeConfiguration.name, + { + selectedThemeName = themeConfiguration.name + if (themeConfiguration != themeManager.currentThemeConfiguration) { + themeManager.currentThemeConfiguration = themeConfiguration + onThemeSelected() + } + dismissDialog() + } + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/MainState.kt b/app/src/main/java/com/orange/ods/app/ui/MainState.kt index c5d2ac900..9c5570fed 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainState.kt @@ -23,43 +23,10 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -/** - * Destinations used in the [MainScreen]. - */ -object MainDestinations { - const val HomeRoute = "home" - - const val GuidelineTypography = "guideline/typography" - const val GuidelineColor = "guideline/color" - const val GuidelineSpacing = "guideline/spacing" - - const val ComponentDetailRoute = "component" - const val ComponentIdKey = "componentId" - const val ComponentVariantDemoRoute = "component/variant" - const val ComponentVariantIdKey = "componentVariantId" - const val ComponentDemoRoute = "component/demo" - - const val AboutItemDetailRoute = "aboutItem" - const val AboutItemIdKey = "aboutItemId" - - const val SearchRoute = "search" -} - -@Composable -fun rememberMainState( - themeState: ThemeState, - navController: NavHostController = rememberNavController(), - topAppBarState: MainTopAppBarState = rememberMainTopAppBarState(), - uiFramework: MutableState = rememberSaveable { mutableStateOf(UiFramework.Compose) } -) = - remember(themeState, navController, topAppBarState, uiFramework) { - MainState(themeState, navController, topAppBarState, uiFramework) - } - class MainState( val themeState: ThemeState, val navController: NavHostController, - val topAppBarState: MainTopAppBarState, + val appBarState: AppBarState, val uiFramework: MutableState ) { @@ -67,7 +34,7 @@ class MainState( // BottomBar state source of truth // ---------------------------------------------------------- - val bottomBarItems = BottomNavigationSections.values() + val bottomBarItems = BottomBarItem.values() private val bottomBarRoutes = bottomBarItems.map { it.route } // Reading this attribute will cause recompositions when the bottom bar needs shown, or not. @@ -84,10 +51,7 @@ class MainState( get() = navController.currentDestination?.route fun upPress() { - with(topAppBarState) { - updateTopAppBar(MainTopAppBarState.DefaultConfiguration) - clearTopAppBarTabs() - } + appBarState.clearAppBarTabs() navController.navigateUp() } @@ -117,6 +81,16 @@ class MainState( } } +@Composable +fun rememberMainState( + themeState: ThemeState, + navController: NavHostController = rememberNavController(), + appBarState: AppBarState = rememberAppBarState(navController), + uiFramework: MutableState = rememberSaveable { mutableStateOf(UiFramework.Compose) } +) = remember(themeState, navController, appBarState, uiFramework) { + MainState(themeState, navController, appBarState, uiFramework) +} + /** * If the lifecycle is not resumed it means this NavBackStackEntry already processed a nav event. * diff --git a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt deleted file mode 100644 index 2d1043488..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ /dev/null @@ -1,240 +0,0 @@ -/* - * - * 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.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -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.platform.LocalContext -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.window.Dialog -import androidx.lifecycle.viewmodel.compose.viewModel -import com.orange.ods.app.R -import com.orange.ods.app.domain.recipes.LocalRecipes -import com.orange.ods.app.ui.components.utilities.clickOnElement -import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled -import com.orange.ods.compose.component.appbar.top.OdsLargeTopAppBarInternal -import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton -import com.orange.ods.compose.component.appbar.top.OdsTopAppBarInternal -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.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsRadioButtonTrailing -import com.orange.ods.compose.component.textfield.search.OdsSearchTextField -import com.orange.ods.compose.text.OdsTextH6 -import com.orange.ods.compose.theme.OdsTheme - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainTopAppBar( - shouldShowUpNavigationIcon: Boolean, - topAppBarState: MainTopAppBarState, - upPress: () -> Unit, - onSearchActionClick: () -> Unit, - mainViewModel: MainViewModel = viewModel(), - scrollBehavior: TopAppBarScrollBehavior? -) { - val themeManager = LocalThemeManager.current - var changeThemeDialogVisible by remember { mutableStateOf(false) } - val showSearchAction = topAppBarState.titleRes.value == R.string.navigation_item_search - - val title = stringResource(id = topAppBarState.titleRes.value) - val navigationIcon = if (shouldShowUpNavigationIcon && topAppBarState.isNavigationIconEnabled) { - OdsTopAppBarNavigationIcon(Icons.Filled.ArrowBack, stringResource(id = R.string.top_app_bar_back_icon_desc), upPress) - } else { - null - } - - val actions = if (showSearchAction) { - listOf(getTopAppBarSearchTextFieldAction(searchedText = topAppBarState.searchedText)) - } else { - getTopAppBarActions(topAppBarState, onSearchActionClick) { changeThemeDialogVisible = true } - } - - val overflowMenuActions = getTopAppBarOverflowMenuActions(topAppBarState) - - if (topAppBarState.isLarge) { - OdsLargeTopAppBarInternal( - title = title, - navigationIcon = navigationIcon, - actions = actions, - overflowMenuActions = overflowMenuActions, - scrollBehavior = if (topAppBarState.hasScrollBehavior) scrollBehavior else null - ) - } else { - OdsTopAppBarInternal( - title = title, - navigationIcon = navigationIcon, - actions = actions, - overflowMenuActions = overflowMenuActions, - elevated = false // elevation is managed in [MainScreen] cause of tabs - ) - } - - if (changeThemeDialogVisible) { - ChangeThemeDialog( - themeManager = themeManager, - dismissDialog = { - changeThemeDialogVisible = false - }, - onThemeSelected = { - mainViewModel.storeUserThemeName(themeManager.currentThemeConfiguration.name) - } - ) - } -} - -@Composable -private fun getTopAppBarSearchTextFieldAction(searchedText: MutableState): OdsComponentContent { - return object : OdsComponentContent() { - - @Composable - override fun Content(modifier: Modifier) { - val focusRequester = remember { FocusRequester() } - OdsSearchTextField( - value = searchedText.value, - onValueChange = { value -> - searchedText.value = value - }, - placeholder = stringResource(id = R.string.search_text_field_hint), - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - } - } -} - -@Composable -private fun getTopAppBarActions( - state: MainTopAppBarState, - onSearchActionClick: () -> Unit, - onChangeThemeActionClick: () -> Unit -): List { - return state.actions.value.mapNotNull { action -> - when (action) { - TopAppBarConfiguration.Action.Search -> getTopAppBarSearchAction(onClick = onSearchActionClick) - TopAppBarConfiguration.Action.Theme -> getTopAppBarChangeThemeAction(onClick = onChangeThemeActionClick) - TopAppBarConfiguration.Action.Mode -> getTopAppBarChangeModeAction() - TopAppBarConfiguration.Action.OverflowMenu -> null - is TopAppBarConfiguration.Action.Custom -> getTopAppBarCustomAction(action = action) - } - } -} - -@Composable -private fun getTopAppBarChangeThemeAction(onClick: () -> Unit): OdsTopAppBarActionButton { - return OdsTopAppBarActionButton( - onClick = onClick, - painter = painterResource(id = R.drawable.ic_palette), - contentDescription = stringResource(id = R.string.top_app_bar_action_change_mode_to_dark_desc) - ) -} - -@Composable -private fun getTopAppBarChangeModeAction(): OdsTopAppBarActionButton { - val configuration = LocalConfiguration.current - val themeManager = LocalThemeManager.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 = { themeManager.darkModeEnabled = !configuration.isDarkModeEnabled }, - painter = painterResource(id = painterRes), - contentDescription = stringResource(id = iconDesc) - ) -} - -@Composable -private fun getTopAppBarSearchAction(onClick: () -> Unit): OdsTopAppBarActionButton { - return OdsTopAppBarActionButton( - onClick = onClick, - painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource(id = R.string.search_content_description) - ) -} - -@Composable -private fun getTopAppBarOverflowMenuActions(state: MainTopAppBarState): List { - return if (state.actions.value.contains(TopAppBarConfiguration.Action.OverflowMenu)) { - val context = LocalContext.current - LocalRecipes.current.map { recipe -> - OdsTopAppBarOverflowMenuActionItem( - text = recipe.title, - onClick = { clickOnElement(context, recipe.title) } - ) - } - } else { - emptyList() - } -} - -@Composable -private fun getTopAppBarCustomAction(action: TopAppBarConfiguration.Action.Custom): OdsTopAppBarActionButton { - val context = LocalContext.current - return OdsTopAppBarActionButton( - onClick = { clickOnElement(context, action.name) }, - painter = painterResource(id = action.iconResId), - contentDescription = action.name - ) -} - -@Composable -private fun ChangeThemeDialog(themeManager: ThemeManager, dismissDialog: () -> Unit, onThemeSelected: () -> Unit) { - val selectedRadio = rememberSaveable { mutableStateOf(themeManager.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(com.orange.ods.R.dimen.spacing_m), bottom = dimensionResource(id = com.orange.ods.R.dimen.spacing_s)) - .padding(horizontal = dimensionResource(com.orange.ods.R.dimen.screen_horizontal_margin)) - ) - themeManager.themeConfigurations.forEach { themeConfiguration -> - OdsListItem( - text = themeConfiguration.name, - trailing = OdsRadioButtonTrailing( - selectedRadio = selectedRadio, - currentRadio = themeConfiguration.name, - onClick = { - if (themeConfiguration != themeManager.currentThemeConfiguration) { - themeManager.currentThemeConfiguration = themeConfiguration - onThemeSelected() - } - dismissDialog() - } - ) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt deleted file mode 100644 index f1315610a..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * - * 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.Parcelable -import androidx.annotation.DrawableRes -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.text.input.TextFieldValue -import com.orange.ods.app.R -import com.orange.ods.app.ui.components.appbars.top.TopAppBarCustomizationState -import kotlinx.parcelize.Parcelize - -val LocalMainTopAppBarManager = staticCompositionLocalOf { error("CompositionLocal LocalMainTopAppBarManager not present") } - -interface MainTopAppBarManager { - fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) - fun updateTopAppBarTitle(titleRes: Int) - - /** If set to true, a large top app bar will be displayed **/ - fun setLargeTopAppBar(value: Boolean) - - fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) - fun clearTopAppBarTabs() -} - -@Composable -fun rememberMainTopAppBarState( - titleRes: MutableState = rememberSaveable { mutableStateOf(R.string.navigation_item_guidelines) }, - actions: MutableState> = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.actions) }, - navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.isNavigationIconEnabled) }, - searchedText: MutableState = remember { mutableStateOf(TextFieldValue("")) }, - large: MutableState = rememberSaveable { mutableStateOf(false) }, - scrollBehavior: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.scrollBehavior) }, - tabsState: MainTabsState = rememberMainTabsState(), -) = - remember(titleRes, actions, searchedText, navigationIconEnabled, large, scrollBehavior, tabsState) { - MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, large, scrollBehavior, tabsState) - } - - -class MainTopAppBarState( - val titleRes: MutableState, - val actions: MutableState>, - var searchedText: MutableState, - private val navigationIconEnabled: MutableState, - private var large: MutableState, - private var scrollBehavior: MutableState, - val tabsState: MainTabsState, -) : MainTopAppBarManager { - - companion object { - val DefaultConfiguration = TopAppBarConfiguration( - isLarge = false, - scrollBehavior = TopAppBarCustomizationState.ScrollBehavior.Collapsible, - isNavigationIconEnabled = true, - actions = listOf(TopAppBarConfiguration.Action.Theme, TopAppBarConfiguration.Action.Mode) - ) - } - - // ---------------------------------------------------------- - // TopAppBar state source of truth - // ---------------------------------------------------------- - - val isLarge: Boolean - get() = large.value - - val hasScrollBehavior: Boolean - get() = scrollBehavior.value != TopAppBarCustomizationState.ScrollBehavior.None - - val isNavigationIconEnabled: Boolean - get() = navigationIconEnabled.value - - override fun setLargeTopAppBar(value: Boolean) { - this.large.value = value - } - - override fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) { - large.value = topAppBarConfiguration.isLarge - scrollBehavior.value = topAppBarConfiguration.scrollBehavior - navigationIconEnabled.value = topAppBarConfiguration.isNavigationIconEnabled - actions.value = topAppBarConfiguration.actions - } - - override fun updateTopAppBarTitle(titleRes: Int) { - this.titleRes.value = titleRes - } - - override fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) { - tabsState.updateTopAppBarTabs(tabsConfiguration) - } - - override fun clearTopAppBarTabs() { - tabsState.clearTopAppBarTabs() - } -} - -data class TopAppBarConfiguration constructor( - val isLarge: Boolean, - val scrollBehavior: TopAppBarCustomizationState.ScrollBehavior, - val isNavigationIconEnabled: Boolean, - val actions: List -) { - - sealed class Action : Parcelable { - - @Parcelize - object Search : Action() - - @Parcelize - object Theme : Action() - - @Parcelize - object Mode : Action() - - @Parcelize - object OverflowMenu : Action() - - @Parcelize - class Custom(val name: String, @DrawableRes val iconResId: Int) : Action() - } - - class Builder { - - private var isLarge = false - private var scrollBehavior = TopAppBarCustomizationState.ScrollBehavior.Collapsible - private var isNavigationIconEnabled = true - private var actions = mutableListOf() - - fun large(value: Boolean) = apply { isLarge = value } - - fun scrollBehavior(value: TopAppBarCustomizationState.ScrollBehavior) = apply { scrollBehavior = value } - - fun navigationIconEnabled(enabled: Boolean) = apply { isNavigationIconEnabled = enabled } - - fun actions(builderAction: MutableList.() -> Unit) = apply { actions.builderAction() } - - fun prependAction(action: Action) = apply { - actions.remove(action) - actions { add(0, action) } - } - - fun appendAction(action: Action) = apply { - actions.remove(action) - actions { add(action) } - } - - fun build() = TopAppBarConfiguration(isLarge, scrollBehavior, isNavigationIconEnabled, actions) - } - - fun newBuilder() = Builder().apply { - navigationIconEnabled(isNavigationIconEnabled) - large(isLarge) - scrollBehavior(scrollBehavior) - actions { - clear() - addAll(actions) - } - } -} diff --git a/app/src/main/java/com/orange/ods/app/ui/Screen.kt b/app/src/main/java/com/orange/ods/app/ui/Screen.kt new file mode 100644 index 000000000..ebf5faca8 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/Screen.kt @@ -0,0 +1,136 @@ +/* + * + * 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.runtime.Composable +import androidx.compose.ui.text.input.TextFieldValue +import com.orange.ods.app.R +import com.orange.ods.app.ui.Screen +import com.orange.ods.app.ui.components.ComponentsNavigation +import com.orange.ods.app.ui.components.ComponentsNavigation.ComponentDemoRoute +import com.orange.ods.app.ui.components.ComponentsNavigation.ComponentDetailRoute +import com.orange.ods.app.ui.components.ComponentsNavigation.ComponentVariantDemoRoute +import com.orange.ods.app.ui.components.Variant +import com.orange.ods.app.ui.guidelines.GuidelinesNavigation +import com.orange.ods.app.ui.utilities.UiString +import com.orange.ods.compose.component.content.OdsComponentContent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +/** + * Returns the [Screen] corresponding to the given [route]. + */ +fun getScreen(route: String, args: Bundle?): Screen? { + val matchElementRouteResult = Regex("^(.+)/\\{.+\\}$").find(route) + return if (matchElementRouteResult != null) { + // Specific element route -> get element id + val (routeRoot) = matchElementRouteResult.destructured + when (routeRoot) { + ComponentDetailRoute, ComponentDemoRoute -> args?.getLong(ComponentsNavigation.ComponentIdKey)?.let { Screen.Component(it) } + ComponentVariantDemoRoute -> args?.getLong(ComponentsNavigation.ComponentVariantIdKey)?.let { Screen.ComponentVariant(it) } + else -> null + } + } else { + // Simple route + val screens = Screen::class.sealedSubclasses.mapNotNull { it.objectInstance } + screens.firstOrNull { screen -> screen.route == route } + } +} + +/** + * Defines application common changing elements for each screen. + * It allows to manage top app bar display according to the screen. + */ +sealed class Screen( + val route: String, + val isLargeAppBar: Boolean = false, + val title: UiString? = null, +) { + + companion object { + private val _appBarActionClicked = MutableSharedFlow(extraBufferCapacity = 1) + val appBarActionClicked: Flow = _appBarActionClicked.asSharedFlow() + } + + val isHome: Boolean + get() = this in listOf(Guidelines, Components, Modules, About) + + val hasCustomAppBar: Boolean + get() = this is ComponentVariant && Variant.fromId(this.variantId)?.customizableTopAppBar == true + + @Composable + fun getAppBarActions(onSearchedTextChange: (TextFieldValue) -> Unit): List> = when { + isHome -> getHomeActions { action -> _appBarActionClicked.tryEmit(action) } + this is Search -> listOf(getSearchFieldAction(onSearchedTextChange)) + else -> getDefaultActions { action -> _appBarActionClicked.tryEmit(action) } + } + + // Bottom navigation screens + + data object Guidelines : Screen( + route = BottomBarItem.Guidelines.route, + title = UiString.StringResource(R.string.navigation_item_guidelines) + ) + + data object Components : Screen( + route = BottomBarItem.Components.route, + title = UiString.StringResource(R.string.navigation_item_components) + ) + + data object Modules : Screen( + route = BottomBarItem.Modules.route, + title = UiString.StringResource(R.string.navigation_item_modules) + ) + + data object About : Screen( + route = BottomBarItem.About.route, + title = UiString.StringResource(R.string.navigation_item_about) + ) + + // Guideline screens + + data object GuidelineColor : Screen( + route = GuidelinesNavigation.GuidelineColor, + title = UiString.StringResource(R.string.guideline_color), + ) + + data object GuidelineTypography : Screen( + route = GuidelinesNavigation.GuidelineTypography, + title = UiString.StringResource(R.string.guideline_typography), + ) + + data object GuidelineSpacing : Screen( + route = GuidelinesNavigation.GuidelineSpacing, + title = UiString.StringResource(R.string.guideline_spacing), + ) + + // Components screens + + data class Component(val componentId: Long) : Screen( + route = ComponentDetailRoute, + title = com.orange.ods.app.ui.components.Component.fromId(componentId)?.titleRes?.let { UiString.StringResource(it) } + ) + + data class ComponentVariant(val variantId: Long) : Screen( + route = ComponentVariantDemoRoute, + title = Variant.fromId(variantId)?.titleRes?.let { UiString.StringResource(it) }, + isLargeAppBar = Variant.fromId(variantId)?.largeTopAppBar == true + ) + + // Search screen + + data object Search : Screen( + route = MainNavigation.SearchRoute + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt b/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt index c72a98fb2..2ddd4b1d0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt @@ -23,37 +23,31 @@ val LocalThemeManager = staticCompositionLocalOf { error("Composit val LocalOdsGuideline = staticCompositionLocalOf { error("CompositionLocal LocalOdsGuideline not present") } interface ThemeManager { - val themeConfigurations: List - var currentThemeConfiguration: OdsThemeConfigurationContract - var darkModeEnabled: Boolean - } -@Composable -fun rememberThemeState( - themeConfigurations: List, - currentThemeConfiguration: MutableState, - darkModeEnabled: MutableState, -) = - remember(themeConfigurations, currentThemeConfiguration, darkModeEnabled) { - ThemeState(themeConfigurations, currentThemeConfiguration, darkModeEnabled) - } - +/** + * Theme state source of truth. + */ class ThemeState( override val themeConfigurations: List, currentThemeConfiguration: MutableState, darkModeEnabled: MutableState ) : ThemeManager { - // ---------------------------------------------------------- - // Theme state source of truth - // ---------------------------------------------------------- - override var currentThemeConfiguration by currentThemeConfiguration override var darkModeEnabled by darkModeEnabled +} + +@Composable +fun rememberThemeState( + themeConfigurations: List, + currentThemeConfiguration: MutableState, + darkModeEnabled: MutableState, +) = remember(themeConfigurations, currentThemeConfiguration, darkModeEnabled) { + ThemeState(themeConfigurations, currentThemeConfiguration, darkModeEnabled) } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/about/AboutFileScreen.kt b/app/src/main/java/com/orange/ods/app/ui/about/AboutFileScreen.kt index 34eba1e21..2143b4771 100644 --- a/app/src/main/java/com/orange/ods/app/ui/about/AboutFileScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/about/AboutFileScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.utilities.Markdown import com.orange.ods.app.ui.utilities.extension.injectLightDarkModeCss import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled @@ -37,7 +36,6 @@ fun AboutFileScreen(aboutItemId: Long) { val aboutItem = remember { aboutItems.firstOrNull { item -> item.id == aboutItemId } as? FileAboutItem } aboutItem?.let { item -> - LocalMainTopAppBarManager.current.updateTopAppBarTitle(item.titleRes) val context = LocalContext.current val configuration = LocalConfiguration.current val colors = OdsTheme.colors diff --git a/app/src/main/java/com/orange/ods/app/ui/about/AboutNavGraph.kt b/app/src/main/java/com/orange/ods/app/ui/about/AboutNavGraph.kt index b12e4388e..0d99588ba 100644 --- a/app/src/main/java/com/orange/ods/app/ui/about/AboutNavGraph.kt +++ b/app/src/main/java/com/orange/ods/app/ui/about/AboutNavGraph.kt @@ -14,18 +14,20 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.MainDestinations -import com.orange.ods.app.ui.MainTopAppBarState + +object AboutNavigation { + const val AboutItemDetailRoute = "aboutItem" + + const val AboutItemIdKey = "aboutItemId" +} fun NavGraphBuilder.addAboutGraph() { composable( - "${MainDestinations.AboutItemDetailRoute}/{${MainDestinations.AboutItemIdKey}}", - arguments = listOf(navArgument(MainDestinations.AboutItemIdKey) { type = NavType.LongType }) + "${AboutNavigation.AboutItemDetailRoute}/{${AboutNavigation.AboutItemIdKey}}", + arguments = listOf(navArgument(AboutNavigation.AboutItemIdKey) { type = NavType.LongType }) ) { backStackEntry -> - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) val arguments = requireNotNull(backStackEntry.arguments) - val aboutItemId = arguments.getLong(MainDestinations.AboutItemIdKey) + val aboutItemId = arguments.getLong(AboutNavigation.AboutItemIdKey) AboutFileScreen(aboutItemId) } } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/about/AboutScreen.kt b/app/src/main/java/com/orange/ods/app/ui/about/AboutScreen.kt index be1170a19..95604f992 100644 --- a/app/src/main/java/com/orange/ods/app/ui/about/AboutScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/about/AboutScreen.kt @@ -12,7 +12,6 @@ package com.orange.ods.app.ui.about import android.content.Context import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -28,7 +27,6 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.compat.PackageManagerCompat import com.orange.ods.app.ui.utilities.extension.versionCode @@ -40,8 +38,6 @@ import com.orange.ods.extension.orElse @Composable fun AboutScreen(onAboutItemClick: (Long) -> Unit) { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_about) - Column( modifier = Modifier .verticalScroll(rememberScrollState()) @@ -73,9 +69,9 @@ fun AboutScreen(onAboutItemClick: (Long) -> Unit) { Spacer(modifier = Modifier.height(dimensionResource(id = com.orange.ods.R.dimen.spacing_m))) for (aboutItem in aboutItems) { - OdsListItem(text = stringResource(id = aboutItem.titleRes), modifier = Modifier.clickable { + OdsListItem(text = stringResource(id = aboutItem.titleRes)) { onAboutItemClick(aboutItem.id) - }) + } } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/Component.kt b/app/src/main/java/com/orange/ods/app/ui/components/Component.kt index 44c3b897a..11c9ab891 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/Component.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/Component.kt @@ -54,6 +54,8 @@ sealed class Component( companion object { const val ImageBackgroundColor = 0xff1b1b1b + + fun fromId(componentId: Long?) = components.firstOrNull { component -> component.id == componentId } } val id: Long = Component::class.sealedSubclasses.indexOf(this::class).toLong() @@ -235,8 +237,7 @@ sealed class Component( null, R.string.component_sliders_description, composableName = OdsComposable.OdsSlider.name, - demoScreen = { _, _ -> ComponentSliders() }, - imageAlignment = Alignment.CenterEnd + demoScreen = { _, _ -> ComponentSliders() } ) object Snackbars : Component( @@ -263,8 +264,7 @@ sealed class Component( R.drawable.il_text_fields_small, R.string.component_text_fields_description, listOf(Variant.TextField, Variant.TextFieldPassword), - demoScreen = { variant, _ -> if (variant != null) ComponentTextField(variant = variant) }, - imageAlignment = Alignment.CenterEnd + demoScreen = { variant, _ -> if (variant != null) ComponentTextField(variant = variant) } ) object Tabs : Component( @@ -282,13 +282,19 @@ val components = Component::class.sealedSubclasses.mapNotNull { it.objectInstanc sealed class Variant( @StringRes val titleRes: Int, val composableName: String, - val largeTopAppBar: Boolean = false + val largeTopAppBar: Boolean = false, + val customizableTopAppBar: Boolean = false ) { + companion object { + fun fromId(variantId: Long?) = components.flatMap { it.variants }.firstOrNull { it.id == variantId } + } + val id: Long = Variant::class.sealedSubclasses.indexOf(this::class).toLong() - object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name) - object AppBarsTopLarge : Variant(R.string.component_app_bars_top_large, OdsComposable.OdsLargeTopAppBar.name, true) + object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name, customizableTopAppBar = true) + object AppBarsTopLarge : + Variant(R.string.component_app_bars_top_large, OdsComposable.OdsLargeTopAppBar.name, largeTopAppBar = true, customizableTopAppBar = true) object ButtonsPrimary : Variant(R.string.component_buttons_high_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Primary.name}") object ButtonsDefault : Variant(R.string.component_buttons_medium_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Default.name}") @@ -322,4 +328,4 @@ sealed class Variant( object TabsFixed : Variant(R.string.component_tabs_fixed, OdsComposable.OdsTabRow.name) object TabsScrollable : Variant(R.string.component_tabs_scrollable, OdsComposable.OdsScrollableTabRow.name) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDetailScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDetailScreen.kt index 7b6abd6a0..801d48acf 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDetailScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDetailScreen.kt @@ -10,7 +10,6 @@ package com.orange.ods.app.ui.components -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -27,7 +26,6 @@ import com.orange.ods.app.ui.utilities.composable.DetailScreenHeader import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsListItemIcon import com.orange.ods.compose.component.list.OdsListItemIconType -import com.orange.ods.compose.component.list.iconType @Composable fun ComponentDetailScreen( @@ -70,10 +68,9 @@ fun ComponentDetailScreen( @Composable private fun ComponentDetailLinkItem(label: String, composableName: String?, onClick: () -> Unit) { OdsListItem( - icon = { OdsListItemIcon(painter = painterResource(id = R.drawable.ic_play_outline), contentDescription = null) }, + icon = OdsListItemIcon(OdsListItemIconType.Icon, painterResource(id = R.drawable.ic_play_outline), ""), text = label, secondaryText = composableName, - modifier = Modifier - .iconType(OdsListItemIconType.Icon) - .clickable { onClick() }) + onClick = onClick + ) } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentsNavGraph.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentsNavGraph.kt index 7197ee5d4..069d66ca3 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentsNavGraph.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentsNavGraph.kt @@ -16,60 +16,58 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.MainDestinations -import com.orange.ods.app.ui.MainTopAppBarState + +object ComponentsNavigation { + const val ComponentDetailRoute = "component" + const val ComponentVariantDemoRoute = "component/variant" + const val ComponentDemoRoute = "component/demo" + + const val ComponentIdKey = "componentId" + const val ComponentVariantIdKey = "componentVariantId" +} fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBackStackEntry) -> Unit, upPress: () -> Unit) { composable( - "${MainDestinations.ComponentDetailRoute}/{${MainDestinations.ComponentIdKey}}", - arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) + "${ComponentsNavigation.ComponentDetailRoute}/{${ComponentsNavigation.ComponentIdKey}}", + arguments = listOf(navArgument(ComponentsNavigation.ComponentIdKey) { type = NavType.LongType }) ) { from -> val arguments = requireNotNull(from.arguments) - val routeComponentId = arguments.getLong(MainDestinations.ComponentIdKey) + val routeComponentId = arguments.getLong(ComponentsNavigation.ComponentIdKey) - val component = remember(routeComponentId) { components.firstOrNull { component -> component.id == routeComponentId } } + val component = remember(routeComponentId) { Component.fromId(routeComponentId) } component?.let { - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) - LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) - ComponentDetailScreen( component = component, - onVariantClick = { variantId -> navigateToElement(MainDestinations.ComponentVariantDemoRoute, variantId, from) }, - onDemoClick = { navigateToElement(MainDestinations.ComponentDemoRoute, routeComponentId, from) } + onVariantClick = { variantId -> navigateToElement(ComponentsNavigation.ComponentVariantDemoRoute, variantId, from) }, + onDemoClick = { navigateToElement(ComponentsNavigation.ComponentDemoRoute, routeComponentId, from) } ) } } composable( - "${MainDestinations.ComponentVariantDemoRoute}/{${MainDestinations.ComponentVariantIdKey}}", - arguments = listOf(navArgument(MainDestinations.ComponentVariantIdKey) { type = NavType.LongType }) + "${ComponentsNavigation.ComponentVariantDemoRoute}/{${ComponentsNavigation.ComponentVariantIdKey}}", + arguments = listOf(navArgument(ComponentsNavigation.ComponentVariantIdKey) { type = NavType.LongType }) ) { from -> val arguments = requireNotNull(from.arguments) - val routeVariantId = arguments.getLong(MainDestinations.ComponentVariantIdKey) - val variant = remember(routeVariantId) { components.flatMap { it.variants }.firstOrNull { it.id == routeVariantId } } + val routeVariantId = arguments.getLong(ComponentsNavigation.ComponentVariantIdKey) + val variant = remember(routeVariantId) { Variant.fromId(routeVariantId) } val component = components.firstOrNull { it.variants.contains(variant) } if (variant != null && component != null) { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) - LocalMainTopAppBarManager.current.setLargeTopAppBar(variant.largeTopAppBar) component.demoScreen(variant, upPress) } } composable( - "${MainDestinations.ComponentDemoRoute}/{${MainDestinations.ComponentIdKey}}", - arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) + "${ComponentsNavigation.ComponentDemoRoute}/{${ComponentsNavigation.ComponentIdKey}}", + arguments = listOf(navArgument(ComponentsNavigation.ComponentIdKey) { type = NavType.LongType }) ) { from -> - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) - val arguments = requireNotNull(from.arguments) - val componentId = arguments.getLong(MainDestinations.ComponentIdKey) + val componentId = arguments.getLong(ComponentsNavigation.ComponentIdKey) val component = remember { components.firstOrNull { it.id == componentId } } component?.let { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) component.demoScreen(null, upPress) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentsScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentsScreen.kt index 8b2fcf4e1..7fbfa88e4 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentsScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentsScreen.kt @@ -27,8 +27,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.compose.component.card.OdsCardImage import com.orange.ods.compose.component.card.OdsSmallCard @@ -37,7 +35,6 @@ import com.orange.ods.extension.orElse @Composable fun ComponentsScreen(onComponentClick: (Long) -> Unit) { val context = LocalContext.current - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_components) val scrollState = rememberScrollState() Column( modifier = Modifier diff --git a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBar.kt b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBar.kt index ff4183ad1..75a3314ca 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBar.kt @@ -32,13 +32,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.MainTopAppBarState -import com.orange.ods.app.ui.TopAppBarConfiguration +import com.orange.ods.app.ui.AppBarConfiguration +import com.orange.ods.app.ui.LocalAppBarManager import com.orange.ods.app.ui.components.Variant import com.orange.ods.app.ui.components.utilities.ComponentCountRow import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold -import com.orange.ods.app.ui.utilities.NavigationItem import com.orange.ods.app.ui.utilities.composable.* import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton @@ -46,11 +44,10 @@ import com.orange.ods.compose.component.appbar.top.OdsTopAppBarNavigationIcon import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.text.OdsTextBody2 import com.orange.ods.compose.text.OdsTextCaption import com.orange.ods.compose.theme.OdsTheme -import kotlin.math.max @OptIn(ExperimentalMaterialApi::class) @Composable @@ -58,25 +55,16 @@ fun ComponentTopAppBar(variant: Variant) { val customizationState = rememberTopAppBarCustomizationState(large = remember { mutableStateOf(variant == Variant.AppBarsTopLarge) }) with(customizationState) { - val customActionCount = max(0, actionCount.value - MainTopAppBarState.DefaultConfiguration.actions.size) - val customActions = NavigationItem.values() - .take(customActionCount) - .map { TopAppBarConfiguration.Action.Custom(stringResource(id = it.textResId), it.iconResId) } - val topAppBarConfiguration = TopAppBarConfiguration.Builder() - .large(isLarge) - .scrollBehavior(scrollBehavior.value) - .navigationIconEnabled(isNavigationIconEnabled) - .actions { - addAll(MainTopAppBarState.DefaultConfiguration.actions.take(actionCount.value)) - addAll(customActions) - if (isOverflowMenuEnabled) add(TopAppBarConfiguration.Action.OverflowMenu) - } - .build() + val appBarConfiguration = AppBarConfiguration( + isLarge = isLarge, + largeTitleRes = if (isLarge) title.value.titleRes else R.string.component_app_bars_top_regular, + scrollBehavior = scrollBehavior.value, + isNavigationIconEnabled = isNavigationIconEnabled, + actionCount = actionCount.value, + isOverflowMenuEnabled = isOverflowMenuEnabled + ) - with(LocalMainTopAppBarManager.current) { - updateTopAppBar(topAppBarConfiguration) - if (isLarge) updateTopAppBarTitle(title.value.titleResId) - } + LocalAppBarManager.current.setCustomAppBar(appBarConfiguration) ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), @@ -204,9 +192,7 @@ private fun CustomizationBottomSheetContent(customizationState: TopAppBarCustomi } OdsListItem( text = stringResource(id = R.string.component_app_bars_top_element_navigation_icon), - trailing = OdsSwitchTrailing( - checked = navigationIconEnabled - ) + trailing = OdsListItemTrailingSwitch(navigationIconEnabled.value, { navigationIconEnabled.value = it }) ) ComponentCountRow( modifier = Modifier.padding(start = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), @@ -219,10 +205,7 @@ private fun CustomizationBottomSheetContent(customizationState: TopAppBarCustomi ) OdsListItem( text = stringResource(id = R.string.component_app_bars_top_element_overflow_menu), - trailing = OdsSwitchTrailing( - checked = overflowMenuEnabled, - enabled = isOverflowMenuSwitchEnabled - ) + trailing = OdsListItemTrailingSwitch(overflowMenuEnabled.value, { overflowMenuEnabled.value = it }, isOverflowMenuSwitchEnabled) ) if (isLarge) { Subtitle(textRes = R.string.component_element_title, horizontalPadding = true) @@ -251,7 +234,7 @@ private fun CustomizationBottomSheetContent(customizationState: TopAppBarCustomi @Composable private fun BlinkingChevronDown(modifier: Modifier = Modifier) { - val infiniteTransition = rememberInfiniteTransition() + val infiniteTransition = rememberInfiniteTransition(label = "") val animationSpec = infiniteRepeatable( animation = tween(1000), repeatMode = RepeatMode.Reverse @@ -260,14 +243,16 @@ private fun BlinkingChevronDown(modifier: Modifier = Modifier) { val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 1.2f, - animationSpec = animationSpec + animationSpec = animationSpec, + label = "" ) val alpha by infiniteTransition.animateFloat( initialValue = 0.4f, targetValue = 1f, - animationSpec = animationSpec + animationSpec = animationSpec, + label = "" ) Icon( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/TopAppBarCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/TopAppBarCustomizationState.kt index 18272b2a3..a864a4fe0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/TopAppBarCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/TopAppBarCustomizationState.kt @@ -16,23 +16,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import com.orange.ods.app.R -import com.orange.ods.app.ui.MainTopAppBarState -import com.orange.ods.app.ui.TopAppBarConfiguration +import com.orange.ods.app.ui.AppBarState @Composable fun rememberTopAppBarCustomizationState( large: MutableState, - navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.isNavigationIconEnabled) }, - actionCount: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.actions.count()) }, - overflowMenuEnabled: MutableState = rememberSaveable { - mutableStateOf( - MainTopAppBarState.DefaultConfiguration.actions.contains( - TopAppBarConfiguration.Action.OverflowMenu - ) - ) - }, + navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(AppBarState.CustomDefaultConfiguration.isNavigationIconEnabled) }, + actionCount: MutableState = rememberSaveable { mutableStateOf(AppBarState.CustomDefaultConfiguration.actionCount) }, + overflowMenuEnabled: MutableState = rememberSaveable { mutableStateOf(AppBarState.CustomDefaultConfiguration.isOverflowMenuEnabled) }, titleLineCount: MutableState = rememberSaveable { mutableStateOf(TopAppBarCustomizationState.Title.Short) }, - scrollBehavior: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.scrollBehavior) } + scrollBehavior: MutableState = rememberSaveable { mutableStateOf(AppBarState.CustomDefaultConfiguration.scrollBehavior) } ) = remember(large, navigationIconEnabled, actionCount, overflowMenuEnabled, titleLineCount, scrollBehavior) { TopAppBarCustomizationState(large, navigationIconEnabled, actionCount, overflowMenuEnabled, titleLineCount, scrollBehavior) @@ -46,7 +39,7 @@ class TopAppBarCustomizationState( val title: MutableState, val scrollBehavior: MutableState<ScrollBehavior> ) { - enum class Title(val titleResId: Int) { + enum class Title(val titleRes: Int) { Short(R.string.component_app_bars_top_large_title_short_value), TwoLines(R.string.component_app_bars_top_large_title_two_lines_value), Long(R.string.component_app_bars_top_large_title_long_value) @@ -62,7 +55,7 @@ class TopAppBarCustomizationState( val isLarge: Boolean get() = large.value - + val isLargeCollapsible: Boolean get() = large.value && scrollBehavior.value == ScrollBehavior.Collapsible diff --git a/app/src/main/java/com/orange/ods/app/ui/components/banners/ComponentBanners.kt b/app/src/main/java/com/orange/ods/app/ui/components/banners/ComponentBanners.kt index c1e6c791d..69a5f955d 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/banners/ComponentBanners.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/banners/ComponentBanners.kt @@ -42,7 +42,7 @@ import com.orange.ods.compose.component.banner.OdsBanner import com.orange.ods.compose.component.banner.OdsBannerButton import com.orange.ods.compose.component.banner.OdsBannerImage import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.extension.ifNotNull @OptIn(ExperimentalMaterialApi::class) @@ -76,7 +76,7 @@ fun ComponentBanners() { ) OdsListItem( text = stringResource(id = R.string.component_banner_image), - trailing = OdsSwitchTrailing(checked = imageChecked) + trailing = OdsListItemTrailingSwitch(checked = imageChecked.value, { imageChecked.value = it }) ) } ) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/bottomnavigation/ComponentBottomNavigation.kt b/app/src/main/java/com/orange/ods/app/ui/components/bottomnavigation/ComponentBottomNavigation.kt index 6cc4879b9..5f29f9069 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/bottomnavigation/ComponentBottomNavigation.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/bottomnavigation/ComponentBottomNavigation.kt @@ -27,6 +27,8 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.orange.ods.app.R +import com.orange.ods.app.databinding.OdsBottomNavigationBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation.MaxNavigationItemCount import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation.MinNavigationItemCount import com.orange.ods.app.ui.components.utilities.ComponentCountRow @@ -53,6 +55,22 @@ fun ComponentBottomNavigation() { val selectedNavigationItemCount = rememberSaveable { mutableStateOf(MinNavigationItemCount) } val selectedNavigationItem = remember { mutableStateOf(navigationItems[0]) } + val bottomNavigationItems = navigationItems.take(selectedNavigationItemCount.value).map { item -> + val label = stringResource(id = item.textResId) + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = item.iconResId), + contentDescription = "" + ), + label = label, + selected = selectedNavigationItem.value.textResId == item.textResId, + onClick = { + selectedNavigationItem.value = item + clickOnElement(context, label) + } + ) + } + ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { @@ -72,26 +90,15 @@ fun ComponentBottomNavigation() { .verticalScroll(rememberScrollState()) .padding(top = dimensionResource(id = com.orange.ods.R.dimen.screen_vertical_margin)) ) { - OdsBottomNavigation( - items = navigationItems.take(selectedNavigationItemCount.value).map { navigationItem -> - val label = stringResource(id = navigationItem.textResId) - OdsBottomNavigationItem( - icon = OdsBottomNavigationItemIcon( - painter = painterResource(id = navigationItem.iconResId), - contentDescription = "" - ), - label = label, - selected = selectedNavigationItem.value.textResId == navigationItem.textResId, - onClick = { - selectedNavigationItem.value = navigationItem - clickOnElement(context, label) - } - ) - } - ) + UiFramework<OdsBottomNavigationBinding>(compose = { + OdsBottomNavigation(items = bottomNavigationItems) + }, xml = { + this.odsBottomNavigation.items = bottomNavigationItems + }) CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( name = OdsComposable.OdsBottomNavigation.name, @@ -113,4 +120,4 @@ fun ComponentBottomNavigation() { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ComponentButtons.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ComponentButtons.kt index c6e46bc87..9aaa13680 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ComponentButtons.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ComponentButtons.kt @@ -37,7 +37,7 @@ import com.orange.ods.compose.component.button.OdsTextButtonStyle import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.text.OdsTextBody2 import com.orange.ods.compose.theme.OdsDisplaySurface import com.orange.ods.compose.theme.OdsTheme @@ -67,8 +67,14 @@ fun ComponentButtons(variant: Variant) { onValueChange = { value -> buttonStyle.value = value }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( - OdsChoiceChip(text = stringResource(id = R.string.component_button_style_functional_positive), value = OdsButtonStyle.FunctionalPositive), - OdsChoiceChip(text = stringResource(id = R.string.component_button_style_functional_negative), value = OdsButtonStyle.FunctionalNegative) + OdsChoiceChip( + text = stringResource(id = R.string.component_button_style_functional_positive), + value = OdsButtonStyle.FunctionalPositive + ), + OdsChoiceChip( + text = stringResource(id = R.string.component_button_style_functional_negative), + value = OdsButtonStyle.FunctionalNegative + ) ) ) } @@ -79,7 +85,7 @@ fun ComponentButtons(variant: Variant) { onValueChange = { value -> textButtonStyle.value = value }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( - OdsChoiceChip(text = stringResource(id = R.string.component_button_style_primary), value = OdsTextButtonStyle.Primary), + OdsChoiceChip(text = stringResource(id = R.string.component_button_style_primary), value = OdsTextButtonStyle.Primary), OdsChoiceChip(text = stringResource(id = R.string.component_button_style_default), value = OdsTextButtonStyle.Default) ) ) @@ -97,7 +103,7 @@ fun ComponentButtons(variant: Variant) { OdsListItem( text = stringResource(id = R.string.component_buttons_text_toggle_group_same_weight), - trailing = OdsSwitchTrailing(checked = sameItemsWeight) + trailing = OdsListItemTrailingSwitch(sameItemsWeight.value, { sameItemsWeight.value = it }) ) } else -> {} @@ -106,17 +112,17 @@ fun ComponentButtons(variant: Variant) { if (variant != Variant.ButtonsTextToggleGroup) { OdsListItem( text = stringResource(id = R.string.component_element_icon), - trailing = OdsSwitchTrailing(checked = leadingIcon) + trailing = OdsListItemTrailingSwitch(leadingIcon.value, { leadingIcon.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_button_full_screen_width), - trailing = OdsSwitchTrailing(checked = fullScreenWidth) + trailing = OdsListItemTrailingSwitch(fullScreenWidth.value, { fullScreenWidth.value = it }) ) } OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) + trailing = OdsListItemTrailingSwitch(enabled.value, { enabled.value = it }) ) }) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ComponentButtonsIcons.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ComponentButtonsIcons.kt index baf3e9b3a..71b6e6cd1 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ComponentButtonsIcons.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ComponentButtonsIcons.kt @@ -22,7 +22,7 @@ import com.orange.ods.app.ui.components.Variant import com.orange.ods.app.ui.components.utilities.ComponentCountRow import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable @@ -46,7 +46,7 @@ fun ComponentButtonsIcons(variant: Variant) { } OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) + trailing = OdsListItemTrailingSwitch(enabled.value, { enabled.value = it }) ) }) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/cards/ComponentCard.kt b/app/src/main/java/com/orange/ods/app/ui/components/cards/ComponentCard.kt index 57fdb17e6..3afc1dd13 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/cards/ComponentCard.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/cards/ComponentCard.kt @@ -26,7 +26,7 @@ import com.orange.ods.compose.component.card.OdsHorizontalCardImagePosition import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable @@ -39,12 +39,12 @@ fun ComponentCard(variant: Variant) { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_card_clickable), - trailing = OdsSwitchTrailing(checked = clickable) + trailing = OdsListItemTrailingSwitch(clickable.value, { clickable.value = it }) ) if (variant == Variant.CardVerticalHeaderFirst) { OdsListItem( text = stringResource(id = R.string.component_element_thumbnail), - trailing = OdsSwitchTrailing(checked = thumbnailChecked) + trailing = OdsListItemTrailingSwitch(thumbnailChecked.value, { thumbnailChecked.value = it }) ) } else if (variant == Variant.CardHorizontal) { Subtitle(textRes = R.string.component_card_horizontal_image_position, horizontalPadding = true) @@ -53,19 +53,25 @@ fun ComponentCard(variant: Variant) { onValueChange = { value -> imagePosition.value = value }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( - OdsChoiceChip(text = stringResource(id = R.string.component_card_horizontal_image_position_start), value = OdsHorizontalCardImagePosition.Start), - OdsChoiceChip(text = stringResource(id = R.string.component_card_horizontal_image_position_end), value = OdsHorizontalCardImagePosition.End) + OdsChoiceChip( + text = stringResource(id = R.string.component_card_horizontal_image_position_start), + value = OdsHorizontalCardImagePosition.Start + ), + OdsChoiceChip( + text = stringResource(id = R.string.component_card_horizontal_image_position_end), + value = OdsHorizontalCardImagePosition.End + ) ) ) } OdsListItem( text = stringResource(id = R.string.component_element_subtitle), - trailing = OdsSwitchTrailing(checked = subtitleChecked) + trailing = OdsListItemTrailingSwitch(subtitleChecked.value, { subtitleChecked.value = it }) ) if (variant in listOf(Variant.CardVerticalHeaderFirst, Variant.CardVerticalImageFirst, Variant.CardHorizontal)) { OdsListItem( text = stringResource(id = R.string.component_element_text), - trailing = OdsSwitchTrailing(checked = textChecked) + trailing = OdsListItemTrailingSwitch(textChecked.value, { textChecked.value = it }) ) ComponentCountRow( title = stringResource(id = R.string.component_card_action_button_count), @@ -81,7 +87,7 @@ fun ComponentCard(variant: Variant) { if (!hasFirstButton) dividerChecked.value = false OdsListItem( text = stringResource(id = R.string.component_element_divider), - trailing = OdsSwitchTrailing(checked = dividerChecked, enabled = hasFirstButton) + trailing = OdsListItemTrailingSwitch(dividerChecked.value, { dividerChecked.value = it }, hasFirstButton) ) } }) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/checkboxes/ComponentCheckboxes.kt b/app/src/main/java/com/orange/ods/app/ui/components/checkboxes/ComponentCheckboxes.kt index c47efd402..5efeef414 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/checkboxes/ComponentCheckboxes.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/checkboxes/ComponentCheckboxes.kt @@ -17,8 +17,10 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -28,14 +30,14 @@ import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSh import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable -import com.orange.ods.compose.component.list.OdsCheckboxTrailing import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingCheckbox +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable fun ComponentCheckboxes() { - val enabled = rememberSaveable { mutableStateOf(true) } + var enabled by rememberSaveable { mutableStateOf(true) } val recipes = LocalRecipes.current val recipe = rememberSaveable { recipes.filter { it.ingredients.count() >= 3 }.random() } @@ -44,7 +46,7 @@ fun ComponentCheckboxes() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) + trailing = OdsListItemTrailingSwitch(enabled, { enabled = it }) ) }) { Column( @@ -53,12 +55,10 @@ fun ComponentCheckboxes() { .padding(vertical = dimensionResource(id = com.orange.ods.R.dimen.spacing_s)) ) { recipe.ingredients.take(3).forEachIndexed { index, ingredient -> + var checked by rememberSaveable { mutableStateOf(index == 0) } OdsListItem( text = ingredient.food.name, - trailing = OdsCheckboxTrailing( - checked = rememberSaveable { mutableStateOf(index == 0) }, - enabled = enabled.value - ) + trailing = OdsListItemTrailingCheckbox(checked, { checked = it }, enabled) ) } @@ -71,7 +71,7 @@ fun ComponentCheckboxes() { parameters = { checked(false) onCheckedChange() - if (!enabled.value) enabled(false) + if (!enabled) enabled(false) } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/chips/Chip.kt b/app/src/main/java/com/orange/ods/app/ui/components/chips/Chip.kt index dbbbd51f4..3c696f9be 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/chips/Chip.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/chips/Chip.kt @@ -44,7 +44,7 @@ import com.orange.ods.compose.component.chip.OdsChipLeadingIcon import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.text.OdsTextBody2 @OptIn(ExperimentalMaterialApi::class) @@ -74,9 +74,7 @@ fun Chip(variant: Variant) { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing( - checked = enabled - ) + trailing = OdsListItemTrailingSwitch(enabled.value, { enabled.value = it }) ) }) { ChipTypeDemo(chipType.value) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/chips/ChipFilter.kt b/app/src/main/java/com/orange/ods/app/ui/components/chips/ChipFilter.kt index 5a802cefc..fb7ee850c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/chips/ChipFilter.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/chips/ChipFilter.kt @@ -41,7 +41,7 @@ import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.chip.OdsFilterChip import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class, ExperimentalLayoutApi::class) @Composable @@ -68,9 +68,7 @@ fun ChipFilter() { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing( - checked = enabled - ) + trailing = OdsListItemTrailingSwitch(enabled.value, { enabled.value = it }) ) }) { var selectedChipIndexes by rememberSaveable { mutableStateOf(emptySet<Int>()) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/dialogs/ComponentDialog.kt b/app/src/main/java/com/orange/ods/app/ui/components/dialogs/ComponentDialog.kt index 823a97625..535ea6414 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/dialogs/ComponentDialog.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/dialogs/ComponentDialog.kt @@ -33,79 +33,81 @@ import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.dialog.OdsAlertDialog import com.orange.ods.compose.component.dialog.OdsAlertDialogButton import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable fun ComponentDialog() { val customizationState = rememberDialogCustomizationState() - val closeDialogAction = { customizationState.openDialog.value = false } - val context = LocalContext.current - val recipes = LocalRecipes.current.filter { it.description.isNotBlank() } + with(customizationState) { + val closeDialogAction = { openDialog.value = false } + val context = LocalContext.current + val recipes = LocalRecipes.current.filter { it.description.isNotBlank() } - ComponentCustomizationBottomSheetScaffold( - bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), - bottomSheetContent = { - OdsListItem( - text = stringResource(id = R.string.component_element_title), - trailing = OdsSwitchTrailing(checked = customizationState.titleChecked) - ) - OdsListItem( - text = stringResource(id = R.string.component_dialog_element_dismiss_button), - trailing = OdsSwitchTrailing(checked = customizationState.dismissButtonChecked) - ) - }) { - val confirmButtonText = - stringResource(id = if (customizationState.isDismissButtonChecked) R.string.component_dialog_action_confirm else R.string.component_dialog_action_ok) - val dismissButtonText = stringResource(id = R.string.component_dialog_action_dismiss) - val recipe = rememberSaveable { recipes.random() } - - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { - ComponentLaunchContentColumn(textRes = R.string.component_dialog_customize, buttonLabelRes = R.string.component_dialog_open) { - customizationState.openDialog.value = true - } + ComponentCustomizationBottomSheetScaffold( + bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), + bottomSheetContent = { + OdsListItem( + text = stringResource(id = R.string.component_element_title), + trailing = OdsListItemTrailingSwitch(titleChecked.value, { titleChecked.value = it }) + ) + OdsListItem( + text = stringResource(id = R.string.component_dialog_element_dismiss_button), + trailing = OdsListItemTrailingSwitch(dismissButtonChecked.value, { dismissButtonChecked.value = it }) + ) + }) { + val confirmButtonText = + stringResource(id = if (isDismissButtonChecked) R.string.component_dialog_action_confirm else R.string.component_dialog_action_ok) + val dismissButtonText = stringResource(id = R.string.component_dialog_action_dismiss) + val recipe = rememberSaveable { recipes.random() } - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)) + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) ) { - FunctionCallCode( - name = OdsComposable.OdsAlertDialog.name, - exhaustiveParameters = false, - parameters = { - simple("text", "<dialog text>") - classInstance<OdsAlertDialogButton>("confirmButton") { - text(confirmButtonText) - onClick() - } - if (customizationState.isTitleChecked) string("title", recipe.title) - if (customizationState.isDismissButtonChecked) { - classInstance<OdsAlertDialogButton>("dismissButton") { - text(dismissButtonText) + ComponentLaunchContentColumn(textRes = R.string.component_dialog_customize, buttonLabelRes = R.string.component_dialog_open) { + openDialog.value = true + } + + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)) + ) { + FunctionCallCode( + name = OdsComposable.OdsAlertDialog.name, + exhaustiveParameters = false, + parameters = { + simple("text", "<dialog text>") + classInstance<OdsAlertDialogButton>("confirmButton") { + text(confirmButtonText) onClick() } + if (isTitleChecked) string("title", recipe.title) + if (isDismissButtonChecked) { + classInstance<OdsAlertDialogButton>("dismissButton") { + text(dismissButtonText) + onClick() + } + } } - } - ) - } + ) + } - if (customizationState.shouldOpenDialog) { - OdsAlertDialog( - title = if (customizationState.isTitleChecked) recipe.title else null, - text = recipe.description, - confirmButton = OdsAlertDialogButton(confirmButtonText) { - clickOnElement(context = context, clickedElement = confirmButtonText) - closeDialogAction() - }, - dismissButton = if (customizationState.isDismissButtonChecked) { - OdsAlertDialogButton(dismissButtonText) { - clickOnElement(context = context, clickedElement = dismissButtonText) + if (shouldOpenDialog) { + OdsAlertDialog( + title = if (isTitleChecked) recipe.title else null, + text = recipe.description, + confirmButton = OdsAlertDialogButton(confirmButtonText) { + clickOnElement(context = context, clickedElement = confirmButtonText) closeDialogAction() - } - } else null, - ) + }, + dismissButton = if (isDismissButtonChecked) { + OdsAlertDialogButton(dismissButtonText) { + clickOnElement(context = context, clickedElement = dismissButtonText) + closeDialogAction() + } + } else null, + ) + } } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/floatingactionbuttons/ComponentFloatingActionButton.kt b/app/src/main/java/com/orange/ods/app/ui/components/floatingactionbuttons/ComponentFloatingActionButton.kt index 721e441fb..dc8b52538 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/floatingactionbuttons/ComponentFloatingActionButton.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/floatingactionbuttons/ComponentFloatingActionButton.kt @@ -38,7 +38,7 @@ import com.orange.ods.compose.component.button.OdsFloatingActionButtonIcon import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable @@ -106,11 +106,11 @@ fun ComponentFloatingActionButton() { ) OdsListItem( text = stringResource(id = R.string.component_element_text), - trailing = OdsSwitchTrailing(checked = text, enabled = isTextEnabled) + trailing = OdsListItemTrailingSwitch(text.value, { text.value = it }, isTextEnabled) ) OdsListItem( text = stringResource(id = R.string.component_floating_action_button_full_screen_width), - trailing = OdsSwitchTrailing(checked = fullScreenWidth, enabled = isFullScreenWidthEnabled) + trailing = OdsListItemTrailingSwitch(fullScreenWidth.value, { fullScreenWidth.value = it }, isFullScreenWidthEnabled) ) }) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imagetile/ComponentImageTile.kt b/app/src/main/java/com/orange/ods/app/ui/components/imagetile/ComponentImageTile.kt index d948f6090..9817f9b9a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imagetile/ComponentImageTile.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imagetile/ComponentImageTile.kt @@ -48,7 +48,7 @@ import com.orange.ods.compose.component.imagetile.OdsImageTileIconToggleButton import com.orange.ods.compose.component.imagetile.OdsImageTileImage import com.orange.ods.compose.component.imagetile.OdsImageTileLegendAreaDisplayType import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable @@ -88,7 +88,7 @@ fun ComponentImageTile() { ) OdsListItem( text = stringResource(id = R.string.component_element_icon), - trailing = OdsSwitchTrailing(checked = iconDisplayed, enabled = hasText) + trailing = OdsListItemTrailingSwitch(iconDisplayed.value, { iconDisplayed.value = it }, hasText) ) }) { Column( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/listitem/ComponentListItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/listitem/ComponentListItem.kt index 63d00aee7..5d8012dc0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/listitem/ComponentListItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/listitem/ComponentListItem.kt @@ -10,13 +10,14 @@ package com.orange.ods.app.ui.components.listitem -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext @@ -41,17 +42,16 @@ import com.orange.ods.app.ui.utilities.composable.Subtitle import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow -import com.orange.ods.compose.component.list.OdsCaptionTrailing -import com.orange.ods.compose.component.list.OdsCheckboxTrailing -import com.orange.ods.compose.component.list.OdsIconTrailing import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsListItemIcon -import com.orange.ods.compose.component.list.OdsListItemIconScope import com.orange.ods.compose.component.list.OdsListItemIconType import com.orange.ods.compose.component.list.OdsListItemTrailing -import com.orange.ods.compose.component.list.OdsSwitchTrailing -import com.orange.ods.compose.component.list.iconType -import com.orange.ods.extension.orElse +import com.orange.ods.compose.component.list.OdsListItemTrailingCaption +import com.orange.ods.compose.component.list.OdsListItemTrailingCheckbox +import com.orange.ods.compose.component.list.OdsListItemTrailingIcon +import com.orange.ods.compose.component.list.OdsListItemTrailingRadioButton +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch +import com.orange.ods.extension.ifNotNull @OptIn(ExperimentalMaterialApi::class) @Composable @@ -68,7 +68,7 @@ fun ComponentListItem() { private fun ComponentListItemBottomSheetContent(listItemCustomizationState: ListItemCustomizationState) { ComponentCountRow( modifier = Modifier.padding(start = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), - title = stringResource(id = R.string.component_list_item_size), + title = stringResource(id = R.string.component_list_item_line_count), count = listItemCustomizationState.lineCount, minusIconContentDescription = stringResource(id = R.string.component_list_item_remove_line), plusIconContentDescription = stringResource(id = R.string.component_list_item_add_line), @@ -78,15 +78,15 @@ private fun ComponentListItemBottomSheetContent(listItemCustomizationState: List Subtitle(textRes = R.string.component_list_leading, horizontalPadding = true) OdsChoiceChipsFlowRow( - value = listItemCustomizationState.selectedLeading.value, - onValueChange = { value -> listItemCustomizationState.selectedLeading.value = value }, + value = listItemCustomizationState.selectedIconType.value, + onValueChange = { value -> listItemCustomizationState.selectedIconType.value = value }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( - OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_none), value = ListItemCustomizationState.Leading.None), - OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_icon), value = ListItemCustomizationState.Leading.Icon), - OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_circular_image), value = ListItemCustomizationState.Leading.CircularImage), - OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_square_image), value = ListItemCustomizationState.Leading.SquareImage), - OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_wide_image), value = ListItemCustomizationState.Leading.WideImage), + OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_none), value = null), + OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_icon), value = OdsListItemIconType.Icon), + OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_circular_image), value = OdsListItemIconType.CircularImage), + OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_square_image), value = OdsListItemIconType.SquareImage), + OdsChoiceChip(text = stringResource(id = R.string.component_list_leading_wide_image), value = OdsListItemIconType.WideImage), ) ) @@ -110,34 +110,23 @@ private fun ComponentListItemContent(listItemCustomizationState: ListItemCustomi resetTrailing() } - val modifier = Modifier - .let { modifier -> - iconType?.let { modifier.iconType(it) }.orElse { modifier } - } val singleLineSecondaryText = lineCount.value == 2 val text = recipe.title val secondaryText = if (lineCount.value > 1) recipe.description else null - val icon: @Composable (OdsListItemIconScope.() -> Unit)? = - listItemCustomizationState.getIconPainter(recipe)?.let { { OdsListItemIcon(painter = it) } } - trailing?.let { listItemTrailing -> - OdsListItem( - modifier = modifier, - text = text, - secondaryText = secondaryText, - singleLineSecondaryText = singleLineSecondaryText, - icon = icon, - trailing = listItemTrailing - ) - }.orElse { - val context = LocalContext.current - OdsListItem( - modifier = modifier.clickable { clickOnElement(context = context, context.getString(R.string.component_list_item)) }, - text = text, - secondaryText = secondaryText, - singleLineSecondaryText = singleLineSecondaryText, - icon = icon - ) + val icon = ifNotNull(getIconPainter(recipe), selectedIconType.value) { painter, type -> + OdsListItemIcon(type, painter, "") + } + + val context = LocalContext.current + OdsListItem( + text = text, + secondaryText = secondaryText, + singleLineSecondaryText = singleLineSecondaryText, + icon = icon, + trailing = trailing + ) { + clickOnElement(context = context, context.getString(R.string.component_list_item)) } CodeImplementationColumn(modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin))) { @@ -149,24 +138,34 @@ private fun ComponentListItemContent(listItemCustomizationState: ListItemCustomi string("secondaryText", secondaryText) if (!singleLineSecondaryText) stringRepresentation("singleLineSecondaryText", false) } - if (selectedLeading.value != ListItemCustomizationState.Leading.None) { - simple("modifier", "Modifier.iconType($iconType)") - simple("icon", "{ OdsListItemIcon(<painter>) }") - } - if (selectedTrailing.value != ListItemCustomizationState.Trailing.None) { - val clazz: Class<*>? = when (selectedTrailing.value) { - ListItemCustomizationState.Trailing.Checkbox -> OdsCheckboxTrailing::class.java - ListItemCustomizationState.Trailing.Switch -> OdsSwitchTrailing::class.java - ListItemCustomizationState.Trailing.Icon -> OdsIconTrailing::class.java - ListItemCustomizationState.Trailing.Caption -> OdsCaptionTrailing::class.java - else -> null + selectedIconType.value?.let { iconType -> + classInstance<OdsListItemIcon>("icon") { + enum("type", iconType) + painter() + contentDescription("") } - - val trailingParamName = "trailing" - clazz?.let { - classInstance(trailingParamName, it) {} - }.orElse { - simple(trailingParamName, "<OdsListItemTrailing>") + } + selectedTrailing.value?.let { trailingClass -> + classInstance("trailing", trailingClass) { + when (trailingClass) { + OdsListItemTrailingCheckbox::class.java, + OdsListItemTrailingSwitch::class.java -> { + simple("checked", "checked") + lambda("onCheckedChange", "checked = it") + } + OdsListItemTrailingRadioButton::class.java -> { + simple("selected", "selected") + lambda("onClick", "selected = it") + } + OdsListItemTrailingIcon::class.java -> { + painter() + contentDescription("") + onClick() + } + OdsListItemTrailingCaption::class.java -> { + text(context.getString(R.string.component_element_caption)) + } + } } } } @@ -176,32 +175,23 @@ private fun ComponentListItemContent(listItemCustomizationState: ListItemCustomi } } -private val ListItemCustomizationState.Trailing.textResId: Int +private val Class<out OdsListItemTrailing>?.textResId: Int get() = when (this) { - ListItemCustomizationState.Trailing.None -> R.string.component_list_trailing_none - ListItemCustomizationState.Trailing.Checkbox -> R.string.component_list_trailing_checkbox - ListItemCustomizationState.Trailing.Switch -> R.string.component_list_trailing_switch - ListItemCustomizationState.Trailing.Icon -> R.string.component_list_trailing_icon - ListItemCustomizationState.Trailing.Caption -> R.string.component_list_trailing_caption - } - -private val ListItemCustomizationState.iconType: OdsListItemIconType? - get() = when (selectedLeading.value) { - ListItemCustomizationState.Leading.None -> null - ListItemCustomizationState.Leading.Icon -> OdsListItemIconType.Icon - ListItemCustomizationState.Leading.CircularImage -> OdsListItemIconType.CircularImage - ListItemCustomizationState.Leading.SquareImage -> OdsListItemIconType.SquareImage - ListItemCustomizationState.Leading.WideImage -> OdsListItemIconType.WideImage + OdsListItemTrailingCheckbox::class.java -> R.string.component_list_trailing_checkbox + OdsListItemTrailingSwitch::class.java -> R.string.component_list_trailing_switch + OdsListItemTrailingRadioButton::class.java -> R.string.component_list_trailing_radio_button + OdsListItemTrailingIcon::class.java -> R.string.component_list_trailing_icon + OdsListItemTrailingCaption::class.java -> R.string.component_list_trailing_caption + else -> R.string.component_list_trailing_none } @Composable private fun ListItemCustomizationState.getIconPainter(recipe: Recipe): Painter? { - return when (selectedLeading.value) { - ListItemCustomizationState.Leading.None -> null - ListItemCustomizationState.Leading.Icon -> recipe.iconResId?.let { painterResource(id = it) } - ListItemCustomizationState.Leading.CircularImage, - ListItemCustomizationState.Leading.SquareImage, - ListItemCustomizationState.Leading.WideImage -> { + return when (selectedIconType.value) { + OdsListItemIconType.Icon -> recipe.iconResId?.let { painterResource(id = it) } + OdsListItemIconType.CircularImage, + OdsListItemIconType.SquareImage, + OdsListItemIconType.WideImage -> { val wideImageSizeWidthPx = with(LocalDensity.current) { dimensionResource(id = com.orange.ods.R.dimen.list_wide_image_width).toPx() } val wideImageSizeHeightPx = with(LocalDensity.current) { dimensionResource(id = com.orange.ods.R.dimen.list_wide_image_height).toPx() } rememberAsyncImagePainter( @@ -214,33 +204,34 @@ private fun ListItemCustomizationState.getIconPainter(recipe: Recipe): Painter? error = painterResource(id = DrawableManager.getPlaceholderSmallResId(error = true)) ) } + null -> null } } private val ListItemCustomizationState.trailing: OdsListItemTrailing? @Composable get() = when (selectedTrailing.value) { - ListItemCustomizationState.Trailing.None -> null - ListItemCustomizationState.Trailing.Checkbox -> { - val checked = remember { mutableStateOf(true) } - OdsCheckboxTrailing(checked = checked) + OdsListItemTrailingCheckbox::class.java -> { + var checked by remember { mutableStateOf(true) } + OdsListItemTrailingCheckbox(checked, { checked = it }) } - ListItemCustomizationState.Trailing.Switch -> { - val checked = remember { mutableStateOf(true) } - OdsSwitchTrailing(checked = checked) + OdsListItemTrailingSwitch::class.java -> { + var checked by remember { mutableStateOf(true) } + OdsListItemTrailingSwitch(checked, { checked = it }) } - ListItemCustomizationState.Trailing.Icon -> { + OdsListItemTrailingRadioButton::class.java -> { + var selected by remember { mutableStateOf(true) } + OdsListItemTrailingRadioButton(selected, { selected = !selected }) + } + OdsListItemTrailingIcon::class.java -> { val context = LocalContext.current val iconText = stringResource(id = R.string.component_element_icon) - OdsIconTrailing( - modifier = Modifier.clickable { - clickOnElement(context, iconText) - }, - painter = painterResource(id = R.drawable.ic_info), - contentDescription = stringResource(id = R.string.component_list_information) - ) + OdsListItemTrailingIcon(painterResource(id = R.drawable.ic_info), stringResource(id = R.string.component_list_information)) { + clickOnElement(context, iconText) + } } - ListItemCustomizationState.Trailing.Caption -> { - OdsCaptionTrailing(text = stringResource(id = R.string.component_element_caption)) + OdsListItemTrailingCaption::class.java -> { + OdsListItemTrailingCaption(stringResource(id = R.string.component_element_caption)) } + else -> null } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/listitem/ListItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/listitem/ListItemCustomizationState.kt index 58996563b..3ce5ab380 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/listitem/ListItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/listitem/ListItemCustomizationState.kt @@ -18,24 +18,31 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import com.orange.ods.compose.component.list.OdsListItemIconType +import com.orange.ods.compose.component.list.OdsListItemTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingCaption +import com.orange.ods.compose.component.list.OdsListItemTrailingCheckbox +import com.orange.ods.compose.component.list.OdsListItemTrailingIcon +import com.orange.ods.compose.component.list.OdsListItemTrailingRadioButton +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable fun rememberListItemCustomizationState( bottomSheetScaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(), lineCount: MutableState<Int> = rememberSaveable { mutableStateOf(ListItemCustomizationState.DefaultLineCount) }, - selectedLeading: MutableState<ListItemCustomizationState.Leading> = rememberSaveable { mutableStateOf(ListItemCustomizationState.Leading.None) }, - selectedTrailing: MutableState<ListItemCustomizationState.Trailing> = rememberSaveable { mutableStateOf(ListItemCustomizationState.Trailing.None) }, + selectedIconType: MutableState<OdsListItemIconType?> = rememberSaveable { mutableStateOf(null) }, + selectedTrailing: MutableState<Class<out OdsListItemTrailing>?> = rememberSaveable { mutableStateOf(null) }, ) = remember(lineCount) { - ListItemCustomizationState(bottomSheetScaffoldState, lineCount, selectedLeading, selectedTrailing) + ListItemCustomizationState(bottomSheetScaffoldState, lineCount, selectedIconType, selectedTrailing) } @OptIn(ExperimentalMaterialApi::class) class ListItemCustomizationState( val bottomSheetScaffoldState: BottomSheetScaffoldState, val lineCount: MutableState<Int>, - val selectedLeading: MutableState<Leading>, - val selectedTrailing: MutableState<Trailing>, + val selectedIconType: MutableState<OdsListItemIconType?>, + val selectedTrailing: MutableState<Class<out OdsListItemTrailing>?>, ) { companion object { const val DefaultLineCount = 2 @@ -43,22 +50,20 @@ class ListItemCustomizationState( const val MaxLineCount = 3 } - enum class Leading { - None, Icon, CircularImage, SquareImage, WideImage - } - - enum class Trailing { - None, Checkbox, Switch, Icon, Caption - } - - val trailings: List<Trailing> + val trailings: List<Class<out OdsListItemTrailing>?> get() = if (lineCount.value < MaxLineCount) { - listOf(Trailing.None, Trailing.Checkbox, Trailing.Switch, Trailing.Icon) + listOf( + null, + OdsListItemTrailingCheckbox::class.java, + OdsListItemTrailingSwitch::class.java, + OdsListItemTrailingRadioButton::class.java, + OdsListItemTrailingIcon::class.java + ) } else { - listOf(Trailing.None, Trailing.Caption) + listOf(null, OdsListItemTrailingCaption::class.java) } fun resetTrailing() { - selectedTrailing.value = Trailing.None + selectedTrailing.value = null } } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuDropdown.kt b/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuDropdown.kt index e902fcee3..0b777842e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuDropdown.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuDropdown.kt @@ -10,7 +10,6 @@ package com.orange.ods.app.ui.components.menus -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -37,9 +36,9 @@ import com.orange.ods.app.ui.components.utilities.clickOnElement import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable -import com.orange.ods.compose.component.list.OdsIconTrailing import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingIcon +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.menu.OdsDropdownMenu import com.orange.ods.compose.component.menu.OdsDropdownMenuItem import com.orange.ods.compose.text.OdsTextBody1 @@ -60,11 +59,11 @@ fun MenuDropdown() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_menu_icons), - trailing = OdsSwitchTrailing(checked = icons) + trailing = OdsListItemTrailingSwitch(icons.value, { icons.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_menu_divider), - trailing = OdsSwitchTrailing(checked = dividerExample) + trailing = OdsListItemTrailingSwitch(dividerExample.value, { dividerExample.value = it }) ) }) { Column( @@ -88,11 +87,10 @@ fun MenuDropdown() { modifier = Modifier.padding(top = dimensionResource(id = com.orange.ods.R.dimen.spacing_s)), text = recipe.title, secondaryText = recipe.subtitle, - trailing = OdsIconTrailing( - modifier = Modifier.clickable { menuExpanded = true }, - painter = rememberVectorPainter(image = Icons.Filled.MoreVert), - contentDescription = stringResource(id = R.string.component_menu_show_ingredients), - ) + trailing = OdsListItemTrailingIcon( + rememberVectorPainter(image = Icons.Filled.MoreVert), + stringResource(id = R.string.component_menu_show_ingredients) + ) { menuExpanded = true } ) val items = recipes.take(MenuDropdownCustomizationState.MenuItemCount) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuExposedDropdown.kt b/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuExposedDropdown.kt index 3be6a9b75..2cc87a158 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuExposedDropdown.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/menus/MenuExposedDropdown.kt @@ -31,7 +31,7 @@ import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.menu.OdsExposedDropdownMenu import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItem @@ -67,11 +67,11 @@ fun MenuExposedDropdown() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) + trailing = OdsListItemTrailingSwitch(enabled.value, { enabled.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_menu_icons), - trailing = OdsSwitchTrailing(checked = icons) + trailing = OdsListItemTrailingSwitch(icons.value, { icons.value = it }) ) }) { Column( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/navigationdrawers/ComponentModalDrawers.kt b/app/src/main/java/com/orange/ods/app/ui/components/navigationdrawers/ComponentModalDrawers.kt index d98d81153..69bf940d3 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/navigationdrawers/ComponentModalDrawers.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/navigationdrawers/ComponentModalDrawers.kt @@ -39,7 +39,7 @@ import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.navigationdrawer.OdsModalDrawer import com.orange.ods.compose.component.navigationdrawer.OdsModalDrawerDivider import com.orange.ods.compose.component.navigationdrawer.OdsModalDrawerHeader @@ -113,7 +113,7 @@ fun ComponentModalDrawers() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_modal_drawer_content_example), - trailing = OdsSwitchTrailing(checked = contentExampleChecked) + trailing = OdsListItemTrailingSwitch(contentExampleChecked.value, { contentExampleChecked.value = it }) ) Subtitle(textRes = R.string.component_modal_drawer_header_image, horizontalPadding = true) OdsChoiceChipsFlowRow( @@ -137,17 +137,11 @@ fun ComponentModalDrawers() { ) OdsListItem( text = stringResource(id = R.string.component_modal_drawer_subtitle), - trailing = OdsSwitchTrailing( - checked = subtitleChecked, - enabled = isContentExampleChecked - ) + trailing = OdsListItemTrailingSwitch(subtitleChecked.value, { subtitleChecked.value = it }, isContentExampleChecked) ) OdsListItem( text = stringResource(id = R.string.component_modal_drawer_list_icon), - trailing = OdsSwitchTrailing( - checked = listIconChecked, - enabled = isContentExampleChecked - ), + trailing = OdsListItemTrailingSwitch(listIconChecked.value, { listIconChecked.value = it }, isContentExampleChecked) ) Subtitle(textRes = R.string.component_modal_drawer_list_example, horizontalPadding = true) OdsChoiceChipsFlowRow( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCircular.kt b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCircular.kt index 1a4d2f348..7941bb086 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCircular.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCircular.kt @@ -10,9 +10,6 @@ package com.orange.ods.app.ui.components.progress -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -22,10 +19,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource @@ -39,22 +32,13 @@ import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.progressindicator.OdsCircularProgressIndicator -private const val DeterminateProgressTargetValue = 0.9f -private const val DeterminateProgressAnimDuration = 5000 - @OptIn(ExperimentalMaterialApi::class) @Composable fun ProgressCircular() { val customizationState = rememberProgressCustomizationState() - var determinateProgressValue by remember { mutableStateOf(0f) } - val determinateProgressAnimation by animateFloatAsState( - targetValue = determinateProgressValue, - animationSpec = tween(durationMillis = DeterminateProgressAnimDuration, easing = FastOutSlowInEasing), - label = "" - ) with(customizationState) { ComponentCustomizationBottomSheetScaffold( @@ -63,16 +47,22 @@ fun ProgressCircular() { Subtitle(textRes = R.string.component_element_type, horizontalPadding = true) OdsChoiceChipsFlowRow( value = type.value, - onValueChange = { value -> type.value = value }, + onValueChange = { + value -> type.value = value + if (value == ProgressCustomizationState.Type.Indeterminate) resetAnimation() + }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( OdsChoiceChip(text = stringResource(id = R.string.component_progress_determinate), value = ProgressCustomizationState.Type.Determinate), - OdsChoiceChip(text = stringResource(id = R.string.component_progress_indeterminate), value = ProgressCustomizationState.Type.Indeterminate) + OdsChoiceChip( + text = stringResource(id = R.string.component_progress_indeterminate), + value = ProgressCustomizationState.Type.Indeterminate + ) ) ) OdsListItem( text = stringResource(id = R.string.component_element_label), - trailing = OdsSwitchTrailing(checked = label) + trailing = OdsListItemTrailingSwitch(label.value, { label.value = it }) ) }) { Column( @@ -82,7 +72,7 @@ fun ProgressCircular() { ) { val text = stringResource(id = R.string.component_progress_label) OdsCircularProgressIndicator( - progress = if (type.value == ProgressCustomizationState.Type.Determinate) determinateProgressAnimation else null, + progress = if (type.value == ProgressCustomizationState.Type.Determinate) determinateProgressAnimation.value else null, label = if (hasLabel) text else null, modifier = Modifier .padding(top = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)) @@ -90,11 +80,11 @@ fun ProgressCircular() { ) if (type.value == ProgressCustomizationState.Type.Determinate) { LaunchedEffect(DeterminateProgressTargetValue) { - determinateProgressValue = DeterminateProgressTargetValue + determinateProgressValue.value = DeterminateProgressTargetValue } } - CodeImplementationColumn { + CodeImplementationColumn(modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin))) { FunctionCallCode( name = OdsComposable.OdsCircularProgressIndicator.name, exhaustiveParameters = false, diff --git a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCustomizationState.kt index 3d0274d3b..a9ef57a82 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressCustomizationState.kt @@ -10,28 +10,36 @@ package com.orange.ods.app.ui.components.progress +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +const val DeterminateProgressTargetValue = 0.9f +private const val DeterminateProgressAnimDuration = 5000 + @Composable fun rememberProgressCustomizationState( type: MutableState<ProgressCustomizationState.Type> = rememberSaveable { mutableStateOf(ProgressCustomizationState.Type.Determinate) }, icon: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) }, currentValue: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) }, - label: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) } + label: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) }, + determinateProgressValue: MutableState<Float> = remember { mutableStateOf(0f) } ) = remember(icon, currentValue, label, type) { - ProgressCustomizationState(type, icon, currentValue, label) + ProgressCustomizationState(type, icon, currentValue, label, determinateProgressValue) } class ProgressCustomizationState( val type: MutableState<Type>, val icon: MutableState<Boolean>, val currentValue: MutableState<Boolean>, - val label: MutableState<Boolean> + val label: MutableState<Boolean>, + val determinateProgressValue: MutableState<Float> ) { enum class Type { Determinate, Indeterminate @@ -48,4 +56,19 @@ class ProgressCustomizationState( val isCurrentValueSwitchEnabled get() = type.value != Type.Indeterminate + + val determinateProgressAnimation + @Composable + get() = animateFloatAsState( + targetValue = determinateProgressValue.value, + animationSpec = tween( + durationMillis = if (determinateProgressValue.value == 0f) 0 else DeterminateProgressAnimDuration, + easing = FastOutSlowInEasing + ), + label = "" + ) + + fun resetAnimation() { + determinateProgressValue.value = 0f + } } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressLinear.kt b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressLinear.kt index 15f61505a..623a05ade 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressLinear.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/progress/ProgressLinear.kt @@ -10,9 +10,6 @@ package com.orange.ods.app.ui.components.progress -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -22,10 +19,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -39,23 +32,14 @@ import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.progressindicator.OdsLinearProgressIndicator import com.orange.ods.compose.component.progressindicator.OdsLinearProgressIndicatorIcon -private const val DeterminateProgressTargetValue = 0.9f -private const val DeterminateProgressAnimDuration = 5000 - @OptIn(ExperimentalMaterialApi::class) @Composable fun ProgressLinear() { - val customizationState = rememberProgressCustomizationState() - var determinateProgressValue by remember { mutableStateOf(0f) } - val determinateProgressAnimation by animateFloatAsState( - targetValue = determinateProgressValue, - animationSpec = tween(durationMillis = DeterminateProgressAnimDuration, easing = FastOutSlowInEasing) - ) with(customizationState) { if (type.value == ProgressCustomizationState.Type.Indeterminate) currentValue.value = false @@ -65,27 +49,30 @@ fun ProgressLinear() { Subtitle(textRes = R.string.component_element_type, horizontalPadding = true) OdsChoiceChipsFlowRow( value = type.value, - onValueChange = { value -> type.value = value }, + onValueChange = { value -> + type.value = value + if (value == ProgressCustomizationState.Type.Indeterminate) resetAnimation() + }, modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( OdsChoiceChip(text = stringResource(id = R.string.component_progress_determinate), value = ProgressCustomizationState.Type.Determinate), - OdsChoiceChip(text = stringResource(id = R.string.component_progress_indeterminate), value = ProgressCustomizationState.Type.Indeterminate) + OdsChoiceChip( + text = stringResource(id = R.string.component_progress_indeterminate), + value = ProgressCustomizationState.Type.Indeterminate + ) ) ) OdsListItem( text = stringResource(id = R.string.component_element_label), - trailing = OdsSwitchTrailing(checked = label) + trailing = OdsListItemTrailingSwitch(label.value, { label.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_element_icon), - trailing = OdsSwitchTrailing(checked = icon) + trailing = OdsListItemTrailingSwitch(icon.value, { icon.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_progress_linear_value), - trailing = OdsSwitchTrailing( - checked = currentValue, - enabled = isCurrentValueSwitchEnabled - ), + trailing = OdsListItemTrailingSwitch(currentValue.value, { currentValue.value = it }, isCurrentValueSwitchEnabled), ) }) { Column( @@ -95,7 +82,7 @@ fun ProgressLinear() { ) { val text = stringResource(id = R.string.component_progress_label) OdsLinearProgressIndicator( - progress = if (type.value == ProgressCustomizationState.Type.Determinate) determinateProgressAnimation else null, + progress = if (type.value == ProgressCustomizationState.Type.Determinate) determinateProgressAnimation.value else null, label = if (hasLabel) text else null, showCurrentValue = hasCurrentValue, icon = if (hasIcon) OdsLinearProgressIndicatorIcon(painterResource(id = R.drawable.ic_arrow_down), "") else null, @@ -105,7 +92,7 @@ fun ProgressLinear() { ) if (type.value == ProgressCustomizationState.Type.Determinate) { LaunchedEffect(DeterminateProgressTargetValue) { - determinateProgressValue = DeterminateProgressTargetValue + determinateProgressValue.value = DeterminateProgressTargetValue } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/radiobuttons/ComponentRadioButtons.kt b/app/src/main/java/com/orange/ods/app/ui/components/radiobuttons/ComponentRadioButtons.kt index 467c88bb9..3fcfd52bd 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/radiobuttons/ComponentRadioButtons.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/radiobuttons/ComponentRadioButtons.kt @@ -18,8 +18,10 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -30,20 +32,20 @@ import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsRadioButtonTrailing -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingRadioButton +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable fun ComponentRadioButtons() { - val enabled = rememberSaveable { mutableStateOf(true) } + var enabled by rememberSaveable { mutableStateOf(true) } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) + trailing = OdsListItemTrailingSwitch(enabled, { enabled = it }) ) }) { Column( @@ -52,16 +54,12 @@ fun ComponentRadioButtons() { .padding(vertical = dimensionResource(id = com.orange.ods.R.dimen.spacing_s)) ) { val recipes = LocalRecipes.current.take(3) - val selectedRadio = rememberSaveable { mutableStateOf(recipes.firstOrNull()?.title) } + var selectedRecipe by rememberSaveable { mutableStateOf(recipes.firstOrNull()) } Column(modifier = Modifier.selectableGroup()) { recipes.forEach { recipe -> OdsListItem( text = recipe.title, - trailing = OdsRadioButtonTrailing( - selectedRadio = selectedRadio, - currentRadio = recipe.title, - enabled = enabled.value - ) + trailing = OdsListItemTrailingRadioButton(selectedRecipe == recipe, { selectedRecipe = recipe }, enabled) ) } } @@ -73,7 +71,7 @@ fun ComponentRadioButtons() { parameters = { selected(false) onClick() - if (!enabled.value) enabled(false) + if (!enabled) enabled(false) } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/sheets/ComponentSheetsBottom.kt b/app/src/main/java/com/orange/ods/app/ui/components/sheets/ComponentSheetsBottom.kt index 50a88a420..cbee6eda5 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/sheets/ComponentSheetsBottom.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/sheets/ComponentSheetsBottom.kt @@ -33,7 +33,6 @@ import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsListItemIcon import com.orange.ods.compose.component.list.OdsListItemIconType -import com.orange.ods.compose.component.list.iconType import com.orange.ods.compose.text.OdsTextBody1 import com.orange.ods.compose.text.OdsTextSubtitle1 @@ -53,11 +52,9 @@ fun ComponentSheetsBottom() { val recipes = LocalRecipes.current recipes.take(3).forEach { recipe -> OdsListItem( - Modifier - .iconType(OdsListItemIconType.Icon) - .alpha(if (isEmpty) 0.0f else 1.0f), + modifier = Modifier.alpha(if (isEmpty) 0.0f else 1.0f), icon = recipe.iconResId?.let { iconRes -> - { OdsListItemIcon(painterResource(id = iconRes)) } + OdsListItemIcon(OdsListItemIconType.Icon, painterResource(id = iconRes), "") }, text = recipe.title ) @@ -81,7 +78,10 @@ fun ComponentSheetsBottom() { modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( OdsChoiceChip(text = stringResource(id = R.string.component_element_empty), value = SheetsBottomCustomizationState.Content.Empty), - OdsChoiceChip(text = stringResource(id = R.string.component_element_example), value = SheetsBottomCustomizationState.Content.Example) + OdsChoiceChip( + text = stringResource(id = R.string.component_element_example), + value = SheetsBottomCustomizationState.Content.Example + ) ) ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/sliders/ComponentSliders.kt b/app/src/main/java/com/orange/ods/app/ui/components/sliders/ComponentSliders.kt index e80aeb41a..b496f4204 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/sliders/ComponentSliders.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/sliders/ComponentSliders.kt @@ -37,7 +37,7 @@ import com.orange.ods.compose.component.control.OdsSlider import com.orange.ods.compose.component.control.OdsSliderIcon import com.orange.ods.compose.component.control.OdsSliderLockups import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable @@ -50,15 +50,15 @@ fun ComponentSliders() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_slider_side_icons), - trailing = OdsSwitchTrailing(checked = sideIcons) + trailing = OdsListItemTrailingSwitch(sideIcons.value, { sideIcons.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_slider_value_displayed), - trailing = OdsSwitchTrailing(checked = valueDisplayed) + trailing = OdsListItemTrailingSwitch(valueDisplayed.value, { valueDisplayed.value = it }) ) OdsListItem( text = stringResource(id = R.string.component_slider_stepped), - trailing = OdsSwitchTrailing(checked = stepped) + trailing = OdsListItemTrailingSwitch(stepped.value, { stepped.value = it }) ) }) { Column( @@ -68,10 +68,10 @@ fun ComponentSliders() { ) { val technicalText = if (shouldDisplayValue) OdsComposable.OdsSliderLockups.name else OdsComposable.OdsSlider.name val steps = if (isStepped) 9 else 0 - val leftIconContentDescription = stringResource(id = R.string.component_slider_low_volume) - val leftIcon = if (hasSideIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_volume_status_1), leftIconContentDescription) else null - val rightIconContentDescription = stringResource(id = R.string.component_slider_high_volume) - val rightIcon = if (hasSideIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_volume_status_4), rightIconContentDescription) else null + val startIconContentDescription = stringResource(id = R.string.component_slider_low_volume) + val startIcon = if (hasSideIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_volume_status_1), startIconContentDescription) else null + val endIconContentDescription = stringResource(id = R.string.component_slider_high_volume) + val endIcon = if (hasSideIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_volume_status_4), endIconContentDescription) else null var sliderPosition by remember { mutableStateOf(0f) } val valueRange = 0f..100f @@ -89,8 +89,8 @@ fun ComponentSliders() { steps = steps, valueRange = valueRange, onValueChange = { sliderPosition = it }, - leftIcon = leftIcon, - rightIcon = rightIcon + startIcon = startIcon, + endIcon = endIcon ) } else { componentName = OdsComposable.OdsSlider.name @@ -99,8 +99,8 @@ fun ComponentSliders() { steps = steps, valueRange = valueRange, onValueChange = { sliderPosition = it }, - leftIcon = leftIcon, - rightIcon = rightIcon + startIcon = startIcon, + endIcon = endIcon ) } @@ -114,13 +114,13 @@ fun ComponentSliders() { lambda("onValueChange") if (isStepped) stringRepresentation("steps", steps) if (hasSideIcons) { - classInstance<OdsSliderIcon>("leftIcon") { + classInstance<OdsSliderIcon>("startIcon") { painter() - contentDescription(leftIconContentDescription) + contentDescription(startIconContentDescription) } - classInstance<OdsSliderIcon>("rightIcon") { + classInstance<OdsSliderIcon>("endIcon") { painter() - contentDescription(rightIconContentDescription) + contentDescription(endIconContentDescription) } } }) @@ -135,8 +135,8 @@ private fun getTitleRes(isStepped: Boolean, hasSideIcons: Boolean, shouldDisplay isStepped && hasSideIcons && !shouldDisplayValue -> R.string.component_slider_discrete_with_icons !isStepped && !hasSideIcons && !shouldDisplayValue -> R.string.component_slider_continuous !isStepped && hasSideIcons && !shouldDisplayValue -> R.string.component_slider_continuous_with_icons - isStepped && shouldDisplayValue && !hasSideIcons -> R.string.component_slider_discrete_lockups - isStepped && shouldDisplayValue && hasSideIcons -> R.string.component_slider_discrete_lockups_with_icons + isStepped && !hasSideIcons -> R.string.component_slider_discrete_lockups + isStepped && hasSideIcons -> R.string.component_slider_discrete_lockups_with_icons !hasSideIcons -> R.string.component_slider_continuous_lockups else -> R.string.component_slider_continuous_lockups_with_icons } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/snackbars/ComponentSnackbars.kt b/app/src/main/java/com/orange/ods/app/ui/components/snackbars/ComponentSnackbars.kt index 338e0411a..73eb67f84 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/snackbars/ComponentSnackbars.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/snackbars/ComponentSnackbars.kt @@ -17,9 +17,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -35,7 +37,7 @@ import com.orange.ods.app.ui.utilities.composable.IndentCodeColumn import com.orange.ods.app.ui.utilities.composable.TechnicalText import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.snackbar.OdsSnackbar import com.orange.ods.compose.component.snackbar.OdsSnackbarHost import com.orange.ods.compose.text.OdsTextBody2 @@ -49,10 +51,10 @@ fun ComponentSnackbars() { val bottomSheetScaffoldState = rememberBottomSheetScaffoldState() val coroutineScope: CoroutineScope = rememberCoroutineScope() - val actionButtonChecked = rememberSaveable { mutableStateOf(false) } - val actionOnNewLineChecked = rememberSaveable { mutableStateOf(false) } - if (!actionButtonChecked.value) { - actionOnNewLineChecked.value = false + var actionButtonChecked by rememberSaveable { mutableStateOf(false) } + var actionOnNewLineChecked by rememberSaveable { mutableStateOf(false) } + if (!actionButtonChecked) { + actionOnNewLineChecked = false } val snackbarMessage = stringResource(id = R.string.component_snackbar_message) @@ -63,7 +65,7 @@ fun ComponentSnackbars() { bottomSheetScaffoldState = bottomSheetScaffoldState, snackbarHost = { OdsSnackbarHost(hostState = it) { data -> - OdsSnackbar(snackbarData = data, actionOnNewLine = actionOnNewLineChecked.value, onActionClick = { + OdsSnackbar(data = data, actionOnNewLine = actionOnNewLineChecked, onActionClick = { clickOnElement(context = context, clickedElement = snackbarActionButton) }) } @@ -71,11 +73,11 @@ fun ComponentSnackbars() { bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_snackbar_action_button), - trailing = OdsSwitchTrailing(checked = actionButtonChecked) + trailing = OdsListItemTrailingSwitch(actionButtonChecked, { actionButtonChecked = it }) ) OdsListItem( text = stringResource(id = R.string.component_snackbar_action_on_new_line), - trailing = OdsSwitchTrailing(checked = actionOnNewLineChecked, enabled = actionButtonChecked.value) + trailing = OdsListItemTrailingSwitch(actionOnNewLineChecked, { actionOnNewLineChecked = it }, actionButtonChecked) ) }) { Column( @@ -85,7 +87,7 @@ fun ComponentSnackbars() { coroutineScope.launch { bottomSheetScaffoldState.snackbarHostState.showSnackbar( message = snackbarMessage, - actionLabel = if (actionButtonChecked.value) snackbarActionLabel else null + actionLabel = if (actionButtonChecked) snackbarActionLabel else null ) } } @@ -95,7 +97,10 @@ fun ComponentSnackbars() { contentBackground = false ) { OdsTextBody2( - modifier = Modifier.padding(bottom = dimensionResource(id = com.orange.ods.R.dimen.spacing_xs)), + modifier = Modifier.padding( + top = dimensionResource(id = com.orange.ods.R.dimen.spacing_s), + bottom = dimensionResource(id = com.orange.ods.R.dimen.spacing_xs) + ), text = stringResource(id = R.string.component_snackbar_code_first_step) ) CodeBackgroundColumn { @@ -107,7 +112,7 @@ fun ComponentSnackbars() { name = OdsComposable.OdsSnackbar.name, parameters = { simple("snackbarData", "data") - if (actionOnNewLineChecked.value) stringRepresentation("actionOnNewLine", true) + if (actionOnNewLineChecked) stringRepresentation("actionOnNewLine", true) lambda("onActionClick") } ) @@ -128,7 +133,7 @@ fun ComponentSnackbars() { name = "scaffoldState.snackbarHostState.showSnackbar", parameters = { string("message", snackbarMessage) - if (actionButtonChecked.value) string("actionLabel", snackbarActionLabel) + if (actionButtonChecked) string("actionLabel", snackbarActionLabel) }) } TechnicalText(text = "}") diff --git a/app/src/main/java/com/orange/ods/app/ui/components/switches/ComponentSwitches.kt b/app/src/main/java/com/orange/ods/app/ui/components/switches/ComponentSwitches.kt index 992a3c53d..4b1862814 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/switches/ComponentSwitches.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/switches/ComponentSwitches.kt @@ -17,8 +17,10 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -29,21 +31,19 @@ import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch @OptIn(ExperimentalMaterialApi::class) @Composable fun ComponentSwitches() { - val enabled = rememberSaveable { mutableStateOf(true) } + var enabled by rememberSaveable { mutableStateOf(true) } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { OdsListItem( text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing( - checked = enabled, - ) + trailing = OdsListItemTrailingSwitch(enabled, { enabled = it }) ) }) { Column( @@ -53,13 +53,10 @@ fun ComponentSwitches() { ) { val recipes = LocalRecipes.current.take(3) recipes.forEach { recipe -> - val checked = rememberSaveable { mutableStateOf(false) } + var checked by rememberSaveable { mutableStateOf(false) } OdsListItem( text = recipe.title, - trailing = OdsSwitchTrailing( - checked = checked, - enabled = enabled.value - ) + trailing = OdsListItemTrailingSwitch(checked, { checked = it }, enabled) ) } @@ -72,7 +69,7 @@ fun ComponentSwitches() { parameters = { checked(false) onCheckedChange() - if (!enabled.value) enabled(false) + if (!enabled) enabled(false) }) } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/tabs/ComponentTabs.kt b/app/src/main/java/com/orange/ods/app/ui/components/tabs/ComponentTabs.kt index 679dab4d4..2cf4005dc 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/tabs/ComponentTabs.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/tabs/ComponentTabs.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager +import com.orange.ods.app.ui.LocalAppBarManager import com.orange.ods.app.ui.TabsConfiguration import com.orange.ods.app.ui.components.Variant import com.orange.ods.app.ui.components.utilities.ComponentCountRow @@ -35,7 +35,7 @@ import com.orange.ods.app.ui.utilities.composable.Subtitle import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.text.OdsTextBody1 private const val MinFixedTabCount = 2 @@ -60,63 +60,60 @@ fun ComponentTabs(variant: Variant, upPress: () -> Unit) { } val tabsCustomizationState = rememberMainTabsCustomizationState(tabsCount = rememberSaveable { mutableStateOf(tabCountMin) }) - LocalMainTopAppBarManager.current.updateTopAppBarTabs( - TabsConfiguration( - scrollableTabs = scrollableTabs, - tabs = tabsCustomizationState.tabs, - pagerState = tabsCustomizationState.pagerState, - tabIconType = tabsCustomizationState.tabIconType.value, - tabTextEnabled = tabsCustomizationState.tabTextEnabled.value, + + with(tabsCustomizationState) { + LocalAppBarManager.current.updateAppBarTabs( + TabsConfiguration(scrollableTabs, tabs, pagerState, tabIconType.value, tabTextEnabled.value) ) - ) - BackHandler { - upPress() - } + BackHandler { + upPress() + } - ComponentCustomizationBottomSheetScaffold( - bottomSheetScaffoldState = tabsCustomizationState.bottomSheetScaffoldState, - bottomSheetContent = { - Subtitle(textRes = R.string.component_element_icon, horizontalPadding = true) - OdsChoiceChipsFlowRow( - value = tabsCustomizationState.tabIconType.value, - onValueChange = { value -> tabsCustomizationState.tabIconType.value = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), - chips = listOf( - OdsChoiceChip( - text = stringResource(id = R.string.component_tab_icon_leading), value = MainTabsCustomizationState.TabIconType.Leading, - enabled = tabsCustomizationState.isTabIconCustomizationEnabled - ), - OdsChoiceChip( - text = stringResource(id = R.string.component_tab_icon_top), value = MainTabsCustomizationState.TabIconType.Top, - enabled = tabsCustomizationState.isTabIconCustomizationEnabled - ), - OdsChoiceChip( - text = stringResource(id = R.string.component_element_none), value = MainTabsCustomizationState.TabIconType.None, - enabled = tabsCustomizationState.isTabIconCustomizationEnabled + ComponentCustomizationBottomSheetScaffold( + bottomSheetScaffoldState = bottomSheetScaffoldState, + bottomSheetContent = { + Subtitle(textRes = R.string.component_element_icon, horizontalPadding = true) + OdsChoiceChipsFlowRow( + value = tabIconType.value, + onValueChange = { value -> tabIconType.value = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + chips = listOf( + OdsChoiceChip( + text = stringResource(id = R.string.component_tab_icon_leading), value = MainTabsCustomizationState.TabIconType.Leading, + enabled = isTabIconCustomizationEnabled + ), + OdsChoiceChip( + text = stringResource(id = R.string.component_tab_icon_top), value = MainTabsCustomizationState.TabIconType.Top, + enabled = isTabIconCustomizationEnabled + ), + OdsChoiceChip( + text = stringResource(id = R.string.component_element_none), value = MainTabsCustomizationState.TabIconType.None, + enabled = isTabIconCustomizationEnabled + ) ) ) - ) - - OdsListItem( - text = stringResource(id = R.string.component_element_text), - trailing = OdsSwitchTrailing(checked = tabsCustomizationState.tabTextEnabled, enabled = tabsCustomizationState.isTabTextCustomizationEnabled) - ) - ComponentCountRow( - modifier = Modifier.padding(start = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), - title = stringResource(id = R.string.component_tabs_count), - count = tabsCustomizationState.tabsCount, - minusIconContentDescription = stringResource(id = R.string.component_tabs_remove_tab), - plusIconContentDescription = stringResource(id = R.string.component_tabs_add_tab), - minCount = tabCountMin, - maxCount = tabCountMax - ) - }) { + OdsListItem( + text = stringResource(id = R.string.component_element_text), + trailing = OdsListItemTrailingSwitch(tabTextEnabled.value, { tabTextEnabled.value = it }, isTabTextCustomizationEnabled) + ) + + ComponentCountRow( + modifier = Modifier.padding(start = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), + title = stringResource(id = R.string.component_tabs_count), + count = tabsCount, + minusIconContentDescription = stringResource(id = R.string.component_tabs_remove_tab), + plusIconContentDescription = stringResource(id = R.string.component_tabs_add_tab), + minCount = tabCountMin, + maxCount = tabCountMax + ) + }) { - HorizontalPager(state = tabsCustomizationState.pagerState, pageCount = tabsCustomizationState.tabs.size) { page -> - val textResId = tabsCustomizationState.tabs[page].textResId - TabsPagerContentScreen(stringResource(id = textResId)) + HorizontalPager(state = pagerState, pageCount = tabs.size) { page -> + val textResId = tabs[page].textResId + TabsPagerContentScreen(stringResource(id = textResId)) + } } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/textfields/ComponentTextField.kt b/app/src/main/java/com/orange/ods/app/ui/components/textfields/ComponentTextField.kt index 78f20e8d1..807cacc2c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/textfields/ComponentTextField.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/textfields/ComponentTextField.kt @@ -37,7 +37,7 @@ import com.orange.ods.app.ui.utilities.composable.Subtitle import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing +import com.orange.ods.compose.component.list.OdsListItemTrailingSwitch import com.orange.ods.compose.component.tab.OdsTab import com.orange.ods.compose.component.tab.OdsTabRow import com.orange.ods.compose.utilities.composable.Keyboard @@ -53,8 +53,8 @@ fun ComponentTextField(variant: Variant) { bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { when (variant) { - Variant.TextField -> TextFieldTextCustomization(textFieldCustomizationState) - Variant.TextFieldPassword -> TextFieldPasswordCustomization(textFieldCustomizationState) + Variant.TextField -> TextFieldTextCustomization(textFieldCustomizationState = textFieldCustomizationState) + Variant.TextFieldPassword -> TextFieldPasswordCustomization(textFieldCustomizationState = textFieldCustomizationState) else -> {} } }) { @@ -110,97 +110,99 @@ private fun TextFieldTextCustomization(textFieldCustomizationState: TextFieldCus @Composable private fun TextFieldPasswordCustomization(textFieldCustomizationState: TextFieldCustomizationState) { - DisplayTypeCustomization(textFieldCustomizationState.displayType) - OdsListItem( - text = stringResource(id = R.string.component_text_field_visualisation_icon), - trailing = OdsSwitchTrailing(checked = textFieldCustomizationState.visualisationIcon) - ) - OdsListItem( - text = stringResource(id = R.string.component_text_field_character_counter), - trailing = OdsSwitchTrailing(checked = textFieldCustomizationState.characterCounter) - ) + with(textFieldCustomizationState) { + DisplayTypeCustomization(displayType) + OdsListItem( + text = stringResource(id = R.string.component_text_field_visualisation_icon), + trailing = OdsListItemTrailingSwitch(visualisationIcon.value, { visualisationIcon.value = it }) + ) + OdsListItem( + text = stringResource(id = R.string.component_text_field_character_counter), + trailing = OdsListItemTrailingSwitch(characterCounter.value, { characterCounter.value = it }) + ) + } } @Composable private fun ComponentCustomizationContent(textFieldCustomizationState: TextFieldCustomizationState) { - OdsListItem( - text = stringResource(id = R.string.component_element_leading_icon), - trailing = OdsSwitchTrailing(checked = textFieldCustomizationState.leadingIcon) - ) - - Subtitle(textRes = R.string.component_text_field_input_type, horizontalPadding = true) - OdsChoiceChipsFlowRow( - value = textFieldCustomizationState.inputType.value, - onValueChange = { value -> textFieldCustomizationState.inputType.value = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), - chips = listOf( - OdsChoiceChip( - text = stringResource(id = R.string.component_text_field_input_type_single_line), value = TextFieldCustomizationState.InputType.SingleLine - ), - OdsChoiceChip( - text = stringResource(id = R.string.component_text_field_input_type_multiline), value = TextFieldCustomizationState.InputType.Multiline - ), - // Note: TextArea chip is disabled cause there is no parameter allowing text area in Jetpack Compose sdk for now - // https://issuetracker.google.com/issues/122476634 - OdsChoiceChip( - text = stringResource(id = R.string.component_text_field_input_type_text_area), - value = TextFieldCustomizationState.InputType.TextArea, - enabled = false - ), + with(textFieldCustomizationState) { + OdsListItem( + text = stringResource(id = R.string.component_element_leading_icon), + trailing = OdsListItemTrailingSwitch(leadingIcon.value, { leadingIcon.value = it }) ) - ) - DisplayTypeCustomization(textFieldCustomizationState.displayType) + Subtitle(textRes = R.string.component_text_field_input_type, horizontalPadding = true) + OdsChoiceChipsFlowRow( + value = inputType.value, + onValueChange = { value -> inputType.value = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + chips = listOf( + OdsChoiceChip( + text = stringResource(id = R.string.component_text_field_input_type_single_line), + value = TextFieldCustomizationState.InputType.SingleLine + ), + OdsChoiceChip( + text = stringResource(id = R.string.component_text_field_input_type_multiline), + value = TextFieldCustomizationState.InputType.Multiline + ), + // Note: TextArea chip is disabled cause there is no parameter allowing text area in Jetpack Compose sdk for now + // https://issuetracker.google.com/issues/122476634 + OdsChoiceChip( + text = stringResource(id = R.string.component_text_field_input_type_text_area), + value = TextFieldCustomizationState.InputType.TextArea, + enabled = false + ), + ) + ) - Subtitle(textRes = R.string.component_element_trailing, horizontalPadding = true) - OdsChoiceChipsFlowRow( - value = textFieldCustomizationState.trailingElement.value, - onValueChange = { value -> textFieldCustomizationState.trailingElement.value = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), - chips = listOf( - OdsChoiceChip( - text = stringResource(id = R.string.component_element_none), value = TextFieldCustomizationState.TrailingElement.None - ), - OdsChoiceChip( - text = stringResource(id = R.string.component_element_icon), value = TextFieldCustomizationState.TrailingElement.Icon - ), - OdsChoiceChip( - text = stringResource(id = R.string.component_element_text), value = TextFieldCustomizationState.TrailingElement.Text - ), + DisplayTypeCustomization(displayType) + + Subtitle(textRes = R.string.component_element_trailing, horizontalPadding = true) + OdsChoiceChipsFlowRow( + value = trailingElement.value, + onValueChange = { value -> trailingElement.value = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + chips = listOf( + OdsChoiceChip(text = stringResource(id = R.string.component_element_none), value = TextFieldCustomizationState.TrailingElement.None), + OdsChoiceChip(text = stringResource(id = R.string.component_element_icon), value = TextFieldCustomizationState.TrailingElement.Icon), + OdsChoiceChip(text = stringResource(id = R.string.component_element_text), value = TextFieldCustomizationState.TrailingElement.Text), + ) ) - ) - OdsListItem( - text = stringResource(id = R.string.component_text_field_character_counter), - trailing = OdsSwitchTrailing(checked = textFieldCustomizationState.characterCounter) - ) + OdsListItem( + text = stringResource(id = R.string.component_text_field_character_counter), + trailing = OdsListItemTrailingSwitch(characterCounter.value, { characterCounter.value = it }) + ) + } } @Composable private fun KeyboardCustomizationContent(textFieldCustomizationState: TextFieldCustomizationState) { - Subtitle(textRes = R.string.component_text_field_keyboard_type, horizontalPadding = true) - OdsChoiceChipsFlowRow( - value = textFieldCustomizationState.softKeyboardType.value, - onValueChange = { value -> textFieldCustomizationState.softKeyboardType.value = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), - chips = TextFieldCustomizationState.SoftKeyboardType.values().map { softKeyboardType -> - OdsChoiceChip(text = stringResource(id = softKeyboardType.labelRes), value = softKeyboardType) - } - ) + with(textFieldCustomizationState) { + Subtitle(textRes = R.string.component_text_field_keyboard_type, horizontalPadding = true) + OdsChoiceChipsFlowRow( + value = softKeyboardType.value, + onValueChange = { value -> softKeyboardType.value = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + chips = TextFieldCustomizationState.SoftKeyboardType.values().map { softKeyboardType -> + OdsChoiceChip(text = stringResource(id = softKeyboardType.labelRes), value = softKeyboardType) + } + ) - OdsListItem( - text = stringResource(id = R.string.component_text_field_keyboard_capitalization), - trailing = OdsSwitchTrailing(checked = textFieldCustomizationState.softKeyboardCapitalization) - ) + OdsListItem( + text = stringResource(id = R.string.component_text_field_keyboard_capitalization), + trailing = OdsListItemTrailingSwitch(softKeyboardCapitalization.value, { softKeyboardCapitalization.value = it }) + ) - Subtitle(textRes = R.string.component_text_field_keyboard_action, horizontalPadding = true) - OdsChoiceChipsFlowRow( - value = textFieldCustomizationState.softKeyboardAction.value, - onValueChange = { value -> textFieldCustomizationState.softKeyboardAction.value = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), - chips = TextFieldCustomizationState.SoftKeyboardAction.values().map { softKeyboardAction -> - OdsChoiceChip(text = stringResource(id = softKeyboardAction.labelRes), value = softKeyboardAction) - } - ) + Subtitle(textRes = R.string.component_text_field_keyboard_action, horizontalPadding = true) + OdsChoiceChipsFlowRow( + value = softKeyboardAction.value, + onValueChange = { value -> softKeyboardAction.value = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + chips = TextFieldCustomizationState.SoftKeyboardAction.values().map { softKeyboardAction -> + OdsChoiceChip(text = stringResource(id = softKeyboardAction.labelRes), value = softKeyboardAction) + } + ) + } } @Composable diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ComponentCustomizationBottomSheet.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ComponentCustomizationBottomSheet.kt index 4c19d2fc7..15e1e832b 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ComponentCustomizationBottomSheet.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ComponentCustomizationBottomSheet.kt @@ -21,8 +21,10 @@ import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics @@ -31,7 +33,7 @@ import androidx.compose.ui.unit.dp import com.orange.ods.app.R import com.orange.ods.app.ui.utilities.composable.OnResumeEffect import com.orange.ods.compose.component.bottomsheet.OdsBottomSheetScaffold -import com.orange.ods.compose.component.list.OdsListItem +import com.orange.ods.compose.text.OdsTextSubtitle1 import com.orange.ods.compose.theme.OdsTheme import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -65,7 +67,7 @@ fun ComponentCustomizationBottomSheetScaffold( snackbarHost = snackbarHost, sheetPeekHeight = 56.dp, sheetContent = { - OdsListItem( + Row( modifier = Modifier .clickable { coroutineScope.launch { @@ -78,18 +80,25 @@ fun ComponentCustomizationBottomSheetScaffold( } .semantics { stateDescription = bottomSheetHeaderStateDescription - }, - text = stringResource(id = titleResId), - icon = { - val degrees = if (bottomSheetScaffoldState.bottomSheetState.isExpanded) 0f else -180f - val angle by animateFloatAsState(targetValue = degrees) - Icon( - modifier = Modifier.rotate(angle), - painter = painterResource(id = R.drawable.ic_chevron_down), - contentDescription = null, - tint = OdsTheme.colors.onSurface - ) - }) + } + .fillMaxWidth() + .height(56.dp) + .padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)), + verticalAlignment = Alignment.CenterVertically + ) { + val degrees = if (bottomSheetScaffoldState.bottomSheetState.isExpanded) 0f else -180f + val angle by animateFloatAsState(targetValue = degrees, label = "ComponentCustomizationBottomSheetScaffoldIconRotation") + Icon( + modifier = Modifier.rotate(angle), + painter = painterResource(id = R.drawable.ic_chevron_down), + contentDescription = null, + tint = OdsTheme.colors.onSurface + ) + OdsTextSubtitle1( + modifier = Modifier.padding(start = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), + text = stringResource(id = titleResId) + ) + } Column(modifier = Modifier.verticalScroll(rememberScrollState())) { bottomSheetContent() diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/Guideline.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/Guideline.kt index 4c458e3d9..7d6b2f97f 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/Guideline.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/Guideline.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import com.orange.ods.app.R -import com.orange.ods.app.ui.MainDestinations @Immutable enum class Guideline( @@ -25,9 +24,9 @@ enum class Guideline( @DrawableRes val imageRes: Int, val route: String ) { - Color(R.string.guideline_color, R.drawable.il_color, MainDestinations.GuidelineColor), - Typography(R.string.guideline_typography, R.drawable.il_typography, MainDestinations.GuidelineTypography), - Spacing(R.string.guideline_spacing, R.drawable.il_spacing, MainDestinations.GuidelineSpacing); + Color(R.string.guideline_color, R.drawable.il_color, GuidelinesNavigation.GuidelineColor), + Typography(R.string.guideline_typography, R.drawable.il_typography, GuidelinesNavigation.GuidelineTypography), + Spacing(R.string.guideline_spacing, R.drawable.il_spacing, GuidelinesNavigation.GuidelineSpacing); val imageBackgroundColor = Color(0xff1b1b1b) diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesNavGraph.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesNavGraph.kt index 85c391a4e..196b30bd8 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesNavGraph.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesNavGraph.kt @@ -12,24 +12,24 @@ package com.orange.ods.app.ui.guidelines import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.MainDestinations -import com.orange.ods.app.ui.MainTopAppBarState import com.orange.ods.app.ui.guidelines.color.GuidelineColorScreen import com.orange.ods.app.ui.guidelines.spacing.GuidelineSpacingScreen import com.orange.ods.app.ui.guidelines.typography.GuidelineTypographyScreen +object GuidelinesNavigation { + const val GuidelineTypography = "guideline/typography" + const val GuidelineColor = "guideline/color" + const val GuidelineSpacing = "guideline/spacing" +} + fun NavGraphBuilder.addGuidelinesGraph() { - composable(MainDestinations.GuidelineColor) { - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + composable(GuidelinesNavigation.GuidelineColor) { GuidelineColorScreen() } - composable(MainDestinations.GuidelineTypography) { - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + composable(GuidelinesNavigation.GuidelineTypography) { GuidelineTypographyScreen() } - composable(MainDestinations.GuidelineSpacing) { - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + composable(GuidelinesNavigation.GuidelineSpacing) { GuidelineSpacingScreen() } } diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesScreen.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesScreen.kt index f3a2fcd01..af045589b 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/GuidelinesScreen.kt @@ -21,15 +21,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.compose.component.card.OdsCardImage import com.orange.ods.compose.component.card.OdsVerticalImageFirstCard @Composable fun GuidelinesScreen(onGuidelineClick: (String) -> Unit) { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_guidelines) val scrollState = rememberScrollState() Column( diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/color/GuidelineColorScreen.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/color/GuidelineColorScreen.kt index fac456abe..79db77380 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/color/GuidelineColorScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/color/GuidelineColorScreen.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.LocalOdsGuideline import com.orange.ods.app.ui.utilities.composable.Title import com.orange.ods.app.ui.utilities.getStringName @@ -63,8 +62,6 @@ import com.orange.ods.theme.guideline.toRgbString @Composable fun GuidelineColorScreen() { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.guideline_color) - val guidelineColors = LocalOdsGuideline.current.guidelineColors val coreColors = guidelineColors.filter { it.type == GuidelineColorType.Core } diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/spacing/GuidelineSpacingScreen.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/spacing/GuidelineSpacingScreen.kt index 87dfc3000..f8894d3dc 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/spacing/GuidelineSpacingScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/spacing/GuidelineSpacingScreen.kt @@ -10,42 +10,41 @@ package com.orange.ods.app.ui.guidelines.spacing -import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.orange.ods.compose.component.list.OdsCaptionTrailing -import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.divider -import com.orange.ods.compose.text.OdsTextSubtitle1 import com.orange.ods.app.R import com.orange.ods.app.ui.LocalThemeManager -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.guidelines.Guideline import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.composable.DetailScreenHeader import com.orange.ods.app.ui.utilities.extension.isOrange +import com.orange.ods.compose.component.list.OdsListItem +import com.orange.ods.compose.component.list.OdsListItemIcon +import com.orange.ods.compose.component.list.OdsListItemIconType +import com.orange.ods.compose.component.list.OdsListItemTrailingCaption +import com.orange.ods.compose.text.OdsTextSubtitle1 import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import java.util.* +import java.util.Locale private val ratioFormatter = DecimalFormat("0.#", DecimalFormatSymbols(Locale.ENGLISH)) @Composable fun GuidelineSpacingScreen() { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.guideline_spacing) - LazyColumn(contentPadding = PaddingValues(bottom = dimensionResource(id = com.orange.ods.R.dimen.spacing_m))) { item { DetailScreenHeader( @@ -63,55 +62,57 @@ fun GuidelineSpacingScreen() { ) } items(Spacing.values()) { spacing -> - val dividerStartIndent = - dimensionResource(id = R.dimen.guideline_spacing_image_width) + dimensionResource(id = com.orange.ods.R.dimen.spacing_m).times(2) val dp = spacing.getDp() val ratio = spacing.getRatio() OdsListItem( - modifier = Modifier.divider(startIndent = dividerStartIndent), text = spacing.tokenName, secondaryText = stringResource(id = R.string.guideline_spacing_dp, dp.value.toInt()) + "\n", singleLineSecondaryText = false, - icon = { GuidelineSpacingImage(spacing = spacing) }, - trailing = OdsCaptionTrailing( - text = stringResource( + icon = OdsListItemIcon(OdsListItemIconType.SquareImage, rememberGuidelineSpacingPainter(spacing = spacing), ""), + trailing = OdsListItemTrailingCaption( + stringResource( id = R.string.guideline_spacing_ratio, if (ratio == 0.0f) "-" else ratioFormatter.format(ratio) ) - ) + ), + divider = true ) } } } @Composable -fun GuidelineSpacingImage(spacing: Spacing) { - // Spacing width is at least 1 dp to make spacing-none visible - val spacingWidth = dimensionResource(id = spacing.dimenRes).coerceAtLeast(1.dp) - val imageWidth = dimensionResource(id = R.dimen.guideline_spacing_image_width) - val imageHeight = dimensionResource(id = R.dimen.guideline_spacing_image_height) - val isOrangeTheme = LocalThemeManager.current.currentThemeConfiguration.isOrange +private fun rememberGuidelineSpacingPainter(spacing: Spacing): GuidelineSpacingPainter { + with(LocalDensity.current) { + val width = dimensionResource(id = com.orange.ods.R.dimen.list_wide_image_width).toPx() + val height = dimensionResource(id = com.orange.ods.R.dimen.list_wide_image_height).toPx() + val spacingWidth = dimensionResource(id = spacing.dimenRes).coerceAtLeast(1.dp).toPx() // Spacing width is at least 1 dp to make spacing-none visible + val isOrangeTheme = LocalThemeManager.current.currentThemeConfiguration.isOrange + val spacingColor = if (isOrangeTheme) 0xff4bb4e6 else 0xff949494 - Canvas( - modifier = Modifier - .width(imageWidth) - .height(imageHeight), - ) { + return remember(spacing) { + GuidelineSpacingPainter(Size(width, height), spacingWidth, spacingColor) + } + } +} + +private class GuidelineSpacingPainter(override val intrinsicSize: Size, val spacingWidth: Float, val spacingColor: Long) : Painter() { + + override fun DrawScope.onDraw() { // Background drawRect(Color(0xfff2f2f7)) // Banner val bannerHeight = 16.dp drawRect( Color(0xff595959), - Offset(0.0f, ((imageHeight - bannerHeight) / 2.0f).toPx()), - Size(imageWidth.toPx(), bannerHeight.toPx()) + Offset(0.0f, ((intrinsicSize.height - bannerHeight.toPx()) / 2.0f)), + Size(intrinsicSize.width, bannerHeight.toPx()) ) // Spacing - val spacingColor = if (isOrangeTheme) 0xff4bb4e6 else 0xff949494 drawRect( Color(spacingColor), - Offset(((imageWidth - spacingWidth) / 2.0f).toPx(), 0.0f), - Size(spacingWidth.toPx(), imageHeight.toPx()) + Offset(((intrinsicSize.width - spacingWidth) / 2.0f), 0.0f), + Size(spacingWidth, intrinsicSize.height) ) } } diff --git a/app/src/main/java/com/orange/ods/app/ui/guidelines/typography/GuidelineTypographyScreen.kt b/app/src/main/java/com/orange/ods/app/ui/guidelines/typography/GuidelineTypographyScreen.kt index 7e2666144..0bc3e77ff 100644 --- a/app/src/main/java/com/orange/ods/app/ui/guidelines/typography/GuidelineTypographyScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/guidelines/typography/GuidelineTypographyScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.LocalOdsGuideline import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.composable.DetailScreenHeader @@ -41,8 +40,6 @@ import com.orange.ods.theme.guideline.GuidelineTextStyle @Composable fun GuidelineTypographyScreen() { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.guideline_typography) - val guidelineTypography = LocalOdsGuideline.current.guidelineTypography LazyColumn( diff --git a/app/src/main/java/com/orange/ods/app/ui/modules/ModulesScreen.kt b/app/src/main/java/com/orange/ods/app/ui/modules/ModulesScreen.kt index c74b84bc3..5b2015010 100644 --- a/app/src/main/java/com/orange/ods/app/ui/modules/ModulesScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/modules/ModulesScreen.kt @@ -20,12 +20,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.compose.theme.OdsTheme @Composable fun ModulesScreen() { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_modules) Column( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/orange/ods/app/ui/search/SearchScreen.kt b/app/src/main/java/com/orange/ods/app/ui/search/SearchScreen.kt index ef8ed9921..d4e669587 100644 --- a/app/src/main/java/com/orange/ods/app/ui/search/SearchScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/search/SearchScreen.kt @@ -10,14 +10,12 @@ package com.orange.ods.app.ui.search -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -31,56 +29,53 @@ import androidx.compose.ui.semantics.LiveRegionMode import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import com.orange.ods.app.R +import com.orange.ods.app.ui.LocalAppBarManager import com.orange.ods.app.ui.LocalOdsGuideline -import com.orange.ods.app.ui.MainDestinations import com.orange.ods.app.ui.components.Component +import com.orange.ods.app.ui.components.ComponentsNavigation import com.orange.ods.app.ui.components.Variant import com.orange.ods.app.ui.components.components +import com.orange.ods.app.ui.guidelines.GuidelinesNavigation import com.orange.ods.app.ui.guidelines.color.DialogColor import com.orange.ods.app.ui.guidelines.spacing.Spacing import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsListItemIcon import com.orange.ods.compose.component.list.OdsListItemIconType -import com.orange.ods.compose.component.list.iconType import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.extension.orElse import com.orange.ods.theme.guideline.GuidelineColor import com.orange.ods.theme.guideline.toHexString @Composable -fun SearchScreen( - searchedText: MutableState<TextFieldValue>, - onResultItemClick: (String, Long?) -> Unit -) { - +fun SearchScreen(onResultItemClick: (String, Long?) -> Unit) { val context = LocalContext.current + val searchedText = LocalAppBarManager.current.searchedText val filteredComponents = components.filter { component -> - searchedText.value.text.isEmpty() || stringResource(id = component.titleRes).lowercase() - .contains(searchedText.value.text.lowercase()) + searchedText.text.isEmpty() || stringResource(id = component.titleRes).lowercase() + .contains(searchedText.text.lowercase()) }.asSequence() val filteredSpacings = Spacing.values().filter { spacing -> - searchedText.value.text.isEmpty() || spacing.tokenName.lowercase() - .contains(searchedText.value.text.lowercase()) + searchedText.text.isEmpty() || spacing.tokenName.lowercase() + .contains(searchedText.text.lowercase()) } val filteredGuidelineColors = LocalOdsGuideline.current.guidelineColors.filter { guidelineColor -> - searchedText.value.text.isEmpty() || guidelineColor.getName().lowercase().contains(searchedText.value.text.lowercase()) || - guidelineColor.lightThemeName.lowercase().contains(searchedText.value.text.lowercase()) || - guidelineColor.darkThemeName.lowercase().contains(searchedText.value.text.lowercase()) + searchedText.text.isEmpty() || guidelineColor.getName().lowercase().contains(searchedText.text.lowercase()) || + guidelineColor.lightThemeName.lowercase().contains(searchedText.text.lowercase()) || + guidelineColor.darkThemeName.lowercase().contains(searchedText.text.lowercase()) } val filteredVariants = components.filter { it.variants.isNotEmpty() } .flatMap { component -> val componentImageRes = component.smallImageRes.orElse { component.imageRes } component.variants.filter { variant -> - searchedText.value.text.isEmpty() || context.getString(variant.titleRes).lowercase() - .contains(searchedText.value.text.lowercase()) + searchedText.text.isEmpty() || context.getString(variant.titleRes).lowercase() + .contains(searchedText.text.lowercase()) }.map { variant -> componentImageRes to variant } @@ -165,31 +160,24 @@ fun SearchScreen( val guidelineColor = filteredGuidelineColors.firstOrNull { guidelineColor -> guidelineColor.getName() == item.title && guidelineColor.getValue(OdsTheme.colors) == item.color } + val painter = when { + item.image != null -> painterResource(id = DrawableManager.getDrawableResIdForCurrentTheme(resId = item.image)) + item.color != null -> ColorPainter(item.color) + else -> painterResource(id = DrawableManager.getPlaceholderResId()) + } OdsListItem( text = item.title, secondaryText = item.subtitle, singleLineSecondaryText = true, - modifier = Modifier - .iconType(OdsListItemIconType.SquareImage) - .clickable { - when (item.data) { - is Component -> onResultItemClick(MainDestinations.ComponentDetailRoute, item.id) - is Variant -> onResultItemClick(MainDestinations.ComponentVariantDemoRoute, item.id) - is Spacing -> onResultItemClick(MainDestinations.GuidelineSpacing, null) - is GuidelineColor -> openDialog.value = true - } - }, - icon = { - print(item) - OdsListItemIcon( - painter = when { - item.image != null -> painterResource(id = DrawableManager.getDrawableResIdForCurrentTheme(resId = item.image)) - item.color != null -> ColorPainter(item.color) - else -> painterResource(id = DrawableManager.getPlaceholderResId()) - } - ) + icon = OdsListItemIcon(OdsListItemIconType.SquareImage, painter, "") + ) { + when (item.data) { + is Component -> onResultItemClick(ComponentsNavigation.ComponentDetailRoute, item.id) + is Variant -> onResultItemClick(ComponentsNavigation.ComponentVariantDemoRoute, item.id) + is Spacing -> onResultItemClick(GuidelinesNavigation.GuidelineSpacing, null) + is GuidelineColor -> openDialog.value = true } - ) + } if (openDialog.value && guidelineColor != null) { DialogColor(color = guidelineColor, openDialog) } diff --git a/app/src/main/java/com/orange/ods/app/ui/utilities/NavigationItem.kt b/app/src/main/java/com/orange/ods/app/ui/utilities/NavigationItem.kt index 82ed1ece5..ae7d9bc0f 100644 --- a/app/src/main/java/com/orange/ods/app/ui/utilities/NavigationItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/utilities/NavigationItem.kt @@ -15,7 +15,6 @@ import androidx.annotation.StringRes import com.orange.ods.app.R enum class NavigationItem(@DrawableRes val iconResId: Int, @StringRes val textResId: Int) { - Coffee(R.drawable.ic_coffee, R.string.navigation_item_coffee), CookingPot(R.drawable.ic_cooking_pot, R.string.navigation_item_cooking_pot), IceCream(R.drawable.ic_ice_cream, R.string.navigation_item_ice_cream), diff --git a/app/src/main/java/com/orange/ods/app/ui/utilities/UiResources.kt b/app/src/main/java/com/orange/ods/app/ui/utilities/UiResources.kt new file mode 100644 index 000000000..fe527b786 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/utilities/UiResources.kt @@ -0,0 +1,51 @@ +/* + * + * 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.utilities + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource + +sealed class UiString { + + data class StringValue(val value: String) : UiString() + + class StringResource( + @StringRes val resourceId: Int, + vararg val args: Any + ) : UiString() + + @Composable + fun asString(): String { + return when (this) { + is StringValue -> value + is StringResource -> stringResource(id = resourceId, formatArgs = args) + } + } +} + +sealed class UiPainter { + + data class PainterValue(val value: Painter) : UiPainter() + + class PainterResource(@DrawableRes val resourceId: Int) : UiPainter() + + @Composable + fun asPainter(): Painter { + return when (this) { + is PainterValue -> value + is PainterResource -> painterResource(id = resourceId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/utilities/composable/CodeImplementation.kt b/app/src/main/java/com/orange/ods/app/ui/utilities/composable/CodeImplementation.kt index c15f6628a..52294c82c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/utilities/composable/CodeImplementation.kt +++ b/app/src/main/java/com/orange/ods/app/ui/utilities/composable/CodeImplementation.kt @@ -53,20 +53,26 @@ private open class SimpleParameter(name: String, val value: String) : CodeParame private open class StringRepresentationParameter<T>(name: String, value: T) : SimpleParameter(name, value.toString()) private open class StringParameter(name: String, textValue: String) : SimpleParameter(name, "\"$textValue\"") -private open class LambdaParameter(name: String) : SimpleParameter(name, "{ }") +private open class LambdaParameter(name: String, val value: String) : CodeParameter(name) { + override val code: @Composable () -> Unit + get() = { + if (value.isNotBlank()) { + TechnicalText(text = "$name = {") + IndentCodeColumn { + TechnicalText(value) + } + TechnicalText(text = "},") + } else { + TechnicalText(text = "$name = {},") + } + } + +} + private class FloatParameter(name: String, value: Float) : SimpleParameter(name, value.toString().plus("f")) private class MutableStateParameter(name: String, stateValue: String) : SimpleParameter(name, "remember { mutableStateOf($stateValue) }") private class EnumParameter<T>(name: String, value: T) : SimpleParameter(name, value.fullName) where T : Enum<T> -private class ComposableParameter(name: String, val value: @Composable () -> Unit) : CodeParameter(name) { - override val code - get() = @Composable { - TechnicalText(text = "$name = {") - IndentCodeColumn(value) - TechnicalText(text = "},") - } -} - private open class FunctionParameter(name: String, val value: Function) : CodeParameter(name) { override val code get() = @Composable { @@ -113,8 +119,8 @@ private sealed class PredefinedParameter { class Placeholder(text: String) : StringParameter("placeholder", text) class ContentDescription(text: String) : StringParameter("contentDescription", text) - object OnClick : LambdaParameter("onClick") - object OnCheckedChange : LambdaParameter("onCheckedChange") + object OnClick : LambdaParameter("onClick", "") + object OnCheckedChange : LambdaParameter("onCheckedChange", "") } @Composable @@ -282,11 +288,10 @@ class ParametersBuilder { fun simple(name: String, value: String) = add(SimpleParameter(name, value)) fun <T> stringRepresentation(name: String, value: T) = add(StringRepresentationParameter(name, value)) fun string(name: String, textValue: String) = add(StringParameter(name, textValue)) - fun lambda(name: String) = add(LambdaParameter(name)) + fun lambda(name: String, value: String = "") = add(LambdaParameter(name, value)) fun float(name: String, value: Float) = add(FloatParameter(name, value)) fun mutableState(name: String, stateValue: String) = add(MutableStateParameter(name, stateValue)) fun <T : Enum<T>> enum(name: String, value: T) = add(EnumParameter(name, value)) - fun composable(name: String, value: @Composable () -> Unit) = add(ComposableParameter(name, value)) inline fun <reified T> classInstance(name: String, noinline parameters: ParametersBuilder.() -> Unit) = classInstance(name, T::class.java, parameters) diff --git a/app/src/main/res/drawable-hdpi/il_text_fields.png b/app/src/main/res/drawable-hdpi/il_text_fields.png index 615e54175..56d9f4c82 100644 Binary files a/app/src/main/res/drawable-hdpi/il_text_fields.png and b/app/src/main/res/drawable-hdpi/il_text_fields.png differ diff --git a/app/src/main/res/drawable-hdpi/il_text_fields_generic.png b/app/src/main/res/drawable-hdpi/il_text_fields_generic.png index 7bc54640b..e494f5833 100644 Binary files a/app/src/main/res/drawable-hdpi/il_text_fields_generic.png and b/app/src/main/res/drawable-hdpi/il_text_fields_generic.png differ diff --git a/app/src/main/res/drawable-hdpi/il_text_fields_small.png b/app/src/main/res/drawable-hdpi/il_text_fields_small.png index d5368707b..ff3fc5954 100644 Binary files a/app/src/main/res/drawable-hdpi/il_text_fields_small.png and b/app/src/main/res/drawable-hdpi/il_text_fields_small.png differ diff --git a/app/src/main/res/drawable-hdpi/il_text_fields_small_generic.png b/app/src/main/res/drawable-hdpi/il_text_fields_small_generic.png index 6a32a4f9c..96d2ea7c7 100644 Binary files a/app/src/main/res/drawable-hdpi/il_text_fields_small_generic.png and b/app/src/main/res/drawable-hdpi/il_text_fields_small_generic.png differ diff --git a/app/src/main/res/drawable-mdpi/il_text_fields.png b/app/src/main/res/drawable-mdpi/il_text_fields.png index a2c26ed97..f3bbd7f5f 100644 Binary files a/app/src/main/res/drawable-mdpi/il_text_fields.png and b/app/src/main/res/drawable-mdpi/il_text_fields.png differ diff --git a/app/src/main/res/drawable-mdpi/il_text_fields_generic.png b/app/src/main/res/drawable-mdpi/il_text_fields_generic.png index c638aaf06..b36d44279 100644 Binary files a/app/src/main/res/drawable-mdpi/il_text_fields_generic.png and b/app/src/main/res/drawable-mdpi/il_text_fields_generic.png differ diff --git a/app/src/main/res/drawable-mdpi/il_text_fields_small.png b/app/src/main/res/drawable-mdpi/il_text_fields_small.png index d1b5b1464..bb056d7c0 100644 Binary files a/app/src/main/res/drawable-mdpi/il_text_fields_small.png and b/app/src/main/res/drawable-mdpi/il_text_fields_small.png differ diff --git a/app/src/main/res/drawable-mdpi/il_text_fields_small_generic.png b/app/src/main/res/drawable-mdpi/il_text_fields_small_generic.png index bdcc3ae86..1d13478f8 100644 Binary files a/app/src/main/res/drawable-mdpi/il_text_fields_small_generic.png and b/app/src/main/res/drawable-mdpi/il_text_fields_small_generic.png differ diff --git a/app/src/main/res/drawable-xhdpi/il_text_fields.png b/app/src/main/res/drawable-xhdpi/il_text_fields.png index 725eabd7d..395a2015f 100644 Binary files a/app/src/main/res/drawable-xhdpi/il_text_fields.png and b/app/src/main/res/drawable-xhdpi/il_text_fields.png differ diff --git a/app/src/main/res/drawable-xhdpi/il_text_fields_generic.png b/app/src/main/res/drawable-xhdpi/il_text_fields_generic.png index 6fbb01b45..4c258e90b 100644 Binary files a/app/src/main/res/drawable-xhdpi/il_text_fields_generic.png and b/app/src/main/res/drawable-xhdpi/il_text_fields_generic.png differ diff --git a/app/src/main/res/drawable-xhdpi/il_text_fields_small.png b/app/src/main/res/drawable-xhdpi/il_text_fields_small.png index c5ee9baec..9a2c18d4f 100644 Binary files a/app/src/main/res/drawable-xhdpi/il_text_fields_small.png and b/app/src/main/res/drawable-xhdpi/il_text_fields_small.png differ diff --git a/app/src/main/res/drawable-xhdpi/il_text_fields_small_generic.png b/app/src/main/res/drawable-xhdpi/il_text_fields_small_generic.png index 25ffb50c3..d052df1b3 100644 Binary files a/app/src/main/res/drawable-xhdpi/il_text_fields_small_generic.png and b/app/src/main/res/drawable-xhdpi/il_text_fields_small_generic.png differ diff --git a/app/src/main/res/drawable-xxhdpi/il_text_fields.png b/app/src/main/res/drawable-xxhdpi/il_text_fields.png index 8476694da..686728d2d 100644 Binary files a/app/src/main/res/drawable-xxhdpi/il_text_fields.png and b/app/src/main/res/drawable-xxhdpi/il_text_fields.png differ diff --git a/app/src/main/res/drawable-xxhdpi/il_text_fields_generic.png b/app/src/main/res/drawable-xxhdpi/il_text_fields_generic.png index f7ed0e853..f99874c13 100644 Binary files a/app/src/main/res/drawable-xxhdpi/il_text_fields_generic.png and b/app/src/main/res/drawable-xxhdpi/il_text_fields_generic.png differ diff --git a/app/src/main/res/drawable-xxhdpi/il_text_fields_small.png b/app/src/main/res/drawable-xxhdpi/il_text_fields_small.png index c8c15cb23..62f245b92 100644 Binary files a/app/src/main/res/drawable-xxhdpi/il_text_fields_small.png and b/app/src/main/res/drawable-xxhdpi/il_text_fields_small.png differ diff --git a/app/src/main/res/drawable-xxhdpi/il_text_fields_small_generic.png b/app/src/main/res/drawable-xxhdpi/il_text_fields_small_generic.png index 9260921dc..73b3d19aa 100644 Binary files a/app/src/main/res/drawable-xxhdpi/il_text_fields_small_generic.png and b/app/src/main/res/drawable-xxhdpi/il_text_fields_small_generic.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/il_text_fields.png b/app/src/main/res/drawable-xxxhdpi/il_text_fields.png index fa4056645..c9a03327f 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/il_text_fields.png and b/app/src/main/res/drawable-xxxhdpi/il_text_fields.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/il_text_fields_generic.png b/app/src/main/res/drawable-xxxhdpi/il_text_fields_generic.png index a0e0658fc..942fa2bca 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/il_text_fields_generic.png and b/app/src/main/res/drawable-xxxhdpi/il_text_fields_generic.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/il_text_fields_small.png b/app/src/main/res/drawable-xxxhdpi/il_text_fields_small.png index 08bf05596..1e39dab98 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/il_text_fields_small.png and b/app/src/main/res/drawable-xxxhdpi/il_text_fields_small.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/il_text_fields_small_generic.png b/app/src/main/res/drawable-xxxhdpi/il_text_fields_small_generic.png index 4c8862793..558abdd38 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/il_text_fields_small_generic.png and b/app/src/main/res/drawable-xxxhdpi/il_text_fields_small_generic.png differ diff --git a/app/src/main/res/drawable/il_sliders.xml b/app/src/main/res/drawable/il_sliders.xml index 5a782c409..e886e34b9 100644 --- a/app/src/main/res/drawable/il_sliders.xml +++ b/app/src/main/res/drawable/il_sliders.xml @@ -1,28 +1,24 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="1600dp" - android:height="800dp" - android:viewportWidth="1600" - android:viewportHeight="800"> + android:width="400dp" + android:height="200dp" + android:viewportWidth="400" + android:viewportHeight="200"> <path - android:pathData="M0,0h1600v800h-1600z" + android:pathData="M0,0h400v200h-400z" + android:strokeWidth="0" android:fillColor="#1b1b1b"/> - <group> - <clip-path - android:pathData="M0,0h1600v800h-1600z"/> - <path - android:pathData="M157.9,385H5527.9c8.3,0 15,6.7 15,15h0c0,8.3 -6.7,15 -15,15H157.9c-8.3,0 -15,-6.7 -15,-15h0c0,-8.3 6.7,-15 15,-15Z" - android:strokeAlpha="0.2" - android:fillColor="#ff7900" - android:fillAlpha="0.2"/> - <group> - <clip-path - android:pathData="M157.9,390L1012.8,390c8.3,0 15,6.7 15,15h0c0,8.3 -6.7,15 -15,15L157.9,420c-8.3,0 -15,-6.7 -15,-15h0c0,-8.3 6.7,-15 15,-15Z"/> - <path - android:pathData="M-1610.4,390L3759.6,390c8.3,0 15,6.7 15,15h0c0,8.3 -6.7,15 -15,15L-1610.4,420c-8.3,0 -15,-6.7 -15,-15h0c0,-8.3 6.7,-15 15,-15Z" - android:fillColor="#ff7900"/> - </group> - <path - android:pathData="M1080.8,400m-90,0a90,90 0,1 1,180 0a90,90 0,1 1,-180 0" - android:fillColor="#ff7900"/> - </group> + <path + android:pathData="M178,96L348,96A4,4 0,0 1,352 100L352,100A4,4 0,0 1,348 104L178,104A4,4 0,0 1,174 100L174,100A4,4 0,0 1,178 96z" + android:strokeAlpha="0.2" + android:strokeWidth="0" + android:fillColor="#ff7900" + android:fillAlpha="0.2"/> + <path + android:pathData="M52,96L222,96A4,4 0,0 1,226 100L226,100A4,4 0,0 1,222 104L52,104A4,4 0,0 1,48 100L48,100A4,4 0,0 1,52 96z" + android:strokeWidth="0" + android:fillColor="#ff7900"/> + <path + android:pathData="M234,100m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" + android:strokeWidth="0" + android:fillColor="#ff7900"/> </vector> diff --git a/app/src/main/res/drawable/il_sliders_generic.xml b/app/src/main/res/drawable/il_sliders_generic.xml index c78115db9..8dd24435d 100644 --- a/app/src/main/res/drawable/il_sliders_generic.xml +++ b/app/src/main/res/drawable/il_sliders_generic.xml @@ -5,16 +5,20 @@ android:viewportHeight="200"> <path android:pathData="M0,0h400v200h-400z" + android:strokeWidth="0" android:fillColor="#1b1b1b"/> <path - android:pathData="M35.5,96.5L235.5,96.5A3.5,3.5 0,0 1,239 100L239,100A3.5,3.5 0,0 1,235.5 103.5L35.5,103.5A3.5,3.5 0,0 1,32 100L32,100A3.5,3.5 0,0 1,35.5 96.5z" - android:fillColor="#949494"/> - <path - android:pathData="M209.5,96.5L409.5,96.5A3.5,3.5 0,0 1,413 100L413,100A3.5,3.5 0,0 1,409.5 103.5L209.5,103.5A3.5,3.5 0,0 1,206 100L206,100A3.5,3.5 0,0 1,209.5 96.5z" + android:pathData="M178,96L348,96A4,4 0,0 1,352 100L352,100A4,4 0,0 1,348 104L178,104A4,4 0,0 1,174 100L174,100A4,4 0,0 1,178 96z" android:strokeAlpha="0.2" + android:strokeWidth="0" android:fillColor="#949494" android:fillAlpha="0.2"/> <path - android:pathData="M251.06,77.61c14.24,-1.42 26.11,10.45 24.7,24.68 -1.04,10.53 -9.56,19.04 -20.09,20.09 -14.24,1.42 -26.11,-10.45 -24.7,-24.68 1.04,-10.53 9.56,-19.04 20.09,-20.09Z" + android:pathData="M52,96L222,96A4,4 0,0 1,226 100L226,100A4,4 0,0 1,222 104L52,104A4,4 0,0 1,48 100L48,100A4,4 0,0 1,52 96z" + android:strokeWidth="0" + android:fillColor="#949494"/> + <path + android:pathData="M234,100m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" + android:strokeWidth="0" android:fillColor="#949494"/> </vector> diff --git a/app/src/main/res/layout/ods_bottom_navigation.xml b/app/src/main/res/layout/ods_bottom_navigation.xml new file mode 100644 index 000000000..0bcb4eff7 --- /dev/null +++ b/app/src/main/res/layout/ods_bottom_navigation.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ /* + ~ * 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. + ~ */ + --> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <com.orange.ods.xml.component.bottomnavigation.OdsBottomNavigation + android:id="@+id/ods_bottom_navigation" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + + <!-- Invisible OdsBottomNavigation used for tests --> + <com.orange.ods.xml.component.bottomnavigation.OdsBottomNavigation + android:id="@+id/ods_bottom_navigation_test" + android:visibility="gone" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + + </FrameLayout> + +</layout> \ No newline at end of file diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml deleted file mode 100644 index 4ad105d1a..000000000 --- a/app/src/main/res/values/dimen.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - ~ /* - ~ * 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. - ~ */ - --> - -<resources> - - <!-- Guideline spacing --> - <dimen name="guideline_spacing_image_width">68dp</dimen> - <dimen name="guideline_spacing_image_height">56dp</dimen> - -</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 725c99f68..15b29abc0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ <resources> <string name="app_name">Orange Design System</string> + <string name="empty" /> + <string name="code_implementation">Code implementation</string> <string name="code_implementation_xml">XML</string> <string name="code_implementation_compose">Jetpack Compose</string> @@ -21,7 +23,6 @@ <string name="navigation_item_components">Components</string> <string name="navigation_item_modules">Modules</string> <string name="navigation_item_about">About</string> - <string name="navigation_item_search" /> <!-- TopAppBar --> <string name="top_app_bar_back_icon_desc">Back</string> @@ -234,7 +235,7 @@ <!-- Component List item --> <string name="component_list_item">List item</string> <string name="component_list_item_description">A list item is the individual element of data entries like text, icons or images that is used to create a list.</string> - <string name="component_list_item_size">List item size</string> + <string name="component_list_item_line_count">Line count</string> <string name="component_list_item_add_line">Add line</string> <string name="component_list_item_remove_line">Remove line</string> <string name="component_list_leading">Leading - Before the list text</string> @@ -247,6 +248,7 @@ <string name="component_list_trailing_none">None</string> <string name="component_list_trailing_checkbox">Checkbox</string> <string name="component_list_trailing_switch">Switch</string> + <string name="component_list_trailing_radio_button">Radio button</string> <string name="component_list_trailing_icon">Icon</string> <string name="component_list_trailing_caption">Caption</string> <string name="component_list_information">Information</string> diff --git a/changelog.md b/changelog.md index 2066f548a..8fdb9497d 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,28 @@ All notable changes done in ODS library will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.17.0](https://github.com/Orange-OpenSource/ods-android/compare/0.16.0...0.17.0) - 2023-11-06 + +### Added + +- \[LibXml\] Add `OdsBottomNavigation` view ([#526](https://github.com/Orange-OpenSource/ods-android/issues/526)) + +### Changed + +- \[App\] Change top app bar management ([#651](https://github.com/Orange-OpenSource/ods-android/issues/651)) +- \[Lib\] Update `OdsBottomNavigation`, `OdsChoiceChipsFlowRow`, `OdsSlider` and `OdsSliderLockups` APIs ([#661](https://github.com/Orange-OpenSource/ods-android/issues/661)) +- \[Lib\] Review and update technical documentation ([#638](https://github.com/Orange-OpenSource/ods-android/issues/638)) +- \[Lib\] Update `OdsListItem` API ([#650](https://github.com/Orange-OpenSource/ods-android/issues/650)) +- \[Lib\] Update `OdsSnackbarHost` API ([#674](https://github.com/Orange-OpenSource/ods-android/issues/674)) + +### Fixed + +- \[App\] Start over progress components animations each time the determinate chip is selected ([#616](https://github.com/Orange-OpenSource/ods-android/issues/616)) +- \[App\] Change illustrations alignment for slider and text field components ([#618](https://github.com/Orange-OpenSource/ods-android/issues/618)) +- \[Lib\] Fix a bug where chip was not properly selected in `OdsChoiceChipsFlowRow` ([#650](https://github.com/Orange-OpenSource/ods-android/issues/650)) +- \[Lib\] Change text color of `OdsTextToggleButtonsRow` selected button for accessibility reason ([#559](https://github.com/Orange-OpenSource/ods-android/issues/559)) +- \[Lib\] Fix vertical padding of buttons in `OdsTextToggleButtonsRow` ([#559](https://github.com/Orange-OpenSource/ods-android/issues/559)) + ## [0.16.0](https://github.com/Orange-OpenSource/ods-android/compare/0.15.1...0.16.0) - 2023-10-10 ### Added diff --git a/docs/0.17.0/404.html b/docs/0.17.0/404.html new file mode 100644 index 000000000..dd331eb8e --- /dev/null +++ b/docs/0.17.0/404.html @@ -0,0 +1,26 @@ +--- +permalink: /404.html +layout: default +--- + +<style type="text/css" media="screen"> + .container { + margin: 10px auto; + max-width: 600px; + text-align: center; + } + h1 { + margin: 30px 0; + font-size: 4em; + line-height: 1; + letter-spacing: -1px; + } + +</style> + +<div class="container"> + <h1>404</h1> + + <p><strong>Page not found :(</strong></p> + <p>The requested page could not be found.</p> +</div> \ No newline at end of file diff --git a/docs/0.17.0/components/AppBarsBottom.md b/docs/0.17.0/components/AppBarsBottom.md new file mode 100644 index 000000000..19ce0cca0 --- /dev/null +++ b/docs/0.17.0/components/AppBarsBottom.md @@ -0,0 +1,31 @@ +--- +layout: detail +title: "App bars: bottom" +description: A bottom app bar displays navigation and key actions at the bottom of mobile screens. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) + +--- + +## Specifications references + +- [Design System Manager - App bars](https://system.design.orange.com/0c1af118d/p/23e0e6-app-bars/b/620966) +- [Material Design - App bars: bottom](https://material.io/components/app-bars-bottom) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Android's bottom app bar component APIs provide support for the navigation icon, +action items, overflow menu and more for informing the user as to what each +action performs. While optional, their use is strongly encouraged. + +**Content descriptions** + +When using icons for navigation icons, action items and other elements of bottom +app bars, you should set a content description on them so that screen readers +like TalkBack are able to announce their purpose or action, if any. \ No newline at end of file diff --git a/docs/0.17.0/components/AppBarsBottom_docs.md b/docs/0.17.0/components/AppBarsBottom_docs.md new file mode 100644 index 000000000..4566c033f --- /dev/null +++ b/docs/0.17.0/components/AppBarsBottom_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: AppBarsBottom.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/AppBarsTop.md b/docs/0.17.0/components/AppBarsTop.md new file mode 100644 index 000000000..af04f2208 --- /dev/null +++ b/docs/0.17.0/components/AppBarsTop.md @@ -0,0 +1,157 @@ +--- +layout: detail +title: "App bars: top" +description: Top app bars display information and actions relating to the current screen. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Regular top app bar](#regular-top-app-bar) + * [Jetpack Compose](#jetpack-compose) + * [OdsTopAppBar API](#odstopappbar-api) + * [Large top app bar](#large-top-app-bar) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsLargeTopAppBar API](#odslargetopappbar-api) + +--- + +## Specifications references + +- [Design System Manager - App bars](https://system.design.orange.com/0c1af118d/p/23e0e6-app-bars/b/620966) +- [Material Design - App bars: top](https://material.io/components/app-bars-top/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +`OdsTopAppBar` provides accessibility support for the navigation icon, +action items, overflow menu and more for informing the user as to what each +action performs. + +## Variants + +### Regular top app bar + +#### Jetpack Compose + +Add `OdsTopAppBar` composable to your Scaffold `topBar`. +Here is an example of use: + +```kotlin +OdsTopAppBar( + title = "Title", + navigationIcon = OdsTopAppBarNavigationIcon( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = "content description", + onClick = { doSomething() } + ), + actions = listOf( + OdsTopAppBarActionButton( + painter = painterResource(id = R.drawable.ic_share), + contentDescription = "content description", + onClick = { doSomething() } + ), + // ... + ), + overflowMenuActions = listOf( + OdsTopAppBarOverflowMenuActionItem( + text = "Text", + onClick = { doSomething() } + ), + // ... + ) +) +``` + +Note: By default, the `OdsTopAppBar` is elevated but you can set `elevated` parameter to `false` if you don't want any shadow below it (for example if you want to display tabs below). + +##### OdsTopAppBar API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title to be displayed in the center of the top app bar +`modifier: Modifier` | `Modifier` |`Modifier` to be applied to the top app bar +`navigationIcon: OdsTopAppBarNavigationIcon?` | `null` | Icon to be displayed at the start of the top app bar +`actions: List<OdsTopAppBarActionButton>` | `emptyList()` | Actions to be displayed at the end of the top app bar. The default layout here is a `Row`, so icons inside will be placed horizontally. +`overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem>` | `emptyList()` | Actions to be displayed in the overflow menu +`elevated: Boolean` | `true` | Controls the elevation of the top app bar: `true` to set an elevation to the top app bar (a shadow is displayed below), `false` otherwise +{:.table} + +### Large top app bar + +#### Jetpack Compose + +First, you have to add this line in your application `build.gradle.kts` file cause this component relies on Compose Material 3 implementation: + +```kotlin +implementation("androidx.compose.material3:material3:<version number>") +``` + +Then you can add `OdsLargeTopAppBar` composable to your Scaffold `topBar`: + +```kotlin +OdsLargeTopAppBar( + title = "Title", + navigationIcon = OdsTopAppBarNavigationIcon( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = "content description", + onClick = { doSomething() } + ), + actions = listOf( + OdsTopAppBarActionButton( + painter = painterResource(id = R.drawable.ic_share), + contentDescription = "content description", + onClick = { doSomething() } + ), + // ... + ), + overflowMenuActions = listOf( + OdsTopAppBarOverflowMenuActionItem( + text = "Text", + onClick = { doSomething() } + ), + // ... + ), + scrollBehavior = null // See below to attach a scroll behavior and make the top app bar collapsible +) +``` + +If you want a collapsible large top app bar, you can follow these steps: + +1 - Define the scroll behavior to use: + +```kotlin +val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) +``` + +2 - Provide this `scrollBehavior` to the `OdsLargeTopAppBar` and as a modifier of your Scaffold in order to listen to the scroll event + +```kotlin +Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + OdsLargeTopAppBar( + //... + scrollBehavior = scrollBehavior, + ) + }, + //... +) { + // Scaffold content +} +``` + +##### OdsLargeTopAppBar API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed in the center of the top app bar +`modifier: Modifier` | `Modifier` |`Modifier` applied to the top app bar +`navigationIcon: OdsTopAppBarNavigationIcon?` | `null` | Icon displayed at the start of the top app bar +`actions: List<OdsTopAppBarActionButton>` | `emptyList()` | Actions displayed at the end of the top app bar. The default layout here is a `Row`, so icons inside will be placed horizontally. +`overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem>` | `emptyList()` | Actions displayed in the overflow menu +`scrollBehavior: TopAppBarScrollBehavior?` | `null` | `TopAppBarScrollBehavior` attached to the top app bar +{:.table} diff --git a/docs/0.17.0/components/AppBarsTop_docs.md b/docs/0.17.0/components/AppBarsTop_docs.md new file mode 100644 index 000000000..1afad632b --- /dev/null +++ b/docs/0.17.0/components/AppBarsTop_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: AppBarsTop.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Banners.md b/docs/0.17.0/components/Banners.md new file mode 100644 index 000000000..e0e54d095 --- /dev/null +++ b/docs/0.17.0/components/Banners.md @@ -0,0 +1,62 @@ +--- +layout: detail +title: Banners +description: Banners displays an important message which requires an action to be dismissed. +--- + +A banner displays an important, succinct message, and provides actions for users to address (or dismiss the banner). +It requires a user action to be dismissed. + +Banners should be displayed at the top of the screen, below a top app bar. They’re persistent and nonmodal, allowing the user to either ignore them or interact with them at any time. +Only one banner should be shown at a time + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsBanner API](#odsbanner-api) + +--- + +## Specifications references + +- [Design System Manager - Banners](https://system.design.orange.com/0c1af118d/p/19a040-banners/b/497b77) +- [Material Design - Banners](https://m2.material.io/components/banners) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +`OdsBanner` is built to support accessibility criteria and is readable by most screen readers, such as TalkBack. The use of an `OdsBannerImage` force the developer to associate a content description to the banner image. + +## Implementation + +![Banner light](images/banner_light.png) + +![Banner dark](images/banner_dark.png) + +### Jetpack Compose + +You can use the `OdsBanner` composable like this: + +```kotlin +OdsBanner( + message = "Message displayed into the banner.", + firstButton = OdsBannerButton("Dismiss") { doSomething() }, + secondButton = OdsBannerButton("Detail") { doSomething() }, + image = OdsBannerImage(painterResource(id = R.drawable.placeholder), "") +) +``` + +#### OdsBanner API + +Parameter | Default value | Description +-- | -- | -- +`message: String` | | Text displayed into the banner +`firstButton: OdsBannerButton` | | Primary button displayed in the banner +`modifier: Modifier` | `Modifier` | `Modifier` applied to the banner +`image: OdsBannerImage?` | `null` | Image displayed in the banner in a circle shape +`secondButton: OdsBannerButton?` | `null` | Secondary button displayed into the banner next to the primary one +{:.table} diff --git a/docs/0.17.0/components/Banners_docs.md b/docs/0.17.0/components/Banners_docs.md new file mode 100644 index 000000000..78ed1c027 --- /dev/null +++ b/docs/0.17.0/components/Banners_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Banners.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Buttons.md b/docs/0.17.0/components/Buttons.md new file mode 100644 index 000000000..9d143e906 --- /dev/null +++ b/docs/0.17.0/components/Buttons.md @@ -0,0 +1,316 @@ +--- +layout: detail +title: Buttons +description: Buttons allow users to take actions, and make choices, with a single tap. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Contained button](#contained-button) + * [Jetpack Compose](#jetpack-compose) + * [OdsButton API](#odsbutton-api) + * [Text button](#text-button) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsTextButton API](#odstextbutton-api) + * [Outlined button](#outlined-button) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsOutlinedButton API](#odsoutlinedbutton-api) + * [Text toggle buttons group](#text-toggle-buttons-group) + * [Jetpack Compose](#jetpack-compose-3) + * [OdsTextToggleButtonsRow API](#odstexttogglebuttonsrow-api) + * [Icon button](#icon-button) + * [Jetpack Compose](#jetpack-compose-4) + * [OdsIconButton API](#odsiconbutton-api) + * [Icon toggle button](#icon-toggle-button) + * [Jetpack Compose](#jetpack-compose-5) + * [OdsIconToggleButton API](#odsicontogglebutton-api) + * [Icon toggle buttons group](#icon-toggle-buttons-group) + * [Jetpack Compose](#jetpack-compose-6) + * [OdsIconToggleButtonsRow API](#odsicontogglebuttonsrow-api) + +--- + +## Specifications references + +- [Design System Manager - Buttons](https://system.design.orange.com/0c1af118d/p/06a393-buttons/b/530521) +- [Material Design - Buttons](https://material.io/components/buttons/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +ODS buttons support accessibility criteria and are readable by most screen readers, such as TalkBack. + +Content descriptions for icons are unnecessary in the case of buttons containing text. For other buttons types, such as `OdsIconButton`, icons content descriptions are mandatory in the APIs. + +## Variants + +### Contained button + +Contained buttons are high-emphasis, distinguished by their use of elevation and fill. They contain +actions that are primary to your app. + +![ContainedButton](images/button_contained_light.png) ![ContainedButton dark](images/button_contained_dark.png) + +Functional positive: + +![ContainedButton positive light](images/button_contained_positive_light.png) ![ContainedButton positive dark](images/button_contained_positive_dark.png) + +Functional negative: + +![ContainedButton negative light](images/button_contained_negative_light.png) ![ContainedButton negative dark](images/button_contained_negative_dark.png) + +#### Jetpack Compose + +Use the `OdsButton` composable: + +```kotlin +OdsButton( + text = "Contained button", + onClick = { doSomething() }, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Line can be removed if you don't need any icon +) +``` + +To display a primary button or a functional green/red button, you need to pass an `OdsButtonStyle` +through the `style` parameter: + +```kotlin +OdsButton( + text = "Positive button", + onClick = { doSomething() }, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Line can be removed if you don't need any icon + style = OdsButtonStyle.FunctionalPositive +) +``` + +##### OdsButton API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked when the button is clicked +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`style: OdsButtonStyle` | `OdsButtonStyle.Default` | Style applied to the button. Set it to `OdsButtonStyle.Primary` for an highlighted button style or use `OdsButtonStyle.FunctionalPositive`/ `OdsButtonStyle.FunctionalNegative` for a functional green/red button style. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Text button + +Text buttons are typically used for less-pronounced actions, including those located in dialogs and +cards. In cards, text buttons help maintain an emphasis on card content. + +![TextButton](images/button_text_light.png) ![TextButton dark](images/button_text_dark.png) + +#### Jetpack Compose + +Use the `OdsTextButton` composable: + +```kotlin +OdsTextButton( + text = "Text button", + onClick = { doSomething() }, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Line can be removed if you don't need any icon + style = OdsTextButtonStyle.Primary +) +``` + +##### OdsTextButton API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked on button click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`style: OdsTextButtonStyle` | `OdsTextButtonStyle.Default` | Style applied to the button. By default `onSurface` color is used for text color. Use `OdsTextButtonStyle.Primary` for an highlighted text color. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Outlined button + +Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren’t +the primary action in an app. + +![ButtonOutlined](images/button_outlined_light.png) ![ButtonOutlined dark](images/button_outlined_dark.png) + +#### Jetpack Compose + +Use the `OdsOutlinedButton` composable: + +```kotlin +OdsOutlinedButton( + text = "Outlined button", + onClick = {}, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Line can be removed if you don't need any icon +) +``` + +##### OdsOutlinedButton API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked on button click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, the button is not clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Text toggle buttons group + +A group of text toggle buttons. Only one option in a group of toggle buttons can be selected and active at a time. +Selecting one option deselects any other. + +![Button text toggle group light](images/button_text_toggle_group_light.png) ![Button text toggle group dark](images/button_text_toggle_group_dark.png) + +#### Jetpack Compose + +Use the `OdsTextToggleButtonsRow` composable: + +```kotlin +OdsTextToggleButtonsRow( + textToggleButtons = listOf( + OdsTextToggleButtonsRowItem("XML", true), + OdsTextToggleButtonsRowItem("COMPOSE", true), + ), + selectedIndex = 0, + onSelectedIndexChange = { + doSomething() // Do something like changing selectedIndex to refresh composable with new selection + }, + sameItemsWeight = false +) +``` + +##### OdsTextToggleButtonsRow API + +Parameter | Default value | Description +-- | -- | -- +`textToggleButtons: List<OdsTextToggleButtonsRowItem>` | | Items displayed into the toggle group +`selectedIndex: Int` | | `textToggleButtons` list index of the selected button +`onSelectedIndexChange: (Int) -> Unit` | | Callback invoked on selection change +`modifier: Modifier` | `Modifier` | `Modifier` applied to the toggle buttons row +`sameItemsWeight: Boolean` | `false` | Controls the place occupied by each item. When `true`, same weight of importance will be applied to each item, they will occupy the same width. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Icon button + +An icon button is a clickable icon, used to represent actions. This component is typically used +inside an App Bar for the navigation icon / actions. + +![OdsIconButton](images/button_icon_light.png) ![OdsIconButton dark](images/button_icon_dark.png) + +#### Jetpack Compose + +Use the `OdsIconButton` composable: + +```kotlin +OdsIconButton( + icon = OdsIconButtonIcon( + painterResource(id = R.drawable.ic_ui_light_mode), + stringResource(id = R.string.theme_changer_icon_content_description_light) + ), + onClick = { doSomething() }, +) +``` + +##### OdsIconButton API + +Parameter | Default value | Description +-- | -- | -- +`icon: OdsIconButtonIcon` | | Icon to be drawn into the button +`onClick: () -> Unit` | | Callback to be invoked when the button is clicked +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the button +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` to be applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Icon toggle button + +An icon button with two states, for icons that can be toggled 'on' and 'off', such as a bookmark +icon, or a navigation icon that opens a drawer. + +![Button icon toggle light](images/button_icon_toggle_light.png) ![Button icon toggle dark](images/button_icon_toggle_dark.png) + +#### Jetpack Compose + +Use the `OdsIconToggleButton` composable: + +```kotlin +OdsIconToggleButton( + checked = false, + onCheckedChange = { doSomething() }, + uncheckedIcon = OdsIconButtonIcon( + painterResource(R.drawable.ic_heart_outlined), + "Add to favorites" + ), + checkedIcon = OdsIconButtonIcon(painterResource(R.drawable.ic_heart), "Remove from favorites") +) +``` + +##### OdsIconToggleButton API + +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls the checked state of the button +`onCheckedChange: (Boolean) -> Unit` | | Callback invoked when the button is checked +`uncheckedIcon: OdsIconButtonIcon` | | Icon displayed when the button is unchecked +`checkedIcon: OdsIconButtonIcon` | | Icon displayed when the button is checked +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + +### Icon toggle buttons group + +A group of toggle buttons. Only one option in a group of toggle buttons can be selected and active +at a time. +Selecting one option deselects any other. + +![Button icon toggle group light](images/button_icon_toggle_group_light.png) ![Button icon toggle group dark](images/button_icon_toggle_group_dark.png) + +#### Jetpack Compose + +Use the `OdsIconToggleButtonsRow` composable: + +```kotlin +OdsIconToggleButtonsRow( + icons = listOf( + OdsIconToggleButtonsRowIcon(painterResource(id = R.drawable.ic_restaurant), "Restaurant"), + OdsIconToggleButtonsRowIcon(painterResource(id = R.drawable.ic_cooking_pot), "Cooking pot"), + OdsIconToggleButtonsRowIcon( + painterResource(id = R.drawable.ic_coffee), + "Coffee", + enabled = false + ) + ), + selectedIndex = 0, + onSelectedIndexChange = { + doSomething() // Do something like changing selectedIndex to refresh composable with new selection + }, + displaySurface = displaySurface +) +``` + +##### OdsIconToggleButtonsRow API + +Parameter | Default value | Description +-- | -- | -- +`icons: List<OdsIconToggleButtonsRowIcon>` | | Icons to be displayed into the toggle group +`selectedIndex: Int` | | `icons` list index of the selected button +`onSelectedIndexChange: (Int) -> Unit` | | Callback invoked on selection change +`modifier: Modifier` | `Modifier` | `Modifier` applied to the toggle buttons group +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} diff --git a/docs/0.17.0/components/Buttons_docs.md b/docs/0.17.0/components/Buttons_docs.md new file mode 100644 index 000000000..2617cf23f --- /dev/null +++ b/docs/0.17.0/components/Buttons_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Buttons.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Cards.md b/docs/0.17.0/components/Cards.md new file mode 100644 index 000000000..183b5d359 --- /dev/null +++ b/docs/0.17.0/components/Cards.md @@ -0,0 +1,222 @@ +--- +layout: detail +title: Cards +description: Cards contain content and actions about a single subject. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Vertical image first card](#vertical-image-first-card) + * [Jetpack Compose](#jetpack-compose) + * [OdsVerticalImageFirstCard API](#odsverticalimagefirstcard-api) + * [Vertical header first card](#vertical-header-first-card) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsVerticalHeaderFirstCard API](#odsverticalheaderfirstcard-api) + * [Small card](#small-card) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsSmallCard API](#odssmallcard-api) + * [Horizontal card](#horizontal-card) + * [Jetpack Compose](#jetpack-compose-3) + * [OdsHorizontalCard API](#odshorizontalcard-api) + +--- + +## Specifications references + +- [Design System Manager - Cards](https://system.design.orange.com/0c1af118d/p/272739-cards/b/991690) +- [Material Design - Cards](https://material.io/components/cards/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +The contents within a card should follow their own accessibility guidelines, such as images having content descriptions set on them. +The ODS library cards APIs forces the developers to add content descriptions on card images. + +## Variants + +The library offers several Composables for Jetpack Compose implementation. + +### Vertical image first card + +This is a full width card containing elements arranged vertically with an image as first element. + +![Vertical image first card light](images/card_vertical_image_first_light.png) ![Vertical image first card dark](images/card_vertical_image_first_dark.png) + +#### Jetpack Compose + +In your composable screen you can use `OdsVerticalImageFirstCard` composable: + +```kotlin +OdsVerticalImageFirstCard( + title = "Title", + image = OdsCardImage( + painterResource(R.drawable.picture), + "Picture content description", + Alignment.Center, + ContentScale.Crop, + Color(0xff1b1b1b) + ), + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + onClick = { doSomething() } +) +``` + +##### OdsVerticalImageFirstCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} + +### Vertical header first card + +This is a full width card containing elements arranged vertically with a header (thumbnail, title & subtitle) as first element. + +![Vertical header first card light](images/card_vertical_header_first_light.png) ![Vertical header first card dark](images/card_vertical_header_first_dark.png) + +#### Jetpack Compose + +In your composable screen you can use `OdsVerticalHeaderFirstCard` composable: + +```kotlin +OdsVerticalHeaderFirstCard( + title = "Title", + image = OdsCardImage( + painterResource(R.drawable.picture), + "Picture content description", + Alignment.Center, + ContentScale.Crop, + Color(0xff1b1b1b) + ), + thumbnail = OdsCardThumbnail( + painterResource(R.drawable.thumbnail), + "Thumbnail content description" + ), + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + onClick = { doSomething() } +) +``` + +##### OdsVerticalHeaderFirstCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`thumbnail: OdsCardThumbnail?` | `null` | Thumbnail displayed into the card next to the title: avatar, logo or icon. +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback called on card click +{:.table} + +### Small card + +This is a small card which takes the half screen width. + +![CardSmall](images/card_small_light.png) ![CardSmall dark](images/card_small_dark.png) + +#### Jetpack Compose + +You can add an `OdsSmallCard` composable in your screen to add a small card: + +```kotlin +Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), +) { + OdsSmallCard( + title = "Title", + image = OdsCardImage( + painterResource(R.drawable.picture), + "Picture content description" + ), + modifier = Modifier.weight(0.5f), + onClick = { doSomething() } + ) + OdsSmallCard( + title = "Title", + image = OdsCardImage( + painterResource(R.drawable.picture), + "Picture content description" + ), + modifier = Modifier.weight(0.5f), + onClick = { doSomething() } + ) +} +``` + +##### OdsSmallCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} + +### Horizontal card + +This is a full screen width card with an image on the side. The image can be displayed on the left or on the right. + +![Horizontal card light](images/card_horizontal_light.png) ![Horizontal card dark](images/card_horizontal_dark.png) + +#### Jetpack Compose + +In your screen you can use `OdsHorizontalCard` composable: + +```kotlin +OdsHorizontalCard( + title = "Title", + image = OdsCardImage( + painterResource(R.drawable.picture), + "Picture content description", + Alignment.Center, + ContentScale.Crop, + Color(0xff1b1b1b) + ), + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + imagePosition = OdsHorizontalCardImagePosition.Start, + divider = false, + onClick = { doSomething() } +) +``` + +##### OdsHorizontalCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`imagePosition: OdsHorizontalCardImagePosition` | `OdsHorizontalCardImagePosition.Start` | Position of the image within the card, it can be set to `OdsHorizontalCardImagePosition.Start` or `OdsHorizontalCardImagePosition.End` +`divider: Boolean` | `true` | Controls the divider display. If `true`, it will be displayed between the card content and the action buttons. +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} diff --git a/docs/0.17.0/components/Cards_docs.md b/docs/0.17.0/components/Cards_docs.md new file mode 100644 index 000000000..ac6a9375c --- /dev/null +++ b/docs/0.17.0/components/Cards_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Cards.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Checkboxes.md b/docs/0.17.0/components/Checkboxes.md new file mode 100644 index 000000000..505d1a8e5 --- /dev/null +++ b/docs/0.17.0/components/Checkboxes.md @@ -0,0 +1,62 @@ +--- +layout: detail +title: Checkboxes +description: Checkbox selection control allows the user to select options. +--- + +Use checkboxes to: + +* Select one or more options from a list +* Present a list containing sub-selections +* Turn an item on or off in a desktop environment + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsCheckbox API](#odscheckbox-api) + +--- + +## Specifications references + +- [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) +- [Material Design - Checkboxes](https://material.io/components/checkboxes/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Checkboxes support content labeling for accessibility and are readable by most screen readers, such +as TalkBack. Text rendered in check boxes is automatically provided to accessibility services. +Additional content labels are usually unnecessary. + +## Implementation + +![Checkbox](images/checkbox_light.png) ![Checkbox dark](images/checkbox_dark.png) + +### Jetpack Compose + +In your composable screen you can use: + +```kotlin +var checked by remember { mutableStateOf(false) } +OdsCheckbox( + checked = checked, + onCheckedChange = { checked = it }, + enabled = true +) +``` + +#### OdsCheckbox API + +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls checked state of the checkbox +`onCheckedChange: ((Boolean) -> Unit)?` | | Callback invoked on checkbox click. If `null`, then this is passive and relies entirely on a higher-level component to control the checked state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the checkbox +`enabled: Boolean` | `true` | Controls enabled state of the checkbox. When `false`, this checkbox will not be clickable. +{:.table} + diff --git a/docs/0.17.0/components/Checkboxes_docs.md b/docs/0.17.0/components/Checkboxes_docs.md new file mode 100644 index 000000000..6e2b8096e --- /dev/null +++ b/docs/0.17.0/components/Checkboxes_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Checkboxes.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Chips.md b/docs/0.17.0/components/Chips.md new file mode 100644 index 000000000..4693aee14 --- /dev/null +++ b/docs/0.17.0/components/Chips.md @@ -0,0 +1,236 @@ +--- +layout: detail +title: Chips +description: Chips are compact elements that represent an input, attribute, or action. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Input chip](#input-chip) + * [Jetpack Compose](#jetpack-compose) + * [OdsChip API](#odschip-api) + * [Choice chip](#choice-chip) + * [Jetpack Compose](#jetpack-compose-1) + * [Filter chip](#filter-chip) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsFilterChip API](#odsfilterchip-api) + * [Action chip](#action-chip) + * [Jetpack Compose](#jetpack-compose-3) +* [Extras](#extras) + * [Choice chips flow row](#choice-chips-flow-row) + * [OdsChoiceChipsFlowRow API](#odschoicechipsflowrow-api) + +--- + +## Specifications references + +- [Design System Manager](https://system.design.orange.com/0c1af118d/p/81aa91-chips/b/13c40e) +- [Material Design](https://material.io/components/chips) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Chips support content labeling for accessibility and are readable by most screen readers, such as +TalkBack. Text rendered in chips is automatically provided to accessibility services. Additional +content labels are usually unnecessary. + +## Variants + +### Input chip + +Input chips (referred to as **entry** chips in Android) represent a complex piece of information in +compact form, such as an entity (person, place, or thing) or text. They enable user input and verify +that input by converting text into chips. + +![Light input chip](images/chips_input_light.png) ![Dark input chip](images/chips_input_dark.png) + +![Light outlined input chip](images/chips_input_outlined_light.png) ![Dark outlined input chip](images/chips_input_outlined_dark.png) + +#### Jetpack Compose + +Use the `OdsChip` composable. +Note that the chip style is outlined or filled according to your OdsTheme component configuration, +outlined by default. + +```kotlin +OdsChip( + text = "chip text", + onClick = { + doSomething() + }, + leadingIcon = null, + leadingAvatar = OdsChipLeadingAvatar( + painterResource(id = R.drawable.avatar), + "Avatar" + ), // Set it to `null` for no avatar or provide a `leadingIcon` + enabled = true, + onCancel = { + doSomething() + } +) +``` + +##### OdsChip API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text to be displayed into the chip +`onClick: () -> Unit` | | Callback called on chip click +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the chip +`enabled: Boolean` | `true` | Controls the enabled state of the chip. When `false`, this chip will not respond to user input. +`selected: Boolean` | `false` | Controls the selected state of the chip. When `true`, the chip is highlighted (useful for choice chips). +`leadingIcon: OdsChipLeadingIcon?` | `null` | Icon to be displayed at the start of the chip, preceding the text +`leadingAvatar: OdsChipLeadingAvatar?` | `null` | Avatar to be displayed in a circle shape at the start of the chip, preceding the content text +`onCancel: (() -> Unit)?` | `null` | Callback called on chip cancel cross click. Pass `null` for no cancel cross. +{:.table} + +### Choice chip + +Choice chips allow selection of a single chip from a set of options. + +Choice chips clearly delineate and display options in a compact area. They are a good alternative to +toggle buttons, radio buttons, and single select menus. + +**Note: To display a set of choice chips please see [Choice chips flow row](#choice-chips-flow-row) +** + +![Light choice chips](images/chips_choice_light.png) ![Dark choice chips](images/chips_choice_dark.png) + +![Light outlined choice chips](images/chips_choice_outlined_light.png) ![Dark outlined choice chips](images/chips_choice_outlined_dark.png) + +#### Jetpack Compose + +Use the `OdsChip` composable. +Note that the chip style is outlined or filled according to your OdsTheme component configuration, +outlined by default. + +```kotlin +OdsChip( + text = "chip text", + onClick = { + doSomething() + }, + enabled = true, +) +``` + +Use the [OdsChip API](#odschip-api). + +### Filter chip + +Filter chips use tags or descriptive words to filter content. + +Filter chips clearly delineate and display options in a compact area. They are a good alternative to +toggle buttons or checkboxes. + +![Light filter chips](images/chips_filter_light.png) ![Dark filter chips](images/chips_filter_dark.png) + +![Light filter chips with avatar](images/chips_filter_avatar_light.png) ![Dark filter chips with avatar](images/chips_filter_avatar_dark.png) + +#### Jetpack Compose + +Use the `OdsFilterChip` composable. +Note that the chip style is outlined or filled according to your OdsTheme component configuration, +outlined by default. + +```kotlin +OdsFilterChip( + text = "chip text", + onClick = { + doSomething() + }, + leadingAvatar = OdsChipLeadingAvatar( + painterResource(id = R.drawable.avatar), + "" + ), // Set it to `null` for no avatar + selected = false, + enabled = true, +) +``` + +##### OdsFilterChip API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text to be displayed into the chip +`onClick: () -> Unit` | | Callback called on chip click +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the chip +`enabled: Boolean` | `true` | Controls the enabled state of the chip. When `false`, this chip will not respond to user input. It also appears visually disabled and is disabled to accessibility services. +`selected: Boolean` | `false` | Controls the selected state of the chip. When `true`, the chip is highlighted. +`leadingAvatar: OdsChipLeadingAvatar?` | `null` | Avatar to be displayed in a circle shape at the start of the chip, preceding the content text +{:.table} + +### Action chip + +Action chips offer actions related to primary content. They should appear dynamically and +contextually in a UI. + +An alternative to action chips are buttons, which should appear persistently and consistently. + +![Light action chip](images/chips_action_light.png) ![Dark action chip](images/chips_action_dark.png) + +![Light outlined action chip](images/chips_action_outlined_light.png) ![Dark outlined action chip](images/chips_action_outlined_dark.png) + +#### Jetpack Compose + +Use the `OdsChip` composable. +Note that the chip style is outlined or filled according to your OdsTheme component configuration, +outlined by default. + +```kotlin +OdsChip( + text = "chip text", + onClick = { + doSomething() + }, + leadingIcon = OdsChipLeadingIcon( + painterResource(id = R.drawable.ic_heart), + "Heart" + ), // set it to `null` for no icon + enabled = true, +) +``` + +Use the [OdsChip API](#odschip-api). + +## Extras + +The ODS library provides some chips related components to facilitate the implementation of chips groups. + +### Choice chips flow row + +This is a full width `FlowRow` containing selectable chips. It works like radio buttons, only one chip of the set can be selected. + +![Light choice chips flow row](images/chips_choice_flow_row_light.png) + +![Dark choice chips flow row](images/chips_choice_flow_row_dark.png) + +Use `OdsChoiceChipsFlowRow` composable. +Note that the chips style is outlined or filled according to your OdsTheme component configuration, +outlined by default. + +```kotlin +OdsChoiceChipsFlowRow( + chips = listOf( + OdsChoiceChip(text = "Choice chip 1", value = 1), + OdsChoiceChip(text = "Choice chip 2", value = 2) + ), + value = chipValue, + onValueChange = { value -> chipValue = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)) +) +``` + +#### OdsChoiceChipsFlowRow API + +Parameter | Default value | Description +-- | -- | -- +`chips: List<OdsChoiceChip<T>>` | | Chips displayed into the flow row +`value: String` | | Initial value of the choice chips flow row +`onValueChange: (value: T) -> Unit` | | Callback invoked when the value changes. The new value is provided as parameter. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the chips flow row +{:.table} diff --git a/docs/0.17.0/components/Chips_docs.md b/docs/0.17.0/components/Chips_docs.md new file mode 100644 index 000000000..c69ad1fd9 --- /dev/null +++ b/docs/0.17.0/components/Chips_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Chips.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Dialogs.md b/docs/0.17.0/components/Dialogs.md new file mode 100644 index 000000000..8d2df08b2 --- /dev/null +++ b/docs/0.17.0/components/Dialogs.md @@ -0,0 +1,68 @@ +--- +layout: detail +title: Dialogs +description: Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks. +--- + +A dialog is a type of modal window that appears in front of app content to +provide critical information or ask for a decision. Dialogs disable all app +functionality when they appear, and remain on screen until confirmed, dismissed, +or a required action has been taken. + +Dialogs are purposefully interruptive, so they should be used sparingly. + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Alert dialog](#alert-dialog) + * [Jetpack Compose](#jetpack-compose) + * [OdsAlertDialog API](#odsalertdialog-api) + +--- + +## Specifications references + +- [Design System Manager - Dialogs](https://system.design.orange.com/0c1af118d/p/02ae02-dialogs/b/81772e) +- [Material Design - Dialogs](https://material.io/components/dialogs) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Variants + +### Alert dialog + +Alert dialogs interrupt users with urgent information, details, or actions. + +![Alert dialog light](images/dialog_alert_light.png) ![Alert dialog dark](images/dialog_alert_dark.png) + +#### Jetpack Compose + +To display an alert dialog in your composable screen, you can use: + +```kotlin +OdsAlertDialog( + modifier = Modifier, + title = "title", + text = "content text of the dialog", + confirmButton = OdsAlertDialogButton("confirm") { doSomething() }, + dismissButton = OdsAlertDialogButton("dismiss") { doSomething() }, + properties = DialogProperties() +) +``` + +##### OdsAlertDialog API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the dialog which presents the details regarding the Dialog's purpose +`confirmButton: OdsAlertDialogButton` | | Button displayed into the dialog which is meant to confirm a proposed action, thus resolving what triggered the dialog +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the dialog +`onDismissRequest: () -> Unit` | `{}` | Callback invoked when the user tries to dismiss the dialog by clicking outside or pressing the back button. This is not called when the dismiss button is clicked. +`dismissButton: OdsAlertDialogButton?` | `null` | Button displayed into the dialog which is meant to dismiss the dialog +`title: String?` | `null` | Title displayed into the dialog which should specify the purpose of the dialog. The title is not mandatory, because there may be sufficient information inside the `text`. +`properties: DialogProperties` | `DialogProperties()` | Typically platform specific properties to further configure the dialog +{:.table} diff --git a/docs/0.17.0/components/Dialogs_docs.md b/docs/0.17.0/components/Dialogs_docs.md new file mode 100644 index 000000000..30e1a52ad --- /dev/null +++ b/docs/0.17.0/components/Dialogs_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Dialogs.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/FloatingActionButtons.md b/docs/0.17.0/components/FloatingActionButtons.md new file mode 100644 index 000000000..969e29ffb --- /dev/null +++ b/docs/0.17.0/components/FloatingActionButtons.md @@ -0,0 +1,127 @@ +--- +layout: detail +title: Floating action buttons +description: A floating action button (FAB) represents the primary action of a screen. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Regular FAB](#regular-fab) + * [Jetpack Compose](#jetpack-compose) + * [OdsFloatingActionButton API](#odsfloatingactionbutton-api) + * [Mini FAB](#mini-fab) + * [Jetpack Compose](#jetpack-compose-1) + * [Extended FAB](#extended-fab) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsExtendedFloatingActionButton API](#odsextendedfloatingactionbutton-api) + +--- + +## Specifications references + +- [Design System Manager - Floating Action Button](https://system.design.orange.com/0c1af118d/p/577022-buttons-fab/b/101b2a) +- [Material Design - Buttons: floating action button](https://material.io/components/buttons-floating-action-button/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +The `OdsFloatingActionButtonIcon` used in Floating Action Buttons APIs force the developers to set a content description to the FABs so that +screen readers like TalkBack are able to announce their purpose or action. + +Text rendered in an extended FAB is automatically provided to accessibility services, so additional content labels are usually unnecessary. +In this context you can set an empty `contentDescription`. + +## Variants + +### Regular FAB + +Regular FABs are FABs that are not expanded and are a regular size. + +![FAB light](images/fab_light.png) ![FAB dark](images/fab_dark.png) + +#### Jetpack Compose + +To display a regular Floating Action Button in your composable screen, use `OdsFloatingActionButton`: + +```kotlin +OdsFloatingActionButton( + onClick = { doSomething() }, + mini = false, + icon = OdsFloatingActionButtonIcon( + painterResource(id = R.drawable.ic_plus), + stringResource(id = R.string.add) + ), + modifier = modifier +) +``` + +##### OdsFloatingActionButton API + +Parameter | Default value | Description +-- | -- | -- +`icon: OdsFloatingActionButtonIcon` | | Icon used into the FAB +`onClick: () -> Unit` | | Callback invoked on FAB click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the FAB +`mini: Boolean` | `false` | Controls the size of the FAB. If `true`, the size of the FAB will be 40dp, otherwise the default size will be used. +{:.table} + +### Mini FAB + +A mini FAB should be used on smaller screens. + +Mini FABs can also be used to create visual continuity with other screen elements. + +![FAB mini light](images/fab_mini_light.png) ![FAB mini dark](images/fab_mini_dark.png) + +#### Jetpack Compose + +To display a mini FAB in your composable screen use `OdsFloatingActionButton` + +```kotlin +OdsFloatingActionButton( + onClick = { doSomething() }, + mini = true, + icon = OdsFloatingActionButtonIcon( + painterResource(id = R.drawable.ic_plus), + stringResource(id = R.string.add) + ), + modifier = modifier +) +``` + +Use [OdsFloatingActionButton API](#odsfloatingactionbutton-api). + +### Extended FAB + +The extended FAB is wider, and it includes a text label. + +![FAB extended light](images/fab_extended_light.png) ![FAB extended dark](images/fab_extended_dark.png) + +![FAB extended full width light](images/fab_extended_full_width_light.png) ![FAB extended full width dark](images/fab_extended_full_width_dark.png) + +#### Jetpack Compose + +To display an extended FAB, use `OdsExtendedFloatingActionButton`: + +```kotlin +OdsExtendedFloatingActionButton( + onClick = { doSomething() }, + text = stringResource(id = R.string.add), + icon = OdsFloatingActionButtonIcon(painterResource(id = R.drawable.ic_plus), ""), + modifier = modifier +) +``` + +##### OdsExtendedFloatingActionButton API + +Parameter | Default value | Description +-- | -- | -- +`icon: OdsFloatingActionButtonIcon` | | Icon used into the FAB +`onClick: () -> Unit` | | Callback invoked on FAB click +`text: String` | | Text displayed into the FAB +`modifier: Modifier` | `Modifier` | `Modifier` applied to the FAB +{:.table} diff --git a/docs/0.17.0/components/FloatingActionButtons_docs.md b/docs/0.17.0/components/FloatingActionButtons_docs.md new file mode 100644 index 000000000..c1bcf38ba --- /dev/null +++ b/docs/0.17.0/components/FloatingActionButtons_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: FloatingActionButtons.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/ImageTile.md b/docs/0.17.0/components/ImageTile.md new file mode 100644 index 000000000..f96dd2a4e --- /dev/null +++ b/docs/0.17.0/components/ImageTile.md @@ -0,0 +1,69 @@ +--- +layout: detail +title: Image Tile +description: +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsImageTile API](#odsimagetile-api) + +--- + +## Specifications references + +- [Design System Manager - Image Tile](https://system.design.orange.com/0c1af118d/p/49434d-image-item) +- [Material Design - Image lists](https://m2.material.io/components/image-lists) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +An image in an `OdsImageTile` should always be associated to a content description. This is the reason why the `OdsImageTileImage` forces the developer to fill a content description parameter. + +## Implementation + +#### Jetpack Compose + +You can use the `OdsImageTile` composable like this: + +```kotlin +OdsImageTile( + image = OdsImageTileImage( + painterResource(id = R.drawable.placeholder), + "Picture content description" + ), + modifier = modifier, + captionDisplayType = OdsImageTileCaptionDisplayType.Overlay, + title = "Component Image Tile", + icon = OdsImageTileIconToggleButton( + uncheckedIcon = OdsIconButtonIcon( + painterResource(id = R.drawable.ic_heart_outlined), + "Add to favourites" + ), + checkedIcon = OdsIconButtonIcon( + painterResource(id = R.drawable.ic_heart), + "Remove from favourites" + ), + checked = false, + onCheckedChange = { doSomething() }, + ), + onClick = { doSomething() } +) +``` + +##### OdsImageTile API + +Parameter | Default value | Description +-- | -- | -- +`image: OdsImageTileImage` | | Image displayed into the tile +`legendAreaDisplayType: OdsImageTileLegendAreaDisplayType` | | Controls how the title and the icon are displayed relatively to the image. If set to `OdsImageTileLegendAreaDisplayType.None`, no legend area will be displayed. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the image tile +`title: String?` | `null` | Title displayed into the tile. It is linked to the image and displayed according to the `legendAreaDisplayType` value. +`icon: OdsImageTileIconToggleButton` | `null` | Clickable icon displayed next to the `title` +`onClick: (() -> Unit)?` | `null` | Callback invoked on tile click +{:.table} diff --git a/docs/0.17.0/components/ImageTile_docs.md b/docs/0.17.0/components/ImageTile_docs.md new file mode 100644 index 000000000..f892cf69b --- /dev/null +++ b/docs/0.17.0/components/ImageTile_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: ImageTile.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/ListItems.md b/docs/0.17.0/components/ListItems.md new file mode 100644 index 000000000..66de11a66 --- /dev/null +++ b/docs/0.17.0/components/ListItems.md @@ -0,0 +1,155 @@ +--- +layout: detail +title: List items +description: Lists are continuous, vertical indexes of text or images. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Single-line list](#single-line-list) + * [Jetpack Compose](#jetpack-compose) + * [OdsListItem API](#odslistitem-api) + * [Two-line list](#two-line-list) + * [Jetpack Compose](#jetpack-compose-1) + * [Three-line list](#three-line-list) + * [Jetpack Compose](#jetpack-compose-2) + +--- + +## Specifications references + +- [Design System Manager - Lists](https://system.design.orange.com/0c1af118d/p/09a804-lists/b/669743) +- [Material Design - Lists](https://material.io/components/lists/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Variants + +### Single-line list + +There are multiple display possibilities for a single-line list, where leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists single-line wide image](images/lists_single_line_wide_image_light.png) ![Lists single-line wide image dark](images/lists_single_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists single-line](images/lists_single_line_light.png) ![Lists single-line dark](images/lists_single_line_dark.png) + +Please note that there is no start padding with wide images. + +#### Jetpack Compose + +The library offers the `OdsListItem` composable to display lists items. + +The `OdsListItem` composable allows you to display a leading icon using the `icon` parameter of the `OdsListItem` method, as well as a trailing element (either a checkbox, a switch, a radio button, an icon or a caption text) using the `trailing` parameter. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + icon = OdsListItemIcon( + OdsListItemIconType.Icon, + painterResource(id = R.drawable.ic_heart), + "Heart" + ), + trailing = OdsListItemTrailingCheckbox(checked, { checked != checked }), + divider = true +) +``` + +##### OdsListItem API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | The primary text of the list item +`modifier: Modifier` | `Modifier` | Modifier to be applied to the list item +`icon: OdsListItemIcon?` | `null` | The leading supporting visual of the list item +`secondaryText: String?` | `null` | The secondary text of the list item +`singleLineSecondaryText: Boolean` | `true` | Whether the secondary text is single line +`overlineText: String?` | `null` | The text displayed above the primary text +`trailing: OdsListItemTrailing?` | `null` | The trailing content to display at the end of the list item +`divider: Boolean` | `false` | Whether or not a divider is displayed at the bottom of the list item +`onClick: (() -> Unit)?` | `null` | Will be called when the user clicks the list item. This parameter only has an effect if trailing is `OdsListItemTrailingIcon` or `null`. +{:.table} + +### Two-line list + +Like single-line list, two-line list leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists two-line wide image](images/lists_two_line_wide_image_light.png) ![Lists two-line wide image dark](images/lists_two_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists two-line](images/lists_two_line_light.png) ![Lists two-line dark](images/lists_two_line_dark.png) + +#### Jetpack Compose + +The only difference with the single-line implementation is that the `secondaryText` property of `OdsListItem` is not null. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + secondaryText = "Secondary text", + icon = OdsListItemIcon( + OdsListItemIconType.CircularImage, + painterResource(id = R.drawable.placeholder, "") + ), + trailing = OdsListItemTrailingIcon( + painterResource(id = R.drawable.ic_drag_handle), + "Drag item" + ), + divider = true +) +``` + +Use [OdsListItem API](#odslistitem-api). + +### Three-line list + +Like single-line list, three-line list leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists three-line wide image](images/lists_three_line_wide_image_light.png) ![Lists three-line wide image dark](images/lists_three_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists three-line](images/lists_three_line_light.png) ![Lists three-line dark](images/lists_three_line_dark.png) + +#### Jetpack Compose + +The only difference with the two-line implementation is that the `singleLineSecondaryText` property of `OdsListItem` is `false`. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + secondaryText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.", + singleLineSecondaryText = false, + icon = OdsListItemIcon( + OdsListItemIconType.SquareImage, + painter = painterResource(id = R.drawable.placeholder), + "" + ), + trailing = OdsListItemTrailingCaption("Caption"), + divider = true +) +``` + +Use [OdsListItem API](#odslistitem-api). diff --git a/docs/0.17.0/components/ListItems_docs.md b/docs/0.17.0/components/ListItems_docs.md new file mode 100644 index 000000000..44e4d0a23 --- /dev/null +++ b/docs/0.17.0/components/ListItems_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: ListItems.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Menus.md b/docs/0.17.0/components/Menus.md new file mode 100644 index 000000000..23d6e16e8 --- /dev/null +++ b/docs/0.17.0/components/Menus.md @@ -0,0 +1,118 @@ +--- +layout: detail +title: Menus +description: Menus appear from a button, action, or other control. It contains at least 2 items that can affect the app, the view or elements within the view. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Dropdown menu](#dropdown-menu) + * [Jetpack Compose](#jetpack-compose) + * [OdsDropdownMenu API](#odsdropdownmenu-api) + * [Exposed dropdown menu](#exposed-dropdown-menu) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsExposedDropdownMenu API](#odsexposeddropdownmenu-api) + +--- + +## Specifications references + +- [Design System Manager - Menus](https://system.design.orange.com/0c1af118d/p/07a69b-menus/b/862cbb) +- [Material Design - Menus](https://m2.material.io/components/menus) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +The icons which can be displayed in a dropdown menu are always associated to a text so they don't need a content description. + +## Variants + +### Dropdown menu + +A dropdown menu is a compact way of displaying multiple choices. It appears upon interaction with an element (such as an icon or button) or when users perform a specific action. + +![Dropdown menu light](images/menu_dropdown_light.png) ![Dropdown menu dark](images/menu_dropdown_dark.png) + +#### Jetpack Compose + +The library offers an `OdsDropdownMenu` container composable in which you can add `OdsDropdownMenuItem` or `OdsDivider` as shown in the following example: + +```kotlin +var menuExpanded by remember { mutableStateOf(false) } + +OdsDropdownMenu( + expanded = menuExpanded, + onDismissRequest = { menuExpanded = false }, + offset = DpOffset(x = (-100).dp, y = (-10).dp), + items = listOf( + OdsDropdownMenuItem( + text = "Summer salad", + icon = painterResource(id = R.drawable.ic_salad), + divider = true, // Allow to add a divider between the 2 items + onClick = { doSomething() } + ), + OdsDropdownMenuItem( + text = "Brocoli soup", + icon = painterResource(id = R.drawable.ic_soup), + onClick = { doSomething() } + ) + ) +) +``` + +##### OdsDropdownMenu API + +Parameter | Default value | Description +-- | -- | -- +`items: List<OdsDropdownMenuItem>` | | Items displayed into the dropdown menu +`expanded: Boolean` | | Controls whether the menu is currently open and visible to the user +`onDismissRequest: () -> Unit` | | Callback invoked when the user requests to dismiss the menu, such as by tapping outside the menu's bounds +`modifier: Modifier` | `Modifier` | `Modifier` applied to the dropdown menu +`offset: DpOffset` | `DpOffset(0.dp, 0.dp)` | Offset added to the menu position +`properties: PopupProperties` | `PopupProperties(focusable = true)` | Properties for further customization of the popup's behavior +{:.table} + +### Exposed dropdown menu + +Exposed dropdown menus display the currently selected menu item above the menu. This is a combination of a text field and a menu. + +![Exposed dropdown menu light](images/menu_exposed_dropdown_light.png) ![Exposed dropdown menu dark](images/menu_exposed_dropdown_dark.png) + +#### Jetpack Compose + +To display an exposed dropdown menu, you can use the `OdsExposedDropdownMenu` composable. As shown below, you should provide a list of `OdsExposedDropdownMenuItem` corresponding to the items displayed in the menu (with or without icons). + +```kotlin +val items = listOf( + OdsExposedDropdownMenuItem("Email", android.R.drawable.ic_dialog_email), + OdsExposedDropdownMenuItem("Map", android.R.drawable.ic_dialog_map), + OdsExposedDropdownMenuItem("Dialer", android.R.drawable.ic_dialog_dialer), +) +val selectedItem = rememberSaveable() { mutableStateOf(items.first()) } + +OdsExposedDropdownMenu( + label = "Dropdown menu label", + items = items, + selectedItem = selectedItem, + onItemSelectionChange = { item -> + doSomething() // Do something like retrieving the selected item + }, + enabled = true +) +``` + +##### OdsExposedDropdownMenu API + +Parameter | Default value | Description +-- | -- | -- +`label: String` | | Label of the exposed menu text field +`items: List<OdsExposedDropdownMenuItem>` | | Items displayed into the dropdown menu +`selectedItem: MutableState<OdsExposedDropdownMenuItem>` | | Selected item displayed into the text field +`onItemSelectionChange: (OdsExposedDropdownMenuItem) -> Unit` | | Callback invoked when a dropdown menu item is selected. It can be used to get the menu value. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the dropdown menu +`enabled: Boolean` | `true` | Controls the enabled state of the dropdown menu. When `false`, the dropdown menu text field will be neither clickable nor focusable, visually it will appear in the disabled state. +{:.table} diff --git a/docs/0.17.0/components/Menus_docs.md b/docs/0.17.0/components/Menus_docs.md new file mode 100644 index 000000000..2bf37b337 --- /dev/null +++ b/docs/0.17.0/components/Menus_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Menus.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/NavigationBottom.md b/docs/0.17.0/components/NavigationBottom.md new file mode 100644 index 000000000..f25cad9bb --- /dev/null +++ b/docs/0.17.0/components/NavigationBottom.md @@ -0,0 +1,117 @@ +--- +layout: detail +title: "Navigation: bottom" +description: Bottom navigation bars allow movement between primary destinations in an app. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsBottomNavigation API](#odsbottomnavigation-api) + * [XML](#xml) + +--- + +## Specifications references + +- [Design System Manager - Navigation: bottom](https://system.design.orange.com/0c1af118d/p/042eb8-navigation-bottom/b/30078d) +- [Material Design - Bottom navigation](https://material.io/components/bottom-navigation) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Note that TalkBack already reads the bottom navigation items labels so the content descriptions of the `OdsBottomNavigationItemIcon`s can be empty. + +## Implementation + +![BottomNavigation light](images/bottom_navigation_light.png) + +![BottomNavigation dark](images/bottom_navigation_dark.png) + +### Jetpack Compose + +In your composable screen, use the `OdsBottomNavigation` composable. It should contain multiple `OdsBottomNavigationItem`s. + +Here is an example of use: + +```kotlin + private data class NavigationItem( + @StringRes val titleResId: Int, + @DrawableRes val iconResId: Int +) + +val items = listOf( + R.string.component_bottom_navigation_coffee to R.drawable.ic_coffee, + R.string.component_bottom_navigation_cooking_pot to R.drawable.ic_cooking_pot, + R.string.component_bottom_navigation_ice_cream to R.drawable.ic_ice_cream, + R.string.component_bottom_navigation_restaurant to R.drawable.ic_restaurant, + R.string.component_bottom_navigation_favorites to R.drawable.ic_heart +) + +var selectedItemIndex by remember { mutableStateOf(0) } + +OdsBottomNavigation( + items = items.mapIndexed { index, item -> + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = item.first), + contentDescription = "" + ), // contentDescription is empty cause TalkBack already read the item's label + label = stringResource(id = item.second), + selected = selectedItemIndex == index, + onClick = { + selectedItemIndex = index + doSomething() + } + ) + } +) +``` + +#### OdsBottomNavigation API + +Parameter | Default value | Description +-- | -- | -- +`items: List<OdsBottomNavigationItem>` | | Items displayed into the bottom navigation +`modifier: Modifier` | `Modifier` | `Modifier` applied to the bottom navigation +{:.table} + +### XML + +In your layout, use the `OdsBottomNavigation` view. + +```xml + +<com.orange.ods.xml.component.bottomnavigation.OdsBottomNavigation + android:id="@+id/ods_bottom_navigation" android:layout_height="wrap_content" + android:layout_width="wrap_content" /> +``` + +Then using view binding, add the bottom navigation items by code: + +```kotlin +binding.odsBottomNavigation.items = listOf( + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = R.drawable.ic_dna), + contentDescription = "" + ), + label = "Guidelines", + selected = true, + onClick = { doSomething() } + ), + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = R.drawable.ic_atom), + contentDescription = "" + ), + label = "Components", + selected = false, + onClick = { doSomething() } + ) +) +``` \ No newline at end of file diff --git a/docs/0.17.0/components/NavigationBottom_docs.md b/docs/0.17.0/components/NavigationBottom_docs.md new file mode 100644 index 000000000..8cc980a8e --- /dev/null +++ b/docs/0.17.0/components/NavigationBottom_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: NavigationBottom.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/NavigationDrawers.md b/docs/0.17.0/components/NavigationDrawers.md new file mode 100644 index 000000000..21da08a2c --- /dev/null +++ b/docs/0.17.0/components/NavigationDrawers.md @@ -0,0 +1,63 @@ +--- +layout: detail +title: Navigation drawers +description: The navigation drawer slides in from the left when the nav icon is tapped. The content should be concerned with identity and/or navigation.. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + +--- + +## Specifications references + +- [Design System Manager - Navigation drawers](https://system.design.orange.com/0c1af118d/p/92bc26-navigation-drawers/b/146f55) +- [Material Design - Navigation drawer](https://m2.material.io/components/navigation-drawer) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Implementation + +### Jetpack Compose + +You can use the `OdsModalDrawer` composable like this: + +```kotlin +OdsModalDrawer( + drawerHeader = { + title = "Side navigation drawer" + imageContentDescription = "" + imageDisplayType = + OdsModalDrawerHeaderImageDisplayType.None // or OdsModalDrawerHeaderImageDisplayType.Avatar or OdsModalDrawerHeaderImageDisplayType.Background + subtitle = "Example" + image = painterResource(id = R.drawable.placeholder) + }, + drawerContentList = listOf<OdsModalDrawerItem>( + OdsModalDrawerListItem( // `OdsModalDrawerListItem` is used to specified an item of the list + icon = R.drawable.ic_heart, + text = "label1" + ), + OdsModalDrawerListItem( + icon = R.drawable.ic_heart, + text = "label2" + ), + OdsModalDrawerDivider, // `OdsModalDrawerDivider` is used to add a divider in a specific level of the list + OdsModalDrawerSectionLabel( + label = "Label" + ), // `OdsModalDrawerSectionLabel` is used to add a divider and the text above the divider + OdsModalDrawerListItem( + icon = R.drawable.ic_heart, + text = "label3" + ) + ), + drawerState = rememberDrawerState(DrawerValue.Closed), // or rememberDrawerState(DrawerValue.Open) +) { + // Put here the rest of the UI content +} +``` diff --git a/docs/0.17.0/components/NavigationDrawers_docs.md b/docs/0.17.0/components/NavigationDrawers_docs.md new file mode 100644 index 000000000..e1c08c075 --- /dev/null +++ b/docs/0.17.0/components/NavigationDrawers_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: NavigationDrawers.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/ProgressIndicators.md b/docs/0.17.0/components/ProgressIndicators.md new file mode 100644 index 000000000..ba7da7b4e --- /dev/null +++ b/docs/0.17.0/components/ProgressIndicators.md @@ -0,0 +1,134 @@ +--- +layout: detail +title: Progress indicators +description: Progress indicators express an unspecified wait time or display the length of a process. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Progress bar](#progress-bar) + * [Jetpack Compose](#jetpack-compose) + * [OdsLinearProgressIndicator API](#odslinearprogressindicator-api) + * [Activity indicator](#activity-indicator) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsCircularProgressIndicator API](#odscircularprogressindicator-api) + +--- + +## Specifications references + +- [Design System Manager - Progress indicators](https://system.design.orange.com/0c1af118d/p/92aec5-progress-indicators------/b/33faf7) +- [Material Design - Progress indicators](https://material.io/components/progress-indicators/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Variants + +### Progress bar + +Progress bars, also called linear progress indicators, display progress by animating an indicator along the length of a fixed, +visible track. The behavior of the indicator is dependent on whether the progress of a process is +known. + +Linear progress indicators support both determinate and indeterminate operations. + +* Determinate operations display the indicator increasing in width + from 0 to 100% of the track, in sync with the process’s progress. +* Indeterminate operations display the indicator continually growing + and shrinking along the track until the process is complete. + + ![Progress bar light](images/progress_linear_light.png) + + ![Progress bar dark](images/progress_linear_dark.png) + +#### Jetpack Compose + +You can use the composable `OdsLinearProgressIndicator` like this: + +For a **determinate** linear progress indicator, provide the progress value: + +```kotlin +OdsLinearProgressIndicator( + progress = 0.9f, + label = "Downloading ...", + icon = OdsLinearProgressIndicator( + painterResource(id = R.drawable.ic_arrow_down), + "" + ), + showCurrentValue = true +) +``` + +For an **indeterminate** linear progress indicator, no need to provide a progress value: + +```kotlin +OdsLinearProgressIndicator( + label = "Downloading ...", + icon = OdsLinearProgressIndicator( + painterResource(id = R.drawable.ic_arrow_down), + "" + ) +) +``` + +##### OdsLinearProgressIndicator API + +Parameter | Default value | Description +-- | -- | -- +`modifier: Modifier` | `Modifier` | `Modifier` applied to the progress indicator +`progress: Float?` | `null` | Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced into the range. If set to `null`, the progress indicator is indeterminate. +`label: String?` | `null` | Label displayed above the linear progress +`icon: OdsLinearProgressIndicatorIcon?` | `null` | Icon displayed above the progress indicator +`showCurrentValue: Boolean` | `false` | Controls the progress indicator current value visibility which is displayed in percent below the progress bar +{:.table} + +### Activity indicator + +Activity indicators, also called circular progress indicators, display progress by animating an indicator along an +invisible circular track in a clockwise direction. They can be applied directly +to a surface, such as a button or card. + +Circular progress indicators support both determinate and indeterminate +processes. + +* Determinate circular indicators fill the invisible, circular track with + color, as the indicator moves from 0 to 360 degrees. +* Indeterminate circular indicators grow and shrink in size while moving along + the invisible track. + +![Activity indicator light](images/progress_circular_light.png) ![Activity indicator dark](images/progress_circular_dark.png) + +#### Jetpack Compose + +You can use the `OdsCircularProgressIndicator` composable like this: + +- For a **determinate** circular progress indicator, provide the progress value: + +```kotlin +OdsCircularProgressIndicator( + progress = 0.9f, + label = "Downloading ..." +) +``` + +- For an **indeterminate** circular progress indicator, no need to provide a progress value: + +```kotlin +OdsCircularProgressIndicator( + label = "Downloading ..." +) +``` + +##### OdsCircularProgressIndicator API + +Parameter | Default value | Description +-- | -- | -- +`modifier: Modifier` | `Modifier` | `Modifier` applied to the progress indicator +`progress: Float?` | `null` | Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced into the range. If set to `null`, the progress indicator is indeterminate. +`label: String?` | `null` | Label displayed below the circular progress +{:.table} diff --git a/docs/0.17.0/components/ProgressIndicators_docs.md b/docs/0.17.0/components/ProgressIndicators_docs.md new file mode 100644 index 000000000..77ee6e5b9 --- /dev/null +++ b/docs/0.17.0/components/ProgressIndicators_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: ProgressIndicators.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/RadioButtons.md b/docs/0.17.0/components/RadioButtons.md new file mode 100644 index 000000000..24aac4962 --- /dev/null +++ b/docs/0.17.0/components/RadioButtons.md @@ -0,0 +1,62 @@ +--- +layout: detail +title: Radio buttons +description: Radio button selection control allows the user to select options. +--- + +Use radio buttons to: + +* Select a single option from a list +* Expose all available options +* If available options can be collapsed, consider using a dropdown menu + instead, as it uses less space. + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsRadioButton API](#odsradiobutton-api) + +--- + +## Specifications references + +- [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) +- [Material Design - Radio buttons](https://material.io/components/radio-buttons/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Radio buttons support content labeling for accessibility and are readable by +most screen readers, such as TalkBack. Text rendered in radio buttons is +automatically provided to accessibility services. Additional content labels are +usually unnecessary. + +## Implementation + +![RadioButton](images/radio_button_light.png) ![RadioButton dark](images/radio_button_dark.png) + +### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsRadioButton( + selected = true, + onClick = { doSomething() }, + enabled = true +) +``` + +#### OdsRadioButton API + +Parameter | Default value | Description +-- | -- | -- +`selected: Boolean` | | Controls the selected state of the radio button +`onClick: (() -> Unit)?` | | Callback invoked on radio button click. If `null`, then the radio button will not handle input events, and only act as a visual indicator of `selected` state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the radio button +`enabled: Boolean` | `true` | Controls the enabled state of the radio button. When `false`, the button will not be selectable and appears disabled. +{:.table} diff --git a/docs/0.17.0/components/RadioButtons_docs.md b/docs/0.17.0/components/RadioButtons_docs.md new file mode 100644 index 000000000..95e949338 --- /dev/null +++ b/docs/0.17.0/components/RadioButtons_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: RadioButtons.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/SheetsBottom.md b/docs/0.17.0/components/SheetsBottom.md new file mode 100644 index 000000000..a80fa5bf4 --- /dev/null +++ b/docs/0.17.0/components/SheetsBottom.md @@ -0,0 +1,55 @@ +--- +layout: detail +title: "Sheets: bottom" +description: Bottom Sheets are surfaces anchored to the bottom of the screen that present users supplement content. +--- + +Use Sheets bottom to: + +* Display content that complements the screen’s primary content +* Expose all complements options + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + +--- + +## Specifications references + +- [Design System Manager - Sheets](https://system.design.orange.com/0c1af118d/p/81f927-sheets-bottom/b/47b99b) +- [Material Design - Sheets: bottom](https://material.io/components/sheets-bottom) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Implementation + +![BottomSheet light](images/sheetbottom_light.png) ![BottomSheet dark](images/sheetbottom_dark.png) + +The contents within a bottom sheet should follow their own accessibility guidelines, such as images having content descriptions set on them. + +### Jetpack Compose + +```kotlin +OdsBottomSheetScaffold( + sheetContent = { + // Put here the content of the sheet + }, + modifier = Modifier, + scaffoldState = rememberBottomSheetScaffoldState(), + topBar = null, + snackbarHost = {}, + floatingActionButton = {}, + floatingActionButtonPosition = FabPosition.End, + sheetGesturesEnabled = true, + sheetPeekHeight = 56.dp, + content = { + // Put here the screen content + } +) +``` diff --git a/docs/0.17.0/components/SheetsBottom_docs.md b/docs/0.17.0/components/SheetsBottom_docs.md new file mode 100644 index 000000000..6602fd47a --- /dev/null +++ b/docs/0.17.0/components/SheetsBottom_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: SheetsBottom.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Sliders.md b/docs/0.17.0/components/Sliders.md new file mode 100644 index 000000000..93c989ff4 --- /dev/null +++ b/docs/0.17.0/components/Sliders.md @@ -0,0 +1,230 @@ +--- +layout: detail +title: Sliders +description: Sliders allow users to make selections from a range of values. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Continuous slider](#continuous-slider) + * [Jetpack Compose](#jetpack-compose) + * [OdsSlider API](#odsslider-api) + * [Continuous lockups slider](#continuous-lockups-slider) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsSliderLockups API](#odssliderlockups-api) + * [Discrete slider](#discrete-slider) + * [Jetpack Compose](#jetpack-compose-2) + * [Discrete lockups slider](#discrete-lockups-slider) + * [Jetpack Compose](#jetpack-compose-3) + +--- + +## Specifications references + +- [Design System Manager - Sliders](https://system.design.orange.com/0c1af118d/p/8077fc-sliders/b/673558) +- [Material Design - Sliders](https://material.io/components/sliders/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Variants + +### Continuous slider + +Continuous sliders allow users to make meaningful selections that don’t require +a specific value. + +![Continuous slider](images/slider_continuous_light.png) ![Continuous slider dark](images/slider_continuous_dark.png) + +With icons: + +![Continuous slider with icons](images/slider_continuous_with_icon_light.png) ![Continuous slider with icons dark](images/slider_continuous_with_icon_dark.png) + +#### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsSlider( + value = 20f, + steps = 9, + onValueChange = { doSomething() } +) +``` + +You can add icons to the continuous slider like this: + +```kotlin +OdsSlider( + value = 20f, + steps = 9, + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) +) +``` + +##### OdsSlider API + +Parameter | Default value | Description +-- | -- | -- +`value: Float` | | Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. +`onValueChange: (Float) -> Unit` | | Callback invoked on slider value change. `value` should be updated here. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the slider +`enabled: Boolean` | `true` | Controls the enabled state of the slider. If `false`, the user cannot interact with it. +`valueRange: ClosedFloatingPointRange<Float>` | `0f..1f` | Range of values that the slider can take. Given `value` will be coerced to this range. +`steps: Int` | `0` | If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. +`onValueChangeFinished: (() -> Unit)?` | `null` | Callback invoked when value change has ended. This callback shouldn't be used to update the slider value (use `onValueChange` for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. +`startIcon: OdsSliderIcon?` | `null` | Icon displayed at the start of the slider +`endIcon: OdsSliderIcon?` | `null` | Icon displayed at the end of the slider +{:.table} + +### Continuous lockups slider + +![Continuous lockups slider](images/slider_continuous_lockups_light.png) ![Continuous lockups slider dark](images/slider_continuous_lockups_light.png) + +With icons: + +![Continuous lockups slider with icons](images/slider_continuous_lockups_with_icon_light.png) ![Continuous lockups slider with icons dark](images/slider_continuous_lockups_with_icon_dark.png) + +#### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsSliderLockups( + value = 20f, + valueRange = 0f..100f, + onValueChange = { doSomething() } +) +``` + +You can add icons to the continuous lockups slider like this: + +```kotlin +OdsSliderLockups( + value = 20f, + valueRange = 0f..100f, + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) +) +``` + +##### OdsSliderLockups API + +Parameter | Default value | Description +-- | -- | -- +`value: Float` | | Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. +`onValueChange: (Float) -> Unit` | | Callback invoked on slider value change. `value` should be updated here. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the slider +`enabled: Boolean` | `true` | Controls the enabled state of the slider. If `false`, the user cannot interact with it. +`valueRange: ClosedFloatingPointRange<Float>` | `0f..1f` | Range of values that the slider can take. Given `value` will be coerced to this range. +`steps: Int` | `0` | If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. +`onValueChangeFinished: (() -> Unit)?` | `null` | Callback invoked when value change has ended. This callback shouldn't be used to update the slider value (use `onValueChange` for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. +`startIcon: OdsSliderIcon?` | `null` | Icon displayed at the start of the slider +`endIcon: OdsSliderIcon?` | `null` | Icon displayed at the end of the slider +{:.table} + +### Discrete slider + +Discrete sliders display a numeric value label upon pressing the thumb, which +allows a user to input an exact value. + +![Discrete slider](images/slider_discrete_light.png) ![Discrete slider dark](images/slider_discrete_dark.png) + +With icons: + +![Discrete slider with icon](images/slider_discrete_with_icon_light.png) ![Discrete slider with icon dark](images/slider_discrete_with_icon_dark.png) + +#### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsSlider( + value = 20f, + valueRange = 0f..100f, + steps = 10, + onValueChange = { doSomething() } +) +``` + +You can add icons to the discrete slider like this: + +```kotlin +OdsSlider( + value = 20f, + valueRange = 0f..100f, + steps = 10, + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) +) +``` + +Use [OdsSlider API](#odsslider-api). + +### Discrete lockups slider + +![Discrete lockups slider](images/slider_discrete_lockups_light.png) ![Discrete lockups slider dark](images/slider_discrete_lockups_dark.png) + +With icons: + +![Discrete lockups slider with icons](images/slider_discrete_lockups_with_icon_light.png) ![Discrete lockups slider with icons dark](images/slider_discrete_lockups_with_icon_dark.png) + +#### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsSliderLockups( + value = 20f, + valueRange = 0f..100f, + steps = 10, + onValueChange = { doSomething() } +) +``` + +You can add icons to the continuous lockups slider like this: + +```kotlin +OdsSliderLockups( + value = 20f, + valueRange = 0f..100f, + steps = 10, + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) +) +``` + +Use [OdsSliderLockups API](#odssliderlockups-api). diff --git a/docs/0.17.0/components/Sliders_docs.md b/docs/0.17.0/components/Sliders_docs.md new file mode 100644 index 000000000..ab068de42 --- /dev/null +++ b/docs/0.17.0/components/Sliders_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Sliders.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Snackbars.md b/docs/0.17.0/components/Snackbars.md new file mode 100644 index 000000000..f3455e335 --- /dev/null +++ b/docs/0.17.0/components/Snackbars.md @@ -0,0 +1,94 @@ +--- +layout: detail +title: Snackbars +description: Snackbars provide brief messages about app processes at the bottom of the screen. +--- + +Snackbars inform users of a process that an app has performed or will perform. +They appear temporarily, towards the bottom of the screen. They shouldn’t +interrupt the user experience, and they don’t require user input to disappear. +They disappear either after a timeout or after a user interaction elsewhere on +the screen, but can also be swiped off the screen. + +Snackbars can also offer the ability to perform an action, such as undoing an +action that was just taken, or retrying an action that had failed. + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsSnackbarHost API](#odssnackbarhost-api) + +--- + +## Specifications references + +- [Design System Manager - Toasts & Snackbars](https://system.design.orange.com/0c1af118d/p/887440-toast--snackbars/b/043ece) +- [Material Design - Snackbars](https://material.io/components/snackbars) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Snackbars support content labeling for accessibility and are readable by most +screen readers, such as TalkBack. Text rendered in snackbars is automatically +provided to accessibility services. Additional content labels are usually +unnecessary. + +## Implementation + +![Snackbar light](images/snackbar_light.png) + +![Snackbar dark](images/snackbar_dark.png) + +With action button: + +![Snackbar with action light](images/snackbar_with_action_light.png) + +![Snackbar with action dark](images/snackbar_with_action_dark.png) + +### Jetpack Compose + +We advise you to use a `Scaffold` to add an `OdsSnackbar` in order to make sure everything is displayed together in the right place according to Material Design. +Then use `OdsSnackbarHost` which provides the good margins to display the snackbar and `OdsSnackbar` as follow: + +```kotlin +val scaffoldState = rememberScaffoldState() +val coroutineScope: CoroutineScope = rememberCoroutineScope() + +Scaffold( + scaffoldState = scaffoldState, + snackbarHost = { + OdsSnackbarHost(hostState = it) { data -> + OdsSnackbar(data = data, actionOnNewLine = true, onActionClick = { + doSomething() + }) + } + }) { + OdsButton( + modifier = Modifier + .padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(id = com.orange.ods.R.dimen.screen_vertical_margin)), + text = "Show snackbar", + onClick = { + coroutineScope.launch { + scaffoldState.snackbarHostState.showSnackbar( + message = "This is the message of the Snackbar.", + actionLabel = "Action" + ) + } + } + ) +} +``` + +#### OdsSnackbarHost API + +Parameter | Default value | Description +-- | -- | -- +`hostState: SnackbarHostState` | | State of this component to read and show `OdsSnackbar` accordingly. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the snackbar host +`snackbar: (SnackbarData) -> OdsSnackbar` | `{ OdsSnackbar(it) }` | Instance of the `OdsSnackbar` to be shown at the appropriate time with appearance based on the `SnackbarData` provided as a param +{:.table} \ No newline at end of file diff --git a/docs/0.17.0/components/Snackbars_docs.md b/docs/0.17.0/components/Snackbars_docs.md new file mode 100644 index 000000000..b1d395cc1 --- /dev/null +++ b/docs/0.17.0/components/Snackbars_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Snackbars.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Switches.md b/docs/0.17.0/components/Switches.md new file mode 100644 index 000000000..fb43230f0 --- /dev/null +++ b/docs/0.17.0/components/Switches.md @@ -0,0 +1,59 @@ +--- +layout: detail +title: Switches +description: Switch selection control allows the user to select options. +--- + +Switches toggle the state of a single setting on or off. They are the preferred +way to adjust settings on mobile. + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) + * [Jetpack Compose](#jetpack-compose) + * [OdsSwitch API](#odsswitch-api) + +--- + +## Specifications references + +- [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) +- [Material Design - Switches](https://material.io/components/switches) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Switches support content labeling for accessibility and are readable by most +screen readers, such as TalkBack. Text rendered in switches is automatically +provided to accessibility services. Additional content labels are usually +unnecessary. + +## Implementation + +![Switch](images/switch_light.png) ![Switch dark](images/switch_dark.png) + +### Jetpack Compose + +In your composable screen you can use: + +```kotlin +OdsSwitch( + checked = true, + onCheckedChange = { doSomething() }, + enabled = true +) +``` + +#### OdsSwitch API + +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls the checked state of the switch +`onCheckedChange: ((Boolean) -> Unit)?` | | Callback invoked on switch check. If `null`, then this is passive and relies entirely on a higher-level component to control the "checked" state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the switch +`enabled: Boolean` | `true` | Controls the enabled state of the switch. When `false`, the switch will not be checkable and appears disabled. +{:.table} + diff --git a/docs/0.17.0/components/Switches_docs.md b/docs/0.17.0/components/Switches_docs.md new file mode 100644 index 000000000..e8d456663 --- /dev/null +++ b/docs/0.17.0/components/Switches_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Switches.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/Tabs.md b/docs/0.17.0/components/Tabs.md new file mode 100644 index 000000000..623a6c6db --- /dev/null +++ b/docs/0.17.0/components/Tabs.md @@ -0,0 +1,148 @@ +--- +layout: detail +title: Tabs +description: Tabs organize content across different screens, data sets, and other interactions. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Fixed tabs](#fixed-tabs) + * [Jetpack Compose](#jetpack-compose) + * [Scrollable tabs](#scrollable-tabs) + * [Jetpack Compose](#jetpack-compose-1) + +--- + +## Specifications references + +- [Design System Manager - Tabs](https://system.design.orange.com/0c1af118d/p/513d27-tabs/b/50cb71) +- [Material Design - Tabs](https://material.io/components/tabs/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +The Android tab components support screen reader descriptions for tabs and +badges. While optional, we strongly encourage their use. + +## Variants + +### Fixed tabs + +Fixed tabs display all tabs on one screen, with each tab at a fixed width. The +width of each tab is determined by dividing the number of tabs by the screen +width. They don’t scroll to reveal more tabs; the visible tab set represents the +only tabs available. + +#### Jetpack Compose + +In Compose, the fixed tabs should be added inside of an `OdsTabRow`. +The used composable for tab depends on the type of tabs to display: classic `OdsTab` or `OdsLeadingIconTab`. + +![Fixed tabs light](images/tabs_fixed_light.png) + +![Fixed tabs dark](images/tabs_fixed_dark.png) + +**`OdsTab` composable:** + +This composable allows to display: + +- an icon only tab +- a text label only tab +- a tab with an icon on top of text label + +```kotlin +OdsTabRow(selectedTabIndex = pagerState.currentPage) { + OdsTab( + icon = painterResource(id = R.drawable.ic_alert), // if set to `null`, no icon will be displayed + text = "Alerts", // if set to `null`, no text will be displayed + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) + OdsTab( + icon = painterResource(id = R.drawable.ic_calendar), // if set to `null`, no icon will be displayed + text = "Calendar", // if set to `null`, no text will be displayed + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) +} +``` + +**`OdsLeadingIconTab` composable:** + +This composable allows to display a tab with a text label and an icon in front of the label. + +```kotlin +OdsTabRow(selectedTabIndex = pagerState.currentPage) { + OdsLeadingIconTab( + icon = painterResource(id = R.drawable.ic_alert), // icon is mandatory in an `OdsLeadingIconTab` + text = "Alerts", // text is mandatory in an `OdsLeadingIconTab` + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) + OdsLeadingIconTab( + icon = painterResource(id = R.drawable.ic_calendar), + text = "Calendar", + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) +} +``` + +### Scrollable tabs + +Scrollable tabs are displayed without fixed widths. They are scrollable, such +that some tabs will remain off-screen until scrolled. + +![Scrollable tabs light](images/tabs_scrollable_light.png) + +![Scrollable tabs dark](images/tabs_scrollable_dark.png) + +#### Jetpack Compose + +For scrollable tabs, the tabs should be added inside of an `OdsScrollableTabRow`. This is the only difference with fixed tabs implementation. +As for fixed tabs, you can use an `OdsTab` composable or an `OdsLeadingIconTab` inside. + +```kotlin +OdsScrollableTabRow(selectedTabIndex = pagerState.currentPage) { + OdsTab( + icon = painterResource(id = R.drawable.ic_alert), // if set to `null`, no icon will be displayed + text = "Alerts", // if set to `null`, no text will be displayed + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) + OdsTab( + icon = painterResource(id = R.drawable.ic_calendar), // if set to `null`, no icon will be displayed + text = "Calendar", // if set to `null`, no text will be displayed + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) +} +``` diff --git a/docs/0.17.0/components/Tabs_docs.md b/docs/0.17.0/components/Tabs_docs.md new file mode 100644 index 000000000..8ad98cfca --- /dev/null +++ b/docs/0.17.0/components/Tabs_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Tabs.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/TextFields.md b/docs/0.17.0/components/TextFields.md new file mode 100644 index 000000000..e10abc148 --- /dev/null +++ b/docs/0.17.0/components/TextFields.md @@ -0,0 +1,172 @@ +--- +layout: detail +title: Text fields +description: Text fields let users enter and edit text. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Text field](#text-field) + * [Jetpack Compose](#jetpack-compose) + * [Password text field](#password-text-field) + * [Jetpack Compose](#jetpack-compose-1) +* [Extras](#extras) + * [Character counter](#character-counter) + * [Jetpack Compose](#jetpack-compose-2) + +--- + +## Specifications references + +- [Design System Manager - Text fields](https://system.design.orange.com/0c1af118d/p/483f94-text-fields/b/720e3b) +- [Material Design - Text fields](https://material.io/components/text-fields/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +Android's text field component APIs support both label text and helper text for informing the user +as to what information is requested for a text field. + +## Variants + +### Text field + +A text field can be filled or outlined. +The outlined version is more accessible in term of contrast. This is the reason why Orange text fields are outlined. + +![TextField outlined light](images/textfield_outlined_light.png) +![TextField outlined dark](images/textfield_outlined_dark.png) + +![TextField filled light](images/textfield_filled_light.png) +![TextField filled dark](images/textfield_filled_dark.png) + +#### Jetpack Compose + +To add a text field in your composable screen you can use the `OdsTextField` composable as follow: + +```kotlin +var text by rememberSaveable { mutableStateOf("") } +OdsTextField( + leadingIcon = painterResource(id = R.drawable.ic_heart), + leadingIconContentDescription = "Like", + onLeadingIconClick = { doSomething() }, + trailing = OdsTextTrailing(text = "units"), // It can be one of the provided `OdsTextFieldTrailing`. See more information below. + enabled = true, + readOnly = false, + isError = false, + errorMessage = "Error message", + value = text, + onValueChange = { text = it }, + label = "Label", + placeholder = "Placeholder", + visualTransformation = VisualTransformation.None, + keyboardOptions = KeyboardOptions.Default, + keyboardActions = KeyboardActions(), + singleLine = false, + maxLines = Int.MAX_VALUE, + characterCounter = { + OdsTextFieldCharacterCounter( + valueLength = valueLength, + maxChars = TextFieldMaxChars, + enabled = enabled + ) + } +) +``` + +The library provides several `OdsTextFieldTrailing` that you can use as a trailing element for text field: + +- `OdsIconTrailing`: Displays an icon as trailing element +- `OdsTextTrailing`: Displays a text as trailing element + +If you want a more complex trailing element, you can use the other `OdsTextField` API which allows you to pass directly a composable as trailing but you will have to maintain it and to manage the accessibility by yourself. + +**Note:** You will find more information about the character counter in [Extras](#extras) + +**Custom theme configuration: +** You can override the default display of text fields in your custom theme by overriding the `textFieldStyle` attribute as below: + +```kotlin +override val components: OdsComponentsConfiguration + get() = object : OdsComponentsConfiguration() { + override val textFieldStyle: ComponentStyle + get() = ComponentStyle.Filled + } +``` + +### Password text field + +Password text field is a text field implementation that includes password visual transformation and optional visualisation icon. + +![TextField outlined password light](images/textfield_outlined_password_light.png) +![TextField outlined password dark](images/textfield_outlined_password_dark.png) + +![TextField filled password light](images/textfield_filled_password_light.png) +![TextField filled password dark](images/textfield_filled_password_dark.png) + +#### Jetpack Compose + +To add a password text field in your composable screen you can use the `OdsPasswordTextField` composable as follow: + +```kotlin +var text by rememberSaveable { mutableStateOf("") } +OdsPasswordTextField( + enabled = true, + readOnly = false, + isError = false, + errorMessage = "Error message", + value = text, + onValueChange = { text = it }, + label = "Label", + placeholder = "Placeholder", + visualisationIcon = true, + keyboardOptions = KeyboardOptions.Default, + keyboardActions = KeyboardActions(), + characterCounter = { + OdsTextFieldCharacterCounter( + valueLength = valueLength, + maxChars = TextFieldMaxChars, + enabled = enabled + ) + } +) +``` + +**Note:** This composable relies on `OdsTextField` in order to keep homogeneity in each application. +Its appearance (outlined or filled) is inherited from text fields style configuration. +See [text field section](#text-field) if you want to change it in your custom theme. + +## Extras + +### Character counter + +![TextField character counter light](images/textfield_character_counter_light.png) +![TextField character counter dark](images/textfield_character_counter_dark.png) + +#### Jetpack Compose + +In each TextField component, you can use the `characterCounter` parameter to add a character counter if there is a restriction on the number of characters in a field. +It will be placed properly below the text field, end aligned. + +Please use the provided `OdsTextFieldCharacterCounter` composable for this behavior as shown below: + +```kotlin +OdsTextFieldCharacterCounter( + modifier = Modifier.align(Alignment.End), + valueLength = valueLength, + maxChars = 20, + enabled = true // If `false` the counter is displayed with a disabled color +) +``` + +Be careful, the limitation behavior should be managed by yourself in the `onValueChange` method of the text field: + +```kotlin +if (text.length <= TextFieldMaxChars) { + value = text +} +``` \ No newline at end of file diff --git a/docs/0.17.0/components/TextFields_docs.md b/docs/0.17.0/components/TextFields_docs.md new file mode 100644 index 000000000..f9039ae91 --- /dev/null +++ b/docs/0.17.0/components/TextFields_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: TextFields.md +--- \ No newline at end of file diff --git a/docs/0.17.0/components/images/app_bar_top_overflow_menu_dark.png b/docs/0.17.0/components/images/app_bar_top_overflow_menu_dark.png new file mode 100644 index 000000000..60322543e Binary files /dev/null and b/docs/0.17.0/components/images/app_bar_top_overflow_menu_dark.png differ diff --git a/docs/0.17.0/components/images/app_bar_top_overflow_menu_light.png b/docs/0.17.0/components/images/app_bar_top_overflow_menu_light.png new file mode 100644 index 000000000..2e3bd9056 Binary files /dev/null and b/docs/0.17.0/components/images/app_bar_top_overflow_menu_light.png differ diff --git a/docs/0.17.0/components/images/banner_dark.png b/docs/0.17.0/components/images/banner_dark.png new file mode 100644 index 000000000..60803f207 Binary files /dev/null and b/docs/0.17.0/components/images/banner_dark.png differ diff --git a/docs/0.17.0/components/images/banner_light.png b/docs/0.17.0/components/images/banner_light.png new file mode 100644 index 000000000..4d174146a Binary files /dev/null and b/docs/0.17.0/components/images/banner_light.png differ diff --git a/docs/0.17.0/components/images/bottom_navigation_dark.png b/docs/0.17.0/components/images/bottom_navigation_dark.png new file mode 100644 index 000000000..4f4bd9087 Binary files /dev/null and b/docs/0.17.0/components/images/bottom_navigation_dark.png differ diff --git a/docs/0.17.0/components/images/bottom_navigation_light.png b/docs/0.17.0/components/images/bottom_navigation_light.png new file mode 100644 index 000000000..dafd1f3b2 Binary files /dev/null and b/docs/0.17.0/components/images/bottom_navigation_light.png differ diff --git a/docs/0.17.0/components/images/button_contained_dark.png b/docs/0.17.0/components/images/button_contained_dark.png new file mode 100644 index 000000000..17180098e Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_dark.png differ diff --git a/docs/0.17.0/components/images/button_contained_light.png b/docs/0.17.0/components/images/button_contained_light.png new file mode 100644 index 000000000..ad4d8dc6d Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_light.png differ diff --git a/docs/0.17.0/components/images/button_contained_negative_dark.png b/docs/0.17.0/components/images/button_contained_negative_dark.png new file mode 100644 index 000000000..d19e6ccf7 Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_negative_dark.png differ diff --git a/docs/0.17.0/components/images/button_contained_negative_light.png b/docs/0.17.0/components/images/button_contained_negative_light.png new file mode 100644 index 000000000..a242cd7ee Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_negative_light.png differ diff --git a/docs/0.17.0/components/images/button_contained_positive_dark.png b/docs/0.17.0/components/images/button_contained_positive_dark.png new file mode 100644 index 000000000..ae3a73886 Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_positive_dark.png differ diff --git a/docs/0.17.0/components/images/button_contained_positive_light.png b/docs/0.17.0/components/images/button_contained_positive_light.png new file mode 100644 index 000000000..730194c7b Binary files /dev/null and b/docs/0.17.0/components/images/button_contained_positive_light.png differ diff --git a/docs/0.17.0/components/images/button_icon_dark.png b/docs/0.17.0/components/images/button_icon_dark.png new file mode 100644 index 000000000..551ce30a0 Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_dark.png differ diff --git a/docs/0.17.0/components/images/button_icon_light.png b/docs/0.17.0/components/images/button_icon_light.png new file mode 100644 index 000000000..b11c9dd5b Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_light.png differ diff --git a/docs/0.17.0/components/images/button_icon_toggle_dark.png b/docs/0.17.0/components/images/button_icon_toggle_dark.png new file mode 100644 index 000000000..d2702c6e4 Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_toggle_dark.png differ diff --git a/docs/0.17.0/components/images/button_icon_toggle_group_dark.png b/docs/0.17.0/components/images/button_icon_toggle_group_dark.png new file mode 100644 index 000000000..1f3fdc9a8 Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_toggle_group_dark.png differ diff --git a/docs/0.17.0/components/images/button_icon_toggle_group_light.png b/docs/0.17.0/components/images/button_icon_toggle_group_light.png new file mode 100644 index 000000000..4f32291c9 Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_toggle_group_light.png differ diff --git a/docs/0.17.0/components/images/button_icon_toggle_light.png b/docs/0.17.0/components/images/button_icon_toggle_light.png new file mode 100644 index 000000000..8256c3953 Binary files /dev/null and b/docs/0.17.0/components/images/button_icon_toggle_light.png differ diff --git a/docs/0.17.0/components/images/button_outlined_dark.png b/docs/0.17.0/components/images/button_outlined_dark.png new file mode 100644 index 000000000..094925f21 Binary files /dev/null and b/docs/0.17.0/components/images/button_outlined_dark.png differ diff --git a/docs/0.17.0/components/images/button_outlined_light.png b/docs/0.17.0/components/images/button_outlined_light.png new file mode 100644 index 000000000..9e308f16b Binary files /dev/null and b/docs/0.17.0/components/images/button_outlined_light.png differ diff --git a/docs/0.17.0/components/images/button_text_dark.png b/docs/0.17.0/components/images/button_text_dark.png new file mode 100644 index 000000000..9e4800910 Binary files /dev/null and b/docs/0.17.0/components/images/button_text_dark.png differ diff --git a/docs/0.17.0/components/images/button_text_light.png b/docs/0.17.0/components/images/button_text_light.png new file mode 100644 index 000000000..9b70e404f Binary files /dev/null and b/docs/0.17.0/components/images/button_text_light.png differ diff --git a/docs/0.17.0/components/images/button_text_toggle_group_dark.png b/docs/0.17.0/components/images/button_text_toggle_group_dark.png new file mode 100644 index 000000000..b8aab9b36 Binary files /dev/null and b/docs/0.17.0/components/images/button_text_toggle_group_dark.png differ diff --git a/docs/0.17.0/components/images/button_text_toggle_group_light.png b/docs/0.17.0/components/images/button_text_toggle_group_light.png new file mode 100644 index 000000000..7c594ce45 Binary files /dev/null and b/docs/0.17.0/components/images/button_text_toggle_group_light.png differ diff --git a/docs/0.17.0/components/images/button_toggle_dark.png b/docs/0.17.0/components/images/button_toggle_dark.png new file mode 100644 index 000000000..20faac6fc Binary files /dev/null and b/docs/0.17.0/components/images/button_toggle_dark.png differ diff --git a/docs/0.17.0/components/images/button_toggle_light.png b/docs/0.17.0/components/images/button_toggle_light.png new file mode 100644 index 000000000..70554b9a7 Binary files /dev/null and b/docs/0.17.0/components/images/button_toggle_light.png differ diff --git a/docs/0.17.0/components/images/card_horizontal_dark.png b/docs/0.17.0/components/images/card_horizontal_dark.png new file mode 100644 index 000000000..7ea820dc7 Binary files /dev/null and b/docs/0.17.0/components/images/card_horizontal_dark.png differ diff --git a/docs/0.17.0/components/images/card_horizontal_light.png b/docs/0.17.0/components/images/card_horizontal_light.png new file mode 100644 index 000000000..e2068ee89 Binary files /dev/null and b/docs/0.17.0/components/images/card_horizontal_light.png differ diff --git a/docs/0.17.0/components/images/card_small_dark.png b/docs/0.17.0/components/images/card_small_dark.png new file mode 100644 index 000000000..9a4097c8c Binary files /dev/null and b/docs/0.17.0/components/images/card_small_dark.png differ diff --git a/docs/0.17.0/components/images/card_small_light.png b/docs/0.17.0/components/images/card_small_light.png new file mode 100644 index 000000000..4b9420763 Binary files /dev/null and b/docs/0.17.0/components/images/card_small_light.png differ diff --git a/docs/0.17.0/components/images/card_vertical_header_first_dark.png b/docs/0.17.0/components/images/card_vertical_header_first_dark.png new file mode 100644 index 000000000..1626639cc Binary files /dev/null and b/docs/0.17.0/components/images/card_vertical_header_first_dark.png differ diff --git a/docs/0.17.0/components/images/card_vertical_header_first_light.png b/docs/0.17.0/components/images/card_vertical_header_first_light.png new file mode 100644 index 000000000..31e9d39cd Binary files /dev/null and b/docs/0.17.0/components/images/card_vertical_header_first_light.png differ diff --git a/docs/0.17.0/components/images/card_vertical_image_first_dark.png b/docs/0.17.0/components/images/card_vertical_image_first_dark.png new file mode 100644 index 000000000..090ce5269 Binary files /dev/null and b/docs/0.17.0/components/images/card_vertical_image_first_dark.png differ diff --git a/docs/0.17.0/components/images/card_vertical_image_first_light.png b/docs/0.17.0/components/images/card_vertical_image_first_light.png new file mode 100644 index 000000000..9dd3ed2dd Binary files /dev/null and b/docs/0.17.0/components/images/card_vertical_image_first_light.png differ diff --git a/docs/0.17.0/components/images/checkbox_dark.png b/docs/0.17.0/components/images/checkbox_dark.png new file mode 100644 index 000000000..ebd5321ed Binary files /dev/null and b/docs/0.17.0/components/images/checkbox_dark.png differ diff --git a/docs/0.17.0/components/images/checkbox_light.png b/docs/0.17.0/components/images/checkbox_light.png new file mode 100644 index 000000000..5650e8b3f Binary files /dev/null and b/docs/0.17.0/components/images/checkbox_light.png differ diff --git a/docs/0.17.0/components/images/chips_action_dark.png b/docs/0.17.0/components/images/chips_action_dark.png new file mode 100644 index 000000000..c9bebe9b6 Binary files /dev/null and b/docs/0.17.0/components/images/chips_action_dark.png differ diff --git a/docs/0.17.0/components/images/chips_action_light.png b/docs/0.17.0/components/images/chips_action_light.png new file mode 100644 index 000000000..2b19d6222 Binary files /dev/null and b/docs/0.17.0/components/images/chips_action_light.png differ diff --git a/docs/0.17.0/components/images/chips_action_outlined_dark.png b/docs/0.17.0/components/images/chips_action_outlined_dark.png new file mode 100644 index 000000000..d88b7e0fc Binary files /dev/null and b/docs/0.17.0/components/images/chips_action_outlined_dark.png differ diff --git a/docs/0.17.0/components/images/chips_action_outlined_light.png b/docs/0.17.0/components/images/chips_action_outlined_light.png new file mode 100644 index 000000000..10bddc302 Binary files /dev/null and b/docs/0.17.0/components/images/chips_action_outlined_light.png differ diff --git a/docs/0.17.0/components/images/chips_choice_dark.png b/docs/0.17.0/components/images/chips_choice_dark.png new file mode 100644 index 000000000..338f76f72 Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_dark.png differ diff --git a/docs/0.17.0/components/images/chips_choice_flow_row_dark.png b/docs/0.17.0/components/images/chips_choice_flow_row_dark.png new file mode 100644 index 000000000..18a7c58ca Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_flow_row_dark.png differ diff --git a/docs/0.17.0/components/images/chips_choice_flow_row_light.png b/docs/0.17.0/components/images/chips_choice_flow_row_light.png new file mode 100644 index 000000000..d80a00356 Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_flow_row_light.png differ diff --git a/docs/0.17.0/components/images/chips_choice_light.png b/docs/0.17.0/components/images/chips_choice_light.png new file mode 100644 index 000000000..f9b38d549 Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_light.png differ diff --git a/docs/0.17.0/components/images/chips_choice_outlined_dark.png b/docs/0.17.0/components/images/chips_choice_outlined_dark.png new file mode 100644 index 000000000..1b4329284 Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_outlined_dark.png differ diff --git a/docs/0.17.0/components/images/chips_choice_outlined_light.png b/docs/0.17.0/components/images/chips_choice_outlined_light.png new file mode 100644 index 000000000..2eaba4744 Binary files /dev/null and b/docs/0.17.0/components/images/chips_choice_outlined_light.png differ diff --git a/docs/0.17.0/components/images/chips_filter_avatar_dark.png b/docs/0.17.0/components/images/chips_filter_avatar_dark.png new file mode 100644 index 000000000..9158aeb7a Binary files /dev/null and b/docs/0.17.0/components/images/chips_filter_avatar_dark.png differ diff --git a/docs/0.17.0/components/images/chips_filter_avatar_light.png b/docs/0.17.0/components/images/chips_filter_avatar_light.png new file mode 100644 index 000000000..1914df0da Binary files /dev/null and b/docs/0.17.0/components/images/chips_filter_avatar_light.png differ diff --git a/docs/0.17.0/components/images/chips_filter_dark.png b/docs/0.17.0/components/images/chips_filter_dark.png new file mode 100644 index 000000000..6a79f17b6 Binary files /dev/null and b/docs/0.17.0/components/images/chips_filter_dark.png differ diff --git a/docs/0.17.0/components/images/chips_filter_light.png b/docs/0.17.0/components/images/chips_filter_light.png new file mode 100644 index 000000000..d39968cd5 Binary files /dev/null and b/docs/0.17.0/components/images/chips_filter_light.png differ diff --git a/docs/0.17.0/components/images/chips_input_dark.png b/docs/0.17.0/components/images/chips_input_dark.png new file mode 100644 index 000000000..d76828f0d Binary files /dev/null and b/docs/0.17.0/components/images/chips_input_dark.png differ diff --git a/docs/0.17.0/components/images/chips_input_light.png b/docs/0.17.0/components/images/chips_input_light.png new file mode 100644 index 000000000..436f48b73 Binary files /dev/null and b/docs/0.17.0/components/images/chips_input_light.png differ diff --git a/docs/0.17.0/components/images/chips_input_outlined_dark.png b/docs/0.17.0/components/images/chips_input_outlined_dark.png new file mode 100644 index 000000000..841191acf Binary files /dev/null and b/docs/0.17.0/components/images/chips_input_outlined_dark.png differ diff --git a/docs/0.17.0/components/images/chips_input_outlined_light.png b/docs/0.17.0/components/images/chips_input_outlined_light.png new file mode 100644 index 000000000..91e8196de Binary files /dev/null and b/docs/0.17.0/components/images/chips_input_outlined_light.png differ diff --git a/docs/0.17.0/components/images/dialog_alert_dark.png b/docs/0.17.0/components/images/dialog_alert_dark.png new file mode 100644 index 000000000..9521ceb28 Binary files /dev/null and b/docs/0.17.0/components/images/dialog_alert_dark.png differ diff --git a/docs/0.17.0/components/images/dialog_alert_light.png b/docs/0.17.0/components/images/dialog_alert_light.png new file mode 100644 index 000000000..03ba441d5 Binary files /dev/null and b/docs/0.17.0/components/images/dialog_alert_light.png differ diff --git a/docs/0.17.0/components/images/fab_dark.png b/docs/0.17.0/components/images/fab_dark.png new file mode 100644 index 000000000..416210dd3 Binary files /dev/null and b/docs/0.17.0/components/images/fab_dark.png differ diff --git a/docs/0.17.0/components/images/fab_extended_dark.png b/docs/0.17.0/components/images/fab_extended_dark.png new file mode 100644 index 000000000..62f560ae2 Binary files /dev/null and b/docs/0.17.0/components/images/fab_extended_dark.png differ diff --git a/docs/0.17.0/components/images/fab_extended_full_width_dark.png b/docs/0.17.0/components/images/fab_extended_full_width_dark.png new file mode 100644 index 000000000..e85c907e0 Binary files /dev/null and b/docs/0.17.0/components/images/fab_extended_full_width_dark.png differ diff --git a/docs/0.17.0/components/images/fab_extended_full_width_light.png b/docs/0.17.0/components/images/fab_extended_full_width_light.png new file mode 100644 index 000000000..71a00d02b Binary files /dev/null and b/docs/0.17.0/components/images/fab_extended_full_width_light.png differ diff --git a/docs/0.17.0/components/images/fab_extended_light.png b/docs/0.17.0/components/images/fab_extended_light.png new file mode 100644 index 000000000..32da1d57b Binary files /dev/null and b/docs/0.17.0/components/images/fab_extended_light.png differ diff --git a/docs/0.17.0/components/images/fab_light.png b/docs/0.17.0/components/images/fab_light.png new file mode 100644 index 000000000..3b2f977d7 Binary files /dev/null and b/docs/0.17.0/components/images/fab_light.png differ diff --git a/docs/0.17.0/components/images/fab_mini_dark.png b/docs/0.17.0/components/images/fab_mini_dark.png new file mode 100644 index 000000000..062d6a74a Binary files /dev/null and b/docs/0.17.0/components/images/fab_mini_dark.png differ diff --git a/docs/0.17.0/components/images/fab_mini_light.png b/docs/0.17.0/components/images/fab_mini_light.png new file mode 100644 index 000000000..d34e524c6 Binary files /dev/null and b/docs/0.17.0/components/images/fab_mini_light.png differ diff --git a/docs/0.17.0/components/images/lists_single_line_dark.png b/docs/0.17.0/components/images/lists_single_line_dark.png new file mode 100644 index 000000000..d46eca2dc Binary files /dev/null and b/docs/0.17.0/components/images/lists_single_line_dark.png differ diff --git a/docs/0.17.0/components/images/lists_single_line_light.png b/docs/0.17.0/components/images/lists_single_line_light.png new file mode 100644 index 000000000..3bda5190e Binary files /dev/null and b/docs/0.17.0/components/images/lists_single_line_light.png differ diff --git a/docs/0.17.0/components/images/lists_single_line_wide_image_dark.png b/docs/0.17.0/components/images/lists_single_line_wide_image_dark.png new file mode 100644 index 000000000..c00f21923 Binary files /dev/null and b/docs/0.17.0/components/images/lists_single_line_wide_image_dark.png differ diff --git a/docs/0.17.0/components/images/lists_single_line_wide_image_light.png b/docs/0.17.0/components/images/lists_single_line_wide_image_light.png new file mode 100644 index 000000000..d839b5cc6 Binary files /dev/null and b/docs/0.17.0/components/images/lists_single_line_wide_image_light.png differ diff --git a/docs/0.17.0/components/images/lists_three_line_dark.png b/docs/0.17.0/components/images/lists_three_line_dark.png new file mode 100644 index 000000000..15e350f3c Binary files /dev/null and b/docs/0.17.0/components/images/lists_three_line_dark.png differ diff --git a/docs/0.17.0/components/images/lists_three_line_light.png b/docs/0.17.0/components/images/lists_three_line_light.png new file mode 100644 index 000000000..f3dabd4e8 Binary files /dev/null and b/docs/0.17.0/components/images/lists_three_line_light.png differ diff --git a/docs/0.17.0/components/images/lists_three_line_wide_image_dark.png b/docs/0.17.0/components/images/lists_three_line_wide_image_dark.png new file mode 100644 index 000000000..2d44de0c3 Binary files /dev/null and b/docs/0.17.0/components/images/lists_three_line_wide_image_dark.png differ diff --git a/docs/0.17.0/components/images/lists_three_line_wide_image_light.png b/docs/0.17.0/components/images/lists_three_line_wide_image_light.png new file mode 100644 index 000000000..b6eaa4363 Binary files /dev/null and b/docs/0.17.0/components/images/lists_three_line_wide_image_light.png differ diff --git a/docs/0.17.0/components/images/lists_two_line_dark.png b/docs/0.17.0/components/images/lists_two_line_dark.png new file mode 100644 index 000000000..7e130d9fa Binary files /dev/null and b/docs/0.17.0/components/images/lists_two_line_dark.png differ diff --git a/docs/0.17.0/components/images/lists_two_line_light.png b/docs/0.17.0/components/images/lists_two_line_light.png new file mode 100644 index 000000000..ea4e52cb9 Binary files /dev/null and b/docs/0.17.0/components/images/lists_two_line_light.png differ diff --git a/docs/0.17.0/components/images/lists_two_line_wide_image_dark.png b/docs/0.17.0/components/images/lists_two_line_wide_image_dark.png new file mode 100644 index 000000000..0ea681e85 Binary files /dev/null and b/docs/0.17.0/components/images/lists_two_line_wide_image_dark.png differ diff --git a/docs/0.17.0/components/images/lists_two_line_wide_image_light.png b/docs/0.17.0/components/images/lists_two_line_wide_image_light.png new file mode 100644 index 000000000..0c214c277 Binary files /dev/null and b/docs/0.17.0/components/images/lists_two_line_wide_image_light.png differ diff --git a/docs/0.17.0/components/images/menu_dropdown_dark.png b/docs/0.17.0/components/images/menu_dropdown_dark.png new file mode 100644 index 000000000..b47bccfe6 Binary files /dev/null and b/docs/0.17.0/components/images/menu_dropdown_dark.png differ diff --git a/docs/0.17.0/components/images/menu_dropdown_light.png b/docs/0.17.0/components/images/menu_dropdown_light.png new file mode 100644 index 000000000..5af3fb658 Binary files /dev/null and b/docs/0.17.0/components/images/menu_dropdown_light.png differ diff --git a/docs/0.17.0/components/images/menu_exposed_dropdown_dark.png b/docs/0.17.0/components/images/menu_exposed_dropdown_dark.png new file mode 100644 index 000000000..029fb349f Binary files /dev/null and b/docs/0.17.0/components/images/menu_exposed_dropdown_dark.png differ diff --git a/docs/0.17.0/components/images/menu_exposed_dropdown_light.png b/docs/0.17.0/components/images/menu_exposed_dropdown_light.png new file mode 100644 index 000000000..202aa7db9 Binary files /dev/null and b/docs/0.17.0/components/images/menu_exposed_dropdown_light.png differ diff --git a/docs/0.17.0/components/images/progress_circular_dark.png b/docs/0.17.0/components/images/progress_circular_dark.png new file mode 100644 index 000000000..69b07e8ce Binary files /dev/null and b/docs/0.17.0/components/images/progress_circular_dark.png differ diff --git a/docs/0.17.0/components/images/progress_circular_light.png b/docs/0.17.0/components/images/progress_circular_light.png new file mode 100644 index 000000000..b183a1d7d Binary files /dev/null and b/docs/0.17.0/components/images/progress_circular_light.png differ diff --git a/docs/0.17.0/components/images/progress_linear_dark.png b/docs/0.17.0/components/images/progress_linear_dark.png new file mode 100644 index 000000000..e4e3e843c Binary files /dev/null and b/docs/0.17.0/components/images/progress_linear_dark.png differ diff --git a/docs/0.17.0/components/images/progress_linear_light.png b/docs/0.17.0/components/images/progress_linear_light.png new file mode 100644 index 000000000..f56e57735 Binary files /dev/null and b/docs/0.17.0/components/images/progress_linear_light.png differ diff --git a/docs/0.17.0/components/images/radio_button_dark.png b/docs/0.17.0/components/images/radio_button_dark.png new file mode 100644 index 000000000..e7d12ea2f Binary files /dev/null and b/docs/0.17.0/components/images/radio_button_dark.png differ diff --git a/docs/0.17.0/components/images/radio_button_light.png b/docs/0.17.0/components/images/radio_button_light.png new file mode 100644 index 000000000..6de7c0510 Binary files /dev/null and b/docs/0.17.0/components/images/radio_button_light.png differ diff --git a/docs/0.17.0/components/images/sheetbottom_dark.png b/docs/0.17.0/components/images/sheetbottom_dark.png new file mode 100644 index 000000000..92919efce Binary files /dev/null and b/docs/0.17.0/components/images/sheetbottom_dark.png differ diff --git a/docs/0.17.0/components/images/sheetbottom_light.png b/docs/0.17.0/components/images/sheetbottom_light.png new file mode 100644 index 000000000..add8e3f0b Binary files /dev/null and b/docs/0.17.0/components/images/sheetbottom_light.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_dark.png b/docs/0.17.0/components/images/slider_continuous_dark.png new file mode 100644 index 000000000..d51cceb18 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_dark.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_light.png b/docs/0.17.0/components/images/slider_continuous_light.png new file mode 100644 index 000000000..79534c544 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_light.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_lockups_dark.png b/docs/0.17.0/components/images/slider_continuous_lockups_dark.png new file mode 100644 index 000000000..6611a0239 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_lockups_dark.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_lockups_light.png b/docs/0.17.0/components/images/slider_continuous_lockups_light.png new file mode 100644 index 000000000..32983ce8d Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_lockups_light.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_dark.png b/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_dark.png new file mode 100644 index 000000000..05224484e Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_dark.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_light.png b/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_light.png new file mode 100644 index 000000000..6684b95d1 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_lockups_with_icon_light.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_with_icon_dark.png b/docs/0.17.0/components/images/slider_continuous_with_icon_dark.png new file mode 100644 index 000000000..c26eb6ea1 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_with_icon_dark.png differ diff --git a/docs/0.17.0/components/images/slider_continuous_with_icon_light.png b/docs/0.17.0/components/images/slider_continuous_with_icon_light.png new file mode 100644 index 000000000..aa2313001 Binary files /dev/null and b/docs/0.17.0/components/images/slider_continuous_with_icon_light.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_dark.png b/docs/0.17.0/components/images/slider_discrete_dark.png new file mode 100644 index 000000000..bb77c044b Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_dark.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_light.png b/docs/0.17.0/components/images/slider_discrete_light.png new file mode 100644 index 000000000..778ae3b6a Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_light.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_lockups_dark.png b/docs/0.17.0/components/images/slider_discrete_lockups_dark.png new file mode 100644 index 000000000..b2b16047e Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_lockups_dark.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_lockups_light.png b/docs/0.17.0/components/images/slider_discrete_lockups_light.png new file mode 100644 index 000000000..078f7d80e Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_lockups_light.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_dark.png b/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_dark.png new file mode 100644 index 000000000..5dac0f3dc Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_dark.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_light.png b/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_light.png new file mode 100644 index 000000000..b455cbcf5 Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_lockups_with_icon_light.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_with_icon_dark.png b/docs/0.17.0/components/images/slider_discrete_with_icon_dark.png new file mode 100644 index 000000000..cfa833cc0 Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_with_icon_dark.png differ diff --git a/docs/0.17.0/components/images/slider_discrete_with_icon_light.png b/docs/0.17.0/components/images/slider_discrete_with_icon_light.png new file mode 100644 index 000000000..300f59ad9 Binary files /dev/null and b/docs/0.17.0/components/images/slider_discrete_with_icon_light.png differ diff --git a/docs/0.17.0/components/images/snackbar_dark.png b/docs/0.17.0/components/images/snackbar_dark.png new file mode 100644 index 000000000..975974543 Binary files /dev/null and b/docs/0.17.0/components/images/snackbar_dark.png differ diff --git a/docs/0.17.0/components/images/snackbar_light.png b/docs/0.17.0/components/images/snackbar_light.png new file mode 100644 index 000000000..581e307cf Binary files /dev/null and b/docs/0.17.0/components/images/snackbar_light.png differ diff --git a/docs/0.17.0/components/images/snackbar_with_action_dark.png b/docs/0.17.0/components/images/snackbar_with_action_dark.png new file mode 100644 index 000000000..0b1db7fea Binary files /dev/null and b/docs/0.17.0/components/images/snackbar_with_action_dark.png differ diff --git a/docs/0.17.0/components/images/snackbar_with_action_light.png b/docs/0.17.0/components/images/snackbar_with_action_light.png new file mode 100644 index 000000000..8590dcf57 Binary files /dev/null and b/docs/0.17.0/components/images/snackbar_with_action_light.png differ diff --git a/docs/0.17.0/components/images/switch_dark.png b/docs/0.17.0/components/images/switch_dark.png new file mode 100644 index 000000000..88e383385 Binary files /dev/null and b/docs/0.17.0/components/images/switch_dark.png differ diff --git a/docs/0.17.0/components/images/switch_light.png b/docs/0.17.0/components/images/switch_light.png new file mode 100644 index 000000000..3e7c48822 Binary files /dev/null and b/docs/0.17.0/components/images/switch_light.png differ diff --git a/docs/0.17.0/components/images/tabs_fixed_dark.png b/docs/0.17.0/components/images/tabs_fixed_dark.png new file mode 100644 index 000000000..1c529c768 Binary files /dev/null and b/docs/0.17.0/components/images/tabs_fixed_dark.png differ diff --git a/docs/0.17.0/components/images/tabs_fixed_light.png b/docs/0.17.0/components/images/tabs_fixed_light.png new file mode 100644 index 000000000..8ceda3637 Binary files /dev/null and b/docs/0.17.0/components/images/tabs_fixed_light.png differ diff --git a/docs/0.17.0/components/images/tabs_scrollable_dark.png b/docs/0.17.0/components/images/tabs_scrollable_dark.png new file mode 100644 index 000000000..e99f89125 Binary files /dev/null and b/docs/0.17.0/components/images/tabs_scrollable_dark.png differ diff --git a/docs/0.17.0/components/images/tabs_scrollable_light.png b/docs/0.17.0/components/images/tabs_scrollable_light.png new file mode 100644 index 000000000..c6496d8db Binary files /dev/null and b/docs/0.17.0/components/images/tabs_scrollable_light.png differ diff --git a/docs/0.17.0/components/images/textfield_character_counter_dark.png b/docs/0.17.0/components/images/textfield_character_counter_dark.png new file mode 100644 index 000000000..9b52ae51b Binary files /dev/null and b/docs/0.17.0/components/images/textfield_character_counter_dark.png differ diff --git a/docs/0.17.0/components/images/textfield_character_counter_light.png b/docs/0.17.0/components/images/textfield_character_counter_light.png new file mode 100644 index 000000000..482d4c207 Binary files /dev/null and b/docs/0.17.0/components/images/textfield_character_counter_light.png differ diff --git a/docs/0.17.0/components/images/textfield_filled_dark.png b/docs/0.17.0/components/images/textfield_filled_dark.png new file mode 100644 index 000000000..38424f0f4 Binary files /dev/null and b/docs/0.17.0/components/images/textfield_filled_dark.png differ diff --git a/docs/0.17.0/components/images/textfield_filled_light.png b/docs/0.17.0/components/images/textfield_filled_light.png new file mode 100644 index 000000000..881386b2a Binary files /dev/null and b/docs/0.17.0/components/images/textfield_filled_light.png differ diff --git a/docs/0.17.0/components/images/textfield_filled_password_dark.png b/docs/0.17.0/components/images/textfield_filled_password_dark.png new file mode 100644 index 000000000..aaffb22ff Binary files /dev/null and b/docs/0.17.0/components/images/textfield_filled_password_dark.png differ diff --git a/docs/0.17.0/components/images/textfield_filled_password_light.png b/docs/0.17.0/components/images/textfield_filled_password_light.png new file mode 100644 index 000000000..8e9b23d4b Binary files /dev/null and b/docs/0.17.0/components/images/textfield_filled_password_light.png differ diff --git a/docs/0.17.0/components/images/textfield_outlined_dark.png b/docs/0.17.0/components/images/textfield_outlined_dark.png new file mode 100644 index 000000000..18ab3afdd Binary files /dev/null and b/docs/0.17.0/components/images/textfield_outlined_dark.png differ diff --git a/docs/0.17.0/components/images/textfield_outlined_light.png b/docs/0.17.0/components/images/textfield_outlined_light.png new file mode 100644 index 000000000..0f971aa24 Binary files /dev/null and b/docs/0.17.0/components/images/textfield_outlined_light.png differ diff --git a/docs/0.17.0/components/images/textfield_outlined_password_dark.png b/docs/0.17.0/components/images/textfield_outlined_password_dark.png new file mode 100644 index 000000000..6f33c5418 Binary files /dev/null and b/docs/0.17.0/components/images/textfield_outlined_password_dark.png differ diff --git a/docs/0.17.0/components/images/textfield_outlined_password_light.png b/docs/0.17.0/components/images/textfield_outlined_password_light.png new file mode 100644 index 000000000..7a6292a76 Binary files /dev/null and b/docs/0.17.0/components/images/textfield_outlined_password_light.png differ diff --git a/docs/0.17.0/guidelines/Colors.md b/docs/0.17.0/guidelines/Colors.md new file mode 100644 index 000000000..f389de153 --- /dev/null +++ b/docs/0.17.0/guidelines/Colors.md @@ -0,0 +1,55 @@ +--- +layout: detail +title: Colors +--- + +--- + +**Page Summary** + +* [Specifications references](#specifications-references) +* [Implementation in Jetpack Compose](#implementation-in-jetpack-compose) + * [Theme colors](#theme-colors) + * [Functional colors](#functional-colors) + * [Other colors from Orange Design System](#other-colors-from-orange-design-system) + +--- + + +## Specifications references + +- [Design System Manager - Colour](https://system.design.orange.com/0c1af118d/p/623630-colour/b/041102) +- [Material Design - The color system](https://material.io/design/color/the-color-system.html#color-usage-and-palettes) + +## Implementation in Jetpack Compose + +### Theme colors + +ODS library provides MaterialTheme colors. You can use these colors by using `MaterialTheme.colors`: + +```kotlin +Text( + text = "Hello world", + color = MaterialTheme.colors.primary +) +``` + +### Functional colors + +Functional colors have been added and can also be used as above: +- `MaterialTheme.colors.functionalPositive` +- `MaterialTheme.colors.functionalInfo` +- `MaterialTheme.colors.functionalAlert` + +Note: These colors will be different depending on whether they are displayed in light or in dark mode. + +### Other colors from Orange Design System + +All the other colors defined in the library can be used directly through their names: + +```kotlin +Text( + text = "Hello world", + color = Pink100 +) +``` diff --git a/docs/0.17.0/guidelines/Colors_docs.md b/docs/0.17.0/guidelines/Colors_docs.md new file mode 100644 index 000000000..1818ea806 --- /dev/null +++ b/docs/0.17.0/guidelines/Colors_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Colors.md +--- \ No newline at end of file diff --git a/docs/0.17.0/guidelines/Typography.md b/docs/0.17.0/guidelines/Typography.md new file mode 100644 index 000000000..615c0a64f --- /dev/null +++ b/docs/0.17.0/guidelines/Typography.md @@ -0,0 +1,49 @@ +--- +layout: detail +title: Typography +--- + +--- + +**Page Summary** + +* [Specifications references](#specifications-references) +* [Implementation in Jetpack Compose](#implementation-in-jetpack-compose) + * [TextStyles](#textstyles) + * [OdsText composables](#odstext-composables) + +--- + +## Specifications references + +- [Design System Manager - Typography](https://system.design.orange.com/0c1af118d/p/90d1e5-typography) +- [Material Design - The type system](https://material.io/design/typography/the-type-system.html#type-scale) + +## Implementation in Jetpack Compose + +### TextStyles + +ODS library uses the Material type system. +TextStyles are accessed via `MaterialTheme.typography`. Retrieve the TextStyles like so: + +```kotlin +Text( + text = "Subtitle2 styled", + style = MaterialTheme.typography.subtitle2 +) +``` + +### OdsText composables + +ODS library also provides `OdsText` composables already using specific styles. They are here to simplify the code you write. + +For example, to display a text styled with subtitle2 typo, you can write: + +```kotlin +OdsTextSubtitle2(text = "Subtitle2 styled") +``` + +Optional parameters are: +- a `Modifier` +- an `OdsDisplaySurface` which allow to force elements appearance to be displayed on light or dark surface. + diff --git a/docs/0.17.0/guidelines/Typography_docs.md b/docs/0.17.0/guidelines/Typography_docs.md new file mode 100644 index 000000000..5ff7b782c --- /dev/null +++ b/docs/0.17.0/guidelines/Typography_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: Typography.md +--- \ No newline at end of file diff --git a/docs/0.17.0/home_content.md b/docs/0.17.0/home_content.md new file mode 100644 index 000000000..b2fd9c785 --- /dev/null +++ b/docs/0.17.0/home_content.md @@ -0,0 +1,105 @@ +## Introduction + +Orange is providing a full Design System to build Orange Mobile Application. The objective of the [Orange Design System](https://system.design.orange.com/0c1af118d/p/019ecc-android/) (ODS) is to propose a set of guidelines on how to apply the Orange Brand on mobile applications. The Orange design System also provides a series of components and modules that show in details how to use this in the Orange apps. + +The Orange Design System has been implemented in a code library that provides: +- a Jetpack Compose code library +- a demo app that can be launched to show the components and modules +- this demo app also shows how to use the lib or style existing components + +Using these resources will allow you to create Orange branded applications faster and will inherit all the work that was done to make sure that all presented codes are fully tested with regard to the brand and the accessibility compliance. + +The ODS library is compatible with **Android 5.0 (API level 21) and higher**. + +## Instructions + +### 1. Depend on our library + +Orange Design System for Android is available through [Maven Central Repository](https://mvnrepository.com/artifact/com.orange.ods.android). To use it: + +1. Open the `build.gradle` file for your application. +2. Make sure that the `repositories` section includes Maven Central. For example: + + ```groovy + allprojects { + repositories { + google() + mavenCentral() + } + } + ``` + +3. Add the library to the `dependencies` section: + + ```groovy + dependencies { + // ... + implementation 'com.orange.ods.android:ods-lib:0.17.0' + // ... + } + ``` + +4. (Optional) Add an `OdsThemeConfiguration` implementation library to the `dependencies` section: + + You have the possibility to use the ODS library with another theme than the Orange theme. + + ```groovy + dependencies { + // ... + implementation(project(":theme-innovation-cup")) + // ... + } + ``` + +### 2. Compile your app with Android 11 + +Orange Design System library depends on Material Design library from Google. For this reason, you +will have to install Android Studio 4.0 or higher to build with Android 11, and update your +app's `compileSdkVersion` to `31`. + +### 3. Ensure you are using `AppCompatActivity` + +Using `AppCompatActivity` will ensure that all the components work correctly. If you are unable to +extend from `AppCompatActivity`, update your activities to use +`AppCompatDelegate`. This will enable the `AppCompat` versions of components to be inflated among +other important things. + +### 4. Change your app theme to inherit from a Orange Design theme + +Note that Orange theme supports both light and dark mode. + +#### In Jetpack Compose app + +In the `Manifest.xml` file, add `Theme.Orange.NoActionBar` to your application: +```xml +<application + android:theme="@style/Theme.Orange.NoActionBar"> + <!-- ... --> +</application> +``` + +Use the `OdsTheme` in your screens which is a Material theme extension for Jetpack Compose applications. +Cause ODS support multi-theme, you should pass the `OrangeThemeConfiguration` as theme configuration to use the Orange theme. +```kotlin +OdsTheme(themeConfiguration = OrangeThemeConfiguration()) { + //... +} +``` + +Note: Use another provided `OdsThemeConfigurationContract` implementation if you want to use a custom theme. For example `InnovationCupThemeConfiguration`. + +#### In XML app + +Update your app theme to inherit from Orange theme, e.g.: +```xml +<style name="Theme.MyApp" parent="Theme.Orange"> + <!-- ... --> +</style> +``` + +This theme will use the default `Toolbar`. If you want to provide your own `Toolbar` please use: +```xml +<style name="Theme.MyApp" parent="Theme.Orange.NoActionBar"> + <!-- ... --> +</style> +``` diff --git a/docs/0.17.0/index.md b/docs/0.17.0/index.md new file mode 100644 index 000000000..2993026ed --- /dev/null +++ b/docs/0.17.0/index.md @@ -0,0 +1,6 @@ +--- +layout: main +title: Getting started with Orange Design System for Android +--- + +{% include_relative home_content.md %} \ No newline at end of file diff --git a/docs/0.17.0/index_content.md b/docs/0.17.0/index_content.md new file mode 100644 index 000000000..e5908590b --- /dev/null +++ b/docs/0.17.0/index_content.md @@ -0,0 +1,6 @@ +--- +layout: detail +title: Getting started with Orange Design System for Android +--- + +{% include_relative home_content.md %} \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml index 733b87e80..d30dabe82 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -47,3 +47,7 @@ defaults: path: "0.16.0" values: version: "0.16.0" + - scope: + path: "0.17.0" + values: + version: "0.17.0" diff --git a/docs/_config_netlify.yml b/docs/_config_netlify.yml index 67a3e5eae..c9d2fd802 100644 --- a/docs/_config_netlify.yml +++ b/docs/_config_netlify.yml @@ -46,3 +46,7 @@ defaults: path: "0.16.0" values: version: "0.16.0" + - scope: + path: "0.17.0" + values: + version: "0.17.0" diff --git a/docs/_data/data_menu.yml b/docs/_data/data_menu.yml index 2ab2e32a5..658aeb3ad 100644 --- a/docs/_data/data_menu.yml +++ b/docs/_data/data_menu.yml @@ -33,8 +33,8 @@ toc2: url: /components/FloatingActionButtons_docs - page: Image tile url: /components/ImageTile_docs - - page: Lists - url: /components/Lists_docs + - page: List items + url: /components/ListItems_docs - page: Menus url: /components/Menus_docs - page: "Navigation: bottom" diff --git a/docs/_layouts/main.html b/docs/_layouts/main.html index 959e4ea1e..bdd0eddd7 100644 --- a/docs/_layouts/main.html +++ b/docs/_layouts/main.html @@ -17,6 +17,14 @@ max-height: 100%; } + h2, h3 { + margin-top: 3rem; + } + + h4, h5 { + margin-top: 2rem; + } + #outer { width:100%; height:100%; diff --git a/docs/components/AppBarsBottom.md b/docs/components/AppBarsBottom.md index c78807e84..19ce0cca0 100644 --- a/docs/components/AppBarsBottom.md +++ b/docs/components/AppBarsBottom.md @@ -4,14 +4,10 @@ title: "App bars: bottom" description: A bottom app bar displays navigation and key actions at the bottom of mobile screens. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) -* [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) --- @@ -19,11 +15,10 @@ description: A bottom app bar displays navigation and key actions at the bottom - [Design System Manager - App bars](https://system.design.orange.com/0c1af118d/p/23e0e6-app-bars/b/620966) - [Material Design - App bars: bottom](https://material.io/components/app-bars-bottom) -- *Technical documentation soon available* ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Android's bottom app bar component APIs provide support for the navigation icon, action items, overflow menu and more for informing the user as to what each @@ -33,135 +28,4 @@ action performs. While optional, their use is strongly encouraged. When using icons for navigation icons, action items and other elements of bottom app bars, you should set a content description on them so that screen readers -like TalkBack are able to announce their purpose or action, if any. - -For an overall content description of the bottom app bar, set an -`android:contentDescription` or use the `setContentDescription` method on the -`BottomAppBar`. - -For the navigation icon, this can be achieved via the -`app:navigationContentDescription` attribute or -`setNavigationContentDescription` method. - -For action items and items within the overflow menu, the content description -needs to be set in the menu: - -```xml -<menu> - <item - android:contentDescription="@string/content_description_one" /> - <item - android:contentDescription="@string/content_description_two" /> -</menu> -``` - -## Implementation - -Bottom app bars provide access to a bottom navigation drawer and up to four -actions, including the floating action button. - -> **Jetpack Compose implementation** - -*Not available yet* - -> **XML implementation** - -API and source code: - -* `CoordinatorLayout`: [Class definition](https://developer.android.com/reference/androidx/coordinatorlayout/widget/CoordinatorLayout) -* `BottomAppBar`: [Class definition](https://developer.android.com/reference/com/google/android/material/bottomappbar/BottomAppBar), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomappbar/BottomAppBar.java) -* `FloatingActionButton`: [Class definition](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java) - -In the layout: - -```xml -<androidx.coordinatorlayout.widget.CoordinatorLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <!-- Note: A RecyclerView can also be used --> - <androidx.core.widget.NestedScrollView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="100dp" - android:clipToPadding="false"> - - <!-- Scrollable content --> - - </androidx.core.widget.NestedScrollView> - - <com.google.android.material.bottomappbar.BottomAppBar - android:id="@+id/bottomAppBar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - app:navigationIcon="@drawable/ic_menu_24dp" - app:menu="@menu/bottom_app_bar" - /> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:srcCompat="@drawable/ic_add_24dp" - app:layout_anchor="@id/bottomAppBar" - /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -In `menu/bottom_app_bar.xml`: - -```xml -<menu> - - <item - android:id="@+id/search" - android:icon="@drawable/ic_search_24dp" - android:title="@string/search" - android:contentDescription="@string/content_description_search" - app:showAsAction="ifRoom" - /> - - <item - android:id="@+id/more" - android:title="@string/more" - android:contentDescription="@string/content_description_more" - app:showAsAction="never" - /> - -</menu> -``` - -In menu/navigation icon drawables: - -```xml -<vector - android:tint="?attr/colorControlNormal"> -</vector> -``` - -In code: - -```kotlin -bottomAppBar.setNavigationOnClickListener { - // Handle navigation icon press -} - -bottomAppBar.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - R.id.search -> { - // Handle search icon press - true - } - R.id.more -> { - // Handle more item (inside overflow menu) press - true - } - else -> false - } -} -``` - -## Component specific tokens - -_Soon available_ +like TalkBack are able to announce their purpose or action, if any. \ No newline at end of file diff --git a/docs/components/AppBarsTop.md b/docs/components/AppBarsTop.md index b51fb0d83..af04f2208 100644 --- a/docs/components/AppBarsTop.md +++ b/docs/components/AppBarsTop.md @@ -4,18 +4,17 @@ title: "App bars: top" description: Top app bars display information and actions relating to the current screen. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Regular top app bar](#regular-top-app-bar) + * [Jetpack Compose](#jetpack-compose) + * [OdsTopAppBar API](#odstopappbar-api) * [Large top app bar](#large-top-app-bar) -* [Extras](#extras) - * [Overflow menu](#overflow-menu) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsLargeTopAppBar API](#odslargetopappbar-api) --- @@ -23,51 +22,23 @@ description: Top app bars display information and actions relating to the curren - [Design System Manager - App bars](https://system.design.orange.com/0c1af118d/p/23e0e6-app-bars/b/620966) - [Material Design - App bars: top](https://material.io/components/app-bars-top/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). -Android's top app bar component APIs provide support for the navigation icon, +`OdsTopAppBar` provides accessibility support for the navigation icon, action items, overflow menu and more for informing the user as to what each -action performs. While optional, their use is strongly encouraged. - -**Content descriptions** - -When using icons for navigation icons, action items and other elements of top -app bars, you should set a content description on them so that screen readers -like TalkBack are able to announce their purpose or action, if any. - -For an overall content description of the top app bar, set an -`android:contentDescription` or use the `setContentDescription` method on the -`MaterialToolbar`. - -For the navigation icon, this can be achieved via the -`app:navigationContentDescription` attribute or -`setNavigationContentDescription` method. - -For action items and items within the overflow menu, the content description -needs to be set in the menu: - -```xml - -<menu> - <item android:contentDescription="@string/content_description_one" /> - <item android:contentDescription="@string/content_description_two" /> -</menu> -``` - -For images within top app bars, set an `android:contentDescription` -or use the `setContentDescription` method on the `ImageView`. +action performs. ## Variants ### Regular top app bar -> **Jetpack Compose implementation** +#### Jetpack Compose -Add `OdsTopAppBar` composable to your Scaffold topBar: +Add `OdsTopAppBar` composable to your Scaffold `topBar`. +Here is an example of use: ```kotlin OdsTopAppBar( @@ -75,20 +46,20 @@ OdsTopAppBar( navigationIcon = OdsTopAppBarNavigationIcon( painter = painterResource(id = R.drawable.ic_back), contentDescription = "content description", - onClick = { /* Do something */ } + onClick = { doSomething() } ), actions = listOf( OdsTopAppBarActionButton( painter = painterResource(id = R.drawable.ic_share), contentDescription = "content description", - onClick = { } + onClick = { doSomething() } ), // ... ), overflowMenuActions = listOf( OdsTopAppBarOverflowMenuActionItem( text = "Text", - onClick = { } + onClick = { doSomething() } ), // ... ) @@ -97,140 +68,21 @@ OdsTopAppBar( Note: By default, the `OdsTopAppBar` is elevated but you can set `elevated` parameter to `false` if you don't want any shadow below it (for example if you want to display tabs below). -> **XML implementation** - -API and source code: - -* `CoordinatorLayout`: [Class definition](https://developer.android.com/reference/androidx/coordinatorlayout/widget/CoordinatorLayout) -* `AppBarLayout`: [Class definition](https://developer.android.com/reference/com/google/android/material/appbar/AppBarLayout), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/appbar/AppBarLayout.java) -* `MaterialToolbar`: [Class definition](https://developer.android.com/reference/com/google/android/material/appbar/MaterialToolbar), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/appbar/MaterialToolbar.java) -* `CollapsingToolbarLayout`: [Class definition](https://developer.android.com/reference/com/google/android/material/appbar/CollapsingToolbarLayout), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java) - -In the layout: - -```xml - -<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" - android:layout_height="match_parent"> - - <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <com.google.android.material.appbar.MaterialToolbar android:id="@+id/topAppBar" - android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - app:title="@string/page_title" app:menu="@menu/top_app_bar" - app:navigationIcon="@drawable/ic_menu_24dp" /> - - </com.google.android.material.appbar.AppBarLayout> - - <!-- Note: A RecyclerView can also be used --> - <androidx.core.widget.NestedScrollView android:layout_width="match_parent" - android:layout_height="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - <!-- Scrollable content --> - - </androidx.core.widget.NestedScrollView> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -In `@menu/top_app_bar.xml`: - -```xml - -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto"> - - <item android:id="@+id/favorite" android:icon="@drawable/ic_favorite_24dp" - android:title="@string/favorite" - android:contentDescription="@string/content_description_favorite" - app:showAsAction="ifRoom" /> - - <item android:id="@+id/search" android:icon="@drawable/ic_search_24dp" - android:title="@string/search" - android:contentDescription="@string/content_description_search" app:showAsAction="ifRoom" /> - - <item android:id="@+id/more" android:title="@string/more" - android:contentDescription="@string/content_description_more" app:showAsAction="never" /> - -</menu> -``` - -In menu/navigation icons: - -```xml - -<vector android:tint="?attr/colorControlNormal" /> -``` - -In code: +##### OdsTopAppBar API -```kotlin -topAppBar.setNavigationOnClickListener { - // Handle navigation icon press -} - -topAppBar.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - R.id.favorite -> { - // Handle favorite icon press - true - } - R.id.search -> { - // Handle search icon press - true - } - R.id.more -> { - // Handle more item (inside overflow menu) press - true - } - else -> false - } -} -``` - -_**Applying scrolling behavior to the top app bar**_ - -The following example shows the top app bar positioned at the same elevation as -content. Upon scroll, it increases elevation and lets content scroll behind it. - -In the layout: - -```xml - -<androidx.coordinatorlayout.widget.CoordinatorLayout> - - <com.google.android.material.appbar.AppBarLayout app:liftOnScroll="true"> - - <com.google.android.material.appbar.MaterialToolbar /> - - </com.google.android.material.appbar.AppBarLayout> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -_**Raised top app bar**_ - -If you need to have a top app bar with some elevation you can set the `@style/Widget.Orange.Toolbar.Raised` - -```xml - -<androidx.coordinatorlayout.widget.CoordinatorLayout> - - <com.google.android.material.appbar.AppBarLayout> - - <com.google.android.material.appbar.MaterialToolbar - style="@style/Widget.Orange.Toolbar.Raised" /> - - </com.google.android.material.appbar.AppBarLayout> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title to be displayed in the center of the top app bar +`modifier: Modifier` | `Modifier` |`Modifier` to be applied to the top app bar +`navigationIcon: OdsTopAppBarNavigationIcon?` | `null` | Icon to be displayed at the start of the top app bar +`actions: List<OdsTopAppBarActionButton>` | `emptyList()` | Actions to be displayed at the end of the top app bar. The default layout here is a `Row`, so icons inside will be placed horizontally. +`overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem>` | `emptyList()` | Actions to be displayed in the overflow menu +`elevated: Boolean` | `true` | Controls the elevation of the top app bar: `true` to set an elevation to the top app bar (a shadow is displayed below), `false` otherwise +{:.table} ### Large top app bar -> **Jetpack Compose implementation** +#### Jetpack Compose First, you have to add this line in your application `build.gradle.kts` file cause this component relies on Compose Material 3 implementation: @@ -238,7 +90,7 @@ First, you have to add this line in your application `build.gradle.kts` file cau implementation("androidx.compose.material3:material3:<version number>") ``` -Then you can add `OdsLargeTopAppBar` composable to your Scaffold topBar: +Then you can add `OdsLargeTopAppBar` composable to your Scaffold `topBar`: ```kotlin OdsLargeTopAppBar( @@ -246,23 +98,23 @@ OdsLargeTopAppBar( navigationIcon = OdsTopAppBarNavigationIcon( painter = painterResource(id = R.drawable.ic_back), contentDescription = "content description", - onClick = { /* Do something */ } + onClick = { doSomething() } ), actions = listOf( OdsTopAppBarActionButton( painter = painterResource(id = R.drawable.ic_share), contentDescription = "content description", - onClick = { } + onClick = { doSomething() } ), // ... ), overflowMenuActions = listOf( OdsTopAppBarOverflowMenuActionItem( text = "Text", - onClick = { } + onClick = { doSomething() } ), // ... - ) + ), scrollBehavior = null // See below to attach a scroll behavior and make the top app bar collapsible ) ``` @@ -292,32 +144,14 @@ Scaffold( } ``` -## Extras - -### Overflow menu - -![Overflow menu light](images/app_bar_top_overflow_menu_light.png) -![Overflow menu dark](images/app_bar_top_overflow_menu_dark.png) - -You can easily add an overflow menu to your top app bar by using the `OdsTopAppBarOverflowMenuBox` composable as follow: - -```kotlin -OdsTopAppBarOverflowMenuBox(overflowIconContentDescription = "more actions") { - OdsDropdownMenuItem( - text = "account", - onClick = { - // Do something - } - ) - OdsDropdownMenuItem( - text = "settings", - onClick = { - // Do something - } - ) -} -``` - -## Component specific tokens - -_Soon available_ +##### OdsLargeTopAppBar API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed in the center of the top app bar +`modifier: Modifier` | `Modifier` |`Modifier` applied to the top app bar +`navigationIcon: OdsTopAppBarNavigationIcon?` | `null` | Icon displayed at the start of the top app bar +`actions: List<OdsTopAppBarActionButton>` | `emptyList()` | Actions displayed at the end of the top app bar. The default layout here is a `Row`, so icons inside will be placed horizontally. +`overflowMenuActions: List<OdsTopAppBarOverflowMenuActionItem>` | `emptyList()` | Actions displayed in the overflow menu +`scrollBehavior: TopAppBarScrollBehavior?` | `null` | `TopAppBarScrollBehavior` attached to the top app bar +{:.table} diff --git a/docs/components/Banners.md b/docs/components/Banners.md index 5311c240a..e0e54d095 100644 --- a/docs/components/Banners.md +++ b/docs/components/Banners.md @@ -10,14 +10,13 @@ It requires a user action to be dismissed. Banners should be displayed at the top of the screen, below a top app bar. They’re persistent and nonmodal, allowing the user to either ignore them or interact with them at any time. Only one banner should be shown at a time ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsBanner API](#odsbanner-api) --- @@ -25,15 +24,12 @@ Only one banner should be shown at a time - [Design System Manager - Banners](https://system.design.orange.com/0c1af118d/p/19a040-banners/b/497b77) - [Material Design - Banners](https://m2.material.io/components/banners) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). -Users should be able to use the left phone buttons to interact with the banner. -The user should be able to hear the current state of the banner at all times. -Recommendation is available at the Orange Accessibility site +`OdsBanner` is built to support accessibility criteria and is readable by most screen readers, such as TalkBack. The use of an `OdsBannerImage` force the developer to associate a content description to the banner image. ## Implementation @@ -41,26 +37,26 @@ Recommendation is available at the Orange Accessibility site ![Banner dark](images/banner_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose You can use the `OdsBanner` composable like this: ```kotlin OdsBanner( - message = "Message displayed in the banner.", - button1Text = "Dismiss", - button2Text = "Detail", // Optional - image = painterResource(id = R.drawable.placeholder), // Optional - imageContentDescription = "", // Optional - onButton1Click = { - // Do something - }, - onButton2Click = { - // Do something - }, // Optional + message = "Message displayed into the banner.", + firstButton = OdsBannerButton("Dismiss") { doSomething() }, + secondButton = OdsBannerButton("Detail") { doSomething() }, + image = OdsBannerImage(painterResource(id = R.drawable.placeholder), "") ) ``` -## Component specific tokens +#### OdsBanner API -_Soon available_ \ No newline at end of file +Parameter | Default value | Description +-- | -- | -- +`message: String` | | Text displayed into the banner +`firstButton: OdsBannerButton` | | Primary button displayed in the banner +`modifier: Modifier` | `Modifier` | `Modifier` applied to the banner +`image: OdsBannerImage?` | `null` | Image displayed in the banner in a circle shape +`secondButton: OdsBannerButton?` | `null` | Secondary button displayed into the banner next to the primary one +{:.table} diff --git a/docs/components/Buttons.md b/docs/components/Buttons.md index 269a6776a..9d143e906 100644 --- a/docs/components/Buttons.md +++ b/docs/components/Buttons.md @@ -4,21 +4,32 @@ title: Buttons description: Buttons allow users to take actions, and make choices, with a single tap. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) + * [Contained button](#contained-button) + * [Jetpack Compose](#jetpack-compose) + * [OdsButton API](#odsbutton-api) * [Text button](#text-button) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsTextButton API](#odstextbutton-api) * [Outlined button](#outlined-button) - * [Contained button](#contained-button) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsOutlinedButton API](#odsoutlinedbutton-api) * [Text toggle buttons group](#text-toggle-buttons-group) + * [Jetpack Compose](#jetpack-compose-3) + * [OdsTextToggleButtonsRow API](#odstexttogglebuttonsrow-api) * [Icon button](#icon-button) + * [Jetpack Compose](#jetpack-compose-4) + * [OdsIconButton API](#odsiconbutton-api) * [Icon toggle button](#icon-toggle-button) + * [Jetpack Compose](#jetpack-compose-5) + * [OdsIconToggleButton API](#odsicontogglebutton-api) * [Icon toggle buttons group](#icon-toggle-buttons-group) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-6) + * [OdsIconToggleButtonsRow API](#odsicontogglebuttonsrow-api) --- @@ -26,104 +37,16 @@ description: Buttons allow users to take actions, and make choices, with a singl - [Design System Manager - Buttons](https://system.design.orange.com/0c1af118d/p/06a393-buttons/b/530521) - [Material Design - Buttons](https://material.io/components/buttons/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -Buttons support content labeling for accessibility and are readable by most screen readers, such as -TalkBack. Text rendered in buttons is automatically provided to accessibility services. Additional -content labels are usually unnecessary. - -## Variants - -### Text button - -Text buttons are typically used for less-pronounced actions, including those located in dialogs and -cards. In cards, text buttons help maintain an emphasis on card content. - -![TextButton](images/button_text_light.png) ![TextButton dark](images/button_text_dark.png) - -> **Jetpack Compose implementation** - -Use the `OdsTextButton` composable: - -```kotlin -OdsTextButton( - text = "Text button", - onClick = {}, - enabled = true, - icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Optional, line can be removed if you don't need any icon - style = OdsTextButtonStyle.Primary -) -``` - -> **XML implementation** - -To create a Text Button using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Text` on your `Button` layout - -In the layout: - -```xml - -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Text button" style="@style/Widget.Orange.Button.Text" /> -``` - -To create a **Text Button having an icon** using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Text.Icon` on your `Button` layout - -```xml - -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Text button with icon" app:icon="@drawable/ic_add_24dp" - style="@style/Widget.Orange.Button.Text.Icon" /> -``` - -### Outlined button - -Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren’t -the primary action in an app. - -![ButtonOutlined](images/button_outlined_light.png) ![ButtonOutlined dark](images/button_outlined_dark.png) - -> **Jetpack Compose implementation** - -Use the `OdsOutlinedButton` composable: - -```kotlin -OdsOutlinedButton( - text = "Outlined button", - onClick = {}, - enabled = true, - icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Optional, line can be removed if you don't need any icon -) -``` - -> **XML implementation** - -To create an Outlined Button using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Outlined` on your `Button` layout. +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). -In the layout: +ODS buttons support accessibility criteria and are readable by most screen readers, such as TalkBack. -```xml +Content descriptions for icons are unnecessary in the case of buttons containing text. For other buttons types, such as `OdsIconButton`, icons content descriptions are mandatory in the APIs. -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Outlined button" style="@style/Widget.Orange.Button.Outlined" /> -``` - -To create an **Outlined Button having an icon** using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Outlined.Icon` on your `Button` layout. - -```xml - -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Outlined button with icon" app:icon="@drawable/ic_add_24dp" - style="@style/Widget.Orange.Button.Outlined.Icon" /> -``` +## Variants ### Contained button @@ -140,16 +63,16 @@ Functional negative: ![ContainedButton negative light](images/button_contained_negative_light.png) ![ContainedButton negative dark](images/button_contained_negative_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsButton` composable: ```kotlin OdsButton( text = "Contained button", - onClick = {}, + onClick = { doSomething() }, enabled = true, - icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Optional, line can be removed if you don't need any icon + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Line can be removed if you don't need any icon ) ``` @@ -159,46 +82,92 @@ through the `style` parameter: ```kotlin OdsButton( text = "Positive button", - onClick = {}, + onClick = { doSomething() }, enabled = true, - icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Optional, line can be removed if you don't need any icon + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Line can be removed if you don't need any icon style = OdsButtonStyle.FunctionalPositive ) ``` -> **XML implementation** +##### OdsButton API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked when the button is clicked +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`style: OdsButtonStyle` | `OdsButtonStyle.Default` | Style applied to the button. Set it to `OdsButtonStyle.Primary` for an highlighted button style or use `OdsButtonStyle.FunctionalPositive`/ `OdsButtonStyle.FunctionalNegative` for a functional green/red button style. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} -_**Note** In XML, the contained button is the default style if the style is not set._ +### Text button -To create a Contained Button using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Contained` on your `Button` layout +Text buttons are typically used for less-pronounced actions, including those located in dialogs and +cards. In cards, text buttons help maintain an emphasis on card content. + +![TextButton](images/button_text_light.png) ![TextButton dark](images/button_text_dark.png) -In the layout: +#### Jetpack Compose -```xml +Use the `OdsTextButton` composable: -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Contained button" style="@style/Widget.Orange.Button.Contained" /> +```kotlin +OdsTextButton( + text = "Text button", + onClick = { doSomething() }, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)), // Line can be removed if you don't need any icon + style = OdsTextButtonStyle.Primary +) ``` -or +##### OdsTextButton API -```xml +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked on button click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`style: OdsTextButtonStyle` | `OdsTextButtonStyle.Default` | Style applied to the button. By default `onSurface` color is used for text color. Use `OdsTextButtonStyle.Primary` for an highlighted text color. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Contained button" /> -``` +### Outlined button -To create a **Contained Button having an icon** using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Contained.Icon` on your `Button` layout +Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren’t +the primary action in an app. -```xml +![ButtonOutlined](images/button_outlined_light.png) ![ButtonOutlined dark](images/button_outlined_dark.png) + +#### Jetpack Compose -<Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Contained button with icon" app:icon="@drawable/ic_add_24dp" - style="@style/Widget.Orange.Button.Contained.Icon" /> +Use the `OdsOutlinedButton` composable: + +```kotlin +OdsOutlinedButton( + text = "Outlined button", + onClick = {}, + enabled = true, + icon = OdsButtonIcon(painterResource(R.drawable.ic_coffee)) // Line can be removed if you don't need any icon +) ``` +##### OdsOutlinedButton API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the button +`onClick: () -> Unit` | | Callback invoked on button click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`icon: OdsButtonIcon?` | `null` | Icon displayed in the button before the text +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, the button is not clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + ### Text toggle buttons group A group of text toggle buttons. Only one option in a group of toggle buttons can be selected and active at a time. @@ -206,7 +175,7 @@ Selecting one option deselects any other. ![Button text toggle group light](images/button_text_toggle_group_light.png) ![Button text toggle group dark](images/button_text_toggle_group_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsTextToggleButtonsRow` composable: @@ -218,12 +187,24 @@ OdsTextToggleButtonsRow( ), selectedIndex = 0, onSelectedIndexChange = { - // Do something like changing selectedIndex to refresh composable with new selection + doSomething() // Do something like changing selectedIndex to refresh composable with new selection }, sameItemsWeight = false ) ``` +##### OdsTextToggleButtonsRow API + +Parameter | Default value | Description +-- | -- | -- +`textToggleButtons: List<OdsTextToggleButtonsRowItem>` | | Items displayed into the toggle group +`selectedIndex: Int` | | `textToggleButtons` list index of the selected button +`onSelectedIndexChange: (Int) -> Unit` | | Callback invoked on selection change +`modifier: Modifier` | `Modifier` | `Modifier` applied to the toggle buttons row +`sameItemsWeight: Boolean` | `false` | Controls the place occupied by each item. When `true`, same weight of importance will be applied to each item, they will occupy the same width. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + ### Icon button An icon button is a clickable icon, used to represent actions. This component is typically used @@ -231,7 +212,7 @@ inside an App Bar for the navigation icon / actions. ![OdsIconButton](images/button_icon_light.png) ![OdsIconButton dark](images/button_icon_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsIconButton` composable: @@ -241,12 +222,21 @@ OdsIconButton( painterResource(id = R.drawable.ic_ui_light_mode), stringResource(id = R.string.theme_changer_icon_content_description_light) ), - onClick = { - // Do something - }, + onClick = { doSomething() }, ) ``` +##### OdsIconButton API + +Parameter | Default value | Description +-- | -- | -- +`icon: OdsIconButtonIcon` | | Icon to be drawn into the button +`onClick: () -> Unit` | | Callback to be invoked when the button is clicked +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the button +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` to be applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + ### Icon toggle button An icon button with two states, for icons that can be toggled 'on' and 'off', such as a bookmark @@ -254,14 +244,14 @@ icon, or a navigation icon that opens a drawer. ![Button icon toggle light](images/button_icon_toggle_light.png) ![Button icon toggle dark](images/button_icon_toggle_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsIconToggleButton` composable: ```kotlin OdsIconToggleButton( checked = false, - onCheckedChange = { }, + onCheckedChange = { doSomething() }, uncheckedIcon = OdsIconButtonIcon( painterResource(R.drawable.ic_heart_outlined), "Add to favorites" @@ -270,6 +260,19 @@ OdsIconToggleButton( ) ``` +##### OdsIconToggleButton API + +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls the checked state of the button +`onCheckedChange: (Boolean) -> Unit` | | Callback invoked when the button is checked +`uncheckedIcon: OdsIconButtonIcon` | | Icon displayed when the button is unchecked +`checkedIcon: OdsIconButtonIcon` | | Icon displayed when the button is checked +`modifier: Modifier` | `Modifier` | `Modifier` applied to the button +`enabled: Boolean` | `true` | Controls the enabled state of the button. When `false`, this button will not be clickable. +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} + ### Icon toggle buttons group A group of toggle buttons. Only one option in a group of toggle buttons can be selected and active @@ -278,7 +281,7 @@ Selecting one option deselects any other. ![Button icon toggle group light](images/button_icon_toggle_group_light.png) ![Button icon toggle group dark](images/button_icon_toggle_group_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsIconToggleButtonsRow` composable: @@ -295,52 +298,19 @@ OdsIconToggleButtonsRow( ), selectedIndex = 0, onSelectedIndexChange = { - // Do something like changing selectedIndex to refresh composable with new selection + doSomething() // Do something like changing selectedIndex to refresh composable with new selection }, - displaySurface = displaySurface // Optional + displaySurface = displaySurface ) ``` -> **XML implementation** - -API and source code: - -* `MaterialButtonToggleGroup`: [Class description](https://developer.android.com/reference/com/google/android/material/button/MaterialButtonToggleGroup) - , [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/button/MaterialButtonToggleGroup.java) -* `MaterialButton`: [Class description](https://developer.android.com/reference/com/google/android/material/button/MaterialButton) - , [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/button/MaterialButton.java) - -In the layout: - -```xml - -<com.google.android.material.button.MaterialButtonToggleGroup android:layout_width="wrap_content" - android:layout_height="wrap_content"> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 1" style="@style/Widget.Orange.Button.Outlined" /> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 2" style="@style/Widget.Orange.Button.Outlined" /> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 3" style="@style/Widget.Orange.Button.Outlined" /> -</com.google.android.material.button.MaterialButtonToggleGroup> -``` - -To create an **icon-only toggle button** using Orange theme you will need to apply -style `@style/Widget.Orange.Button.Outlined.IconOnly` on your `Button` layout. - -```xml - -<com.google.android.material.button.MaterialButtonToggleGroup android:layout_width="wrap_content" - android:layout_height="wrap_content"> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 1" style="@style/Widget.Orange.Button.Outlined.IconOnly" /> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 2" style="@style/Widget.Orange.Button.Outlined.IconOnly" /> - <Button android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Button 3" style="@style/Widget.Orange.Button.Outlined.IconOnly" /> -</com.google.android.material.button.MaterialButtonToggleGroup> -``` - -## Component specific tokens +##### OdsIconToggleButtonsRow API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`icons: List<OdsIconToggleButtonsRowIcon>` | | Icons to be displayed into the toggle group +`selectedIndex: Int` | | `icons` list index of the selected button +`onSelectedIndexChange: (Int) -> Unit` | | Callback invoked on selection change +`modifier: Modifier` | `Modifier` | `Modifier` applied to the toggle buttons group +`displaySurface: OdsDisplaySurface` | `OdsDisplaySurface.Default` | `OdsDisplaySurface` applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. +{:.table} diff --git a/docs/components/Cards.md b/docs/components/Cards.md index 0b2edea6e..183b5d359 100644 --- a/docs/components/Cards.md +++ b/docs/components/Cards.md @@ -4,18 +4,23 @@ title: Cards description: Cards contain content and actions about a single subject. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Vertical image first card](#vertical-image-first-card) + * [Jetpack Compose](#jetpack-compose) + * [OdsVerticalImageFirstCard API](#odsverticalimagefirstcard-api) * [Vertical header first card](#vertical-header-first-card) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsVerticalHeaderFirstCard API](#odsverticalheaderfirstcard-api) * [Small card](#small-card) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsSmallCard API](#odssmallcard-api) * [Horizontal card](#horizontal-card) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-3) + * [OdsHorizontalCard API](#odshorizontalcard-api) --- @@ -23,30 +28,25 @@ description: Cards contain content and actions about a single subject. - [Design System Manager - Cards](https://system.design.orange.com/0c1af118d/p/272739-cards/b/991690) - [Material Design - Cards](https://material.io/components/cards/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). The contents within a card should follow their own accessibility guidelines, such as images having content descriptions set on them. - -If you have a draggable card, you should set an -[`AccessibilityDelegate`](https://developer.android.com/reference/android/view/View.AccessibilityDelegate) -on it, so that the behavior can be accessible via screen readers such as TalkBack. -See the [draggable card section](https://material.io/components/cards/android#making-a-card-draggable) section for more info. +The ODS library cards APIs forces the developers to add content descriptions on card images. ## Variants -The library offers several Composables for Jetpack Compose implementation. In XML, the library is only styling `MaterialCardView`. +The library offers several Composables for Jetpack Compose implementation. ### Vertical image first card This is a full width card containing elements arranged vertically with an image as first element. - ![Vertical image first card light](images/card_vertical_image_first_light.png) ![Vertical image first card dark](images/card_vertical_image_first_dark.png) +![Vertical image first card light](images/card_vertical_image_first_light.png) ![Vertical image first card dark](images/card_vertical_image_first_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use `OdsVerticalImageFirstCard` composable: @@ -58,27 +58,37 @@ OdsVerticalImageFirstCard( "Picture content description", Alignment.Center, ContentScale.Crop, - Color(0xff1b1b1b) //Optional + Color(0xff1b1b1b) ), - subtitle = "Subtitle", //Optional - text = "Text", //Optional - firstButton = OdsCardButton("First button") {}, //Optional - secondButton = OdsCardButton("Second button") {}, //Optional - onClick = {} //Optional + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + onClick = { doSomething() } ) ``` -> **XML implementation** +##### OdsVerticalImageFirstCard API -See [Cards implementation in XML](#cards-implementation-in-xml) +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} ### Vertical header first card This is a full width card containing elements arranged vertically with a header (thumbnail, title & subtitle) as first element. - ![Vertical header first card light](images/card_vertical_header_first_light.png) ![Vertical header first card dark](images/card_vertical_header_first_dark.png) +![Vertical header first card light](images/card_vertical_header_first_light.png) ![Vertical header first card dark](images/card_vertical_header_first_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use `OdsVerticalHeaderFirstCard` composable: @@ -90,33 +100,44 @@ OdsVerticalHeaderFirstCard( "Picture content description", Alignment.Center, ContentScale.Crop, - Color(0xff1b1b1b) //Optional + Color(0xff1b1b1b) ), thumbnail = OdsCardThumbnail( painterResource(R.drawable.thumbnail), "Thumbnail content description" - ), //Optional - subtitle = "Subtitle", //Optional - text = "Text", //Optional - firstButton = OdsCardButton("First button") {}, //Optional - secondButton = OdsCardButton("Second button") {}, //Optional - onClick = {} //Optional + ), + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + onClick = { doSomething() } ) ``` -> **XML implementation** - -See [Cards implementation in XML](#cards-implementation-in-xml) +##### OdsVerticalHeaderFirstCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`thumbnail: OdsCardThumbnail?` | `null` | Thumbnail displayed into the card next to the title: avatar, logo or icon. +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback called on card click +{:.table} ### Small card This is a small card which takes the half screen width. - ![CardSmall](images/card_small_light.png) ![CardSmall dark](images/card_small_dark.png) +![CardSmall](images/card_small_light.png) ![CardSmall dark](images/card_small_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose -You can add an `OdsSmallCard`composable in your screen to add a small card: +You can add an `OdsSmallCard` composable in your screen to add a small card: ```kotlin Row( @@ -129,7 +150,7 @@ Row( "Picture content description" ), modifier = Modifier.weight(0.5f), - onClick = {} + onClick = { doSomething() } ) OdsSmallCard( title = "Title", @@ -138,22 +159,29 @@ Row( "Picture content description" ), modifier = Modifier.weight(0.5f), - onClick = {} + onClick = { doSomething() } ) } ``` -> **XML implementation** +##### OdsSmallCard API -See [Cards implementation in XML](#cards-implementation-in-xml) +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} ### Horizontal card This is a full screen width card with an image on the side. The image can be displayed on the left or on the right. - ![Horizontal card light](images/card_horizontal_light.png) ![Horizontal card dark](images/card_horizontal_dark.png) +![Horizontal card light](images/card_horizontal_light.png) ![Horizontal card dark](images/card_horizontal_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your screen you can use `OdsHorizontalCard` composable: @@ -165,112 +193,30 @@ OdsHorizontalCard( "Picture content description", Alignment.Center, ContentScale.Crop, - Color(0xff1b1b1b) //Optional + Color(0xff1b1b1b) ), - subtitle = "Subtitle", //Optional - text = "Text", //Optional - firstButton = OdsCardButton("First button") {}, //Optional - secondButton = OdsCardButton("Second button") {}, //Optional - imagePosition = OdsHorizontalCardImagePosition.Start, //Optional. Start by default. - divider = false, // Optional. True by default. - onClick = {} //Optional + subtitle = "Subtitle", + text = "Text", + firstButton = OdsCardButton("First button") { doSomething() }, + secondButton = OdsCardButton("Second button") { doSomething() }, + imagePosition = OdsHorizontalCardImagePosition.Start, + divider = false, + onClick = { doSomething() } ) ``` - -> **XML implementation** - -See [Cards implementation in XML](#cards-implementation-in-xml) - -### Cards implementation in XML - -To have a Card in your layout you must add `com.google.android.material.card.MaterialCardView` in your layout. - -API and source code: - -* `MaterialCardView`: [Class definition](https://developer.android.com/reference/com/google/android/material/card/MaterialCardView), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/card/MaterialCardView.java) - -In the layout: - -```xml -<com.google.android.material.card.MaterialCardView - android:id="@+id/card" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="8dp"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <!-- Media --> - <ImageView - android:layout_width="match_parent" - android:layout_height="194dp" - app:srcCompat="@drawable/media" - android:scaleType="centerCrop" - android:contentDescription="@string/content_description_media" - /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:padding="16dp"> - - <!-- Title, secondary and supporting text --> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/title" - android:textAppearance="?attr/textAppearanceHeadline6" - /> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/secondary_text" - android:textAppearance="?attr/textAppearanceBody2" - android:textColor="?android:attr/textColorSecondary" - /> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:text="@string/supporting_text" - android:textAppearance="?attr/textAppearanceBody2" - android:textColor="?android:attr/textColorSecondary" - /> - - </LinearLayout> - - <!-- Buttons --> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="8dp" - android:orientation="horizontal"> - <com.google.android.material.button.MaterialButton - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="8dp" - android:text="@string/action_1" - style="@style/Widget.Orange.Button.Text" - /> - <com.google.android.material.button.MaterialButton - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/action_2" - style="@style/Widget.Orange.Button.Text" - /> - </LinearLayout> - - </LinearLayout> - -</com.google.android.material.card.MaterialCardView> -``` - -## Component specific tokens - -_Soon available_ +##### OdsHorizontalCard API + +Parameter | Default value | Description +-- | -- | -- +`title: String` | | Title displayed into the card +`image: OdsCardImage` | | Image displayed into the card +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the card +`subtitle: String?` | `null` | Subtitle displayed into the card +`text: String?` | `null` | Text displayed into the card +`firstButton: OdsCardButton?` | `null` | First button displayed into the card +`secondButton: OdsCardButton?` | `null` | Second button displayed into the card +`imagePosition: OdsHorizontalCardImagePosition` | `OdsHorizontalCardImagePosition.Start` | Position of the image within the card, it can be set to `OdsHorizontalCardImagePosition.Start` or `OdsHorizontalCardImagePosition.End` +`divider: Boolean` | `true` | Controls the divider display. If `true`, it will be displayed between the card content and the action buttons. +`onClick: (() -> Unit)?` | `null` | Callback invoked on card click +{:.table} diff --git a/docs/components/Checkboxes.md b/docs/components/Checkboxes.md index af6f09eb5..505d1a8e5 100644 --- a/docs/components/Checkboxes.md +++ b/docs/components/Checkboxes.md @@ -5,18 +5,18 @@ description: Checkbox selection control allows the user to select options. --- Use checkboxes to: + * Select one or more options from a list * Present a list containing sub-selections * Turn an item on or off in a desktop environment ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsCheckbox API](#odscheckbox-api) --- @@ -24,49 +24,39 @@ Use checkboxes to: - [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) - [Material Design - Checkboxes](https://material.io/components/checkboxes/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Checkboxes support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in check boxes is automatically provided to accessibility services. Additional content labels are usually unnecessary. -### Implementation +## Implementation ![Checkbox](images/checkbox_light.png) ![Checkbox dark](images/checkbox_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose In your composable screen you can use: ```kotlin +var checked by remember { mutableStateOf(false) } OdsCheckbox( - checked = true, - onCheckedChange = { }, - enabled = true, + checked = checked, + onCheckedChange = { checked = it }, + enabled = true ) ``` -> **XML implementation** - -To create a Checkbox you just have to add a `Checkbox` in your layout. Orange theme will be -automatically applied - -In the layout: - -```xml - -<CheckBox - android:layout_width="match_parent" - android:layout_height="match_parent" - android:checked="true" - android:text="@string/label" -/> -``` +#### OdsCheckbox API -## Component specific tokens +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls checked state of the checkbox +`onCheckedChange: ((Boolean) -> Unit)?` | | Callback invoked on checkbox click. If `null`, then this is passive and relies entirely on a higher-level component to control the checked state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the checkbox +`enabled: Boolean` | `true` | Controls enabled state of the checkbox. When `false`, this checkbox will not be clickable. +{:.table} -_Soon available_ diff --git a/docs/components/Chips.md b/docs/components/Chips.md index 99420dd5f..4693aee14 100644 --- a/docs/components/Chips.md +++ b/docs/components/Chips.md @@ -4,20 +4,24 @@ title: Chips description: Chips are compact elements that represent an input, attribute, or action. --- ---- - -**Page summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Input chip](#input-chip) + * [Jetpack Compose](#jetpack-compose) + * [OdsChip API](#odschip-api) * [Choice chip](#choice-chip) + * [Jetpack Compose](#jetpack-compose-1) * [Filter chip](#filter-chip) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsFilterChip API](#odsfilterchip-api) * [Action chip](#action-chip) -* [Modules](#modules) + * [Jetpack Compose](#jetpack-compose-3) +* [Extras](#extras) * [Choice chips flow row](#choice-chips-flow-row) -* [Component specific tokens](#component-specific-tokens) + * [OdsChoiceChipsFlowRow API](#odschoicechipsflowrow-api) --- @@ -25,11 +29,10 @@ description: Chips are compact elements that represent an input, attribute, or a - [Design System Manager](https://system.design.orange.com/0c1af118d/p/81aa91-chips/b/13c40e) - [Material Design](https://material.io/components/chips) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Chips support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in chips is automatically provided to accessibility services. Additional @@ -47,7 +50,7 @@ that input by converting text into chips. ![Light outlined input chip](images/chips_input_outlined_light.png) ![Dark outlined input chip](images/chips_input_outlined_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsChip` composable. Note that the chip style is outlined or filled according to your OdsTheme component configuration, @@ -57,36 +60,33 @@ outlined by default. OdsChip( text = "chip text", onClick = { - // Something executed on chip click + doSomething() }, leadingIcon = null, leadingAvatar = OdsChipLeadingAvatar( painterResource(id = R.drawable.avatar), "Avatar" - ), // set it to `null` for no avatar or provide a `leadingIcon` - enabled = true, // Set it to `false` to disabled the chip + ), // Set it to `null` for no avatar or provide a `leadingIcon` + enabled = true, onCancel = { - // Something executed on cancel cross click + doSomething() } ) ``` -> **XML implementation** - -To create an input chip you must add a `com.google.android.material.chip.Chip` component to your -layout and set `style` property to `@style/Widget.MaterialComponents.Chip.Entry` - -In the layout: +##### OdsChip API -```xml - -<com.google.android.material.chip.ChipGroup> - <com.google.android.material.chip.Chip android:id="@+id/chip_1" - style="@style/Widget.MaterialComponents.Chip.Entry" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="@string/text_input_1" /> - -</com.google.android.material.chip.ChipGroup> -``` +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text to be displayed into the chip +`onClick: () -> Unit` | | Callback called on chip click +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the chip +`enabled: Boolean` | `true` | Controls the enabled state of the chip. When `false`, this chip will not respond to user input. +`selected: Boolean` | `false` | Controls the selected state of the chip. When `true`, the chip is highlighted (useful for choice chips). +`leadingIcon: OdsChipLeadingIcon?` | `null` | Icon to be displayed at the start of the chip, preceding the text +`leadingAvatar: OdsChipLeadingAvatar?` | `null` | Avatar to be displayed in a circle shape at the start of the chip, preceding the content text +`onCancel: (() -> Unit)?` | `null` | Callback called on chip cancel cross click. Pass `null` for no cancel cross. +{:.table} ### Choice chip @@ -102,7 +102,7 @@ toggle buttons, radio buttons, and single select menus. ![Light outlined choice chips](images/chips_choice_outlined_light.png) ![Dark outlined choice chips](images/chips_choice_outlined_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsChip` composable. Note that the chip style is outlined or filled according to your OdsTheme component configuration, @@ -112,28 +112,13 @@ outlined by default. OdsChip( text = "chip text", onClick = { - // Something executed on chip click + doSomething() }, - enabled = true, // Set it to `false` to disabled the chip + enabled = true, ) ``` -> **XML implementation** - -To create a choice chip you must add a `com.google.android.material.chip.Chip` component to your -layout and set `style` property to `@style/Widget.MaterialComponents.Chip.Choice` - -In the layout: - -```xml - -<com.google.android.material.chip.ChipGroup...> - <com.google.android.material.chip.Chip android:id="@+id/chip_1" - style="@style/Widget.MaterialComponents.Chip.Choice" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="@string/text_choice_1" /> - -</com.google.android.material.chip.ChipGroup> -``` +Use the [OdsChip API](#odschip-api). ### Filter chip @@ -146,7 +131,7 @@ toggle buttons or checkboxes. ![Light filter chips with avatar](images/chips_filter_avatar_light.png) ![Dark filter chips with avatar](images/chips_filter_avatar_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsFilterChip` composable. Note that the chip style is outlined or filled according to your OdsTheme component configuration, @@ -156,33 +141,28 @@ outlined by default. OdsFilterChip( text = "chip text", onClick = { - // Something executed on chip click + doSomething() }, leadingAvatar = OdsChipLeadingAvatar( painterResource(id = R.drawable.avatar), "" - ), // set it to `null` for no avatar - selected = false, // `true` to display the chip selected - enabled = true, // Set it to `false` to disabled the chip + ), // Set it to `null` for no avatar + selected = false, + enabled = true, ) ``` -> **XML implementation** - -To create a filter chip you must add a `com.google.android.material.chip.Chip` component to your -layout and set `style` property to `@style/Widget.MaterialComponents.Chip.Filter` +##### OdsFilterChip API -In the layout: - -```xml - -<com.google.android.material.chip.ChipGroup...> - <com.google.android.material.chip.Chip android:id="@+id/chip_1" - style="@style/Widget.MaterialComponents.Chip.Filter" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="@string/text_choice_1" /> - -</com.google.android.material.chip.ChipGroup> -``` +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text to be displayed into the chip +`onClick: () -> Unit` | | Callback called on chip click +`modifier: Modifier` | `Modifier` | `Modifier` to be applied to the chip +`enabled: Boolean` | `true` | Controls the enabled state of the chip. When `false`, this chip will not respond to user input. It also appears visually disabled and is disabled to accessibility services. +`selected: Boolean` | `false` | Controls the selected state of the chip. When `true`, the chip is highlighted. +`leadingAvatar: OdsChipLeadingAvatar?` | `null` | Avatar to be displayed in a circle shape at the start of the chip, preceding the content text +{:.table} ### Action chip @@ -195,7 +175,7 @@ An alternative to action chips are buttons, which should appear persistently and ![Light outlined action chip](images/chips_action_outlined_light.png) ![Dark outlined action chip](images/chips_action_outlined_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose Use the `OdsChip` composable. Note that the chip style is outlined or filled according to your OdsTheme component configuration, @@ -205,38 +185,23 @@ outlined by default. OdsChip( text = "chip text", onClick = { - // Something executed on chip click + doSomething() }, leadingIcon = OdsChipLeadingIcon( painterResource(id = R.drawable.ic_heart), "Heart" ), // set it to `null` for no icon - enabled = true, // Set it to `false` to disabled the chip + enabled = true, ) ``` -> **XML implementation** - -To create an action chip you must add a `com.google.android.material.chip.Chip` component to your -layout and set `style` property to `@style/Widget.MaterialComponents.Chip.Action` - -In the layout: - -```xml +Use the [OdsChip API](#odschip-api). -<com.google.android.material.chip.ChipGroup...> - <com.google.android.material.chip.Chip android:id="@+id/chip_1" - style="@style/Widget.MaterialComponents.Chip.Action" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="@string/text_choice_1" /> +## Extras -</com.google.android.material.chip.ChipGroup> -``` - -## Modules +The ODS library provides some chips related components to facilitate the implementation of chips groups. -The ODS library provides some modules directly related to chips. - -## Choice chips flow row +### Choice chips flow row This is a full width `FlowRow` containing selectable chips. It works like radio buttons, only one chip of the set can be selected. @@ -250,16 +215,22 @@ outlined by default. ```kotlin OdsChoiceChipsFlowRow( - value = chipValue, - onValueChange = { value -> chipValue = value }, - modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)), chips = listOf( OdsChoiceChip(text = "Choice chip 1", value = 1), OdsChoiceChip(text = "Choice chip 2", value = 2) - ) + ), + value = chipValue, + onValueChange = { value -> chipValue = value }, + modifier = Modifier.padding(horizontal = dimensionResource(id = com.orange.ods.R.dimen.spacing_m)) ) ``` -## Component specific tokens +#### OdsChoiceChipsFlowRow API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`chips: List<OdsChoiceChip<T>>` | | Chips displayed into the flow row +`value: String` | | Initial value of the choice chips flow row +`onValueChange: (value: T) -> Unit` | | Callback invoked when the value changes. The new value is provided as parameter. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the chips flow row +{:.table} diff --git a/docs/components/Dialogs.md b/docs/components/Dialogs.md index f497f7210..8d2df08b2 100644 --- a/docs/components/Dialogs.md +++ b/docs/components/Dialogs.md @@ -11,16 +11,14 @@ or a required action has been taken. Dialogs are purposefully interruptive, so they should be used sparingly. ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Alert dialog](#alert-dialog) - * [Simple dialog](#simple-dialog) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsAlertDialog API](#odsalertdialog-api) --- @@ -28,16 +26,10 @@ Dialogs are purposefully interruptive, so they should be used sparingly. - [Design System Manager - Dialogs](https://system.design.orange.com/0c1af118d/p/02ae02-dialogs/b/81772e) - [Material Design - Dialogs](https://material.io/components/dialogs) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -The contents within a dialog should follow their own accessibility guidelines, -such as an icon on a title having a content description via the -`android:contentDescription` attribute set in the -`MaterialAlertDialog.MaterialComponents.Title.Icon` style or descendant. +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). ## Variants @@ -47,81 +39,30 @@ Alert dialogs interrupt users with urgent information, details, or actions. ![Alert dialog light](images/dialog_alert_light.png) ![Alert dialog dark](images/dialog_alert_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To display an alert dialog in your composable screen, you can use: ```kotlin OdsAlertDialog( - modifier = Modifier, // Optional, `Modifier` if not set - title = "title", // Optional + modifier = Modifier, + title = "title", text = "content text of the dialog", confirmButton = OdsAlertDialogButton("confirm") { doSomething() }, - dismissButton = OdsAlertDialogButton("dismiss") { doSomething() }, // Optional - properties = DialogProperties() // Optional, `DialogProperties()` if not set + dismissButton = OdsAlertDialogButton("dismiss") { doSomething() }, + properties = DialogProperties() ) ``` -> **XML implementation** - -In code: - -```kotlin -MaterialAlertDialogBuilder(context) - .setTitle(resources.getString(R.string.title)) - .setMessage(resources.getString(R.string.supporting_text)) - .setNeutralButton(resources.getString(R.string.cancel)) { dialog, which -> - // Respond to neutral button press - } - .setNegativeButton(resources.getString(R.string.decline)) { dialog, which -> - // Respond to negative button press - } - .setPositiveButton(resources.getString(R.string.accept)) { dialog, which -> - // Respond to positive button press - } - .show() -``` - -Centered dialog: - -```kotlin -MaterialAlertDialogBuilder(context, R.style.Widget.Orange.Dialog.Centered) - .setTitle("title") - .setMessage("message") - .setPositiveButton("positiveText", null) - .setNegativeButton("negativeText", null) - .setIcon(R.drawable.your_drawable) - .show() -``` - -### Simple dialog - -Simple dialogs can display items that are immediately actionable when selected. -They don’t have text buttons. - -As simple dialogs are interruptive, they should be used sparingly. -Alternatively, dropdown menus provide options in a non-modal, less disruptive -way. - -> **Jetpack Compose implementation** - -*Not available yet* - -> **XML implementation** - -In code: - -```kotlin -val items = arrayOf("Item 1", "Item 2", "Item 3") - -MaterialAlertDialogBuilder(context) - .setTitle(resources.getString(R.string.title)) - .setItems(items) { dialog, which -> - // Respond to item chosen - } - .show() -``` - -## Component specific tokens - -_Soon available_ +##### OdsAlertDialog API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | Text displayed into the dialog which presents the details regarding the Dialog's purpose +`confirmButton: OdsAlertDialogButton` | | Button displayed into the dialog which is meant to confirm a proposed action, thus resolving what triggered the dialog +`modifier: Modifier` | `Modifier` | `Modifier` applied to the layout of the dialog +`onDismissRequest: () -> Unit` | `{}` | Callback invoked when the user tries to dismiss the dialog by clicking outside or pressing the back button. This is not called when the dismiss button is clicked. +`dismissButton: OdsAlertDialogButton?` | `null` | Button displayed into the dialog which is meant to dismiss the dialog +`title: String?` | `null` | Title displayed into the dialog which should specify the purpose of the dialog. The title is not mandatory, because there may be sufficient information inside the `text`. +`properties: DialogProperties` | `DialogProperties()` | Typically platform specific properties to further configure the dialog +{:.table} diff --git a/docs/components/FloatingActionButtons.md b/docs/components/FloatingActionButtons.md index c8abb5e2e..969e29ffb 100644 --- a/docs/components/FloatingActionButtons.md +++ b/docs/components/FloatingActionButtons.md @@ -4,39 +4,36 @@ title: Floating action buttons description: A floating action button (FAB) represents the primary action of a screen. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Regular FAB](#regular-fab) + * [Jetpack Compose](#jetpack-compose) + * [OdsFloatingActionButton API](#odsfloatingactionbutton-api) * [Mini FAB](#mini-fab) + * [Jetpack Compose](#jetpack-compose-1) * [Extended FAB](#extended-fab) -* [More implementation information](#more-implementation-information) - * [Visibility](#visibility) - * [Extending and Shrinking](#extending-and-shrinking) - * [Sizing FABs](#sizing-fabs) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-2) + * [OdsExtendedFloatingActionButton API](#odsextendedfloatingactionbutton-api) --- ## Specifications references -- Design System Manager - Floating Action Button (soon available) +- [Design System Manager - Floating Action Button](https://system.design.orange.com/0c1af118d/p/577022-buttons-fab/b/101b2a) - [Material Design - Buttons: floating action button](https://material.io/components/buttons-floating-action-button/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). -You should set a content description on a FAB via the -`android:contentDescription` attribute or `setContentDescription` method so that -screen readers like TalkBack are able to announce their purpose or action. Text -rendered in Extended FABs is automatically provided to accessibility services, -so additional content labels are usually unnecessary. +The `OdsFloatingActionButtonIcon` used in Floating Action Buttons APIs force the developers to set a content description to the FABs so that +screen readers like TalkBack are able to announce their purpose or action. + +Text rendered in an extended FAB is automatically provided to accessibility services, so additional content labels are usually unnecessary. +In this context you can set an empty `contentDescription`. ## Variants @@ -46,16 +43,14 @@ Regular FABs are FABs that are not expanded and are a regular size. ![FAB light](images/fab_light.png) ![FAB dark](images/fab_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To display a regular Floating Action Button in your composable screen, use `OdsFloatingActionButton`: ```kotlin OdsFloatingActionButton( - onClick = { - // Do something - }, - mini = false, // Set to `true` for Mini FAB variant + onClick = { doSomething() }, + mini = false, icon = OdsFloatingActionButtonIcon( painterResource(id = R.drawable.ic_plus), stringResource(id = R.string.add) @@ -64,39 +59,15 @@ OdsFloatingActionButton( ) ``` -> **XML implementation** - -API and source code: - -* `FloatingActionButton`: [Class description](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java) - -To have a regular FAB in your layout you must add `com.google.android.material.floatingactionbutton.FloatingActionButton` in your layout. - -In the layout: - -```xml +##### OdsFloatingActionButton API -<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" - android:layout_height="match_parent"> - - <!-- Main content --> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/floating_action_button" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:layout_gravity="bottom|end" - android:layout_margin="16dp" android:contentDescription="@string/fab_content_desc" - app:srcCompat="@drawable/ic_plus_24" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -In code: - -```kotlin -fab.setOnClickListener { - // Respond to FAB click -} -``` +Parameter | Default value | Description +-- | -- | -- +`icon: OdsFloatingActionButtonIcon` | | Icon used into the FAB +`onClick: () -> Unit` | | Callback invoked on FAB click +`modifier: Modifier` | `Modifier` | `Modifier` applied to the FAB +`mini: Boolean` | `false` | Controls the size of the FAB. If `true`, the size of the FAB will be 40dp, otherwise the default size will be used. +{:.table} ### Mini FAB @@ -106,15 +77,13 @@ Mini FABs can also be used to create visual continuity with other screen element ![FAB mini light](images/fab_mini_light.png) ![FAB mini dark](images/fab_mini_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To display a mini FAB in your composable screen use `OdsFloatingActionButton` ```kotlin OdsFloatingActionButton( - onClick = { - // Do something - }, + onClick = { doSomething() }, mini = true, icon = OdsFloatingActionButtonIcon( painterResource(id = R.drawable.ic_plus), @@ -124,33 +93,7 @@ OdsFloatingActionButton( ) ``` -> **XML implementation** - -API and source code: - -* `FloatingActionButton`: [Class description](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java) - -To have a mini FAB in your layout you must add `com.google.android.material.floatingactionbutton.FloatingActionButton` in your layout and set property `fabSize` to `mini`. - -In the layout: - -```xml - -<androidx.coordinatorlayout.widget.CoordinatorLayout> - - <!-- Main content --> - <com.google.android.material.floatingactionbutton.FloatingActionButton app:fabSize="mini" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -In code: - -```kotlin -fab.setOnClickListener { - // Respond to FAB click -} -``` +Use [OdsFloatingActionButton API](#odsfloatingactionbutton-api). ### Extended FAB @@ -160,104 +103,25 @@ The extended FAB is wider, and it includes a text label. ![FAB extended full width light](images/fab_extended_full_width_light.png) ![FAB extended full width dark](images/fab_extended_full_width_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To display an extended FAB, use `OdsExtendedFloatingActionButton`: ```kotlin OdsExtendedFloatingActionButton( - onClick = { - // Do something - }, + onClick = { doSomething() }, text = stringResource(id = R.string.add), icon = OdsFloatingActionButtonIcon(painterResource(id = R.drawable.ic_plus), ""), modifier = modifier ) ``` -> **XML implementation** - -API and source code: - -* `ExtendedFloatingActionButton`: [Class description](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java) - -To have an extended FAB in your layout you must add `com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton` in your layout. -Icon should be set with property `icon`. - -In the layout: - -```xml - -<androidx.coordinatorlayout.widget.CoordinatorLayout> - - <!-- Main content --> - - <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton - android:id="@+id/extended_fab" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:layout_margin="16dp" - android:layout_gravity="bottom|end" - android:contentDescription="@string/extended_fab_content_desc" - android:text="@string/extended_fab_label" app:icon="@drawable/ic_plus_24px" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> -``` - -In code: - -```kotlin -extendedFab.setOnClickListener { - // Respond to Extended FAB click -} -``` - -## More implementation information - -### Visibility - -Use the `show` and `hide` methods to animate the visibility of a -`FloatingActionButton` or an `ExtendedFloatingActionButton`. The show animation -grows the widget and fades it in, while the hide animation shrinks the widget -and fades it out. - -```kotlin -// To show: -fab.show() -// To hide: -fab.hide() -``` - -### Extending and Shrinking - -Use the `extend` and `shrink` methods to animate showing and hiding the text of -an `ExtendedFloatingActionButton`. The extend animation extends the FAB to show -the text and the icon. The shrink animation shrinks the FAB to show just the -icon. - -```kotlin -// To extend: -extendedFab.extend() -// To shrink: -extendedFab.shrink() -``` - -### Sizing FABs - -The `FloatingActionButton` can be sized either by using the discrete sizing -modes or a custom size. - -There are three `app:fabSize` modes: - -* `normal` - the normal sized button, 56dp. -* `mini` - the mini sized button, 40dp. -* `auto` (default) - the button size will change based on the window size. For - small sized windows (largest screen dimension < 470dp) this will select a - mini sized button, and for larger sized windows it will select a normal - sized button. - -Or, you can set a custom size via the `app:fabCustomSize` attribute. If set, -`app:fabSize` will be ignored, unless the custom size is cleared via the -`clearCustomSize` method. - -## Component specific tokens +##### OdsExtendedFloatingActionButton API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`icon: OdsFloatingActionButtonIcon` | | Icon used into the FAB +`onClick: () -> Unit` | | Callback invoked on FAB click +`text: String` | | Text displayed into the FAB +`modifier: Modifier` | `Modifier` | `Modifier` applied to the FAB +{:.table} diff --git a/docs/components/ImageTile.md b/docs/components/ImageTile.md index 45fc433cb..f96dd2a4e 100644 --- a/docs/components/ImageTile.md +++ b/docs/components/ImageTile.md @@ -4,14 +4,13 @@ title: Image Tile description: --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsImageTile API](#odsimagetile-api) --- @@ -19,15 +18,16 @@ description: - [Design System Manager - Image Tile](https://system.design.orange.com/0c1af118d/p/49434d-image-item) - [Material Design - Image lists](https://m2.material.io/components/image-lists) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +An image in an `OdsImageTile` should always be associated to a content description. This is the reason why the `OdsImageTileImage` forces the developer to fill a content description parameter. ## Implementation -> **Jetpack Compose implementation** +#### Jetpack Compose You can use the `OdsImageTile` composable like this: @@ -39,7 +39,7 @@ OdsImageTile( ), modifier = modifier, captionDisplayType = OdsImageTileCaptionDisplayType.Overlay, - title = "Component Image Tile", // Optional + title = "Component Image Tile", icon = OdsImageTileIconToggleButton( uncheckedIcon = OdsIconButtonIcon( painterResource(id = R.drawable.ic_heart_outlined), @@ -50,12 +50,20 @@ OdsImageTile( "Remove from favourites" ), checked = false, - onCheckedChange = { }, - ), // Optional - onClick = { } // Optional + onCheckedChange = { doSomething() }, + ), + onClick = { doSomething() } ) ``` -## Component specific tokens +##### OdsImageTile API -_Soon available_ \ No newline at end of file +Parameter | Default value | Description +-- | -- | -- +`image: OdsImageTileImage` | | Image displayed into the tile +`legendAreaDisplayType: OdsImageTileLegendAreaDisplayType` | | Controls how the title and the icon are displayed relatively to the image. If set to `OdsImageTileLegendAreaDisplayType.None`, no legend area will be displayed. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the image tile +`title: String?` | `null` | Title displayed into the tile. It is linked to the image and displayed according to the `legendAreaDisplayType` value. +`icon: OdsImageTileIconToggleButton` | `null` | Clickable icon displayed next to the `title` +`onClick: (() -> Unit)?` | `null` | Callback invoked on tile click +{:.table} diff --git a/docs/components/ListItems.md b/docs/components/ListItems.md new file mode 100644 index 000000000..66de11a66 --- /dev/null +++ b/docs/components/ListItems.md @@ -0,0 +1,155 @@ +--- +layout: detail +title: List items +description: Lists are continuous, vertical indexes of text or images. +--- + +<br>**On this page** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Single-line list](#single-line-list) + * [Jetpack Compose](#jetpack-compose) + * [OdsListItem API](#odslistitem-api) + * [Two-line list](#two-line-list) + * [Jetpack Compose](#jetpack-compose-1) + * [Three-line list](#three-line-list) + * [Jetpack Compose](#jetpack-compose-2) + +--- + +## Specifications references + +- [Design System Manager - Lists](https://system.design.orange.com/0c1af118d/p/09a804-lists/b/669743) +- [Material Design - Lists](https://material.io/components/lists/) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +## Variants + +### Single-line list + +There are multiple display possibilities for a single-line list, where leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists single-line wide image](images/lists_single_line_wide_image_light.png) ![Lists single-line wide image dark](images/lists_single_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists single-line](images/lists_single_line_light.png) ![Lists single-line dark](images/lists_single_line_dark.png) + +Please note that there is no start padding with wide images. + +#### Jetpack Compose + +The library offers the `OdsListItem` composable to display lists items. + +The `OdsListItem` composable allows you to display a leading icon using the `icon` parameter of the `OdsListItem` method, as well as a trailing element (either a checkbox, a switch, a radio button, an icon or a caption text) using the `trailing` parameter. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + icon = OdsListItemIcon( + OdsListItemIconType.Icon, + painterResource(id = R.drawable.ic_heart), + "Heart" + ), + trailing = OdsListItemTrailingCheckbox(checked, { checked != checked }), + divider = true +) +``` + +##### OdsListItem API + +Parameter | Default value | Description +-- | -- | -- +`text: String` | | The primary text of the list item +`modifier: Modifier` | `Modifier` | Modifier to be applied to the list item +`icon: OdsListItemIcon?` | `null` | The leading supporting visual of the list item +`secondaryText: String?` | `null` | The secondary text of the list item +`singleLineSecondaryText: Boolean` | `true` | Whether the secondary text is single line +`overlineText: String?` | `null` | The text displayed above the primary text +`trailing: OdsListItemTrailing?` | `null` | The trailing content to display at the end of the list item +`divider: Boolean` | `false` | Whether or not a divider is displayed at the bottom of the list item +`onClick: (() -> Unit)?` | `null` | Will be called when the user clicks the list item. This parameter only has an effect if trailing is `OdsListItemTrailingIcon` or `null`. +{:.table} + +### Two-line list + +Like single-line list, two-line list leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists two-line wide image](images/lists_two_line_wide_image_light.png) ![Lists two-line wide image dark](images/lists_two_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists two-line](images/lists_two_line_light.png) ![Lists two-line dark](images/lists_two_line_dark.png) + +#### Jetpack Compose + +The only difference with the single-line implementation is that the `secondaryText` property of `OdsListItem` is not null. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + secondaryText = "Secondary text", + icon = OdsListItemIcon( + OdsListItemIconType.CircularImage, + painterResource(id = R.drawable.placeholder, "") + ), + trailing = OdsListItemTrailingIcon( + painterResource(id = R.drawable.ic_drag_handle), + "Drag item" + ), + divider = true +) +``` + +Use [OdsListItem API](#odslistitem-api). + +### Three-line list + +Like single-line list, three-line list leading can optionally be an icon, a circular, a square or a wide image. + +Here are two examples: + +- with a wide image and a checkbox + + ![Lists three-line wide image](images/lists_three_line_wide_image_light.png) ![Lists three-line wide image dark](images/lists_three_line_wide_image_dark.png) + +- with a standard icon and a checkbox + + ![Lists three-line](images/lists_three_line_light.png) ![Lists three-line dark](images/lists_three_line_dark.png) + +#### Jetpack Compose + +The only difference with the two-line implementation is that the `singleLineSecondaryText` property of `OdsListItem` is `false`. + +```kotlin +OdsListItem( + modifier = Modifier.clickable { doSomething() }, + text = "Primary text", + secondaryText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.", + singleLineSecondaryText = false, + icon = OdsListItemIcon( + OdsListItemIconType.SquareImage, + painter = painterResource(id = R.drawable.placeholder), + "" + ), + trailing = OdsListItemTrailingCaption("Caption"), + divider = true +) +``` + +Use [OdsListItem API](#odslistitem-api). diff --git a/docs/components/ListItems_docs.md b/docs/components/ListItems_docs.md new file mode 100644 index 000000000..44e4d0a23 --- /dev/null +++ b/docs/components/ListItems_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: ListItems.md +--- \ No newline at end of file diff --git a/docs/components/Lists.md b/docs/components/Lists.md deleted file mode 100644 index cecb6565c..000000000 --- a/docs/components/Lists.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -layout: detail -title: Lists -description: Lists are continuous, vertical indexes of text or images. ---- - ---- - -**Page Summary** - -* [Specifications references](#specifications-references) -* [Accessibility](#accessibility) -* [Variants](#variants) - * [Single-line list](#single-line-list) - * [Two-line list](#two-line-list) - * [Three-line list](#three-line-list) -* [Component specific tokens](#component-specific-tokens) - ---- - -## Specifications references - -- [Design System Manager - Lists](https://system.design.orange.com/0c1af118d/p/09a804-lists/b/669743) -- [Material Design - Lists](https://material.io/components/lists/) -- Technical documentation soon available - -## Accessibility - -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -## Variants - -### Single-line list - -There are multiple display possibilities for a single-line list, where leading can optionally be an icon, a circular, a square or a wide image. - -Here are two examples: - -- with a wide image and a checkbox - - ![Lists single-line wide image](images/lists_single_line_wide_image_light.png) ![Lists single-line wide image dark](images/lists_single_line_wide_image_dark.png) - -- with a standard icon and a checkbox - - ![Lists single-line](images/lists_single_line_light.png) ![Lists single-line dark](images/lists_single_line_dark.png) - -Please note that there is no start padding with wide images. - -> **Jetpack Compose implementation** - -The library offers the `OdsListItem` composable to display lists items. - -**Leading icon:** - -To specify an icon type, use the `Modifier.iconType` method on the `OdsListItem` modifier and call `OdsListItemScope.OdsListItemIcon` in the `icon` lambda. - -**Trailing element:** - -The `OdsListItem` composable allows you to display an `OdsListItemTrailing` (Checkbox, Switch, RadioButton, Icon or a Caption text) as trailing element. If this does not meet your -needs and only in this case, you can use the `OdsListItem` method signature which accept any Composable as trailing. - -Note: The first signature (with `OdsListItemTrailing`) automatically manages accessibility, if you use the second one, don't forget to manage it on your side. - -**Dividers:** - -A divider can also be displayed at the bottom of the list item using the `Modifier.divider` method on the `OdsListItem` modifier. - - -```kotlin -OdsListItem( - modifier = Modifier - .clickable { doSomething() } - .iconType(OdsListItemIconType.Icon) - .divider(), - text = "Primary text", - icon = { OdsListItemIcon(painter = painterResource(id = R.drawable.ic_heart), contentDescription = "Heart") }, - trailing = OdsCheckboxTrailing(checked = itemChecked) -) -``` - -> **XML implementation** - -*Not available yet* - -### Two-line list - -Like single-line list, two-line list leading can optionally be an icon, a circular, a square or a wide image. - -Here are two examples: - -- with a wide image and a checkbox - - ![Lists two-line wide image](images/lists_two_line_wide_image_light.png) ![Lists two-line wide image dark](images/lists_two_line_wide_image_dark.png) - -- with a standard icon and a checkbox - - ![Lists two-line](images/lists_two_line_light.png) ![Lists two-line dark](images/lists_two_line_dark.png) - -> **Jetpack Compose implementation** - -The only difference with the single-line implementation is that the `secondaryText` property of `OdsListItem` is not null. - -```kotlin -OdsListItem( - modifier = Modifier - .clickable { doSomething() } - .iconType(OdsListItemIconType.CircularImage) - .divider(), - text = "Primary text", - secondaryText = "Secondary text", - icon = { OdsListItemIcon(painter = painterResource(id = R.drawable.placeholder)) }, - trailing = OdsIconTrailing(painter = painterResource(id = R.drawable.ic_drag_handle), contentDescription = "Drag item") -) -``` - -> **XML implementation** - -*Not available yet* - -### Three-line list - -Like single-line list, three-line list leading can optionally be an icon, a circular, a square or a wide image. - -Here are two examples: - -- with a wide image and a checkbox - - ![Lists three-line wide image](images/lists_three_line_wide_image_light.png) ![Lists three-line wide image dark](images/lists_three_line_wide_image_dark.png) - -- with a standard icon and a checkbox - - ![Lists three-line](images/lists_three_line_light.png) ![Lists three-line dark](images/lists_three_line_dark.png) - -> **Jetpack Compose implementation** - -The only difference with the two-line implementation is that the `singleLineSecondaryText` property of `OdsListItem` is `false`. - -```kotlin -OdsListItem( - modifier = Modifier - .clickable { doSomething() } - .iconType(OdsListItemIconType.SquareImage) - .divider(), - text = "Primary text", - secondaryText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.", - singleLineSecondaryText = false, - icon = { OdsListItemIcon(painter = painterResource(id = R.drawable.placeholder)) }, - trailing = OdsCaptionTrailing(text = "Caption") -) -``` - -> **XML implementation** - -*Not available yet* - -## Component specific tokens - -_Soon available_ diff --git a/docs/components/Lists_docs.md b/docs/components/Lists_docs.md deleted file mode 100644 index 52b64be04..000000000 --- a/docs/components/Lists_docs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -layout: main -content_page: Lists.md ---- \ No newline at end of file diff --git a/docs/components/Menus.md b/docs/components/Menus.md index 40b633717..23d6e16e8 100644 --- a/docs/components/Menus.md +++ b/docs/components/Menus.md @@ -4,16 +4,17 @@ title: Menus description: Menus appear from a button, action, or other control. It contains at least 2 items that can affect the app, the view or elements within the view. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Dropdown menu](#dropdown-menu) + * [Jetpack Compose](#jetpack-compose) + * [OdsDropdownMenu API](#odsdropdownmenu-api) * [Exposed dropdown menu](#exposed-dropdown-menu) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsExposedDropdownMenu API](#odsexposeddropdownmenu-api) --- @@ -21,11 +22,12 @@ description: Menus appear from a button, action, or other control. It contains a - [Design System Manager - Menus](https://system.design.orange.com/0c1af118d/p/07a69b-menus/b/862cbb) - [Material Design - Menus](https://m2.material.io/components/menus) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). + +The icons which can be displayed in a dropdown menu are always associated to a text so they don't need a content description. ## Variants @@ -35,7 +37,7 @@ A dropdown menu is a compact way of displaying multiple choices. It appears upon ![Dropdown menu light](images/menu_dropdown_light.png) ![Dropdown menu dark](images/menu_dropdown_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose The library offers an `OdsDropdownMenu` container composable in which you can add `OdsDropdownMenuItem` or `OdsDivider` as shown in the following example: @@ -51,24 +53,28 @@ OdsDropdownMenu( text = "Summer salad", icon = painterResource(id = R.drawable.ic_salad), divider = true, // Allow to add a divider between the 2 items - onClick = { - // Do something - } + onClick = { doSomething() } ), OdsDropdownMenuItem( text = "Brocoli soup", icon = painterResource(id = R.drawable.ic_soup), - onClick = { - // Do something - } + onClick = { doSomething() } ) ) ) ``` -> **XML implementation** +##### OdsDropdownMenu API -*Not available yet* +Parameter | Default value | Description +-- | -- | -- +`items: List<OdsDropdownMenuItem>` | | Items displayed into the dropdown menu +`expanded: Boolean` | | Controls whether the menu is currently open and visible to the user +`onDismissRequest: () -> Unit` | | Callback invoked when the user requests to dismiss the menu, such as by tapping outside the menu's bounds +`modifier: Modifier` | `Modifier` | `Modifier` applied to the dropdown menu +`offset: DpOffset` | `DpOffset(0.dp, 0.dp)` | Offset added to the menu position +`properties: PopupProperties` | `PopupProperties(focusable = true)` | Properties for further customization of the popup's behavior +{:.table} ### Exposed dropdown menu @@ -76,7 +82,7 @@ Exposed dropdown menus display the currently selected menu item above the menu. ![Exposed dropdown menu light](images/menu_exposed_dropdown_light.png) ![Exposed dropdown menu dark](images/menu_exposed_dropdown_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To display an exposed dropdown menu, you can use the `OdsExposedDropdownMenu` composable. As shown below, you should provide a list of `OdsExposedDropdownMenuItem` corresponding to the items displayed in the menu (with or without icons). @@ -93,16 +99,20 @@ OdsExposedDropdownMenu( items = items, selectedItem = selectedItem, onItemSelectionChange = { item -> - // Do something like retrieving the selected item + doSomething() // Do something like retrieving the selected item }, enabled = true ) ``` -> **XML implementation** - -*Not available yet* - -## Component specific tokens - -_Soon available_ +##### OdsExposedDropdownMenu API + +Parameter | Default value | Description +-- | -- | -- +`label: String` | | Label of the exposed menu text field +`items: List<OdsExposedDropdownMenuItem>` | | Items displayed into the dropdown menu +`selectedItem: MutableState<OdsExposedDropdownMenuItem>` | | Selected item displayed into the text field +`onItemSelectionChange: (OdsExposedDropdownMenuItem) -> Unit` | | Callback invoked when a dropdown menu item is selected. It can be used to get the menu value. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the dropdown menu +`enabled: Boolean` | `true` | Controls the enabled state of the dropdown menu. When `false`, the dropdown menu text field will be neither clickable nor focusable, visually it will appear in the disabled state. +{:.table} diff --git a/docs/components/NavigationBottom.md b/docs/components/NavigationBottom.md index 86e891b20..f25cad9bb 100644 --- a/docs/components/NavigationBottom.md +++ b/docs/components/NavigationBottom.md @@ -4,14 +4,14 @@ title: "Navigation: bottom" description: Bottom navigation bars allow movement between primary destinations in an app. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsBottomNavigation API](#odsbottomnavigation-api) + * [XML](#xml) --- @@ -19,131 +19,99 @@ description: Bottom navigation bars allow movement between primary destinations - [Design System Manager - Navigation: bottom](https://system.design.orange.com/0c1af118d/p/042eb8-navigation-bottom/b/30078d) - [Material Design - Bottom navigation](https://material.io/components/bottom-navigation) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -You should set an `android:title` for each of your `menu` items so that screen -readers like TalkBack can properly announce what each navigation item -represents: +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). -```xml -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:title="@string/text_label"/> -</menu> -``` - -The `labelVisibilityMode` attribute can be used to adjust the behavior of the -text labels for each navigation item. There are four visibility modes: - -* `LABEL_VISIBILITY_AUTO` (default): The label behaves as “labeled” when there - are 3 items or less, or “selected” when there are 4 items or more -* `LABEL_VISIBILITY_SELECTED`: The label is only shown on the selected - navigation item -* `LABEL_VISIBILITY_LABELED`: The label is shown on all navigation items -* `LABEL_VISIBILITY_UNLABELED`: The label is hidden for all navigation items +Note that TalkBack already reads the bottom navigation items labels so the content descriptions of the `OdsBottomNavigationItemIcon`s can be empty. ## Implementation - ![BottomNavigation light](images/bottom_navigation_light.png) +![BottomNavigation light](images/bottom_navigation_light.png) - ![BottomNavigation dark](images/bottom_navigation_dark.png) +![BottomNavigation dark](images/bottom_navigation_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose In your composable screen, use the `OdsBottomNavigation` composable. It should contain multiple `OdsBottomNavigationItem`s. -Here is an example: +Here is an example of use: ```kotlin - private data class NavigationItem(@StringRes val titleResId: Int, @DrawableRes val iconResId: Int) - - val items = listOf( - R.string.component_bottom_navigation_coffee to R.drawable.ic_coffee, - R.string.component_bottom_navigation_cooking_pot to R.drawable.ic_cooking_pot, - R.string.component_bottom_navigation_ice_cream to R.drawable.ic_ice_cream, - R.string.component_bottom_navigation_restaurant to R.drawable.ic_restaurant, - R.string.component_bottom_navigation_favorites to R.drawable.ic_heart - ) - - var selectedItemIndex by remember { mutableStateOf(0) } - - OdsBottomNavigation( - items = items.mapIndexed { index, item -> - OdsBottomNavigationItem( - icon = OdsBottomNavigationItemIcon(painter = painterResource(id = item.first), contentDescription = ""), // contentDescription is empty cause TalkBack already read the item's title - label = stringResource(id = item.second), - selected = selectedItemIndex == index, - onClick = { - selectedItemIndex = index - // Do what you want with a piece of code - } - ) - } - ) - ) + private data class NavigationItem( + @StringRes val titleResId: Int, + @DrawableRes val iconResId: Int +) + +val items = listOf( + R.string.component_bottom_navigation_coffee to R.drawable.ic_coffee, + R.string.component_bottom_navigation_cooking_pot to R.drawable.ic_cooking_pot, + R.string.component_bottom_navigation_ice_cream to R.drawable.ic_ice_cream, + R.string.component_bottom_navigation_restaurant to R.drawable.ic_restaurant, + R.string.component_bottom_navigation_favorites to R.drawable.ic_heart +) + +var selectedItemIndex by remember { mutableStateOf(0) } + +OdsBottomNavigation( + items = items.mapIndexed { index, item -> + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = item.first), + contentDescription = "" + ), // contentDescription is empty cause TalkBack already read the item's label + label = stringResource(id = item.second), + selected = selectedItemIndex == index, + onClick = { + selectedItemIndex = index + doSomething() + } + ) + } +) ``` -> **XML implementation** +#### OdsBottomNavigation API -API and source code: +Parameter | Default value | Description +-- | -- | -- +`items: List<OdsBottomNavigationItem>` | | Items displayed into the bottom navigation +`modifier: Modifier` | `Modifier` | `Modifier` applied to the bottom navigation +{:.table} -* `BottomNavigationView`: [Class description](https://developer.android.com/reference/com/google/android/material/bottomnavigation/BottomNavigationView), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/bottomnavigation/BottomNavigationView.java) +### XML -In `layout.xml`: +In your layout, use the `OdsBottomNavigation` view. ```xml -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <com.google.android.material.bottomnavigation.BottomNavigationView - android:id="@+id/bottom_navigation" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:menu="@menu/bottom_navigation_menu" /> - -</LinearLayout> -``` -In `bottom_navigation_menu.xml` inside a `menu` resource directory: - -```xml -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:id="@+id/page_1" - android:enabled="true" - android:icon="@drawable/ic_favorite" - android:title="@string/favorites"/> - <item - android:id="@+id/page_2" - android:enabled="true" - android:icon="@drawable/ic_music" - android:title="@string/music"/> - <item - android:id="@+id/page_3" - android:enabled="true" - android:icon="@drawable/ic_places" - android:title="@string/places"/> - <item - android:id="@+id/page_4" - android:enabled="true" - android:icon="@drawable/ic_news" - android:title="@string/news"/> -</menu> +<com.orange.ods.xml.component.bottomnavigation.OdsBottomNavigation + android:id="@+id/ods_bottom_navigation" android:layout_height="wrap_content" + android:layout_width="wrap_content" /> ``` -In code: +Then using view binding, add the bottom navigation items by code: ```kotlin -bottomNavigation.selectedItemId = R.id.page_2 -``` - -## Component specific tokens - -_Soon available_ +binding.odsBottomNavigation.items = listOf( + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = R.drawable.ic_dna), + contentDescription = "" + ), + label = "Guidelines", + selected = true, + onClick = { doSomething() } + ), + OdsBottomNavigationItem( + icon = OdsBottomNavigationItemIcon( + painter = painterResource(id = R.drawable.ic_atom), + contentDescription = "" + ), + label = "Components", + selected = false, + onClick = { doSomething() } + ) +) +``` \ No newline at end of file diff --git a/docs/components/NavigationDrawers.md b/docs/components/NavigationDrawers.md index 6a0b81f24..21da08a2c 100644 --- a/docs/components/NavigationDrawers.md +++ b/docs/components/NavigationDrawers.md @@ -4,14 +4,12 @@ title: Navigation drawers description: The navigation drawer slides in from the left when the nav icon is tapped. The content should be concerned with identity and/or navigation.. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) --- @@ -19,28 +17,26 @@ description: The navigation drawer slides in from the left when the nav icon is - [Design System Manager - Navigation drawers](https://system.design.orange.com/0c1af118d/p/92bc26-navigation-drawers/b/146f55) - [Material Design - Navigation drawer](https://m2.material.io/components/navigation-drawer) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). ## Implementation -> **Jetpack Compose implementation** +### Jetpack Compose You can use the `OdsModalDrawer` composable like this: ```kotlin OdsModalDrawer( drawerHeader = { - title = "Side navigation drawer" // title is mandatory in an `OdsModalDrawer` - imageContentDescription = "" // optional + title = "Side navigation drawer" + imageContentDescription = "" imageDisplayType = OdsModalDrawerHeaderImageDisplayType.None // or OdsModalDrawerHeaderImageDisplayType.Avatar or OdsModalDrawerHeaderImageDisplayType.Background - subtitle = "Example" // optional - image = - painterResource(id = R.drawable.placeholder) + subtitle = "Example" + image = painterResource(id = R.drawable.placeholder) }, drawerContentList = listOf<OdsModalDrawerItem>( OdsModalDrawerListItem( // `OdsModalDrawerListItem` is used to specified an item of the list @@ -60,12 +56,8 @@ OdsModalDrawer( text = "label3" ) ), - drawerState = rememberDrawerState(DrawerValue.Closed),// or rememberDrawerState(DrawerValue.Open) + drawerState = rememberDrawerState(DrawerValue.Closed), // or rememberDrawerState(DrawerValue.Open) ) { - // The content of the rest of the UI + // Put here the rest of the UI content } ``` - -## Component specific tokens - -_Soon available_ \ No newline at end of file diff --git a/docs/components/ProgressIndicators.md b/docs/components/ProgressIndicators.md index 2732d7d92..ba7da7b4e 100644 --- a/docs/components/ProgressIndicators.md +++ b/docs/components/ProgressIndicators.md @@ -4,16 +4,17 @@ title: Progress indicators description: Progress indicators express an unspecified wait time or display the length of a process. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) * [Progress bar](#progress-bar) + * [Jetpack Compose](#jetpack-compose) + * [OdsLinearProgressIndicator API](#odslinearprogressindicator-api) * [Activity indicator](#activity-indicator) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsCircularProgressIndicator API](#odscircularprogressindicator-api) --- @@ -21,20 +22,10 @@ description: Progress indicators express an unspecified wait time or display the - [Design System Manager - Progress indicators](https://system.design.orange.com/0c1af118d/p/92aec5-progress-indicators------/b/33faf7) - [Material Design - Progress indicators](https://material.io/components/progress-indicators/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -Progress indicators inherit accessibility support from the `LinearProgressIndicator` class in the framework. -Please consider setting the content descriptor for use with screen readers. - -That can be done in XML via the `android:contentDescription` attribute or programmatically like so: - -```kotlin -progressIndicator.contentDescription = contentDescription -``` +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). ## Variants @@ -55,7 +46,7 @@ Linear progress indicators support both determinate and indeterminate operations ![Progress bar dark](images/progress_linear_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose You can use the composable `OdsLinearProgressIndicator` like this: @@ -64,12 +55,12 @@ For a **determinate** linear progress indicator, provide the progress value: ```kotlin OdsLinearProgressIndicator( progress = 0.9f, - label = "Downloading ...", // Optional + label = "Downloading ...", icon = OdsLinearProgressIndicator( painterResource(id = R.drawable.ic_arrow_down), "" - ), // Optional - showCurrentValue = true // Display the value in percent below the progress bar if set to true + ), + showCurrentValue = true ) ``` @@ -77,43 +68,24 @@ For an **indeterminate** linear progress indicator, no need to provide a progres ```kotlin OdsLinearProgressIndicator( - label = "Downloading ...", // Optional + label = "Downloading ...", icon = OdsLinearProgressIndicator( painterResource(id = R.drawable.ic_arrow_down), "" - ) // Optional + ) ) ``` -> **XML implementation** - -To create a linear progress indicator you will need to -add `com.google.android.material.progressindicator.LinearProgressIndicator` in your layout - -API and source code: - -* `LinearProgressIndicator`: [Class description](https://developer.android.com/reference/com/google/android/material/progressindicator/LinearProgressIndicator), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/progressindicator/LinearProgressIndicator.java) - -The following example shows a **determinate** linear progress indicator. - -In the layout: - -```xml - -<com.google.android.material.progressindicator.LinearProgressIndicator - android:layout_width="match_parent" android:layout_height="wrap_content" /> -``` - -The following example shows an **indeterminate** linear progress indicator. - -In the layout: +##### OdsLinearProgressIndicator API -```xml - -<com.google.android.material.progressindicator.LinearProgressIndicator - android:layout_width="match_parent" android:layout_height="wrap_content" - android:indeterminate="true" /> -``` +Parameter | Default value | Description +-- | -- | -- +`modifier: Modifier` | `Modifier` | `Modifier` applied to the progress indicator +`progress: Float?` | `null` | Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced into the range. If set to `null`, the progress indicator is indeterminate. +`label: String?` | `null` | Label displayed above the linear progress +`icon: OdsLinearProgressIndicatorIcon?` | `null` | Icon displayed above the progress indicator +`showCurrentValue: Boolean` | `false` | Controls the progress indicator current value visibility which is displayed in percent below the progress bar +{:.table} ### Activity indicator @@ -131,7 +103,7 @@ processes. ![Activity indicator light](images/progress_circular_light.png) ![Activity indicator dark](images/progress_circular_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose You can use the `OdsCircularProgressIndicator` composable like this: @@ -140,7 +112,7 @@ You can use the `OdsCircularProgressIndicator` composable like this: ```kotlin OdsCircularProgressIndicator( progress = 0.9f, - label = "Downloading ..." // Optional + label = "Downloading ..." ) ``` @@ -148,40 +120,15 @@ OdsCircularProgressIndicator( ```kotlin OdsCircularProgressIndicator( - label = "Downloading ..." // Optional + label = "Downloading ..." ) ``` -> **XML implementation** - -To create a circular progress indicator you will need to -add `com.google.android.material.progressindicator.CircularProgressIndicator` in your layout - -API and source code: - -* `CircularProgressIndicator`: [Class description](https://developer.android.com/reference/com/google/android/material/progressindicator/CircularProgressIndicator), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/progressindicator/CircularProgressIndicator.java) - -The following example shows a **determinate** circular progress indicator. - -In the layout: - -```xml - -<com.google.android.material.progressindicator.CircularProgressIndicator - android:layout_width="wrap_content" android:layout_height="wrap_content" /> -``` - -The following example shows an **indeterminate** circular progress indicator. - -In the layout: - -```xml - -<com.google.android.material.progressindicator.CircularProgressIndicator - android:layout_width="wrap_content" android:layout_height="wrap_content" - android:indeterminate="true" /> -``` - -## Component specific tokens +##### OdsCircularProgressIndicator API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`modifier: Modifier` | `Modifier` | `Modifier` applied to the progress indicator +`progress: Float?` | `null` | Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced into the range. If set to `null`, the progress indicator is indeterminate. +`label: String?` | `null` | Label displayed below the circular progress +{:.table} diff --git a/docs/components/RadioButtons.md b/docs/components/RadioButtons.md index 28f85566d..24aac4962 100644 --- a/docs/components/RadioButtons.md +++ b/docs/components/RadioButtons.md @@ -5,19 +5,19 @@ description: Radio button selection control allows the user to select options. --- Use radio buttons to: -* Select a single option from a list -* Expose all available options -* If available options can be collapsed, consider using a dropdown menu - instead, as it uses less space. ---- +* Select a single option from a list +* Expose all available options +* If available options can be collapsed, consider using a dropdown menu + instead, as it uses less space. -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsRadioButton API](#odsradiobutton-api) --- @@ -25,11 +25,10 @@ Use radio buttons to: - [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) - [Material Design - Radio buttons](https://material.io/components/radio-buttons/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Radio buttons support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in radio buttons is @@ -40,76 +39,24 @@ usually unnecessary. ![RadioButton](images/radio_button_light.png) ![RadioButton dark](images/radio_button_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose In your composable screen you can use: ```kotlin OdsRadioButton( selected = true, - onClick = { }, + onClick = { doSomething() }, enabled = true ) ``` -> **XML implementation** - -To create a Radio Button you just have to add a `RadioButton` in your layout. Orange theme will be -automatically applied. - -In the layout: - -```xml -<RadioButton - android:id="@+id/radio_button" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/label"/> -``` - -Changes in the states of one radio button can affect other buttons in the group. -Specifically, selecting a `RadioButton` in a `RadioGroup` will de-select all -other buttons in that group. - -Example showing a **radio button group** with five radio buttons. - -In the layout: - -```xml -<RadioGroup - android:id="@+id/radioGroup" - android:checkedButton="@+id/radio_button_1" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <RadioButton - android:id="@+id/radio_button_1" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/label_1"/> - <RadioButton - android:id="@+id/radio_button_2" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/label_2"/> - <RadioButton - android:id="@+id/radio_button_3" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/label_3"/> - <RadioButton - android:id="@+id/radio_button_4" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/label_4"/> - <RadioButton - android:id="@+id/radio_button_5" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:enabled="false" - android:text="@string/label_5"/> -</RadioGroup> -``` - -## Component specific tokens +#### OdsRadioButton API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`selected: Boolean` | | Controls the selected state of the radio button +`onClick: (() -> Unit)?` | | Callback invoked on radio button click. If `null`, then the radio button will not handle input events, and only act as a visual indicator of `selected` state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the radio button +`enabled: Boolean` | `true` | Controls the enabled state of the radio button. When `false`, the button will not be selectable and appears disabled. +{:.table} diff --git a/docs/components/SheetsBottom.md b/docs/components/SheetsBottom.md index 45757e424..a80fa5bf4 100644 --- a/docs/components/SheetsBottom.md +++ b/docs/components/SheetsBottom.md @@ -9,14 +9,12 @@ Use Sheets bottom to: * Display content that complements the screen’s primary content * Expose all complements options ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) --- @@ -24,33 +22,34 @@ Use Sheets bottom to: - [Design System Manager - Sheets](https://system.design.orange.com/0c1af118d/p/81f927-sheets-bottom/b/47b99b) - [Material Design - Sheets: bottom](https://material.io/components/sheets-bottom) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). ## Implementation ![BottomSheet light](images/sheetbottom_light.png) ![BottomSheet dark](images/sheetbottom_dark.png) -> **Jetpack Compose implementation** +The contents within a bottom sheet should follow their own accessibility guidelines, such as images having content descriptions set on them. + +### Jetpack Compose ```kotlin OdsBottomSheetScaffold( - sheetContent = {}, + sheetContent = { + // Put here the content of the sheet + }, modifier = Modifier, scaffoldState = rememberBottomSheetScaffoldState(), topBar = null, - snackbarHost = { }, + snackbarHost = {}, floatingActionButton = {}, floatingActionButtonPosition = FabPosition.End, sheetGesturesEnabled = true, sheetPeekHeight = 56.dp, - content = {} + content = { + // Put here the screen content + } ) ``` - -## Component specific tokens - -_Soon available_ \ No newline at end of file diff --git a/docs/components/Sliders.md b/docs/components/Sliders.md index d1c6342a6..93c989ff4 100644 --- a/docs/components/Sliders.md +++ b/docs/components/Sliders.md @@ -4,18 +4,21 @@ title: Sliders description: Sliders allow users to make selections from a range of values. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) - * [Continuous slider](#continuous-slider) - * [Continuous lockups slider](#continuous-lockups-slider) - * [Discrete slider](#discrete-slider) - * [Discrete lockups slider](#discrete-lockups-slider) -* [Component specific tokens](#component-specific-tokens) + * [Continuous slider](#continuous-slider) + * [Jetpack Compose](#jetpack-compose) + * [OdsSlider API](#odsslider-api) + * [Continuous lockups slider](#continuous-lockups-slider) + * [Jetpack Compose](#jetpack-compose-1) + * [OdsSliderLockups API](#odssliderlockups-api) + * [Discrete slider](#discrete-slider) + * [Jetpack Compose](#jetpack-compose-2) + * [Discrete lockups slider](#discrete-lockups-slider) + * [Jetpack Compose](#jetpack-compose-3) --- @@ -23,29 +26,14 @@ description: Sliders allow users to make selections from a range of values. - [Design System Manager - Sliders](https://system.design.orange.com/0c1af118d/p/8077fc-sliders/b/673558) - [Material Design - Sliders](https://material.io/components/sliders/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) - -Sliders support setting content descriptors for use with screen readers. While -optional, we strongly encourage their use. - -That can be done in XML via the `android:contentDescription` attribute or -programmatically like so: - -```kotlin -slider.contentDescription = contentDescription -``` - -Additionally, if using a `TextView` to display the value of the slider, you -should set `android:labelFor` on it, so that screen readers announce that -`TextView` refers to the slider. +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). ## Variants -## Continuous slider +### Continuous slider Continuous sliders allow users to make meaningful selections that don’t require a specific value. @@ -56,15 +44,15 @@ With icons: ![Continuous slider with icons](images/slider_continuous_with_icon_light.png) ![Continuous slider with icons dark](images/slider_continuous_with_icon_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use: ```kotlin OdsSlider( value = 20f, - valueRange = 0f..100f, - onValueChange = { } + steps = 9, + onValueChange = { doSomething() } ) ``` @@ -73,46 +61,35 @@ You can add icons to the continuous slider like this: ```kotlin OdsSlider( value = 20f, - valueRange = 0f..100f, - onValueChange = { }, - leftIcon = painterResource(id = R.drawable.ic_volume_status_1), - leftIconContentDescription = stringResource(id = R.string.component_slider_low_volume), - rightIcon = painterResource(id = R.drawable.ic_volume_status_4), - rightIconContentDescription = stringResource(id = R.string.component_slider_high_volume) + steps = 9, + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) ) ``` -> **XML implementation** - -API and source code: - -* `Slider`: [Class definition](https://developer.android.com/reference/com/google/android/material/slider/Slider), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/slider/Slider.java) +##### OdsSlider API -Note: By default, the slider will show a value label above the thumb when it's -selected. You can change how it's drawn via the `app:labelBehavior` attribute or -`setLabelBehavior` method. +Parameter | Default value | Description +-- | -- | -- +`value: Float` | | Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. +`onValueChange: (Float) -> Unit` | | Callback invoked on slider value change. `value` should be updated here. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the slider +`enabled: Boolean` | `true` | Controls the enabled state of the slider. If `false`, the user cannot interact with it. +`valueRange: ClosedFloatingPointRange<Float>` | `0f..1f` | Range of values that the slider can take. Given `value` will be coerced to this range. +`steps: Int` | `0` | If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. +`onValueChangeFinished: (() -> Unit)?` | `null` | Callback invoked when value change has ended. This callback shouldn't be used to update the slider value (use `onValueChange` for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. +`startIcon: OdsSliderIcon?` | `null` | Icon displayed at the start of the slider +`endIcon: OdsSliderIcon?` | `null` | Icon displayed at the end of the slider +{:.table} -The modes of `app:labelBehavior` are: - -* `floating` (default) - draws the label floating above the bounds of this - view -* `withinBounds` - draws the label floating within the bounds of this view -* `gone` - prevents the label from being drawn - -**Adding a continuous slider in the layout** - -```xml -<com.google.android.material.slider.Slider - android:id="@+id/slider" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:contentDescription="@string/slider_desc" - android:value="20.0" - android:valueFrom="0.0" - android:valueTo="100.0" /> -``` - -## Continuous lockups slider +### Continuous lockups slider ![Continuous lockups slider](images/slider_continuous_lockups_light.png) ![Continuous lockups slider dark](images/slider_continuous_lockups_light.png) @@ -120,7 +97,7 @@ With icons: ![Continuous lockups slider with icons](images/slider_continuous_lockups_with_icon_light.png) ![Continuous lockups slider with icons dark](images/slider_continuous_lockups_with_icon_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use: @@ -128,7 +105,7 @@ In your composable screen you can use: OdsSliderLockups( value = 20f, valueRange = 0f..100f, - onValueChange = { } + onValueChange = { doSomething() } ) ``` @@ -138,14 +115,33 @@ You can add icons to the continuous lockups slider like this: OdsSliderLockups( value = 20f, valueRange = 0f..100f, - onValueChange = { }, - leftIcon = painterResource(id = R.drawable.ic_volume_status_1), - leftIconContentDescription = stringResource(id = R.string.component_slider_low_volume), - rightIcon = painterResource(id = R.drawable.ic_volume_status_4), - rightIconContentDescription = stringResource(id = R.string.component_slider_high_volume) + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) ) ``` +##### OdsSliderLockups API + +Parameter | Default value | Description +-- | -- | -- +`value: Float` | | Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. +`onValueChange: (Float) -> Unit` | | Callback invoked on slider value change. `value` should be updated here. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the slider +`enabled: Boolean` | `true` | Controls the enabled state of the slider. If `false`, the user cannot interact with it. +`valueRange: ClosedFloatingPointRange<Float>` | `0f..1f` | Range of values that the slider can take. Given `value` will be coerced to this range. +`steps: Int` | `0` | If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. +`onValueChangeFinished: (() -> Unit)?` | `null` | Callback invoked when value change has ended. This callback shouldn't be used to update the slider value (use `onValueChange` for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. +`startIcon: OdsSliderIcon?` | `null` | Icon displayed at the start of the slider +`endIcon: OdsSliderIcon?` | `null` | Icon displayed at the end of the slider +{:.table} + ### Discrete slider Discrete sliders display a numeric value label upon pressing the thumb, which @@ -157,7 +153,7 @@ With icons: ![Discrete slider with icon](images/slider_discrete_with_icon_light.png) ![Discrete slider with icon dark](images/slider_discrete_with_icon_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use: @@ -166,7 +162,7 @@ OdsSlider( value = 20f, valueRange = 0f..100f, steps = 10, - onValueChange = { } + onValueChange = { doSomething() } ) ``` @@ -177,33 +173,19 @@ OdsSlider( value = 20f, valueRange = 0f..100f, steps = 10, - onValueChange = { }, - leftIcon = painterResource(id = R.drawable.ic_volume_status_1), - leftIconContentDescription = stringResource(id = R.string.component_slider_low_volume), - rightIcon = painterResource(id = R.drawable.ic_volume_status_4), - rightIconContentDescription = stringResource(id = R.string.component_slider_high_volume) + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) ) ``` -> **XML implementation** - -API and source code: - -* `Slider`: [Class definition](https://developer.android.com/reference/com/google/android/material/slider/Slider), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/slider/Slider.java) - -In the layout: - -```xml -<com.google.android.material.slider.Slider - android:id="@+id/slider" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:contentDescription="@string/slider_desc" - android:value="20.0" - android:valueFrom="0.0" - android:valueTo="100.0" - android:stepSize="5.0" /> -``` +Use [OdsSlider API](#odsslider-api). ### Discrete lockups slider @@ -213,7 +195,7 @@ With icons: ![Discrete lockups slider with icons](images/slider_discrete_lockups_with_icon_light.png) ![Discrete lockups slider with icons dark](images/slider_discrete_lockups_with_icon_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In your composable screen you can use: @@ -222,7 +204,7 @@ OdsSliderLockups( value = 20f, valueRange = 0f..100f, steps = 10, - onValueChange = { } + onValueChange = { doSomething() } ) ``` @@ -233,14 +215,16 @@ OdsSliderLockups( value = 20f, valueRange = 0f..100f, steps = 10, - onValueChange = { }, - leftIcon = painterResource(id = R.drawable.ic_volume_status_1), - leftIconContentDescription = stringResource(id = R.string.component_slider_low_volume), - rightIcon = painterResource(id = R.drawable.ic_volume_status_4), - rightIconContentDescription = stringResource(id = R.string.component_slider_high_volume) + onValueChange = { doSomething() }, + startIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_1), + stringResource(id = R.string.component_slider_low_volume) + ), + endIcon = OdsSliderIcon( + painterResource(id = R.drawable.ic_volume_status_4), + stringResource(id = R.string.component_slider_high_volume) + ) ) ``` -## Component specific tokens - -_Soon available_ +Use [OdsSliderLockups API](#odssliderlockups-api). diff --git a/docs/components/Snackbars.md b/docs/components/Snackbars.md index 3cc7679e4..f3455e335 100644 --- a/docs/components/Snackbars.md +++ b/docs/components/Snackbars.md @@ -13,14 +13,13 @@ the screen, but can also be swiped off the screen. Snackbars can also offer the ability to perform an action, such as undoing an action that was just taken, or retrying an action that had failed. ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsSnackbarHost API](#odssnackbarhost-api) --- @@ -28,30 +27,29 @@ action that was just taken, or retrying an action that had failed. - [Design System Manager - Toasts & Snackbars](https://system.design.orange.com/0c1af118d/p/887440-toast--snackbars/b/043ece) - [Material Design - Snackbars](https://material.io/components/snackbars) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Snackbars support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in snackbars is automatically provided to accessibility services. Additional content labels are usually unnecessary. -### Implementation +## Implementation - ![Snackbar light](images/snackbar_light.png) +![Snackbar light](images/snackbar_light.png) - ![Snackbar dark](images/snackbar_dark.png) +![Snackbar dark](images/snackbar_dark.png) With action button: - ![Snackbar with action light](images/snackbar_with_action_light.png) +![Snackbar with action light](images/snackbar_with_action_light.png) - ![Snackbar with action dark](images/snackbar_with_action_dark.png) +![Snackbar with action dark](images/snackbar_with_action_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose We advise you to use a `Scaffold` to add an `OdsSnackbar` in order to make sure everything is displayed together in the right place according to Material Design. Then use `OdsSnackbarHost` which provides the good margins to display the snackbar and `OdsSnackbar` as follow: @@ -64,7 +62,9 @@ Scaffold( scaffoldState = scaffoldState, snackbarHost = { OdsSnackbarHost(hostState = it) { data -> - OdsSnackbar(snackbarData = data) + OdsSnackbar(data = data, actionOnNewLine = true, onActionClick = { + doSomething() + }) } }) { OdsButton( @@ -84,57 +84,11 @@ Scaffold( } ``` -> **XML implementation** - -Calling `make` creates the snackbar, but doesn't cause it to be visible on the -screen. To show it, use the `show` method on the returned `Snackbar` instance. -Note that only one snackbar will be shown at a time. Showing a new snackbar will -dismiss any previous ones first. - -To show a snackbar with a message and no action: - -```kotlin -// The view used to make the snackbar. -// This should be contained within the view hierarchy you want to display the -// snackbar. Generally it can be the view that was interacted with to trigger -// the snackbar, such as a button that was clicked, or a card that was swiped. -val contextView = findViewById<View>(R.id.context_view) - -Snackbar.make(contextView, R.string.text_label, Snackbar.LENGTH_SHORT) - .show() -``` - -**Adding an action** - -To add an action, use the `setAction` method on the object returned from `make`. -Snackbars are automatically dismissed when the action is clicked. - -To show a snackbar with a message and an action: - -```kotlin -Snackbar.make(contextView, R.string.text_label, Snackbar.LENGTH_LONG) - .setAction(R.string.action_text) { - // Responds to click on the action - } - .show() -``` - -**Anchoring a snackbar** - -By default, `Snackbar`s will be anchored to the bottom edge of their parent -view. However, you can use the `setAnchorView` method to make a `Snackbar` -appear above a specific view within your layout, e.g. a `FloatingActionButton`. - -```kotlin -Snackbar.make(...) - .setAnchorView(fab) - ... -``` - -This is especially helpful if you would like to place a `Snackbar` above -navigational elements at the bottom of the screen, such as a `BottomAppBar` or -`BottomNavigationView`. - -## Component specific tokens +#### OdsSnackbarHost API -_Soon available_ +Parameter | Default value | Description +-- | -- | -- +`hostState: SnackbarHostState` | | State of this component to read and show `OdsSnackbar` accordingly. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the snackbar host +`snackbar: (SnackbarData) -> OdsSnackbar` | `{ OdsSnackbar(it) }` | Instance of the `OdsSnackbar` to be shown at the appropriate time with appearance based on the `SnackbarData` provided as a param +{:.table} \ No newline at end of file diff --git a/docs/components/Switches.md b/docs/components/Switches.md index d30ba6730..fb43230f0 100644 --- a/docs/components/Switches.md +++ b/docs/components/Switches.md @@ -7,14 +7,13 @@ description: Switch selection control allows the user to select options. Switches toggle the state of a single setting on or off. They are the preferred way to adjust settings on mobile. ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Implementation](#implementation) -* [Component specific tokens](#component-specific-tokens) + * [Jetpack Compose](#jetpack-compose) + * [OdsSwitch API](#odsswitch-api) --- @@ -22,87 +21,39 @@ way to adjust settings on mobile. - [Design System Manager - Selection controls](https://system.design.orange.com/0c1af118d/p/14638a-selection-controls/b/352c00) - [Material Design - Switches](https://material.io/components/switches) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Switches support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in switches is automatically provided to accessibility services. Additional content labels are usually unnecessary. -### Implementation +## Implementation ![Switch](images/switch_light.png) ![Switch dark](images/switch_dark.png) -> **Jetpack Compose implementation** +### Jetpack Compose In your composable screen you can use: ```kotlin OdsSwitch( checked = true, - onCheckedChange = { - // Do something - }, + onCheckedChange = { doSomething() }, enabled = true ) ``` -> **XML implementation** - -To create a Switch using Orange theme you will have to add `com.google.android.material.switchmaterial.SwitchMaterial` into your layout. - -API and source code: - -* `SwitchMaterial`: [Class definition](https://developer.android.com/reference/com/google/android/material/switchmaterial/SwitchMaterial), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/switchmaterial/SwitchMaterial.java) - -In the layout: - -```xml -<com.google.android.material.switchmaterial.SwitchMaterial - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:checked="true" - android:text="@string/label_1" -/> -<com.google.android.material.switchmaterial.SwitchMaterial - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:text="@string/label_2" -/> -<com.google.android.material.switchmaterial.SwitchMaterial - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:text="@string/label_3" -/> -<com.google.android.material.switchmaterial.SwitchMaterial - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:text="@string/label_4" -/> -<com.google.android.material.switchmaterial.SwitchMaterial - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:enabled="false" - android:text="@string/label_5" -/> -``` - -In code: - -```kotlin -// To check a switch -switchMaterial.isChecked = true - -// To listen for a switch's checked/unchecked state changes -switchMaterial.setOnCheckedChangeListener { buttonView, isChecked - // Responds to switch being checked/unchecked -} -``` +#### OdsSwitch API -## Component specific tokens +Parameter | Default value | Description +-- | -- | -- +`checked: Boolean` | | Controls the checked state of the switch +`onCheckedChange: ((Boolean) -> Unit)?` | | Callback invoked on switch check. If `null`, then this is passive and relies entirely on a higher-level component to control the "checked" state. +`modifier: Modifier` | `Modifier` | `Modifier` applied to the switch +`enabled: Boolean` | `true` | Controls the enabled state of the switch. When `false`, the switch will not be checkable and appears disabled. +{:.table} -_Soon available_ diff --git a/docs/components/Tabs.md b/docs/components/Tabs.md index b705914e2..623a6c6db 100644 --- a/docs/components/Tabs.md +++ b/docs/components/Tabs.md @@ -4,16 +4,15 @@ title: Tabs description: Tabs organize content across different screens, data sets, and other interactions. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) - * [Fixed tabs](#fixed-tabs) - * [Scrollable tabs](#scrollable-tabs) -* [Component specific tokens](#component-specific-tokens) + * [Fixed tabs](#fixed-tabs) + * [Jetpack Compose](#jetpack-compose) + * [Scrollable tabs](#scrollable-tabs) + * [Jetpack Compose](#jetpack-compose-1) --- @@ -21,24 +20,14 @@ description: Tabs organize content across different screens, data sets, and othe - [Design System Manager - Tabs](https://system.design.orange.com/0c1af118d/p/513d27-tabs/b/50cb71) - [Material Design - Tabs](https://material.io/components/tabs/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). The Android tab components support screen reader descriptions for tabs and badges. While optional, we strongly encourage their use. -**Content description** - -Adding a content description to the entire `TabLayout` can be done in XML with -the `android:contentDescription` attribute or programmatically like so: - -```kotlin -tabLayout.contentDescription = contentDescription -``` - ## Variants ### Fixed tabs @@ -48,18 +37,19 @@ width of each tab is determined by dividing the number of tabs by the screen width. They don’t scroll to reveal more tabs; the visible tab set represents the only tabs available. - > **Jetpack Compose implementation** +#### Jetpack Compose In Compose, the fixed tabs should be added inside of an `OdsTabRow`. The used composable for tab depends on the type of tabs to display: classic `OdsTab` or `OdsLeadingIconTab`. - ![Fixed tabs light](images/tabs_fixed_light.png) +![Fixed tabs light](images/tabs_fixed_light.png) - ![Fixed tabs dark](images/tabs_fixed_dark.png) +![Fixed tabs dark](images/tabs_fixed_dark.png) **`OdsTab` composable:** This composable allows to display: + - an icon only tab - a text label only tab - a tab with an icon on top of text label @@ -116,45 +106,6 @@ OdsTabRow(selectedTabIndex = pagerState.currentPage) { } ) } -``` - - > **XML implementation** - -API and source code: - -* `TabLayout`: [Class definition](https://developer.android.com/reference/com/google/android/material/tabs/TabLayout), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/tabs/TabLayout.java) -* `TabItem`: [Class definition](https://developer.android.com/reference/com/google/android/material/tabs/TabItem), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/tabs/TabItem.java) - -In the layout: - -```xml -<com.google.android.material.tabs.TabLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:tabMode="fixed"> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_1" - android:icon="@drawable/ic_favorite_24dp" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_2" - android:icon="@drawable/ic_music_24dp" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_3" - android:icon="@drawable/ic_search_24dp" - /> - -</com.google.android.material.tabs.TabLayout> ``` ### Scrollable tabs @@ -162,11 +113,11 @@ In the layout: Scrollable tabs are displayed without fixed widths. They are scrollable, such that some tabs will remain off-screen until scrolled. - ![Scrollable tabs light](images/tabs_scrollable_light.png) +![Scrollable tabs light](images/tabs_scrollable_light.png) - ![Scrollable tabs dark](images/tabs_scrollable_dark.png) +![Scrollable tabs dark](images/tabs_scrollable_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose For scrollable tabs, the tabs should be added inside of an `OdsScrollableTabRow`. This is the only difference with fixed tabs implementation. As for fixed tabs, you can use an `OdsTab` composable or an `OdsLeadingIconTab` inside. @@ -195,56 +146,3 @@ OdsScrollableTabRow(selectedTabIndex = pagerState.currentPage) { ) } ``` - -> **XML implementation** - -API and source code: - -* `TabLayout`: [Class definition](https://developer.android.com/reference/com/google/android/material/tabs/TabLayout), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/tabs/TabLayout.java) -* `TabItem`: [Class definition](https://developer.android.com/reference/com/google/android/material/tabs/TabItem), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/tabs/TabItem.java) - -In the layout: - -```xml -<com.google.android.material.tabs.TabLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:tabMode="scrollable" - app:tabContentStart="56dp"> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_1" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_2" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_3" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_4" - /> - - <com.google.android.material.tabs.TabItem - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/tab_5" - /> - -</com.google.android.material.tabs.TabLayout> -``` - -## Component specific tokens - -_Soon available_ diff --git a/docs/components/TextFields.md b/docs/components/TextFields.md index c69381bb4..e10abc148 100644 --- a/docs/components/TextFields.md +++ b/docs/components/TextFields.md @@ -4,18 +4,18 @@ title: Text fields description: Text fields let users enter and edit text. --- ---- - -**Page Summary** +<br>**On this page** * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) - * [Text field](#text-field) - * [Password text field](#password-text-field) + * [Text field](#text-field) + * [Jetpack Compose](#jetpack-compose) + * [Password text field](#password-text-field) + * [Jetpack Compose](#jetpack-compose-1) * [Extras](#extras) - * [Character counter](#character-counter) -* [Component specific tokens](#component-specific-tokens) + * [Character counter](#character-counter) + * [Jetpack Compose](#jetpack-compose-2) --- @@ -23,40 +23,13 @@ description: Text fields let users enter and edit text. - [Design System Manager - Text fields](https://system.design.orange.com/0c1af118d/p/483f94-text-fields/b/720e3b) - [Material Design - Text fields](https://material.io/components/text-fields/) -- Technical documentation soon available ## Accessibility -Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/) +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/android/development/). Android's text field component APIs support both label text and helper text for informing the user -as to what information is requested for a text field. While optional, their use is strongly -encouraged. - -**Content description** - -When using **custom icons**, you should set a content description on them so that screen readers -like TalkBack are able to announce their purpose or action, if any. - -For the leading icon, that can be achieved via the -`app:startIconContentDescription` attribute or `setStartIconContentDescription` -method. For the trailing icon, that can be achieved via the -`app:endIconContentDescription` attribute or `setEndIconContentDescription` -method. - -When setting an **error message** that contains special characters that screen readers or other -accessibility systems are not able to read, you should set a content description via -the `app:errorContentDescription` attribute or -`setErrorContentDescription` method. That way, when the error needs to be announced, it will -announce the content description instead. - -**Custom `EditText`** - -If you are using a custom `EditText` as `TextInputLayout`'s child and your text field requires -different accessibility support than the one offered by -`TextInputLayout`, you can set a `TextInputLayout.AccessibilityDelegate` via the -`setTextInputAccessibilityDelegate` method. This method should be used in place of providing -an `AccessibilityDelegate` directly on the `EditText`. +as to what information is requested for a text field. ## Variants @@ -65,47 +38,48 @@ an `AccessibilityDelegate` directly on the `EditText`. A text field can be filled or outlined. The outlined version is more accessible in term of contrast. This is the reason why Orange text fields are outlined. - ![TextField outlined light](images/textfield_outlined_light.png) - ![TextField outlined dark](images/textfield_outlined_dark.png) +![TextField outlined light](images/textfield_outlined_light.png) +![TextField outlined dark](images/textfield_outlined_dark.png) - ![TextField filled light](images/textfield_filled_light.png) - ![TextField filled dark](images/textfield_filled_dark.png) +![TextField filled light](images/textfield_filled_light.png) +![TextField filled dark](images/textfield_filled_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To add a text field in your composable screen you can use the `OdsTextField` composable as follow: ```kotlin var text by rememberSaveable { mutableStateOf("") } OdsTextField( - leadingIcon = painterResource(id = R.drawable.ic_heart), // Optional - leadingIconContentDescription = "Like", // Optional - onLeadingIconClick = { doSomething() }, // Optional - trailing = OdsTextTrailing(text = "units"), // Optional, it can be one of the provided `OdsTextFieldTrailing`. See more information below. - enabled = true, // true if not set - readOnly = false, // false if not set - isError = false, // false if not set - errorMessage = "Error message", // Optional + leadingIcon = painterResource(id = R.drawable.ic_heart), + leadingIconContentDescription = "Like", + onLeadingIconClick = { doSomething() }, + trailing = OdsTextTrailing(text = "units"), // It can be one of the provided `OdsTextFieldTrailing`. See more information below. + enabled = true, + readOnly = false, + isError = false, + errorMessage = "Error message", value = text, onValueChange = { text = it }, - label = "Label", // Optional - placeholder = "Placeholder", // Optional - visualTransformation = VisualTransformation.None, // `VisualTransformation.None` if not set - keyboardOptions = KeyboardOptions.Default, // `KeyboardOptions.Default` if not set - keyboardActions = KeyboardActions(), // `KeyboardActions()` if not set - singleLine = false, // false if not set - maxLines = Int.MAX_VALUE, // `Int.MAX_VALUE` if not set + label = "Label", + placeholder = "Placeholder", + visualTransformation = VisualTransformation.None, + keyboardOptions = KeyboardOptions.Default, + keyboardActions = KeyboardActions(), + singleLine = false, + maxLines = Int.MAX_VALUE, characterCounter = { OdsTextFieldCharacterCounter( valueLength = valueLength, maxChars = TextFieldMaxChars, enabled = enabled - ) - } // Optional + ) + } ) ``` The library provides several `OdsTextFieldTrailing` that you can use as a trailing element for text field: + - `OdsIconTrailing`: Displays an icon as trailing element - `OdsTextTrailing`: Displays a text as trailing element @@ -113,7 +87,8 @@ If you want a more complex trailing element, you can use the other `OdsTextField **Note:** You will find more information about the character counter in [Extras](#extras) -**Custom theme configuration:** You can override the default display of text fields in your custom theme by overriding the `textFieldStyle` attribute as below: +**Custom theme configuration: +** You can override the default display of text fields in your custom theme by overriding the `textFieldStyle` attribute as below: ```kotlin override val components: OdsComponentsConfiguration @@ -123,95 +98,41 @@ override val components: OdsComponentsConfiguration } ``` -> **XML implementation** - -_**Note:** The filled text field is the default style if the style is not set._ - -API and source code: - -* `TextInputLayout`: [Class definition](https://developer.android.com/reference/com/google/android/material/textfield/TextInputLayout), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/textfield/TextInputLayout.java) -* `TextInputEditText`: [Class definition](https://developer.android.com/reference/com/google/android/material/textfield/TextInputEditText), [Class source](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/textfield/TextInputEditText.java) - -In the layout: - -```xml - -<com.google.android.material.textfield.TextInputLayout - android:id="@+id/filledTextField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/label"> - - <com.google.android.material.textfield.TextInputEditText - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - -</com.google.android.material.textfield.TextInputLayout> -``` - -For outlined version: - -```xml -<com.google.android.material.textfield.TextInputLayout - android:id="@+id/outlinedTextField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/label" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> - - <com.google.android.material.textfield.TextInputEditText - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - -</com.google.android.material.textfield.TextInputLayout> -``` - -In code: - -```kotlin -// Get input text -val inputText = textField.editText?.text.toString() - -textField.editText?.doOnTextChanged { inputText, _, _, _ -> - // Respond to input text change -} -``` - ### Password text field Password text field is a text field implementation that includes password visual transformation and optional visualisation icon. - ![TextField outlined password light](images/textfield_outlined_password_light.png) - ![TextField outlined password dark](images/textfield_outlined_password_dark.png) +![TextField outlined password light](images/textfield_outlined_password_light.png) +![TextField outlined password dark](images/textfield_outlined_password_dark.png) - ![TextField filled password light](images/textfield_filled_password_light.png) - ![TextField filled password dark](images/textfield_filled_password_dark.png) +![TextField filled password light](images/textfield_filled_password_light.png) +![TextField filled password dark](images/textfield_filled_password_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose To add a password text field in your composable screen you can use the `OdsPasswordTextField` composable as follow: ```kotlin var text by rememberSaveable { mutableStateOf("") } OdsPasswordTextField( - enabled = true, // true if not set - readOnly = false, // false if not set - isError = false, // false if not set - errorMessage = "Error message", // Optional + enabled = true, + readOnly = false, + isError = false, + errorMessage = "Error message", value = text, onValueChange = { text = it }, - label = "Label", // Optional - placeholder = "Placeholder", // Optional - visualisationIcon = true, // `true` if not set - keyboardOptions = KeyboardOptions.Default, // `KeyboardOptions.Default` if not set - keyboardActions = KeyboardActions(), // `KeyboardActions()` if not set + label = "Label", + placeholder = "Placeholder", + visualisationIcon = true, + keyboardOptions = KeyboardOptions.Default, + keyboardActions = KeyboardActions(), characterCounter = { OdsTextFieldCharacterCounter( valueLength = valueLength, maxChars = TextFieldMaxChars, enabled = enabled - ) - } // Optional + ) + } ) ``` @@ -219,19 +140,14 @@ OdsPasswordTextField( Its appearance (outlined or filled) is inherited from text fields style configuration. See [text field section](#text-field) if you want to change it in your custom theme. -> **XML implementation** - -_Not available_ - - ## Extras ### Character counter - ![TextField character counter light](images/textfield_character_counter_light.png) - ![TextField character counter dark](images/textfield_character_counter_dark.png) +![TextField character counter light](images/textfield_character_counter_light.png) +![TextField character counter dark](images/textfield_character_counter_dark.png) -> **Jetpack Compose implementation** +#### Jetpack Compose In each TextField component, you can use the `characterCounter` parameter to add a character counter if there is a restriction on the number of characters in a field. It will be placed properly below the text field, end aligned. @@ -241,9 +157,9 @@ Please use the provided `OdsTextFieldCharacterCounter` composable for this behav ```kotlin OdsTextFieldCharacterCounter( modifier = Modifier.align(Alignment.End), - valueLength = valueLength, + valueLength = valueLength, maxChars = 20, - enabled = true // `true` if not set. If `false` the counter is displayed with a disabled color + enabled = true // If `false` the counter is displayed with a disabled color ) ``` @@ -253,8 +169,4 @@ Be careful, the limitation behavior should be managed by yourself in the `onValu if (text.length <= TextFieldMaxChars) { value = text } -``` - -## Component specific tokens - -_Soon available_ +``` \ No newline at end of file diff --git a/docs/home_content.md b/docs/home_content.md index d750a08c3..b2fd9c785 100644 --- a/docs/home_content.md +++ b/docs/home_content.md @@ -34,7 +34,7 @@ Orange Design System for Android is available through [Maven Central Repository] ```groovy dependencies { // ... - implementation 'com.orange.ods.android:ods-lib:0.16.0' + implementation 'com.orange.ods.android:ods-lib:0.17.0' // ... } ``` diff --git a/gradle.properties b/gradle.properties index dce897884..3cc5faa9f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,4 +29,4 @@ android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official kapt.incremental.apt=true -version=0.16.0 \ No newline at end of file +version=0.17.0 \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/bottomnavigation/OdsBottomNavigation.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/bottomnavigation/OdsBottomNavigation.kt new file mode 100644 index 000000000..4adfdfcb4 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/bottomnavigation/OdsBottomNavigation.kt @@ -0,0 +1,32 @@ +/* + * + * 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.xml.component.bottomnavigation + +import android.content.Context +import android.util.AttributeSet +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.orange.ods.compose.component.bottomnavigation.OdsBottomNavigation +import com.orange.ods.compose.component.bottomnavigation.OdsBottomNavigationItem +import com.orange.ods.xml.component.OdsAbstractComposeView + + +class OdsBottomNavigation @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var items by mutableStateOf<List<OdsBottomNavigationItem>>(emptyList()) + + @Composable + override fun OdsContent() { + OdsBottomNavigation(items = items) + } +} diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 92eb8d45b..4a21bfa2c 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -18,6 +18,7 @@ <enum name="light" value="2" /> </attr> <attr name="iconContentDescription" format="string" /> + <declare-styleable name="OdsBanner"> <attr name="message" format="string" /> <attr name="firstButtonText" format="string" /> @@ -25,6 +26,7 @@ <attr name="imageContentDescription" format="string" /> <attr name="secondButtonText" format="string" /> </declare-styleable> + <declare-styleable name="OdsButton"> <attr name="icon" /> <attr name="text" /> @@ -37,12 +39,14 @@ </attr> <attr name="displaySurface" /> </declare-styleable> + <declare-styleable name="OdsIconButton"> <attr name="icon" /> <attr name="iconContentDescription" /> <attr name="enabled" /> <attr name="displaySurface" /> </declare-styleable> + <declare-styleable name="OdsIconToggleButton"> <attr name="checked" format="boolean" /> <attr name="checkedIcon" format="reference" /> @@ -52,16 +56,19 @@ <attr name="enabled" /> <attr name="displaySurface" /> </declare-styleable> + <declare-styleable name="OdsIconToggleButtonsRow"> <attr name="selectedIndex" format="integer" /> <attr name="displaySurface" /> </declare-styleable> + <declare-styleable name="OdsOutlinedButton"> <attr name="icon" /> <attr name="text" /> <attr name="enabled" /> <attr name="displaySurface" /> </declare-styleable> + <declare-styleable name="OdsTextButton"> <attr name="text" /> <attr name="icon" /> @@ -72,4 +79,5 @@ </attr> <attr name="displaySurface" /> </declare-styleable> + </resources> diff --git a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt index bde309f00..29ca6bec1 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt @@ -37,6 +37,18 @@ import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews import com.orange.ods.compose.theme.OdsTheme +/** + * <a href="https://system.design.orange.com/0c1af118d/p/23e0e6-app-bars/b/620966" class="external" target="_blank">Material ODS Top App Bar</a>. + * + * The top app bar displays information and actions relating to the current screen. + * + * @param title Title displayed in the center of the top app bar. + * @param modifier [Modifier] applied to the top app bar. + * @param navigationIcon Icon displayed at the start of the top app bar. + * @param actions Actions displayed at the end of the top app bar. The default layout here is a [androidx.compose.foundation.layout.Row], so icons inside will be placed horizontally. + * @param overflowMenuActions Actions displayed in the overflow menu. + * @param scrollBehavior [TopAppBarScrollBehavior] attached to the top app bar. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsTopAppBar.kt b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsTopAppBar.kt index 81904ab5d..2a51b545d 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsTopAppBar.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsTopAppBar.kt @@ -10,7 +10,6 @@ package com.orange.ods.compose.component.appbar.top -import androidx.compose.foundation.layout.Row import androidx.compose.material.AppBarDefaults import androidx.compose.material.Text import androidx.compose.material.TopAppBar @@ -36,13 +35,12 @@ import com.orange.ods.compose.theme.OdsTheme * centering the title, use the other TopAppBar overload for a generic TopAppBar with no * restriction on content. * - * @param title The title to be displayed in the center of the TopAppBar - * @param modifier The [Modifier] to be applied to this TopAppBar - * @param navigationIcon Optional navigation icon displayed at the start of the TopAppBar. - * @param actions The actions displayed at the end of the TopAppBar. - * The default layout here is a [Row], so icons inside will be placed horizontally. - * @param overflowMenuActions The actions displayed in the overflow menu. - * @param elevated True to set an elevation to the top app bar (shadow displayed), false otherwise. + * @param title Title displayed in the center of the top app bar. + * @param modifier [Modifier] applied to the top app bar. + * @param navigationIcon Icon displayed at the start of the top app bar. + * @param actions Actions displayed at the end of the top app bar. The default layout here is a [androidx.compose.foundation.layout.Row], so icons inside will be placed horizontally. + * @param overflowMenuActions Actions displayed in the overflow menu. + * @param elevated Controls the elevation of the top app bar: `true` to set an elevation to the top app bar (a shadow is displayed below), `false` otherwise. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/banner/OdsBanner.kt b/lib/src/main/java/com/orange/ods/compose/component/banner/OdsBanner.kt index 6886f5ea4..1a124d186 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/banner/OdsBanner.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/banner/OdsBanner.kt @@ -30,7 +30,7 @@ import com.orange.ods.R import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.button.OdsTextButton import com.orange.ods.compose.component.button.OdsTextButtonStyle -import com.orange.ods.compose.component.content.OdsComponentCircleImage +import com.orange.ods.compose.component.content.OdsComponentCircularImage import com.orange.ods.compose.component.content.OdsComponentContent import com.orange.ods.compose.component.divider.OdsDivider import com.orange.ods.compose.component.utilities.BasicPreviewParameterProvider @@ -41,12 +41,11 @@ import com.orange.ods.compose.theme.OdsTheme /** * <a href="https://system.design.orange.com/0c1af118d/p/19a040-banners/b/497b77" class="external" target="_blank">ODS banners</a>. * - * - * @param message text displayed in the banner. - * @param firstButton principal button in the banner. - * @param modifier modifiers for the Banner layout. - * @param image image display in the banner. - * @param secondButton Optional second button in the banner. + * @param message Text displayed into the banner. + * @param firstButton Primary button displayed in the banner. + * @param modifier [Modifier] applied to the banner layout. + * @param image Image displayed in the banner in a circle shape. + * @param secondButton Secondary button displayed into the banner next to the primary one. */ @Composable @OdsComposable @@ -120,7 +119,7 @@ class OdsBannerButton(private val text: String, private val onClick: () -> Unit) /** * An image in an [OdsBanner]. */ -class OdsBannerImage : OdsComponentCircleImage { +class OdsBannerImage : OdsComponentCircularImage { /** * Creates an instance of [OdsBannerImage]. diff --git a/lib/src/main/java/com/orange/ods/compose/component/bottomnavigation/OdsBottomNavigation.kt b/lib/src/main/java/com/orange/ods/compose/component/bottomnavigation/OdsBottomNavigation.kt index b41858b81..e16d79375 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/bottomnavigation/OdsBottomNavigation.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/bottomnavigation/OdsBottomNavigation.kt @@ -43,15 +43,14 @@ import com.orange.ods.compose.theme.OdsTheme * See [OdsBottomNavigationItem] for configuration specific to each item, and not the overall * OdsBottomNavigation component. * - * @param modifier optional [Modifier] for this OdsBottomNavigation - * @param items destinations inside this OdsBottomNavigation, this contain multiple - * [OdsBottomNavigationItem]s + * @param items List of [OdsBottomNavigationItem] displayed into the bottom navigation. + * @param modifier [Modifier] applied to the bottom navigation. */ @Composable @OdsComposable fun OdsBottomNavigation( - modifier: Modifier = Modifier, - items: List<OdsBottomNavigationItem> + items: List<OdsBottomNavigationItem>, + modifier: Modifier = Modifier ) { BottomNavigation( modifier = modifier, @@ -77,12 +76,12 @@ fun OdsBottomNavigation( * An OdsBottomNavigationItem always shows text labels (if it exists) when selected. Showing text * labels if not selected is controlled by [alwaysShowLabel]. * - * @param selected whether this item is selected - * @param onClick the callback to be invoked when this item is selected - * @param icon icon for this item + * @param selected whether this item is selected. + * @param onClick the callback to be invoked when this item is selected. + * @param icon icon for this item. * @param enabled controls the enabled state of this item. When `false`, this item will not * be clickable and will appear disabled to accessibility services. - * @param label optional text label for this item + * @param label optional text label for this item. * @param alwaysShowLabel whether to always show the label for this item. If false, the label will * only be shown when this item is selected. */ diff --git a/lib/src/main/java/com/orange/ods/compose/component/bottomsheet/OdsBottomSheetScaffold.kt b/lib/src/main/java/com/orange/ods/compose/component/bottomsheet/OdsBottomSheetScaffold.kt index f3c261251..9c73f620f 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/bottomsheet/OdsBottomSheetScaffold.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/bottomsheet/OdsBottomSheetScaffold.kt @@ -32,16 +32,16 @@ import com.orange.ods.compose.theme.OdsTheme * * Bottom Sheets are surfaces anchored to the bottom of the screen that present users supplement content. * - * @param sheetContent The content of the bottom sheet - * @param modifier optional [Modifier] for this OdsBottomSheetScaffold - * @param scaffoldState The state of the scaffold - * @param topBar An optional top app bar - * @param snackbarHost The composable hosting the snackbars shown inside the scaffold - * @param floatingActionButton An optional floating action button - * @param floatingActionButtonPosition The position of the floating action button - * @param sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures - * @param sheetPeekHeight The height of the bottom sheet when it is collapsed - * @param content destinations inside this OdsBottomSheetScaffold + * @param sheetContent The content of the bottom sheet. + * @param modifier optional [Modifier] for this OdsBottomSheetScaffold. + * @param scaffoldState The state of the scaffold. + * @param topBar An optional top app bar. + * @param snackbarHost The composable hosting the snackbars shown inside the scaffold. + * @param floatingActionButton An optional floating action button. + * @param floatingActionButtonPosition The position of the floating action button. + * @param sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures. + * @param sheetPeekHeight The height of the bottom sheet when it is collapsed. + * @param content destinations inside this OdsBottomSheetScaffold. */ @Composable @ExperimentalMaterialApi diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsButton.kt index b45b15ce2..6b250960e 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsButton.kt @@ -56,18 +56,16 @@ enum class OdsButtonStyle { * Contained buttons are high-emphasis, distinguished by their use of elevation and fill. They * contain actions that are primary to your app. * - * @param text Text displayed in the button - * @param onClick Will be called when the user clicks the button - * @param modifier Modifier to be applied to the button - * @param icon Icon displayed in the button before the text. If `null`, no icon will be displayed. - * @param enabled Controls the enabled state of the button. When `false`, this button will not - * be clickable - * @param style Controls the style of the button. Use `OdsButtonStyle.Primary` for an highlighted button style. To get a green/red button - * style on contained buttons, you can use `OdsButtonStyle.FunctionalPositive` or `OdsButtonStyle.FunctionalNegative` values. - * @param displaySurface optional allow to force the button display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param text Text displayed into the button. + * @param onClick Callback invoked when the button is clicked. + * @param modifier [Modifier] applied to the button. + * @param icon Icon displayed in the button before the text. + * @param enabled Controls the enabled state of the button. When `false`, this button will not be clickable. + * @param style Style applied to the button. Set it to [OdsButtonStyle.Primary] for an highlighted button style or use. + * [OdsButtonStyle.FunctionalPositive]/[OdsButtonStyle.FunctionalNegative] for a functional green/red button style. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. + * By default the appearance applied is based on the system night mode value. */ - @Composable @OdsComposable fun OdsButton( diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsFloatingActionButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsFloatingActionButton.kt index c13d14431..2ced7ff86 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsFloatingActionButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsFloatingActionButton.kt @@ -48,10 +48,10 @@ private val FabIconSize = 24.dp * * @see androidx.compose.material.FloatingActionButton for further information. * - * @param icon The FAB icon. - * @param onClick callback invoked when this FAB is clicked - * @param modifier [Modifier] to be applied to this FAB. - * @param mini If `true`, the size of the FAB will be 40dp, otherwise the default size will be used. + * @param icon Icon used into the FAB. + * @param onClick Callback invoked on FAB click. + * @param modifier [Modifier] applied to the FAB. + * @param mini Controls the size of the FAB. If `true`, the size of the FAB will be 40dp, otherwise the default size will be used. */ @Composable @OdsComposable @@ -81,10 +81,10 @@ fun OdsFloatingActionButton( * * @see androidx.compose.material.ExtendedFloatingActionButton for further information. * - * @param icon The FAB icon. - * @param onClick callback invoked when this FAB is clicked - * @param text The text displayed in the [OdsExtendedFloatingActionButton] - * @param modifier [Modifier] to be applied to this FAB. + * @param icon Icon used into the FAB. + * @param onClick Callback invoked on FAB click. + * @param text Text displayed into the FAB. + * @param modifier [Modifier] applied to the FAB. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconButton.kt index e9797b168..a088e8c2d 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconButton.kt @@ -32,13 +32,11 @@ import com.orange.ods.compose.theme.OdsDisplaySurface * * This component is typically used inside an App Bar for the navigation icon / actions. * - * @param icon The icon to be drawn inside the OdsIconButton - * @param onClick The lambda to be invoked when this icon is pressed - * @param modifier The [Modifier] for this OdsIconButton - * @param enabled Whether or not this [OdsIconButton] will handle input events and appear enabled for - * semantics purposes, true by default - * @param displaySurface Force the button display on a dark or light surface. By default the appearance applied - * is based on the system night mode value. + * @param icon Icon to be drawn into the button. + * @param onClick Callback to be invoked when the button is clicked. + * @param modifier [Modifier] to be applied to the button. + * @param enabled Controls the enabled state of the button. When `false`, this button will not be clickable. + * @param displaySurface [OdsDisplaySurface] to be applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButton.kt index 49d7bc66f..41fd13e71 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButton.kt @@ -32,15 +32,13 @@ import com.orange.ods.compose.theme.OdsDisplaySurface * An [IconButton] with two states, for icons that can be toggled 'on' and 'off', such as a * bookmark icon, or a navigation icon that opens a drawer. * - * @param checked Whether this OdsIconToggleButton is currently checked - * @param onCheckedChange Callback to be invoked when this icon is selected - * @param uncheckedIcon The icon displayed when unchecked - * @param checkedIcon The icon displayed when checked - * @param modifier [Modifier] for this OdsIconToggleButton - * @param enabled Whether or not this OdsIconToggleButton will handle input events and appear - * enabled for semantics purposes - * @param displaySurface Allow to force the button display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param checked Controls the checked state of the button. + * @param onCheckedChange Callback invoked when the button is checked. + * @param uncheckedIcon Icon displayed when the button is unchecked. + * @param checkedIcon Icon displayed when the button is checked. + * @param modifier [Modifier] applied to the button. + * @param enabled Controls the enabled state of the button. When `false`, this button will not be clickable. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. By default, the appearance applied is based on the system night mode value. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButtonsRow.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButtonsRow.kt index d4a3c2806..04e72472b 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButtonsRow.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsIconToggleButtonsRow.kt @@ -51,12 +51,12 @@ import com.orange.ods.compose.utilities.extension.enable * A group of toggle buttons. Only one option in a group of toggle buttons can be selected and active at a time. * Selecting one option deselects any other. * - * @param icons Contains the [OdsIconToggleButtonsRowIcon] to display in the toggle group - * @param selectedIndex The [icons] list index of the selected button. - * @param onSelectedIndexChange Callback to be invoked when the selection change. - * @param modifier optional [Modifier] for this OdsIconToggleButtonsRow - * @param displaySurface optional allow to force the group display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param icons List of [OdsIconToggleButtonsRowIcon] displayed into the toggle group. + * @param selectedIndex [icons] list index of the selected button. + * @param onSelectedIndexChange Callback invoked on selection change. + * @param modifier [Modifier] applied to the toggle buttons group. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. By default, the + * appearance applied is based on the system night mode value. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsOutlinedButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsOutlinedButton.kt index 67c8860ec..b1360916e 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsOutlinedButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsOutlinedButton.kt @@ -35,14 +35,13 @@ import com.orange.ods.theme.colors.OdsColors * Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren't * the primary action in an app. * - * @param text Text displayed in the button - * @param onClick Will be called when the user clicks the button - * @param modifier Modifier to be applied to the button - * @param icon Icon displayed in the button before the text. If `null`, no icon will be displayed. - * @param enabled Controls the enabled state of the button. When `false`, this button will not - * be clickable - * @param displaySurface optional allow to force the button display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param text Text displayed into the button. + * @param onClick Callback invoked on button click. + * @param modifier [Modifier] applied to the button. + * @param icon Icon displayed in the button before the text. + * @param enabled Controls the enabled state of the button. When `false`, the button is not clickable. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. By default, the + * appearance applied is based on the system night mode value. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextButton.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextButton.kt index fdae998c5..755a82b8b 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextButton.kt @@ -46,15 +46,15 @@ enum class OdsTextButtonStyle { * Text buttons are typically used for less-pronounced actions, including those located in dialogs * and cards. In cards, text buttons help maintain an emphasis on card content. * - * @param text Text displayed in the button - * @param onClick Will be called when the user clicks the button - * @param modifier Modifier to be applied to the button - * @param icon Icon displayed before the text. If `null`, no icon will be displayed. - * @param enabled Controls the enabled state of the button. When `false`, this button will not - * be clickable. - * @param style Controls the style of the button. By default the `onSurface` color is used. - * @param displaySurface optional allow to force the button display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param text Text displayed into the button. + * @param onClick Callback invoked on button click. + * @param modifier [Modifier] applied to the button. + * @param icon Icon displayed in the button before the text. + * @param enabled Controls the enabled state of the button. When `false`, this button will not be clickable. + * @param style Style applied to the button. By default `onSurface` color is used for text color. Use [OdsTextButtonStyle.Primary] for an highlighted + * text color. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. By default, + * the appearance applied is based on the system night mode value. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextToggleButtonsRow.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextToggleButtonsRow.kt index 848f81c4f..aa1725db3 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextToggleButtonsRow.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsTextToggleButtonsRow.kt @@ -41,13 +41,14 @@ import com.orange.ods.compose.theme.OdsDisplaySurface * A group of toggle buttons. Only one option in a group of toggle buttons can be selected and active at a time. * Selecting one option deselects any other. * - * @param textToggleButtons Contains the [OdsTextToggleButtonsRowItem] to display in the toggle group - * @param selectedIndex The [textToggleButtons] list index of the selected button. - * @param onSelectedIndexChange Callback to be invoked when the selection change. - * @param modifier [Modifier] for this OdsTextToggleButtonsRow - * @param sameItemsWeight if true, same weight of importance will be applied to each item, they will occupy the same width. - * @param displaySurface allow to force the group display on a dark or light - * surface. By default the appearance applied is based on the system night mode value. + * @param textToggleButtons List of [OdsTextToggleButtonsRowItem] displayed into the toggle group. + * @param selectedIndex [textToggleButtons] list index of the selected button. + * @param onSelectedIndexChange Callback invoked on selection change. + * @param modifier [Modifier] applied to the toggle buttons row. + * @param sameItemsWeight Controls the place occupied by each item. When `true`, same weight of importance will be applied to each item, they will occupy + * the same width. + * @param displaySurface [OdsDisplaySurface] applied to the button. It allows to force the button display on light or dark surface. By default, + * the appearance applied is based on the system night mode value. */ @Composable @OdsComposable @@ -111,13 +112,15 @@ private fun RowScope.TextToggleButtonsRowItem( text = textToggleButton.text, enabled = textToggleButton.enabled, modifier = Modifier - .background(color = buttonToggleBackgroundColor(displaySurface).copy(alpha = backgroundAlpha)).let { + .background(color = buttonToggleBackgroundColor(displaySurface).copy(alpha = backgroundAlpha)) + .fillMaxHeight() + .let { if (sameItemsWeight) it.weight(1f) else it }, maxLines = 1, overflow = TextOverflow.Ellipsis, displaySurface = displaySurface, - style = if (selected) OdsTextButtonStyle.Primary else OdsTextButtonStyle.Default, + style = OdsTextButtonStyle.Default, onClick = { onClick(index) } ) } diff --git a/lib/src/main/java/com/orange/ods/compose/component/card/OdsCardsCommon.kt b/lib/src/main/java/com/orange/ods/compose/component/card/OdsCardsCommon.kt index 9bc927acf..bf48afd17 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/card/OdsCardsCommon.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/card/OdsCardsCommon.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.semantics.semantics import com.orange.ods.compose.component.button.OdsTextButton import com.orange.ods.compose.component.button.OdsTextButtonStyle -import com.orange.ods.compose.component.content.OdsComponentCircleImage +import com.orange.ods.compose.component.content.OdsComponentCircularImage import com.orange.ods.compose.component.content.OdsComponentContent import com.orange.ods.compose.component.content.OdsComponentImage @@ -130,7 +130,7 @@ class OdsCardImage private constructor( /** * A thumbnail in a card. */ -class OdsCardThumbnail : OdsComponentCircleImage { +class OdsCardThumbnail : OdsComponentCircularImage { /** * Creates an instance of [OdsCardThumbnail]. diff --git a/lib/src/main/java/com/orange/ods/compose/component/card/OdsHorizontalCard.kt b/lib/src/main/java/com/orange/ods/compose/component/card/OdsHorizontalCard.kt index 0969119fb..49de54fd3 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/card/OdsHorizontalCard.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/card/OdsHorizontalCard.kt @@ -41,16 +41,16 @@ import com.orange.ods.compose.theme.OdsTheme * * Cards contain content and actions about a single subject. * - * @param title The title to be displayed in the card. - * @param image The card image. - * @param modifier Modifier to be applied to the layout of the card. - * @param subtitle Optional subtitle to be displayed in the card. - * @param text Optional text description to be displayed in the card. It is truncated to fit on 2 lines. - * @param firstButton Optional first button in the card. - * @param secondButton Optional second button in the card. - * @param imagePosition Position of the image, it can be [OdsHorizontalCardImagePosition.Start] or [OdsHorizontalCardImagePosition.End] in the card. [OdsHorizontalCardImagePosition.Start] by default. - * @param divider If true, a divider is displayed between card content and the action buttons. True by default. - * @param onClick Optional click on the card itself. + * @param title Title displayed into the card. + * @param image [OdsCardImage] displayed into the card. + * @param modifier [Modifier] applied to the layout of the card. + * @param subtitle Subtitle displayed into the card. + * @param text Text displayed into the card. + * @param firstButton First [OdsCardButton] displayed into the card. + * @param secondButton Second [OdsCardButton] displayed into the card. + * @param imagePosition Position of the image within the card, it can be set to [OdsHorizontalCardImagePosition.Start] or [OdsHorizontalCardImagePosition.End]. [OdsHorizontalCardImagePosition.Start] by default. + * @param divider Controls the divider display. If `true`, it will be displayed between the card content and the action buttons. + * @param onClick Callback invoked on card click. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/card/OdsSmallCard.kt b/lib/src/main/java/com/orange/ods/compose/component/card/OdsSmallCard.kt index 257ec6ed5..d13481253 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/card/OdsSmallCard.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/card/OdsSmallCard.kt @@ -31,12 +31,11 @@ import com.orange.ods.compose.theme.OdsTheme * * Cards contain content and actions about a single subject. * - * @param title The title to be displayed in the card. - * @param image The card image. - * @param modifier Modifier to be applied to the layout of the card. - * @param subtitle Optional subtitle to be displayed in the card. - * @param onClick Optional click on the card itself. - * + * @param title Title displayed into the card. + * @param image [OdsCardImage] displayed into the card. + * @param modifier [Modifier] applied to the layout of the card. + * @param subtitle Subtitle displayed into the card. + * @param onClick Callback invoked on card click. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalHeaderFirstCard.kt b/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalHeaderFirstCard.kt index d39b616a1..c65c15bc2 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalHeaderFirstCard.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalHeaderFirstCard.kt @@ -37,15 +37,15 @@ import com.orange.ods.extension.orElse * * Cards contain content and actions about a single subject. * - * @param title The title to be displayed in the card. - * @param image The card image. - * @param modifier Modifier to be applied to the layout of the card. - * @param thumbnail Optional card thumbnail: avatar, logo or icon. - * @param subtitle Optional subtitle to be displayed in the card. - * @param text Optional text description to be displayed in the card. - * @param firstButton Optional first button in the card. - * @param secondButton Optional second button in the card. - * @param onClick Optional click on the card itself. + * @param title Title displayed into the card. + * @param image [OdsCardImage] displayed into the card. + * @param modifier [Modifier] applied to the layout of the card. + * @param thumbnail [OdsCardThumbnail] displayed into the card next to the title: avatar, logo or icon. + * @param subtitle Subtitle displayed into the card. + * @param text Text displayed into the card. + * @param firstButton First [OdsCardButton] displayed into the card. + * @param secondButton Second [OdsCardButton] displayed into the card. + * @param onClick Callback invoked on card click. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalImageFirstCard.kt b/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalImageFirstCard.kt index db0a271c7..7c81a0362 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalImageFirstCard.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/card/OdsVerticalImageFirstCard.kt @@ -33,14 +33,14 @@ import com.orange.ods.compose.text.OdsTextSubtitle2 * * Cards contain content and actions about a single subject. * - * @param title The title to be displayed in the card. - * @param image The card image. - * @param modifier Modifier to be applied to the layout of the card. - * @param subtitle Optional subtitle to be displayed in the card. - * @param text Optional text description to be displayed in the card. - * @param firstButton Optional first button in the card. - * @param secondButton Optional second button in the card. - * @param onClick Optional click on the card itself. + * @param title Title displayed into the card. + * @param image [OdsCardImage] displayed into the card. + * @param modifier [Modifier] applied to the layout of the card. + * @param subtitle Subtitle displayed into the card. + * @param text Text displayed into the card. + * @param firstButton First [OdsCardButton] displayed into the card. + * @param secondButton Second [OdsCardButton] displayed into the card. + * @param onClick Callback invoked on card click. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChip.kt b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChip.kt index 9086a149d..0a13cc0d0 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChip.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChip.kt @@ -49,15 +49,14 @@ import com.orange.ods.theme.OdsComponentsConfiguration * * Use Accompanist's [Flow Layouts](https://google.github.io/accompanist/flowlayout/) to wrap chips to a new line. * - * @param text Text to display in the chip. - * @param onClick Called when the chip is clicked. - * @param modifier Modifier to be applied to the chip - * @param enabled When disabled, chip will not respond to user input. It will also appear visually - * disabled and disabled to accessibility services. - * @param selected When selected the chip is highlighted (useful for choice chips). - * @param leadingIcon Icon at the start of the chip, preceding the content text. - * @param leadingAvatar Avatar at the start of the chip displayed in a circle shape, preceding the content text. - * @param onCancel Called when the cancel cross of the chip is clicked. Pass `null` here for no cancel cross. + * @param text Text displayed into the chip. + * @param onClick Callback invoked on chip click. + * @param modifier [Modifier] applied to the chip. + * @param enabled Controls the enabled state of the chip. When `false`, this chip will not respond to user input. + * @param selected Controls the selected state of the chip. When `true`, the chip is highlighted (useful for choice chips). + * @param leadingIcon [OdsChipLeadingIcon] displayed at the start of the chip, preceding the text. + * @param leadingAvatar [OdsChipLeadingAvatar] displayed in a circle shape at the start of the chip, preceding the content text. + * @param onCancel Callback called on chip cancel cross click. Pass `null` for no cancel cross. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChoiceChipsFlowRow.kt b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChoiceChipsFlowRow.kt index f3ffd42bb..72fee407e 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChoiceChipsFlowRow.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsChoiceChipsFlowRow.kt @@ -17,10 +17,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.selection.selectableGroup import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.semantics.SemanticsPropertyReceiver @@ -39,22 +37,20 @@ import com.orange.ods.compose.theme.OdsTheme * * Note that [OdsChoiceChip] are displayed outlined or filled according to your [OdsTheme] component configuration, outlined by default. * - * @param value The initial value of this OdsChoiceChipsFlowRow. - * @param onValueChange The callback that is triggered when the value change. - * @param modifier Modifier to be applied to the flow row. - * @param chips The list of [OdsChoiceChip]s displayed inside this OdsChoiceChipsFlowRow. + * @param chips The list of [OdsChoiceChip] displayed into the chips flow row. + * @param value The initial value of the choice chips flow row. + * @param onValueChange Callback invoked when the value changes. The new value is provided as parameter. + * @param modifier [Modifier] applied to the chips flow row. */ @OptIn(ExperimentalLayoutApi::class) @Composable @OdsComposable fun <T> OdsChoiceChipsFlowRow( + chips: List<OdsChoiceChip<T>>, value: T, onValueChange: (value: T) -> Unit, - modifier: Modifier = Modifier, - chips: List<OdsChoiceChip<T>> + modifier: Modifier = Modifier ) { - var selectedChipValue by remember { mutableStateOf(value) } - FlowRow( modifier = modifier .fillMaxWidth() @@ -62,9 +58,8 @@ fun <T> OdsChoiceChipsFlowRow( horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing_s)), content = { chips.forEachIndexed { index, odsChoiceChip -> - odsChoiceChip.Content(selected = selectedChipValue == odsChoiceChip.value) { selected -> + odsChoiceChip.Content(selected = value == odsChoiceChip.value) { selected -> if (selected) { - selectedChipValue = chips[index].value onValueChange(chips[index].value) } } @@ -76,10 +71,10 @@ fun <T> OdsChoiceChipsFlowRow( /** * OdsChoiceChip used in a [OdsChoiceChipsFlowRow] * - * @param text Text displayed in the chip - * @param value The chip value + * @param text Text displayed in the chip. + * @param value The chip value. * @param enabled If set to false, the chip is no more clickable and appears as disabled. True by default. - * @param semantics The semantics applied on this choice chip + * @param semantics The semantics applied on this choice chip. */ class OdsChoiceChip<T>( val text: String, diff --git a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsFilterChip.kt b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsFilterChip.kt index 8bff263c0..268ca5ae1 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/chip/OdsFilterChip.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/chip/OdsFilterChip.kt @@ -51,14 +51,13 @@ import com.orange.ods.theme.OdsComponentsConfiguration * * Use Accompanist's [Flow Layouts](https://google.github.io/accompanist/flowlayout/) to wrap chips to a new line. * - * @param text Text to display in the chip. - * @param onClick called when the chip is clicked. - * @param modifier Modifier to be applied to the chip - * @param enabled When disabled, chip will not respond to user input. It will also appear visually - * disabled and disabled to accessibility services. - * @param selected Highlight the chip and display a selected icon if set to true. - * @param leadingAvatar Optional avatar at the start of the chip, preceding the content text. - * @param leadingContentDescription Content description associated to the leading element. + * @param text Text to be displayed into the chip. + * @param onClick Callback called on chip click. + * @param modifier [Modifier] to be applied to the chip. + * @param enabled Controls the enabled state of the chip. When `false`, this chip will not respond to user input. It also appears visually + * disabled and is disabled to accessibility services. + * @param selected Controls the selected state of the chip. When `true`, the chip is highlighted. + * @param leadingAvatar [OdsChipLeadingAvatar] to be displayed in a circle shape at the start of the chip, preceding the content text. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircleImage.kt b/lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircularImage.kt similarity index 83% rename from lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircleImage.kt rename to lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircularImage.kt index 8d138873e..5ae679972 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircleImage.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/content/OdsComponentCircularImage.kt @@ -21,32 +21,32 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp -abstract class OdsComponentCircleImage private constructor( +abstract class OdsComponentCircularImage internal constructor( graphicsObject: Any, contentDescription: String ) : OdsComponentImage<Nothing>(graphicsObject, contentDescription, contentScale = ContentScale.Crop) { /** - * Creates an instance of [OdsComponentCircleImage]. + * Creates an instance of [OdsComponentCircularImage]. * * @param painter The painter to draw. - * @param contentDescription The content description associated to this [OdsComponentCircleImage]. + * @param contentDescription The content description associated to this [OdsComponentCircularImage]. */ constructor(painter: Painter, contentDescription: String) : this(painter as Any, contentDescription) /** - * Creates an instance of [OdsComponentCircleImage]. + * Creates an instance of [OdsComponentCircularImage]. * * @param imageVector The image vector to draw. - * @param contentDescription The content description associated to this [OdsComponentCircleImage]. + * @param contentDescription The content description associated to this [OdsComponentCircularImage]. */ constructor(imageVector: ImageVector, contentDescription: String) : this(imageVector as Any, contentDescription) /** - * Creates an instance of [OdsComponentCircleImage]. + * Creates an instance of [OdsComponentCircularImage]. * * @param bitmap The image bitmap to draw. - * @param contentDescription The content description associated to this [OdsComponentCircleImage]. + * @param contentDescription The content description associated to this [OdsComponentCircularImage]. */ constructor(bitmap: ImageBitmap, contentDescription: String) : this(bitmap as Any, contentDescription) diff --git a/lib/src/main/java/com/orange/ods/compose/component/control/OdsCheckbox.kt b/lib/src/main/java/com/orange/ods/compose/component/control/OdsCheckbox.kt index 9d108d351..225022227 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/control/OdsCheckbox.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/control/OdsCheckbox.kt @@ -34,12 +34,11 @@ import com.orange.ods.compose.utilities.extension.enable * or off. * * - * @param checked whether Checkbox is checked or unchecked - * @param onCheckedChange callback to be invoked when checkbox is being clicked, - * therefore the change of checked state in requested. If null, then this is passive - * and relies entirely on a higher-level component to control the "checked" state. - * @param modifier Modifier to be applied to the layout of the checkbox - * @param enabled whether the component is enabled or grayed out + * @param checked Controls checked state of the checkbox. + * @param onCheckedChange Callback invoked on checkbox click. If `null`, then this is passive and relies entirely on a higher-level component to control + * the checked state. + * @param modifier [Modifier] applied to the layout of the checkbox. + * @param enabled Controls enabled state of the checkbox. When `false`, this checkbox will not be clickable. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/control/OdsRadioButton.kt b/lib/src/main/java/com/orange/ods/compose/component/control/OdsRadioButton.kt index 5ce8e4ab8..3670bf552 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/control/OdsRadioButton.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/control/OdsRadioButton.kt @@ -38,12 +38,11 @@ import com.orange.ods.compose.utilities.extension.enable * [RadioButton]s can be combined together with [Text] in the desired layout (e.g. [Column] or * [Row]) to achieve radio group-like behaviour, where the entire layout is selectable. * - * @param selected whether this radio button is selected or not - * @param onClick callback to be invoked when the RadioButton is clicked. If null, then this - * RadioButton will not handle input events, and only act as a visual indicator of [selected] state - * @param modifier Modifier to be applied to the radio button - * @param enabled Controls the enabled state of the [RadioButton]. When `false`, this button will - * not be selectable and appears disabled + * @param selected Controls the selected state of the radio button. + * @param onClick Callback invoked on radio button click. If `null`, then the radio button will not handle input events, and only act as + * a visual indicator of [selected] state. + * @param modifier [Modifier] applied to the radio button. + * @param enabled Controls the enabled state of the radio button. When `false`, the button will not be selectable and appears disabled. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/control/OdsSlider.kt b/lib/src/main/java/com/orange/ods/compose/component/control/OdsSlider.kt index 265c6e567..e432fc5d5 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/control/OdsSlider.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/control/OdsSlider.kt @@ -62,21 +62,17 @@ private const val ActiveTickColorAlpha = 0.4f * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values. * - * @param value current value of the Slider. If outside of [valueRange] provided, value will be - * coerced to this range. - * @param onValueChange lambda in which value should be updated - * @param modifier modifiers for the OdsSlider layout - * @param enabled whether or not component is enabled and can be interacted with or not - * @param valueRange range of values that Slider value can take. Passed [value] will be coerced to - * this range - * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed - * between across the whole value range. If 0, slider will behave as a continuous slider and allow - * to choose any value from the range specified. Must not be negative. - * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback - * shouldn't be used to update the slider value (use [onValueChange] for that), but rather to - * know when the user has completed selecting a new value by ending a drag or a click. - * @param leftIcon Icon displayed on the left of the slider - * @param rightIcon Icon displayed on the right of the slider + * @param value Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. + * @param onValueChange Callback invoked on slider value change. `value` should be updated here. + * @param modifier [Modifier] applied to the slider. + * @param enabled Controls the enabled state of the slider. If `false`, the user cannot interact with it. + * @param valueRange Range of values that the slider can take. Given [value] will be coerced to this range. + * @param steps If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will + * behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. + * @param onValueChangeFinished Callback invoked when value change has ended. This callback shouldn't be used to update + * the slider value (use [onValueChange] for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. + * @param startIcon [OdsSliderIcon] displayed at the start of the slider. + * @param endIcon [OdsSliderIcon] displayed at the end of the slider. */ @Composable @OdsComposable @@ -88,15 +84,15 @@ fun OdsSlider( valueRange: ClosedFloatingPointRange<Float> = 0f..1f, steps: Int = 0, onValueChangeFinished: (() -> Unit)? = null, - leftIcon: OdsSliderIcon? = null, - rightIcon: OdsSliderIcon? = null + startIcon: OdsSliderIcon? = null, + endIcon: OdsSliderIcon? = null ) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing_m)) ) { - leftIcon?.Content() + startIcon?.Content() // For the moment we cannot change the height of the slider track (need to check in jetpack compose future versions) Slider( value = value, @@ -109,7 +105,7 @@ fun OdsSlider( onValueChangeFinished = onValueChangeFinished, colors = SliderDefaults.colors(activeTickColor = OdsTheme.colors.surface.copy(alpha = ActiveTickColorAlpha)) ) - rightIcon?.Content() + endIcon?.Content() } } @@ -129,21 +125,17 @@ fun OdsSlider( * You can allow the user to choose only between predefined set of values by specifying the amount * of steps between min and max values. * - * @param value current value of the Slider. If outside of [valueRange] provided, value will be - * coerced to this range. - * @param onValueChange lambda in which value should be updated - * @param modifier modifiers for the OdsSlider layout - * @param enabled whether or not component is enabled and can be interacted with or not - * @param valueRange range of values that Slider value can take. Passed [value] will be coerced to - * this range - * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed - * between across the whole value range. If 0, slider will behave as a continuous slider and allow - * to choose any value from the range specified. Must not be negative. - * @param onValueChangeFinished lambda to be invoked when value change has ended. This callback - * shouldn't be used to update the slider value (use [onValueChange] for that), but rather to - * know when the user has completed selecting a new value by ending a drag or a click. - * @param leftIcon Icon displayed on the left of the slider - * @param rightIcon Icon displayed on the right of the slider + * @param value Current value of the slider. If outside of `valueRange` provided, value will be coerced to this range. + * @param onValueChange Callback invoked on slider value change. `value` should be updated here. + * @param modifier [Modifier] applied to the slider. + * @param enabled Controls the enabled state of the slider. If `false`, the user cannot interact with it. + * @param valueRange Range of values that the slider can take. Given [value] will be coerced to this range. + * @param steps If greater than `0`, specifies the amounts of discrete values, evenly distributed between across the whole value range. If `0`, slider will + * behave as a continuous slider and allow to choose any value from the range specified. Must not be negative. + * @param onValueChangeFinished Callback invoked when value change has ended. This callback shouldn't be used to update + * the slider value (use [onValueChange] for that), but rather to know when the user has completed selecting a new value by ending a drag or a click. + * @param startIcon [OdsSliderIcon] displayed at the start of the slider. + * @param endIcon [OdsSliderIcon] displayed at the end of the slider. */ @Composable @OdsComposable @@ -155,8 +147,8 @@ fun OdsSliderLockups( valueRange: ClosedFloatingPointRange<Float> = 0f..1f, steps: Int = 0, onValueChangeFinished: (() -> Unit)? = null, - leftIcon: OdsSliderIcon? = null, - rightIcon: OdsSliderIcon? = null + startIcon: OdsSliderIcon? = null, + endIcon: OdsSliderIcon? = null ) { val labelMinWidth = 32.dp val sideIconBottomPadding = 12.dp @@ -165,7 +157,7 @@ fun OdsSliderLockups( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing_xs)) ) { - leftIcon?.Content( + startIcon?.Content( modifier = Modifier .align(alignment = Alignment.Bottom) .padding(bottom = sideIconBottomPadding) @@ -202,7 +194,7 @@ fun OdsSliderLockups( onValueChangeFinished = onValueChangeFinished, ) } - rightIcon?.Content( + endIcon?.Content( modifier = Modifier .align(alignment = Alignment.Bottom) .padding(bottom = sideIconBottomPadding), @@ -293,8 +285,8 @@ private fun PreviewOdsSlider(@PreviewParameter(OdsSliderPreviewParameterProvider value = sliderValue.value, onValueChange = { sliderValue.value = it }, steps = 9, - leftIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_crosset_out_eye), "") else null, - rightIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_eye), "") else null, + startIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_crosset_out_eye), "") else null, + endIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_eye), "") else null, ) } @@ -306,8 +298,8 @@ private fun PreviewOdsSliderLockups(@PreviewParameter(OdsSliderPreviewParameterP value = value, valueRange = 0f..100f, onValueChange = { value = it }, - leftIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_crosset_out_eye), "") else null, - rightIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_eye), "") else null, + startIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_crosset_out_eye), "") else null, + endIcon = if (withIcons) OdsSliderIcon(painterResource(id = R.drawable.ic_eye), "") else null, ) } diff --git a/lib/src/main/java/com/orange/ods/compose/component/control/OdsSwitch.kt b/lib/src/main/java/com/orange/ods/compose/component/control/OdsSwitch.kt index 953345449..b3e377e32 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/control/OdsSwitch.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/control/OdsSwitch.kt @@ -31,12 +31,11 @@ import com.orange.ods.compose.theme.OdsTheme * * Switches toggle the state of a single item on or off. * - * @param checked whether or not this component is checked - * @param onCheckedChange callback to be invoked when Switch is being clicked, - * therefore the change of checked state is requested. If null, then this is passive - * and relies entirely on a higher-level component to control the "checked" state. - * @param modifier Modifier to be applied to the switch layout - * @param enabled whether the component is enabled or grayed out + * @param checked Controls the checked state of the switch. + * @param onCheckedChange Callback invoked on switch check. If `null`, then this is passive and relies entirely on a higher-level component to control + * the "checked" state. + * @param modifier [Modifier] applied to the switch. + * @param enabled Controls the enabled state of the switch. When `false`, the switch will not be checkable and appears disabled. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/dialog/OdsAlertDialog.kt b/lib/src/main/java/com/orange/ods/compose/component/dialog/OdsAlertDialog.kt index f3dbc920b..c526aacc2 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/dialog/OdsAlertDialog.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/dialog/OdsAlertDialog.kt @@ -32,15 +32,15 @@ import com.orange.ods.compose.text.OdsTextSubtitle1 * place them horizontally next to each other and fallback to horizontal placement if not enough * space is available. * - * @param text The text which presents the details regarding the Dialog's purpose. - * @param confirmButton The button which is meant to confirm a proposed action, thus resolving - * what triggered the dialog. - * @param modifier Modifier to be applied to the layout of the dialog. - * @param onDismissRequest Executes when the user tries to dismiss the Dialog by clicking outside - * or pressing the back button. This is not called when the dismiss button is clicked. - * @param dismissButton The button which is meant to dismiss the dialog. - * @param title The title of the Dialog which should specify the purpose of the Dialog. The title - * is not mandatory, because there may be sufficient information inside the [text]. + * @param text Text displayed into the dialog which presents the details regarding the Dialog's purpose. + * @param confirmButton [OdsAlertDialogButton] displayed into the dialog which is meant to confirm a proposed action, thus resolving what triggered + * the dialog + * @param modifier [Modifier] applied to the layout of the dialog. + * @param onDismissRequest Callback invoked when the user tries to dismiss the dialog by clicking outside or pressing the back button. This is not called + * when the dismiss button is clicked. + * @param dismissButton Button displayed into the dialog which is meant to dismiss the dialog. + * @param title Title displayed into the dialog which should specify the purpose of the dialog. The title is not mandatory, because there may be + * sufficient information inside the `text`. * @param properties Typically platform specific properties to further configure the dialog. */ @Composable diff --git a/lib/src/main/java/com/orange/ods/compose/component/divider/OdsDivider.kt b/lib/src/main/java/com/orange/ods/compose/component/divider/OdsDivider.kt index 595db4df9..b4b79ef7e 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/divider/OdsDivider.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/divider/OdsDivider.kt @@ -25,8 +25,8 @@ private const val OdsDividerAlpha = 0.12f /** * An [OdsDivider] is a thin line of 1dp thickness that groups content in lists and layouts. * - * @param modifier Modifier to be applied to the divider - * @param startIndent start offset of this line, no offset by default + * @param modifier Modifier to be applied to the divider. + * @param startIndent start offset of this line, no offset by default. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/imagetile/OdsImageTile.kt b/lib/src/main/java/com/orange/ods/compose/component/imagetile/OdsImageTile.kt index 059196f02..d7384a51a 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imagetile/OdsImageTile.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imagetile/OdsImageTile.kt @@ -47,16 +47,16 @@ import com.orange.ods.extension.orElse /** - * A image tile contains primary information which is an image. It can also contain secondary information such as text or action. Tiles have no more than two + * An image tile contains primary information which is an image. It can also contain secondary information such as text or action. Tiles have no more than two * actions. They are usually used in grids. * - * @param image Image display in the [OdsImageTile]. - * @param legendAreaDisplayType Specify how the title and the icon are displayed relatively to the image. If set to [OdsImageTileLegendAreaDisplayType.None], + * @param image [OdsImageTileImage] displayed into the tile. + * @param legendAreaDisplayType Controls how the title and the icon are displayed relatively to the image. If set to [OdsImageTileLegendAreaDisplayType.None], * no legend area will be displayed. - * @param modifier Modifier to be applied to this [OdsImageTile] - * @param title Title linked to the image. It is displayed according the [legendAreaDisplayType] value. - * @param icon The [OdsImageTileIconToggleButton] displayed next to the title - * @param onClick Callback to be invoked on tile click. + * @param modifier [Modifier] applied to the image tile. + * @param title Title displayed into the tile. It is linked to the image and displayed according to the [legendAreaDisplayType] value. + * @param icon [OdsImageTileIconToggleButton] displayed next to the title. + * @param onClick Callback invoked on tile click. */ @Composable @OdsComposable @@ -175,10 +175,10 @@ class OdsImageTileImage : OdsComponentImage<Nothing> { /** * An icon toggle button in an [OdsImageTile]. * - * @param checked Specify if icon is currently checked - * @param onCheckedChange Callback to be invoked when this icon is selected - * @param checkedIcon Icon displayed in front of the [OdsImageTile] when icon is checked - * @param uncheckedIcon Icon displayed in front of the [OdsImageTile] when icon is unchecked + * @param checked Specify if icon is currently checked. + * @param onCheckedChange Callback to be invoked when this icon is selected. + * @param checkedIcon Icon displayed in front of the [OdsImageTile] when icon is checked. + * @param uncheckedIcon Icon displayed in front of the [OdsImageTile] when icon is unchecked. */ class OdsImageTileIconToggleButton( val checked: Boolean, diff --git a/lib/src/main/java/com/orange/ods/compose/component/list/OdsListItem.kt b/lib/src/main/java/com/orange/ods/compose/component/list/OdsListItem.kt index b2826ad51..db61fd7b2 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/list/OdsListItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/list/OdsListItem.kt @@ -10,7 +10,6 @@ package com.orange.ods.compose.component.list -import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -25,85 +24,67 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon import androidx.compose.material.ListItem import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBox import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.orange.ods.R import com.orange.ods.compose.component.OdsComposable +import com.orange.ods.compose.component.content.OdsComponentCircularImage +import com.orange.ods.compose.component.content.OdsComponentContent +import com.orange.ods.compose.component.content.OdsComponentIcon +import com.orange.ods.compose.component.content.OdsComponentImage import com.orange.ods.compose.component.control.OdsCheckbox import com.orange.ods.compose.component.control.OdsRadioButton import com.orange.ods.compose.component.control.OdsSwitch import com.orange.ods.compose.component.divider.OdsDivider import com.orange.ods.compose.component.utilities.BasicPreviewParameterProvider -import com.orange.ods.compose.component.utilities.OdsImageCircleShape import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews import com.orange.ods.compose.text.OdsTextCaption -import com.orange.ods.compose.text.OdsTextSubtitle1 import com.orange.ods.compose.theme.OdsTheme -import com.orange.ods.compose.utilities.extension.getElementOfType import com.orange.ods.extension.isNotNullOrBlank import com.orange.ods.extension.orElse /** - * <a href="https://system.design.orange.com/0c1af118d/p/09a804-lists/b/669743" target="_blank">ODS Lists</a>. - * - * Lists are continuous, vertical indexes of text or images. - * - * This composable allows you to display a Checkbox, a Switch, a RadioButton, an Icon or a Caption text as trailing element. If this does not meet your - * needs, you can use the other [OdsListItem] signature which accept any Composable as trailing. - * - * To specify an icon type, use [Modifier.iconType] on [modifier] and call [OdsListItemIconScope.OdsListItemIcon] in the [icon] lambda. - * - * @param modifier Modifier to be applied to the list item - * @param text The primary text of the list item - * @param trailing The `OdsListItemTrailing` element to display at the end of the list item - * @param icon The leading supporting visual of the list item - * @param secondaryText The secondary text of the list item - * @param singleLineSecondaryText Whether the secondary text is single line - * @param overlineText The text displayed above the primary text + * Represents the various types of icon that can be displayed in an [OdsListItem]. */ -@Composable -@OdsComposable -fun OdsListItem( - modifier: Modifier = Modifier, - text: String, - trailing: OdsListItemTrailing, - icon: @Composable (OdsListItemIconScope.() -> Unit)? = null, - secondaryText: String? = null, - singleLineSecondaryText: Boolean = true, - overlineText: String? = null -) { - OdsListItem( - modifier = modifier.trailing(trailing), - text = text, - icon = icon, - secondaryText = secondaryText, - singleLineSecondaryText = singleLineSecondaryText, - overlineText = overlineText, - trailing = { OdsListItemTrailing(trailing = trailing) } - ) +enum class OdsListItemIconType { + + /** A standard icon. */ + Icon, + + /** An image cropped into a circle. */ + CircularImage, + + /** An image cropped into a square. */ + SquareImage, + + /** An image cropped into a rectangle. */ + WideImage } /** @@ -111,72 +92,70 @@ fun OdsListItem( * * Lists are continuous, vertical indexes of text or images. * - * Note: Prefer the usage of [OdsListItem] with an [OdsListItemTrailing] parameter as trailing if possible. - * @see com.orange.ods.compose.component.list.OdsListItem - * - * To make this [OdsListItem] clickable, use [Modifier.clickable]. - * To specify an icon type, use [Modifier.iconType] on [modifier] and call [OdsListItemIconScope.OdsListItemIcon] in the [icon] lambda. + * This composable allows you to display a Checkbox, a Switch, a RadioButton, an Icon or a Caption text as trailing element. If this does not meet your + * needs, you can use the other [OdsListItem] signature which accept any Composable as trailing. * - * @param modifier Modifier to be applied to the list item - * @param text The primary text of the list item - * @param icon The leading supporting visual of the list item - * @param secondaryText The secondary text of the list item - * @param singleLineSecondaryText Whether the secondary text is single line - * @param overlineText The text displayed above the primary text - * @param trailing The trailing composable. Prefer other [OdsListItem] signature with an [OdsListItemTrailing] parameter as trailing if the trailing is one of - * the following elements: Checkbox, Switch, RadioButton, Icon or Caption text + * @param text The primary text of the list item. + * @param modifier [Modifier] applied to the list item. + * @param icon The leading supporting visual of the list item. + * @param secondaryText The secondary text of the list item. + * @param singleLineSecondaryText Whether the secondary text is single line. + * @param overlineText The text displayed above the primary text. + * @param trailing The trailing content to display at the end of the list item. + * @param divider Whether or not a divider is displayed at the bottom of the list item. + * @param onClick Will be called when the user clicks the list item. This parameter only has an effect if [trailing] is [OdsListItemTrailingIcon] or `null`. */ @Composable @OdsComposable fun OdsListItem( - modifier: Modifier = Modifier, text: String, - icon: @Composable (OdsListItemIconScope.() -> Unit)? = null, + modifier: Modifier = Modifier, + icon: OdsListItemIcon? = null, secondaryText: String? = null, singleLineSecondaryText: Boolean = true, overlineText: String? = null, - trailing: @Composable (() -> Unit)? = null + trailing: OdsListItemTrailing? = null, + divider: Boolean = false, + onClick: (() -> Unit)? = null ) { - val hasText = text.isNotBlank() OdsListItem( + text = text, + textColor = OdsTheme.colors.onSurface, + textStyle = OdsTheme.typography.subtitle1, modifier = modifier, - text = { - if (hasText) { - OdsTextSubtitle1(text = text) - } - }, - hasText = hasText, icon = icon, secondaryText = secondaryText, singleLineSecondaryText = singleLineSecondaryText, overlineText = overlineText, - trailing = trailing + trailing = trailing, + divider = divider, + onClick = onClick ) } @Composable internal fun OdsListItem( + text: String, + textColor: Color, + textStyle: TextStyle, modifier: Modifier = Modifier, - text: @Composable () -> Unit, - hasText: Boolean, - icon: @Composable (OdsListItemIconScope.() -> Unit)? = null, + icon: OdsListItemIcon? = null, secondaryText: String? = null, singleLineSecondaryText: Boolean = true, overlineText: String? = null, - trailing: @Composable (() -> Unit)? = null + trailing: OdsListItemTrailing? = null, + divider: Boolean = false, + onClick: (() -> Unit)? = null ) { - val iconType = modifier.getElementOfType<OdsListItemIconTypeModifier>()?.iconType - val listItemIconScope = OdsListItemIconScope(iconType) - if (iconType == OdsListItemIconType.WideImage) { - Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { - icon?.let { listItemIconScope.it() } + val rootModifier = modifier.rootModifier(trailing, onClick) + if (icon?.iconType == OdsListItemIconType.WideImage) { + Row(modifier = rootModifier, verticalAlignment = Alignment.CenterVertically) { + icon.Content() OdsListItemInternal( - modifier = Modifier - .weight(1f) - .iconType(OdsListItemIconType.WideImage), - listItemScope = listItemIconScope, text = text, - hasText = hasText, + textColor = textColor, + textStyle = textStyle, + modifier = Modifier.weight(1f), icon = null, secondaryText = secondaryText, singleLineSecondaryText = singleLineSecondaryText, @@ -186,10 +165,10 @@ internal fun OdsListItem( } } else { OdsListItemInternal( - modifier = modifier, - listItemScope = listItemIconScope, text = text, - hasText = hasText, + textColor = textColor, + textStyle = textStyle, + modifier = rootModifier, icon = icon, secondaryText = secondaryText, singleLineSecondaryText = singleLineSecondaryText, @@ -198,31 +177,28 @@ internal fun OdsListItem( ) } - modifier.getElementOfType<OdsListItemDividerModifier>()?.let { dividerModifier -> - val startIndent = dividerModifier.startIndent - .orElse { iconType?.getDividerStartIndent() } - .orElse { dimensionResource(id = R.dimen.spacing_m) } - OdsDivider(startIndent = startIndent) + if (divider) { + OdsDivider(startIndent = getDividerStartIndent(icon?.iconType)) } } @OptIn(ExperimentalMaterialApi::class) @Composable private fun OdsListItemInternal( + text: String, + textColor: Color, + textStyle: TextStyle, modifier: Modifier = Modifier, - listItemScope: OdsListItemIconScope, - text: @Composable () -> Unit, - hasText: Boolean, - icon: @Composable (OdsListItemIconScope.() -> Unit)? = null, + icon: OdsListItemIcon? = null, secondaryText: String? = null, singleLineSecondaryText: Boolean = true, overlineText: String? = null, - trailing: @Composable (() -> Unit)? = null, + trailing: OdsListItemTrailing? = null ) { - val iconType = modifier.getElementOfType<OdsListItemIconTypeModifier>()?.iconType + val hasText = text.isNotBlank() val requiredHeight = computeRequiredHeight( hasIcon = icon != null, - iconType = iconType, + iconType = icon?.iconType, hasOverlineText = overlineText.isNotNullOrBlank(), hasText = hasText, hasSecondaryText = secondaryText.isNotNullOrBlank(), @@ -234,9 +210,7 @@ private fun OdsListItemInternal( modifier = modifier .fillMaxWidth() .requiredHeight(requiredHeight), - - icon = icon?.let { { listItemScope.it() } }, - + icon = icon?.let { { it.Content() } }, secondaryText = if (secondaryText.isNotNullOrBlank()) { { Text( @@ -252,8 +226,12 @@ private fun OdsListItemInternal( overlineText = if (overlineText.isNotNullOrBlank()) { { Text(text = overlineText, style = OdsTheme.typography.overline, color = OdsTheme.colors.onSurface.copy(alpha = 0.6f)) } } else null, - trailing = trailing, - text = text + trailing = (trailing as? OdsComponentContent<*>)?.let { { it.Content() } }, + text = { + if (hasText) { + Text(text = text, style = textStyle, color = textColor) + } + } ) } @@ -281,7 +259,7 @@ private fun computeRequiredHeight( // three-lines hasOverlineText && hasSecondaryText -> R.dimen.list_three_line_item_height // two-lines - hasOverlineText || (hasSecondaryText && singleLineSecondaryText) -> if (hasIcon || wideImage) R.dimen.list_two_line_with_icon_item_height else R.dimen.list_two_line_item_height + hasOverlineText || singleLineSecondaryText -> if (hasIcon || wideImage) R.dimen.list_two_line_with_icon_item_height else R.dimen.list_two_line_item_height // three-lines else -> R.dimen.list_three_line_item_height } @@ -289,269 +267,298 @@ private fun computeRequiredHeight( return dimensionResource(id = heightRes) } -//region OdsListItem Icon - -/** - * Displays an icon in a list item. - * - * This method throws an exception if no icon type has been specified on the [OdsListItem] modifier using the [Modifier.iconType] method. - * - * @param painter Painter of the icon - * @param contentDescription Content description of the icon - * @param tint Icon color. Has not effect if icon type is different from `OdsListItemIconType.Icon` - */ -@Composable -@OdsComposable -fun OdsListItemIconScope.OdsListItemIcon(painter: Painter, contentDescription: String? = null, tint: Color = OdsTheme.colors.onSurface) { - when (iconType) { - OdsListItemIconType.Icon -> { - Column(modifier = Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) { - Icon(painter = painter, contentDescription = contentDescription, tint = tint) - } - } - OdsListItemIconType.CircularImage -> { - OdsImageCircleShape(painter = painter, contentDescription = contentDescription) - } - OdsListItemIconType.SquareImage -> { - Image( - painter = painter, - contentDescription = contentDescription, - modifier = Modifier - .size(dimensionResource(id = R.dimen.list_square_image_size)) - .clip(MaterialTheme.shapes.medium), - contentScale = ContentScale.Crop +private fun Modifier.rootModifier(trailing: OdsListItemTrailing?, onListItemClick: (() -> Unit)?): Modifier { + return with(trailing) { + when { + this is OdsListItemTrailingCheckbox && onCheckedChange != null -> toggleable( + value = checked, + role = Role.Checkbox, + enabled = enabled, + onValueChange = onCheckedChange ) - } - OdsListItemIconType.WideImage -> { - Image( - painter = painter, - contentDescription = contentDescription, - contentScale = ContentScale.Crop, - modifier = Modifier - .height(dimensionResource(id = R.dimen.list_wide_image_height)) - .width(dimensionResource(id = R.dimen.list_wide_image_width)) + this is OdsListItemTrailingRadioButton && onClick != null -> selectable( + selected = selected, + role = Role.RadioButton, + enabled = enabled, + onClick = onClick + ) + this is OdsListItemTrailingSwitch && onCheckedChange != null -> toggleable( + value = checked, + role = Role.Switch, + enabled = enabled, + onValueChange = onCheckedChange ) + else -> onListItemClick?.let { clickable(onClick = it) }.orElse { this@rootModifier } } - null -> throw Exception("OdsListItemIcon(Painter, String?) method has been called without specifying an icon type. Please specify an icon type by calling the Modifier.iconType(OdsListItemIcon) method on the OdsList modifier.") } } /** - * An [OdsListItemIconScope] provides a scope for the icon of [OdsListItem]. - * - * @param iconType The icon type + * A leading icon in an [OdsListItem]. */ -data class OdsListItemIconScope(val iconType: OdsListItemIconType?) +class OdsListItemIcon private constructor( + internal val iconType: OdsListItemIconType, + private val graphicsObject: Any, + private val contentDescription: String, + tint: Color? +) : OdsComponentContent<Nothing>() { + + /** + * Creates an instance of [OdsListItemIcon]. + * + * @param type The type of icon. + * @param painter Painter of the icon. + * @param contentDescription The content description associated to this [OdsListItemIcon]. + */ + constructor(type: OdsListItemIconType, painter: Painter, contentDescription: String) : this(type, painter as Any, contentDescription, null) + + /** + * Creates an instance of [OdsListItemIcon]. + * + * @param type The type of icon. + * @param imageVector Image vector of the icon. + * @param contentDescription The content description associated to this [OdsListItemIcon]. + */ + constructor(type: OdsListItemIconType, imageVector: ImageVector, contentDescription: String) : this(type, imageVector as Any, contentDescription, null) + + /** + * Creates an instance of [OdsListItemIcon]. + * + * @param type The type of icon. + * @param bitmap Image bitmap of the icon. + * @param contentDescription The content description associated to this [OdsListItemIcon]. + */ + constructor(type: OdsListItemIconType, bitmap: ImageBitmap, contentDescription: String) : this(type, bitmap as Any, contentDescription, null) + + internal constructor (painter: Painter, contentDescription: String, tint: Color?) : this(OdsListItemIconType.Icon, painter as Any, contentDescription, tint) + + private val icon = when (iconType) { + OdsListItemIconType.Icon -> getIcon(tint) + OdsListItemIconType.CircularImage -> getCircularImage() + OdsListItemIconType.SquareImage -> getSquareImage() + OdsListItemIconType.WideImage -> getWideImage() + } -/** - * Represents the various types of icon that can be displayed in an [OdsListItem]. - */ -enum class OdsListItemIconType { + @Composable + override fun Content(modifier: Modifier) { + icon.Content(modifier = modifier) + } - /** A standard icon. */ - Icon, + private fun getIcon(tint: Color?): OdsComponentContent<Nothing> { + return object : OdsComponentIcon<Nothing>(graphicsObject, contentDescription) { + override val tint: Color? + @Composable + get() = tint.orElse { OdsTheme.colors.onSurface } + + @Composable + override fun Content(modifier: Modifier) { + Column(modifier = modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) { + super.Content(modifier = modifier) + } + } + } + } - /** An image cropped into a circle. */ - CircularImage, + private fun getCircularImage(): OdsComponentContent<Nothing> { + return object : OdsComponentCircularImage(graphicsObject, contentDescription) {} + } - /** An image cropped into a square. */ - SquareImage, + private fun getSquareImage(): OdsComponentContent<Nothing> { + return object : OdsComponentImage<Nothing>(graphicsObject, contentDescription, contentScale = ContentScale.Crop) { + @Composable + override fun Content(modifier: Modifier) { + super.Content( + modifier = modifier + .size(dimensionResource(id = R.dimen.list_square_image_size)) + .clip(MaterialTheme.shapes.medium) + ) + } + } + } - /** An image cropped into a rectangle. */ - WideImage + private fun getWideImage(): OdsComponentContent<Nothing> { + return object : OdsComponentImage<Nothing>(graphicsObject, contentDescription, contentScale = ContentScale.Crop) { + @Composable + override fun Content(modifier: Modifier) { + super.Content( + modifier = modifier + .height(dimensionResource(id = R.dimen.list_wide_image_height)) + .width(dimensionResource(id = R.dimen.list_wide_image_width)) + ) + } + } + } } /** - * Specifies the icon type to display in an [OdsListItem]. - * - * @param iconType The icon type + * A trailing content in an [OdsListItem]. */ -fun Modifier.iconType(iconType: OdsListItemIconType): Modifier { - return then(object : OdsListItemIconTypeModifier { - override val iconType: OdsListItemIconType - get() = iconType - }) -} +sealed interface OdsListItemTrailing /** - * A modifier that allows to configure the icon type in an [OdsListItem]. + * A trailing checkbox in an [OdsListItem]. + * + * @constructor Creates an instance of [OdsListItemTrailingCheckbox]. + * @param checked Whether Checkbox is checked or unchecked. + * @param onCheckedChange Callback to be invoked when checkbox is being clicked, + * therefore the change of checked state in requested. If null, then this is passive. + * and relies entirely on a higher-level component to control the "checked" state. + * @param enabled Whether the component is enabled or grayed out. */ -private interface OdsListItemIconTypeModifier : Modifier.Element { - val iconType: OdsListItemIconType -} - -//endregion - -//region OdsListItem Trailing - -private fun Modifier.trailing(trailing: OdsListItemTrailing): Modifier = when (trailing) { - is OdsCheckboxTrailing -> then(toggleable( - value = trailing.checked.value, - role = Role.Checkbox, - enabled = trailing.enabled, - onValueChange = { trailing.onCheckedChange() } - )) - is OdsRadioButtonTrailing<*> -> then(selectable( - selected = trailing.selected, - role = Role.RadioButton, - enabled = trailing.enabled, - onClick = { - trailing.onClick() - } - )) - is OdsSwitchTrailing -> then(toggleable( - value = trailing.checked.value, - role = Role.Switch, - enabled = trailing.enabled, - onValueChange = { trailing.onCheckedChange() } - )) - else -> this -} - -sealed class OdsListItemTrailing -class OdsCheckboxTrailing(val checked: MutableState<Boolean>, val enabled: Boolean = true, onCheckedChange: () -> Unit = {}) : OdsListItemTrailing() { - val onCheckedChange: () -> Unit = { - checked.value = !checked.value - onCheckedChange() +class OdsListItemTrailingCheckbox( + internal val checked: Boolean, + internal val onCheckedChange: ((Boolean) -> Unit)?, + internal val enabled: Boolean = true +) : OdsComponentContent<Nothing>(), OdsListItemTrailing { + + @Composable + override fun Content(modifier: Modifier) { + OdsCheckbox(modifier = modifier, checked = checked, onCheckedChange = onCheckedChange, enabled = enabled) } } -class OdsSwitchTrailing(val checked: MutableState<Boolean>, val enabled: Boolean = true, onCheckedChange: () -> Unit = {}) : OdsListItemTrailing() { - val onCheckedChange: () -> Unit = { - checked.value = !checked.value - onCheckedChange() +/** + * A trailing switch in an [OdsListItem]. + * + * @constructor Creates an instance of [OdsListItemTrailingSwitch]. + * @param checked Whether or not this component is checked. + * @param onCheckedChange Callback to be invoked when Switch is being clicked, + * therefore the change of checked state is requested. If null, then this is passive. + * and relies entirely on a higher-level component to control the "checked" state. + * @param enabled Whether the component is enabled or grayed out. + */ +class OdsListItemTrailingSwitch( + internal val checked: Boolean, + internal val onCheckedChange: ((Boolean) -> Unit)?, + internal val enabled: Boolean = true +) : OdsComponentContent<Nothing>(), OdsListItemTrailing { + + @Composable + override fun Content(modifier: Modifier) { + OdsSwitch(modifier = modifier, checked = checked, onCheckedChange = onCheckedChange, enabled = enabled) } } -class OdsRadioButtonTrailing<T>(val selectedRadio: MutableState<T>, val currentRadio: T, val enabled: Boolean = true, onClick: () -> Unit = {}) : - OdsListItemTrailing() { - val selected: Boolean - get() = selectedRadio.value == currentRadio - val onClick: () -> Unit = { - selectedRadio.value = currentRadio - onClick() +/** + * A trailing radio button in an [OdsListItem]. + * + * @constructor Creates an instance of [OdsListItemTrailingRadioButton]. + * @param selected Whether this radio button is selected or not. + * @param onClick Callback to be invoked when the radio button is clicked. If null, then this + * radio button will not handle input events, and only act as a visual indicator of [selected] state. + * @param enabled Controls the enabled state of the radio button. When `false`, this button will + * not be selectable and appears disabled. + */ +class OdsListItemTrailingRadioButton( + internal val selected: Boolean, + internal val onClick: (() -> Unit)?, + internal val enabled: Boolean = true +) : OdsComponentContent<Nothing>(), OdsListItemTrailing { + + @Composable + override fun Content(modifier: Modifier) { + OdsRadioButton(modifier = modifier, selected = selected, onClick = onClick, enabled = enabled) } } -class OdsIconTrailing(val painter: Painter, val contentDescription: String?, val modifier: Modifier = Modifier) : OdsListItemTrailing() -class OdsCaptionTrailing(val text: String) : OdsListItemTrailing() +/** + * A trailing icon in an [OdsListItem]. + */ +class OdsListItemTrailingIcon : OdsComponentIcon<Nothing>, OdsListItemTrailing { + + /** + * Creates an instance of [OdsListItemTrailingIcon]. + * + * @param painter The painter to draw. + * @param contentDescription The content description associated to this [OdsListItemTrailingIcon]. + * @param onClick Will be called when the user clicks on the icon. + */ + constructor(painter: Painter, contentDescription: String, onClick: (() -> Unit)?) : super(painter, contentDescription, onClick = onClick) + + /** + * Creates an instance of [OdsListItemTrailingIcon]. + * + * @param imageVector The image vector to draw. + * @param contentDescription The content description associated to this [OdsListItemTrailingIcon]. + * @param onClick Will be called when the user clicks on the icon. + */ + constructor(imageVector: ImageVector, contentDescription: String, onClick: (() -> Unit)?) : super( + imageVector, + contentDescription, + onClick = onClick + ) -@Composable -private fun OdsListItemTrailing(trailing: OdsListItemTrailing) { - when (trailing) { - is OdsCheckboxTrailing -> { - OdsCheckbox(checked = trailing.checked.value, onCheckedChange = { trailing.onCheckedChange() }, enabled = trailing.enabled) - } - is OdsRadioButtonTrailing<*> -> { - OdsRadioButton(selected = trailing.selected, onClick = { trailing.onClick() }, enabled = trailing.enabled) - } - is OdsSwitchTrailing -> { - OdsSwitch(checked = trailing.checked.value, onCheckedChange = { trailing.onCheckedChange() }, enabled = trailing.enabled) - } - is OdsIconTrailing -> { - with(trailing) { - Icon( - modifier = modifier.clip(RoundedCornerShape(12.dp)), - painter = painter, - tint = OdsTheme.colors.onSurface, - contentDescription = contentDescription - ) - } - } - is OdsCaptionTrailing -> { - OdsTextCaption(text = trailing.text) - } + /** + * Creates an instance of [OdsListItemTrailingIcon]. + * + * @param bitmap The image bitmap to draw. + * @param contentDescription The content description associated to this [OdsListItemTrailingIcon]. + * @param onClick Will be called when the user clicks on the icon. + */ + constructor(bitmap: ImageBitmap, contentDescription: String, onClick: (() -> Unit)?) : super(bitmap, contentDescription, onClick = onClick) + + override val tint: Color? + @Composable + get() = OdsTheme.colors.onSurface + + @Composable + override fun Content(modifier: Modifier) { + super.Content(modifier = modifier.clip(RoundedCornerShape(12.dp))) } } -//endregion - -//region OdsListItem Divider - /** - * Displays a divider at the bottom of an [OdsListItem]. + * A trailing caption in an [OdsListItem]. * - * @param startIndent The start indent of the divider + * @constructor Creates an instance of [OdsListItemTrailingCaption]. + * @param text The caption text. */ -fun Modifier.divider(startIndent: Dp? = null): Modifier { - return then(object : OdsListItemDividerModifier { - override val startIndent: Dp? - get() = startIndent - }) +class OdsListItemTrailingCaption(private val text: String) : OdsComponentContent<Nothing>(), OdsListItemTrailing { + + @Composable + override fun Content(modifier: Modifier) { + OdsTextCaption(modifier = modifier, text = text) + } } @Composable -private fun OdsListItemIconType.getDividerStartIndent(): Dp { - return when (this) { +private fun getDividerStartIndent(iconType: OdsListItemIconType?): Dp { + return when (iconType) { OdsListItemIconType.Icon, OdsListItemIconType.CircularImage -> dimensionResource(id = R.dimen.avatar_size) + dimensionResource(id = R.dimen.spacing_m).times(2) OdsListItemIconType.SquareImage -> dimensionResource(id = R.dimen.list_square_image_size) + dimensionResource(id = R.dimen.spacing_m).times(2) OdsListItemIconType.WideImage -> dimensionResource(id = R.dimen.list_wide_image_width) + dimensionResource(id = R.dimen.spacing_m) + null -> dimensionResource(id = R.dimen.spacing_m) } } -/** - * A modifier that allows to display a divider at the bottom of an [OdsListItem]. - */ -private interface OdsListItemDividerModifier : Modifier.Element { - val startIndent: Dp? -} - -//endregion - -@Composable -private fun getTrailingPreview(parameter: OdsListItemPreviewParameter): @Composable (() -> Unit)? { - val checkedState = remember { mutableStateOf(false) } - val selectedRadio = remember { mutableStateOf(0) } - val trailing = when (parameter.previewTrailingType) { - OdsCheckboxTrailing::class.java -> OdsCheckboxTrailing(checked = checkedState, enabled = true) - OdsSwitchTrailing::class.java -> OdsSwitchTrailing(checked = checkedState) - OdsRadioButtonTrailing::class.java -> OdsRadioButtonTrailing(selectedRadio = selectedRadio, currentRadio = 0) - OdsIconTrailing::class.java -> OdsIconTrailing( - painter = painterResource(id = android.R.drawable.ic_dialog_info), - contentDescription = null - ) - OdsCaptionTrailing::class.java -> OdsCaptionTrailing(text = "caption") - else -> null - } - - return trailing?.let { { OdsListItemTrailing(trailing = trailing) } } -} - @UiModePreviews.Default @Composable private fun PreviewOdsListItem(@PreviewParameter(OdsListItemPreviewParameterProvider::class) parameter: OdsListItemPreviewParameter) = Preview { with(parameter) { - val painter = when (iconType) { - OdsListItemIconType.Icon -> rememberVectorPainter(image = Icons.Default.AccountBox) - OdsListItemIconType.CircularImage, - OdsListItemIconType.SquareImage, - OdsListItemIconType.WideImage -> painterResource(R.drawable.placeholder_small) - null -> null - } - - if (previewTrailingType == null) { - OdsListItem( - modifier = Modifier.let { modifier -> - iconType?.let { modifier.iconType(it) }.orElse { modifier } - }, - text = "Text", - icon = painter?.let { { OdsListItemIcon(painter = it) } }, - secondaryText = secondaryText, - singleLineSecondaryText = singleLineSecondaryText - ) - } else { - OdsListItem( - modifier = Modifier.let { modifier -> - iconType?.let { modifier.iconType(it) }.orElse { modifier } - }, - text = "Text", - icon = painter?.let { { OdsListItemIcon(painter = it) } }, - secondaryText = secondaryText, - singleLineSecondaryText = singleLineSecondaryText, - trailing = getTrailingPreview(parameter = this) - ) - } + var trailingState by remember { mutableStateOf(false) } + OdsListItem( + text = "Text", + icon = iconType?.let { iconType -> + val painter = when (iconType) { + OdsListItemIconType.Icon -> rememberVectorPainter(image = Icons.Default.AccountBox) + OdsListItemIconType.CircularImage, + OdsListItemIconType.SquareImage, + OdsListItemIconType.WideImage -> painterResource(id = R.drawable.placeholder_small) + } + OdsListItemIcon(iconType, painter, "") + }, + secondaryText = secondaryText, + singleLineSecondaryText = singleLineSecondaryText, + trailing = when (trailingClass) { + OdsListItemTrailingCheckbox::class.java -> OdsListItemTrailingCheckbox(trailingState, { trailingState = it }) + OdsListItemTrailingSwitch::class.java -> OdsListItemTrailingSwitch(trailingState, { trailingState = it }) + OdsListItemTrailingRadioButton::class.java -> OdsListItemTrailingRadioButton(trailingState, { trailingState = !trailingState }) + OdsListItemTrailingIcon::class.java -> OdsListItemTrailingIcon(painterResource(id = android.R.drawable.ic_dialog_info), "", null) + OdsListItemTrailingCaption::class.java -> OdsListItemTrailingCaption(text = "caption") + else -> null + } + ) } } @@ -559,7 +566,7 @@ internal data class OdsListItemPreviewParameter( val secondaryText: String?, val singleLineSecondaryText: Boolean, val iconType: OdsListItemIconType?, - val previewTrailingType: Class<out OdsListItemTrailing>? + val trailingClass: Class<out OdsListItemTrailing>? ) private class OdsListItemPreviewParameterProvider : BasicPreviewParameterProvider<OdsListItemPreviewParameter>(*previewParameterValues.toTypedArray()) @@ -571,10 +578,10 @@ private val previewParameterValues: List<OdsListItemPreviewParameter> return listOf( OdsListItemPreviewParameter(null, true, null, null), - OdsListItemPreviewParameter(longSecondaryText, true, null, OdsCheckboxTrailing::class.java), - OdsListItemPreviewParameter(shortSecondaryText, true, OdsListItemIconType.Icon, OdsIconTrailing::class.java), - OdsListItemPreviewParameter(longSecondaryText, false, OdsListItemIconType.SquareImage, OdsSwitchTrailing::class.java), - OdsListItemPreviewParameter(longSecondaryText, false, OdsListItemIconType.WideImage, OdsCaptionTrailing::class.java), - OdsListItemPreviewParameter(shortSecondaryText, true, OdsListItemIconType.CircularImage, OdsRadioButtonTrailing::class.java) + OdsListItemPreviewParameter(longSecondaryText, true, null, OdsListItemTrailingCheckbox::class.java), + OdsListItemPreviewParameter(shortSecondaryText, true, OdsListItemIconType.Icon, OdsListItemTrailingIcon::class.java), + OdsListItemPreviewParameter(longSecondaryText, false, OdsListItemIconType.SquareImage, OdsListItemTrailingSwitch::class.java), + OdsListItemPreviewParameter(longSecondaryText, false, OdsListItemIconType.WideImage, OdsListItemTrailingCaption::class.java), + OdsListItemPreviewParameter(shortSecondaryText, true, OdsListItemIconType.CircularImage, OdsListItemTrailingRadioButton::class.java) ) - } \ No newline at end of file + } diff --git a/lib/src/main/java/com/orange/ods/compose/component/menu/OdsDropdownMenu.kt b/lib/src/main/java/com/orange/ods/compose/component/menu/OdsDropdownMenu.kt index e5c033c56..fee38896e 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/menu/OdsDropdownMenu.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/menu/OdsDropdownMenu.kt @@ -40,13 +40,12 @@ import com.orange.ods.compose.utilities.extension.enable * * @see androidx.compose.material.DropdownMenu * - * @param items The items of the dropdown menu - * @param expanded Whether the menu is currently open and visible to the user - * @param onDismissRequest Called when the user requests to dismiss the menu, such as by - * tapping outside the menu's bounds - * @param modifier The modifier to be applied to the menu - * @param offset [DpOffset] to be added to the position of the menu - * @param properties [PopupProperties] for further customization of the popup's behavior + * @param items List of [OdsDropdownMenuItem] displayed into the dropdown menu. + * @param expanded Controls whether the menu is currently open and visible to the user. + * @param onDismissRequest Callback invoked when the user requests to dismiss the menu, such as by tapping outside the menu's bounds. + * @param modifier [Modifier] applied to the dropdown menu. + * @param offset [DpOffset] added to the menu position. + * @param properties [PopupProperties] for further customization of the popup's behavior. */ @OdsComposable @Composable @@ -80,11 +79,11 @@ fun OdsDropdownMenu( * @property onClick Called when the menu item was clicked */ class OdsDropdownMenuItem private constructor( - val text: String, - val icon: Any?, - val enabled: Boolean, - val divider: Boolean, - val onClick: () -> Unit + private val text: String, + private val icon: Any?, + private val enabled: Boolean, + private val divider: Boolean, + private val onClick: () -> Unit ) : OdsComponentContent<OdsDropdownMenuItem.ExtraParameters>() { data class ExtraParameters(val onDismissRequest: () -> Unit) : OdsComponentContent.ExtraParameters() @@ -92,10 +91,10 @@ class OdsDropdownMenuItem private constructor( /** * Creates an instance of [OdsDropdownMenuItem]. * - * @param text The text of the menu item - * @param enabled Controls the enabled state of the menu item - when `false`, the menu item - * @param divider Whether or not a divider is displayed at the bottom of the menu item - * @param onClick Called when the menu item was clicked + * @param text The text of the menu item. + * @param enabled Controls the enabled state of the menu item - when `false`, the menu item. + * @param divider Whether or not a divider is displayed at the bottom of the menu item. + * @param onClick Called when the menu item was clicked. */ constructor( text: String, @@ -107,11 +106,11 @@ class OdsDropdownMenuItem private constructor( /** * Creates an instance of [OdsDropdownMenuItem]. * - * @param text The text of the menu item - * @param icon Optional icon to display in the menu item - * @param enabled Controls the enabled state of the menu item - when `false`, the menu item - * @param divider Whether or not a divider is displayed at the bottom of the menu item - * @param onClick Called when the menu item was clicked + * @param text The text of the menu item. + * @param icon Optional icon to display in the menu item. + * @param enabled Controls the enabled state of the menu item - when `false`, the menu item. + * @param divider Whether or not a divider is displayed at the bottom of the menu item. + * @param onClick Called when the menu item was clicked. */ constructor( text: String, @@ -124,11 +123,11 @@ class OdsDropdownMenuItem private constructor( /** * Creates an instance of [OdsDropdownMenuItem]. * - * @param text The text of the menu item - * @param icon Optional icon to display in the menu item - * @param enabled Controls the enabled state of the menu item - when `false`, the menu item - * @param divider Whether or not a divider is displayed at the bottom of the menu item - * @param onClick Called when the menu item was clicked + * @param text The text of the menu item. + * @param icon Optional icon to display in the menu item. + * @param enabled Controls the enabled state of the menu item - when `false`, the menu item. + * @param divider Whether or not a divider is displayed at the bottom of the menu item. + * @param onClick Called when the menu item was clicked. */ constructor( text: String, @@ -141,11 +140,11 @@ class OdsDropdownMenuItem private constructor( /** * Creates an instance of [OdsDropdownMenuItem]. * - * @param text The text of the menu item - * @param icon Optional icon to display in the menu item - * @param enabled Controls the enabled state of the menu item - when `false`, the menu item - * @param divider Whether or not a divider is displayed at the bottom of the menu item - * @param onClick Called when the menu item was clicked + * @param text The text of the menu item. + * @param icon Optional icon to display in the menu item. + * @param enabled Controls the enabled state of the menu item - when `false`, the menu item. + * @param divider Whether or not a divider is displayed at the bottom of the menu item. + * @param onClick Called when the menu item was clicked. */ constructor( text: String, diff --git a/lib/src/main/java/com/orange/ods/compose/component/menu/OdsExposedDropdownMenu.kt b/lib/src/main/java/com/orange/ods/compose/component/menu/OdsExposedDropdownMenu.kt index 08708229c..3ca58fe2d 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/menu/OdsExposedDropdownMenu.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/menu/OdsExposedDropdownMenu.kt @@ -35,13 +35,13 @@ import kotlinx.parcelize.Parcelize /** * <a href="https://system.design.orange.com/0c1af118d/p/07a69b-menus/b/862cbb" class="external" target="_blank">ODS menus</a>. * - * @param label The label of the text field - * @param items The [OdsExposedDropdownMenuItem]s displayed in the dropdown menu - * @param selectedItem The selected item displayed in the text field - * @param onItemSelectionChange The action executed when a dropdown menu item is selected. Can be used to get the menu value. - * @param modifier The modifier to be applied to the menu - * @param enabled controls the enabled state of the [OdsExposedDropdownMenu]. When `false`, the dropdown menu text field will - * be neither clickable nor focusable, visually it will appear in the disabled UI state + * @param label The label of the text field. + * @param items List of [OdsExposedDropdownMenuItem] displayed in the dropdown menu. + * @param selectedItem Selected item displayed into the text field. + * @param onItemSelectionChange Callback invoked when a dropdown menu item is selected. It can be used to get the menu value. + * @param modifier [Modifier] applied to the dropdown menu. + * @param enabled Controls the enabled state of the dropdown menu. When `false`, the dropdown menu text field will be neither clickable nor focusable, + * visually it will appear in the disabled state. */ @OdsComposable @OptIn(ExperimentalMaterialApi::class) diff --git a/lib/src/main/java/com/orange/ods/compose/component/navigationdrawer/OdsModalDrawer.kt b/lib/src/main/java/com/orange/ods/compose/component/navigationdrawer/OdsModalDrawer.kt index d592e8887..9be118d89 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/navigationdrawer/OdsModalDrawer.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/navigationdrawer/OdsModalDrawer.kt @@ -50,8 +50,6 @@ import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.divider.OdsDivider import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsListItemIcon -import com.orange.ods.compose.component.list.OdsListItemIconType -import com.orange.ods.compose.component.list.iconType import com.orange.ods.compose.component.utilities.BasicPreviewParameterProvider import com.orange.ods.compose.component.utilities.OdsImageCircleShape import com.orange.ods.compose.component.utilities.Preview @@ -66,11 +64,11 @@ private const val SelectedItemOpacity = 20f / 255f /** * Navigation drawers provide ergonomic access to destinations in an app. * - * @param drawerHeader content inside the header of the drawer - * @param drawerContentList content inside the body of the drawer - * @param modifier to be applied to this drawer - * @param drawerState state of the drawer - * @param content content of the rest of the UI + * @param drawerHeader content inside the header of the drawer. + * @param drawerContentList content inside the body of the drawer. + * @param modifier to be applied to this drawer. + * @param drawerState state of the drawer. + * @param content content of the rest of the UI. */ @Composable @OdsComposable @@ -122,27 +120,20 @@ private fun ModalDrawerItem(item: OdsModalDrawerItem, selected: Boolean, onClick is OdsModalDrawerListItem -> { CompositionLocalProvider(LocalRippleTheme provides OdsModalDrawerListItemRippleTheme) { OdsListItem( - text = { - Text( - text = item.text, - color = if (selected) OdsTheme.colors.primaryVariant else OdsTheme.colors.onSurface, - style = if (selected) OdsTheme.typography.subtitle2 else OdsTheme.typography.subtitle2.copy(fontWeight = FontWeight.Bold) - ) - }, - hasText = true, + text = item.text, + textColor = if (selected) OdsTheme.colors.primaryVariant else OdsTheme.colors.onSurface, + textStyle = if (selected) OdsTheme.typography.subtitle2 else OdsTheme.typography.subtitle2.copy(fontWeight = FontWeight.Bold), modifier = Modifier - .iconType(OdsListItemIconType.Icon) .selectable(selected = selected, onClick = { onClick(item) }) .let { if (selected) it.background(OdsTheme.colors.primaryVariant.copy(alpha = SelectedItemOpacity)) else it }, icon = item.icon?.let { - { - OdsListItemIcon( - painter = painterResource(id = it), - tint = if (selected) OdsTheme.colors.primaryVariant else OdsTheme.colors.onSurface - ) - } + OdsListItemIcon( + painterResource(id = it), + "", + if (selected) OdsTheme.colors.primaryVariant else OdsTheme.colors.onSurface + ) } ) } diff --git a/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsCircularProgressIndicator.kt b/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsCircularProgressIndicator.kt index 1701f8c8a..1743ba0f7 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsCircularProgressIndicator.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsCircularProgressIndicator.kt @@ -32,11 +32,10 @@ import com.orange.ods.extension.orElse * * @see androidx.compose.material.CircularProgressIndicator * - * @param modifier The modifier applied to this progress indicator - * @param progress The progress of this progress indicator, where 0.0 represents no progress and 1.0 - * represents full progress. Values outside of this range are coerced into the range. If set to `null`, - * the progress indicator is indeterminate. - * @param label The label displayed below the circular progress + * @param modifier [Modifier] applied to the progress indicator. + * @param progress Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced + * into the range. If set to `null`, the progress indicator is indeterminate. + * @param label Label displayed below the circular progress. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsLinearProgressIndicator.kt b/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsLinearProgressIndicator.kt index d7e058320..7fc90f2fb 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsLinearProgressIndicator.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/progressindicator/OdsLinearProgressIndicator.kt @@ -50,13 +50,12 @@ import com.orange.ods.extension.orElse * * @see androidx.compose.material.LinearProgressIndicator * - * @param modifier The modifier applied to this progress indicator - * @param progress The value of this progress indicator, where 0.0 represents no progress and 1.0 - * represents full progress. Values outside of this range are coerced into the range. If set to `null`, - * the progress indicator is indeterminate. - * @param label The label displayed above the linear progress - * @param icon The icon displayed above the linear progress - * @param showCurrentValue Indicates whether the current value is displayed + * @param modifier [Modifier] applied to the progress indicator. + * @param progress Progress indicator value where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced + * into the range. If set to `null`, the progress indicator is indeterminate. + * @param label Label displayed above the linear progress. + * @param icon Icon displayed above the progress indicator. + * @param showCurrentValue Controls the progress indicator current value visibility. */ @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -80,7 +79,7 @@ fun OdsLinearProgressIndicator( ) { icon?.Content() Spacer(Modifier.width(ButtonDefaults.IconSpacing)) - + if (label != null) { Text( text = label, diff --git a/lib/src/main/java/com/orange/ods/compose/component/snackbar/OdsSnackbar.kt b/lib/src/main/java/com/orange/ods/compose/component/snackbar/OdsSnackbar.kt index 693baf628..363b36df5 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/snackbar/OdsSnackbar.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/snackbar/OdsSnackbar.kt @@ -26,26 +26,73 @@ import com.orange.ods.R import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.button.OdsTextButton import com.orange.ods.compose.component.button.OdsTextButtonStyle +import com.orange.ods.compose.component.content.OdsComponentContent import com.orange.ods.compose.component.utilities.BasicPreviewParameterProvider import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews import com.orange.ods.compose.theme.OdsDisplaySurface /** - * <a href="https://system.design.orange.com/0c1af118d/p/887440-toast--snackbars/b/043ece" class="external" target="_blank">ODS snackbar</a>. + * Host for [OdsSnackbar]s to be used in [Scaffold] to properly show, hide and dismiss items based + * on Material specification and the [hostState]. + * The [OdsSnackbarHost] uses the padding provided by the Orange Design System. + * + * @see androidx.compose.material.SnackbarHost + * + * @param hostState State of this component to read and show [OdsSnackbar]s accordingly. + * @param modifier [Modifier] applied to the snackbar host. + * @param snackbar Instance of the [OdsSnackbar] to be shown at the appropriate time with appearance based on + * the [SnackbarData] provided as a param. + */ +@OdsComposable +@Composable +fun OdsSnackbarHost( + hostState: SnackbarHostState, + modifier: Modifier = Modifier, + snackbar: (SnackbarData) -> OdsSnackbar = { OdsSnackbar(it) } +) { + SnackbarHost( + modifier = modifier.padding(dimensionResource(id = R.dimen.spacing_s)), + hostState = hostState, + snackbar = { snackbar(it).Content(modifier = Modifier) } + ) +} + +/** + * A snackbar in an [OdsSnackbarHost]. * - * @see androidx.compose.material.Snackbar + * @constructor Creates an instance of [OdsSnackbar]. + * @param data Data used to create the snackbar. + * @param actionOnNewLine Whether or not the action should be put on a separate line. Recommended for action with long action text. + * @param onActionClick Callback invoked when the action button is clicked. + */ +class OdsSnackbar(private val data: SnackbarData, private val actionOnNewLine: Boolean = false, private val onActionClick: () -> Unit = {}) : + OdsComponentContent<Nothing>() { + + @Composable + override fun Content(modifier: Modifier) { + OdsSnackbar( + modifier = modifier, + message = data.message, + actionLabel = data.actionLabel, + actionOnNewLine = actionOnNewLine, + onActionClick = onActionClick + ) + } +} + +/** + * Please directly use [OdsSnackbarHost] to display a snackbar. * - * @param message text displayed in the snackbar - * @param modifier modifiers for the Snackbar layout - * @param actionLabel if set, it displays an [OdsTextButton] with the given [actionLabel] as an action of the snackbar. - * @param actionOnNewLine whether or not action should be put on the separate line. Recommended - * for action with long action text - * @param onActionClick executed on action button click. + * @param message Text displayed into the snackbar. + * @param modifier [Modifier] applied to the snackbar layout. + * @param actionLabel If set, it displays an [OdsTextButton] with the given [actionLabel] as an action of the snackbar. + * @param actionOnNewLine Whether or not action should be put on a separate line. Recommended for action with long action text. + * @param onActionClick Callback invoked when the action button is clicked. */ @Composable @OdsComposable -fun OdsSnackbar( +private fun OdsSnackbar( message: String, modifier: Modifier = Modifier, actionLabel: String? = null, @@ -68,67 +115,31 @@ fun OdsSnackbar( ) { Text(text = message) } } -/** - * <a href="https://system.design.orange.com/0c1af118d/p/887440-toast--snackbars/b/043ece" class="external" target="_blank">ODS snackbar</a>. - * - * @see androidx.compose.material.Snackbar - * - * @param snackbarData data about the current snackbar showing via [SnackbarHostState] - * @param modifier modifiers for the Snackbar layout - * @param actionOnNewLine whether or not action should be put on the separate line. Recommended - * for action with long action text - * @param onActionClick executed on action button click. - */ +@UiModePreviews.Default @Composable -fun OdsSnackbar( - snackbarData: SnackbarData, - modifier: Modifier = Modifier, - actionOnNewLine: Boolean = false, - onActionClick: () -> Unit = {} -) { - OdsSnackbar( - modifier = modifier, - message = snackbarData.message, - actionLabel = snackbarData.actionLabel, - actionOnNewLine = actionOnNewLine, - onActionClick = onActionClick - ) +private fun PreviewOdsSnackbar(@PreviewParameter(OdsSnackbarPreviewParameterProvider::class) parameter: OdsSnackbarPreviewParameter) = Preview { + with(parameter) { + OdsSnackbar( + message = "This is the message of the snackbar.", + actionLabel = if (action) "Action" else null, + actionOnNewLine = actionOnNewLine + ) + } } -/** - * Host for [OdsSnackbar]s to be used in [Scaffold] to properly show, hide and dismiss items based - * on material specification and the [hostState]. - * The [OdsSnackbarHost] use the padding provided by the Orange Design System. - * - * @see androidx.compose.material.SnackbarHost - * - * @param hostState state of this component to read and show [OdsSnackbar]s accordingly - * @param modifier optional modifier for this component - * @param snackbar the instance of the [OdsSnackbar] to be shown at the appropriate time with - * appearance based on the [SnackbarData] provided as a param - */ -@OdsComposable -@Composable -fun OdsSnackbarHost( - hostState: SnackbarHostState, - modifier: Modifier = Modifier, - snackbar: @Composable (SnackbarData) -> Unit = { OdsSnackbar(snackbarData = it) } -) { - SnackbarHost( - modifier = modifier.padding(dimensionResource(id = R.dimen.spacing_s)), - hostState = hostState, - snackbar = snackbar - ) -} +private data class OdsSnackbarPreviewParameter( + val action: Boolean, + val actionOnNewLine: Boolean = false +) -@UiModePreviews.Default -@Composable -private fun PreviewOdsSnackbar(@PreviewParameter(OdsSnackbarPreviewParameterProvider::class) actionOnNewLine: Boolean) = Preview { - OdsSnackbar( - message = "This is the message of the snackbar.", - actionLabel = "Action", - actionOnNewLine = actionOnNewLine - ) -} +private class OdsSnackbarPreviewParameterProvider : + BasicPreviewParameterProvider<OdsSnackbarPreviewParameter>(*previewParameterValues.toTypedArray()) -private class OdsSnackbarPreviewParameterProvider : BasicPreviewParameterProvider<Boolean>(false, true) +private val previewParameterValues: List<OdsSnackbarPreviewParameter> + get() { + return listOf( + OdsSnackbarPreviewParameter(action = false), + OdsSnackbarPreviewParameter(action = true), + OdsSnackbarPreviewParameter(action = true, actionOnNewLine = true), + ) + } \ No newline at end of file diff --git a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsLeadingIconTab.kt b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsLeadingIconTab.kt index d61ddbcb3..d51fe444d 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsLeadingIconTab.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsLeadingIconTab.kt @@ -35,11 +35,11 @@ import com.orange.ods.compose.theme.OdsTheme * * This should typically be used inside of an [OdsTabRow]. * - * @param selected whether this tab is selected or not - * @param onClick the callback to be invoked when this tab is selected - * @param text the text label displayed in this tab - * @param icon the icon displayed in this tab - * @param modifier optional [Modifier] for this tab + * @param selected whether this tab is selected or not. + * @param onClick the callback to be invoked when this tab is selected. + * @param text the text label displayed in this tab. + * @param icon the icon displayed in this tab. + * @param modifier optional [Modifier] for this tab. * @param enabled controls the enabled state of this tab. When `false`, this tab will not * be clickable and will appear disabled to accessibility services. * diff --git a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsScrollableTabRow.kt b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsScrollableTabRow.kt index cd59df52e..e78640c40 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsScrollableTabRow.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsScrollableTabRow.kt @@ -33,8 +33,8 @@ import com.orange.ods.compose.theme.OdsTheme * An OdsScrollableTabRow is a Jetpack Compose [ScrollableTabRow] with the Orange design and theme. * @see ScrollableTabRow documentation * - * @param selectedTabIndex the index of the currently selected tab - * @param modifier optional [Modifier] for this TabRow + * @param selectedTabIndex the index of the currently selected tab. + * @param modifier optional [Modifier] for this TabRow. * @param tabs the tabs inside this TabRow. Typically this will be multiple [Tab]s. Each element * inside this lambda will be measured and placed evenly across the TabRow, each taking up equal * space. Use [OdsTab] to display Orange styled tabs. diff --git a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTab.kt b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTab.kt index 5b39ca7a7..9781b30a4 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTab.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTab.kt @@ -35,13 +35,13 @@ import com.orange.ods.compose.theme.OdsTheme * * This should typically be used inside of an [OdsTabRow]. * - * @param selected whether this tab is selected or not - * @param onClick the callback to be invoked when this tab is selected - * @param modifier optional [Modifier] for this tab + * @param selected whether this tab is selected or not. + * @param onClick the callback to be invoked when this tab is selected. + * @param modifier optional [Modifier] for this tab. * @param enabled controls the enabled state of this tab. When `false`, this tab will not * be clickable and will appear disabled to accessibility services. * @param text the text label displayed in this tab. Always displayed in uppercase. - * @param icon the optional icon displayed in this tab + * @param icon the optional icon displayed in this tab. * * @see OdsLeadingIconTab */ diff --git a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTabRow.kt b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTabRow.kt index acf38b17b..33dd878b2 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTabRow.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/tab/OdsTabRow.kt @@ -33,8 +33,8 @@ import com.orange.ods.compose.theme.OdsTheme * An OdsTabRow is a Jetpack Compose [TabRow] to which we applied the Orange design and theme. * @see TabRow documentation * - * @param selectedTabIndex the index of the currently selected tab - * @param modifier optional [Modifier] for this OdsTabRow + * @param selectedTabIndex the index of the currently selected tab. + * @param modifier optional [Modifier] for this OdsTabRow. * @param tabs the tabs inside this TabRow. Typically this will be multiple [Tab]s. Each element * inside this lambda will be measured and placed evenly across the TabRow, each taking up equal * space. Use [OdsTab] to display Orange styled tabs. diff --git a/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextField.kt b/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextField.kt index 750afc82b..5837998cd 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextField.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextField.kt @@ -43,11 +43,11 @@ import com.orange.ods.theme.OdsComponentsConfiguration * @see [OdsOutlinedTextField] * @see [OdsFilledTextField] * - * @param value the input text to be shown in the text field + * @param value the input text to be shown in the text field. * @param onValueChange the callback that is triggered when the input service updates the text. An * updated text comes as a parameter of the callback - * @param trailing The `OdsTextFieldTrailing` element to display at the end of the text field - * @param modifier a [Modifier] for this text field + * @param trailing The `OdsTextFieldTrailing` element to display at the end of the text field. + * @param modifier a [Modifier] for this text field. * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will * be neither editable nor focusable, the input of the text field will not be selectable, * visually text field will appear in the disabled UI state @@ -61,11 +61,11 @@ import com.orange.ods.theme.OdsComponentsConfiguration * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1] * @param leadingIcon the optional leading icon painter to be displayed at the beginning of the text field * container - * @param leadingIconContentDescription the optional content description for the leading icon - * @param onLeadingIconClick the optional action executed on leading icon click + * @param leadingIconContentDescription the optional content description for the leading icon. + * @param onLeadingIconClick the optional action executed on leading icon click. * @param isError indicates if the text field's current value is in error state. If set to * true, the label, bottom indicator and trailing icon by default will be displayed in error color - * @param errorMessage displayed when the [OdsTextField] is in error + * @param errorMessage displayed when the [OdsTextField] is in error. * @param visualTransformation transforms the visual representation of the input [value]. * For example, you can use [androidx.compose.ui.text.input.PasswordVisualTransformation] to create a password * text field. By default no visual transformation is applied @@ -137,10 +137,10 @@ fun OdsTextField( * * If you are looking for an outlined version, see [OdsOutlinedTextField]. * - * @param value the input text to be shown in the text field + * @param value the input text to be shown in the text field. * @param onValueChange the callback that is triggered when the input service updates the text. An * updated text comes as a parameter of the callback - * @param modifier a [Modifier] for this text field + * @param modifier a [Modifier] for this text field. * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will * be neither editable nor focusable, the input of the text field will not be selectable, * visually text field will appear in the disabled UI state @@ -154,13 +154,13 @@ fun OdsTextField( * the input text is empty. The default text style for internal [Text] is [Typography.subtitle1] * @param leadingIcon the optional leading icon painter to be displayed at the beginning of the text field * container - * @param leadingIconContentDescription the optional content description for the leading icon - * @param onLeadingIconClick the optional action executed on leading icon click + * @param leadingIconContentDescription the optional content description for the leading icon. + * @param onLeadingIconClick the optional action executed on leading icon click. * @param trailing The trailing composable. Prefer other [OdsTextField] signature with an [OdsTextFieldTrailing] parameter as trailing if the trailing is one of * the following elements: Text, Icon or Dropdown menu arrow * @param isError indicates if the text field's current value is in error state. If set to * true, the label, bottom indicator and trailing icon by default will be displayed in error color - * @param errorMessage displayed when the [OdsTextField] is in error + * @param errorMessage displayed when the [OdsTextField] is in error. * @param visualTransformation transforms the visual representation of the input [value]. * For example, you can use [androidx.compose.ui.text.input.PasswordVisualTransformation] to create a password * text field. By default no visual transformation is applied diff --git a/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextFieldsCommon.kt b/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextFieldsCommon.kt index 86b61fe67..a1fbad12a 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextFieldsCommon.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/textfield/OdsTextFieldsCommon.kt @@ -37,10 +37,10 @@ import com.orange.ods.compose.theme.OdsTheme /** * A character counter to display below the text field * - * @param valueLength the text field current value length + * @param valueLength the text field current value length. * @param maxChars the maximum of characters to display in the counter. Note: the limitation behavior should be managed by yourself * in the `onValueChange` method of the text field. - * @param enabled set to false to display the text with a disabled color + * @param enabled set to false to display the text with a disabled color. */ @Composable @OdsComposable diff --git a/lib/src/main/java/com/orange/ods/compose/component/textfield/password/OdsPasswordTextField.kt b/lib/src/main/java/com/orange/ods/compose/component/textfield/password/OdsPasswordTextField.kt index 97afe0200..cd583bab6 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/textfield/password/OdsPasswordTextField.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/textfield/password/OdsPasswordTextField.kt @@ -43,10 +43,10 @@ import com.orange.ods.compose.component.utilities.UiModePreviews * * @see [OdsTextField] * - * @param value the input text to be shown in the text field + * @param value the input text to be shown in the text field. * @param onValueChange the callback that is triggered when the input service updates the text. An * updated text comes as a parameter of the callback - * @param modifier a [Modifier] for this text field + * @param modifier a [Modifier] for this text field. * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will * be neither editable nor focusable, the input of the text field will not be selectable, * visually text field will appear in the disabled UI state @@ -61,7 +61,7 @@ import com.orange.ods.compose.component.utilities.UiModePreviews * @param visualisationIcon If `true`, an eye icon will be display to allow showing/hiding password. * @param isError indicates if the text field's current value is in error state. If set to * true, the label, bottom indicator and trailing icon by default will be displayed in error color - * @param errorMessage displayed when the [OdsTextField] is in error + * @param errorMessage displayed when the [OdsTextField] is in error. * @param keyboardOptions software keyboard options that contains configuration such as * [KeyboardType] and [ImeAction]. * @param keyboardActions when the input service emits an IME action, the corresponding callback diff --git a/lib/src/main/java/com/orange/ods/compose/component/textfield/search/OdsSearchTextField.kt b/lib/src/main/java/com/orange/ods/compose/component/textfield/search/OdsSearchTextField.kt index f45a36e68..499d0d167 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/textfield/search/OdsSearchTextField.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/textfield/search/OdsSearchTextField.kt @@ -40,10 +40,10 @@ import com.orange.ods.compose.theme.OdsTheme * OdsSearchTextField component allows to display a text field in the top app bar of a search screen. * @see androidx.compose.material.TextField * - * @param value the input text to be shown in the text field + * @param value the input text to be shown in the text field. * @param onValueChange the callback that is triggered when the input service updates the text. An * updated text comes as a parameter of the callback - * @param modifier a [Modifier] for this text field + * @param modifier a [Modifier] for this text field. * @param placeholder the placeholder to be displayed in the text field when the input text is empty. * */ diff --git a/lib/src/main/java/com/orange/ods/compose/component/utilities/Image.kt b/lib/src/main/java/com/orange/ods/compose/component/utilities/Image.kt index fc59d6ffe..f600146d3 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/utilities/Image.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/utilities/Image.kt @@ -26,11 +26,11 @@ import com.orange.ods.R /** * Displays an image in a disc * - * @param painter to draw - * @param modifier Modifier applied to the image - * @param contentDescription Content description of the image - * @param circleSize The size of the final image, 40x40 by default - * @param alpha Optional opacity to be applied to the Painter when it is rendered onscreen the default renders the Painter completely opaque + * @param painter to draw. + * @param modifier Modifier applied to the image. + * @param contentDescription Content description of the image. + * @param circleSize The size of the final image, 40x40 by default. + * @param alpha Optional opacity to be applied to the Painter when it is rendered onscreen the default renders the Painter completely opaque. */ @Composable fun OdsImageCircleShape( diff --git a/lib/src/main/java/com/orange/ods/compose/theme/OdsTheme.kt b/lib/src/main/java/com/orange/ods/compose/theme/OdsTheme.kt index 069cfb26d..f73ebfcde 100644 --- a/lib/src/main/java/com/orange/ods/compose/theme/OdsTheme.kt +++ b/lib/src/main/java/com/orange/ods/compose/theme/OdsTheme.kt @@ -73,8 +73,8 @@ object OdsTheme { * ODS theme is the theme to apply to your screens in an Orange Jetpack Compose application. * * @param themeConfiguration The configuration of the OdsTheme: colors, typography... - * @param darkThemeEnabled Indicates whether the dark theme is enabled or not - * @param content The content of the theme + * @param darkThemeEnabled Indicates whether the dark theme is enabled or not. + * @param content The content of the theme. */ @Composable fun OdsTheme(