From 63b2fd29032af6eb8fd7ea17a30febd62096b760 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Tue, 11 Apr 2023 17:31:27 +0200 Subject: [PATCH 001/112] [#473] Add imageLists v1 --- .../orange/ods/app/ui/components/Component.kt | 7 + .../app/ui/components/ComponentDemoScreen.kt | 3 +- .../imagelists/ComponentImageLists.kt | 55 ++++++++ .../ImageListsCustomizationState.kt | 38 +++++ app/src/main/res/values/strings.xml | 4 + .../component/imagelist/OdsImageList.kt | 130 ++++++++++++++++++ 6 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt create mode 100644 app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt create mode 100644 lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt 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 de5d0eba8..95f56d84b 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 @@ -125,6 +125,13 @@ sealed class Component( composableName = OdsComposable.OdsFloatingActionButton.name ) + object ImageLists : Component( + R.string.component_image_lists, + R.drawable.il_fab, + null, + R.string.component_floating_action_buttons_description + ) + object Lists : Component( R.string.component_lists, R.drawable.il_lists, diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt index 94c0278e1..395f0d31e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt @@ -18,6 +18,7 @@ import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigati import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes import com.orange.ods.app.ui.components.dialogs.ComponentDialog import com.orange.ods.app.ui.components.floatingactionbuttons.ComponentFloatingActionButton +import com.orange.ods.app.ui.components.imagelists.ComponentImageLists import com.orange.ods.app.ui.components.lists.ComponentLists import com.orange.ods.app.ui.components.navigationdrawers.ComponentModalDrawers import com.orange.ods.app.ui.components.radiobuttons.ComponentRadioButtons @@ -38,6 +39,7 @@ fun ComponentDemoScreen(componentId: Long) { Component.Checkboxes -> ComponentCheckboxes() Component.Dialogs -> ComponentDialog() Component.FloatingActionButtons -> ComponentFloatingActionButton() + Component.ImageLists -> ComponentImageLists() Component.Lists -> ComponentLists() Component.ModalDrawers -> ComponentModalDrawers() Component.RadioButtons -> ComponentRadioButtons() @@ -48,5 +50,4 @@ fun ComponentDemoScreen(componentId: Long) { else -> {} } } - } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt b/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt new file mode 100644 index 000000000..5cc899441 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt @@ -0,0 +1,55 @@ +/* + * + * 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.components.imagelists + +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.rememberBottomSheetScaffoldState +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.orange.ods.app.R +import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold +import com.orange.ods.compose.component.imagelist.OdsImageList +import com.orange.ods.compose.component.list.OdsListItem +import com.orange.ods.compose.component.list.OdsSwitchTrailing + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ComponentImageLists() { + val imageListsCustomizationState = rememberImageListsCustomizationState() + + with(imageListsCustomizationState) { + if (!hasText) { + sideIcons.value = false + } + ComponentCustomizationBottomSheetScaffold( + bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), + bottomSheetContent = { + OdsListItem( + text = stringResource(id = R.string.component_element_text), + trailing = OdsSwitchTrailing(checked = textDisplayed) + ) + OdsListItem( + text = stringResource(id = R.string.component_element_icon), + trailing = OdsSwitchTrailing(checked = sideIcons, enabled = hasText) + ) + }) { + + OdsImageList( + image = painterResource(id = R.drawable.placeholder), + icon = if (hasSideIcons) painterResource(id = R.drawable.ic_heart) else null, + title = if (hasText) stringResource(id = R.string.component_image_lists) else null + ) + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt new file mode 100644 index 000000000..e3caebf2e --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt @@ -0,0 +1,38 @@ +/* + * + * 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.components.imagelists + +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 + +@Composable +fun rememberImageListsCustomizationState( + sideIcons: MutableState = rememberSaveable { mutableStateOf(false) }, + textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } +) = + remember(sideIcons, textDisplayed) { + ImageListsCustomizationState(sideIcons, textDisplayed) + } + +class ImageListsCustomizationState( + val sideIcons: MutableState, + val textDisplayed: MutableState +) { + val hasSideIcons + get() = sideIcons.value + + val hasText + get() = textDisplayed.value + +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9614be3a7..5fe7383a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -202,6 +202,10 @@ Mini Full screen width + + Lists: image + A Floating Action Button (FAB) is an interactive element that enables the user to initiate an immediate action. There are a number of different options for how they can be presented. + Menus 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. diff --git a/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt b/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt new file mode 100644 index 000000000..cfc530cdd --- /dev/null +++ b/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt @@ -0,0 +1,130 @@ +/* + * + * 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.compose.component.imagelist + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import com.orange.ods.R +import com.orange.ods.compose.component.OdsComponentApi +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.OdsTheme + +/** + * + * @param image image display in the ImageList. + * @param modifier to be applied to this ImageList + * @param imageContentDescription Optional image content description. + * @param icon Optional icon display in front of the test. + * @param iconContentDescription Optional icon content description.. + * @param title text display in the image + */ +@Composable +@OdsComponentApi +fun OdsImageList( + image: Painter, + modifier: Modifier = Modifier, + imageContentDescription: String? = null, + icon: Painter? = null, + iconContentDescription: String? = null, + title: String? = null +) { + Box( + modifier = modifier + .fillMaxWidth() + ) { + Image( + painter = image, + contentDescription = imageContentDescription, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + ) + title?.let { + Surface( + color = Color.Black.copy(alpha = 0.5f), + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + ) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier.padding(top = dimensionResource(id = R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_m)) + ) { + Text( + text = it, + modifier.padding(start = dimensionResource(id = R.dimen.spacing_s)), + color = Color.White, + style = OdsTheme.typography.subtitle1 + ) + icon?.let { + Icon( + painter = it, + modifier = modifier.padding(end = dimensionResource(id = R.dimen.spacing_s)), + contentDescription = iconContentDescription, + tint = Color.White + ) + } + } + } + } + } +} + +@UiModePreviews.Default +@Composable +private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterProvider::class) parameter: OdsImageListPreviewParameter) = + Preview { + OdsImageList( + image = painterResource(id = parameter.image), + icon = parameter.icon?.let { painterResource(id = it) }, + title = parameter.title + ) + } + +private data class OdsImageListPreviewParameter( + val image: Int, + val title: String?, + val icon: Int? +) + +private class OdsImageListPreviewParameterProvider : + BasicPreviewParameterProvider(*previewParameterValues.toTypedArray()) + +private val previewParameterValues: List + get() { + val title = "Subtitle 1" + val image = R.drawable.placeholder + val icon = R.drawable.ic_check + + return listOf( + OdsImageListPreviewParameter(image, title = null, icon), + OdsImageListPreviewParameter(image, title, icon = null), + OdsImageListPreviewParameter(image, title, icon) + ) + } \ No newline at end of file From 1d2799ca5b50f7f3b15ab5bd563d78f584cc922a Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Wed, 12 Apr 2023 15:30:51 +0200 Subject: [PATCH 002/112] [#473] Add component image item in demo app --- .../orange/ods/app/ui/components/Component.kt | 4 +- .../app/ui/components/ComponentDemoScreen.kt | 4 +- .../imageitem/ComponentImageItem.kt | 82 +++++++++++++++++++ .../ImageItemCustomizationState.kt} | 8 +- .../imagelists/ComponentImageLists.kt | 55 ------------- app/src/main/res/values/strings.xml | 4 +- changelog.md | 6 ++ .../{OdsImageList.kt => OdsImageItem.kt} | 46 +++++++---- 8 files changed, 129 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt rename app/src/main/java/com/orange/ods/app/ui/components/{imagelists/ImageListsCustomizationState.kt => imageitem/ImageItemCustomizationState.kt} (81%) delete mode 100644 app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt rename lib/src/main/java/com/orange/ods/compose/component/imagelist/{OdsImageList.kt => OdsImageItem.kt} (71%) 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 95f56d84b..6395f8c64 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 @@ -126,10 +126,10 @@ sealed class Component( ) object ImageLists : Component( - R.string.component_image_lists, + R.string.component_image_item, R.drawable.il_fab, null, - R.string.component_floating_action_buttons_description + R.string.component_image_item_description ) object Lists : Component( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt index 395f0d31e..44c9f6cb1 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt @@ -18,7 +18,7 @@ import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigati import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes import com.orange.ods.app.ui.components.dialogs.ComponentDialog import com.orange.ods.app.ui.components.floatingactionbuttons.ComponentFloatingActionButton -import com.orange.ods.app.ui.components.imagelists.ComponentImageLists +import com.orange.ods.app.ui.components.imageitem.ComponentImageItem import com.orange.ods.app.ui.components.lists.ComponentLists import com.orange.ods.app.ui.components.navigationdrawers.ComponentModalDrawers import com.orange.ods.app.ui.components.radiobuttons.ComponentRadioButtons @@ -39,7 +39,7 @@ fun ComponentDemoScreen(componentId: Long) { Component.Checkboxes -> ComponentCheckboxes() Component.Dialogs -> ComponentDialog() Component.FloatingActionButtons -> ComponentFloatingActionButton() - Component.ImageLists -> ComponentImageLists() + Component.ImageLists -> ComponentImageItem() Component.Lists -> ComponentLists() Component.ModalDrawers -> ComponentModalDrawers() Component.RadioButtons -> ComponentRadioButtons() diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt new file mode 100644 index 000000000..f1fdfa16d --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -0,0 +1,82 @@ +/* + * + * 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.components.imageitem + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.rememberBottomSheetScaffoldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.rememberAsyncImagePainter +import com.orange.ods.app.R +import com.orange.ods.app.domain.recipes.LocalRecipes +import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold +import com.orange.ods.compose.component.imagelist.OdsImageList +import com.orange.ods.compose.component.list.OdsListItem +import com.orange.ods.compose.component.list.OdsSwitchTrailing + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ComponentImageItem() { + val imageItemCustomizationState = rememberImageItemCustomizationState() + val iconCheckedState = rememberSaveable { mutableStateOf(false) } + val recipes = LocalRecipes.current + val recipe = rememberSaveable { recipes.random() } + + with(imageItemCustomizationState) { + if (!hasText) { + sideIcons.value = false + } + ComponentCustomizationBottomSheetScaffold( + bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), + bottomSheetContent = { + OdsListItem( + text = stringResource(id = R.string.component_element_text), + trailing = OdsSwitchTrailing(checked = textDisplayed) + ) + OdsListItem( + text = stringResource(id = R.string.component_element_icon), + trailing = OdsSwitchTrailing(checked = sideIcons, enabled = hasText) + ) + }) { + Column( + modifier = Modifier + .size(400.dp, 250.dp) + .padding(dimensionResource(id = R.dimen.spacing_m)) + ) { + OdsImageList( + image = rememberAsyncImagePainter( + model = recipe.imageUrl, + placeholder = painterResource(id = R.drawable.placeholder), + error = painterResource(id = R.drawable.placeholder) + ), + icon = if (hasSideIcons && !iconCheckedState.value) painterResource(id = R.drawable.ic_heart_outlined) + else if (hasSideIcons && iconCheckedState.value) painterResource(id = R.drawable.ic_heart) else null, + title = if (hasText) recipe.title else null, + checkedIcon = iconCheckedState.value, + iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + onCheckedChange = { checked -> iconCheckedState.value = checked } + ) + } + + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt similarity index 81% rename from app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt rename to app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index e3caebf2e..9a3c1ca26 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ImageListsCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -8,7 +8,7 @@ * / */ -package com.orange.ods.app.ui.components.imagelists +package com.orange.ods.app.ui.components.imageitem import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -17,15 +17,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @Composable -fun rememberImageListsCustomizationState( +fun rememberImageItemCustomizationState( sideIcons: MutableState = rememberSaveable { mutableStateOf(false) }, textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } ) = remember(sideIcons, textDisplayed) { - ImageListsCustomizationState(sideIcons, textDisplayed) + ImageItemCustomizationState(sideIcons, textDisplayed) } -class ImageListsCustomizationState( +class ImageItemCustomizationState( val sideIcons: MutableState, val textDisplayed: MutableState ) { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt b/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt deleted file mode 100644 index 5cc899441..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/components/imagelists/ComponentImageLists.kt +++ /dev/null @@ -1,55 +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.components.imagelists - -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import com.orange.ods.app.R -import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold -import com.orange.ods.compose.component.imagelist.OdsImageList -import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsSwitchTrailing - - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun ComponentImageLists() { - val imageListsCustomizationState = rememberImageListsCustomizationState() - - with(imageListsCustomizationState) { - if (!hasText) { - sideIcons.value = false - } - ComponentCustomizationBottomSheetScaffold( - bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), - bottomSheetContent = { - OdsListItem( - text = stringResource(id = R.string.component_element_text), - trailing = OdsSwitchTrailing(checked = textDisplayed) - ) - OdsListItem( - text = stringResource(id = R.string.component_element_icon), - trailing = OdsSwitchTrailing(checked = sideIcons, enabled = hasText) - ) - }) { - - OdsImageList( - image = painterResource(id = R.drawable.placeholder), - icon = if (hasSideIcons) painterResource(id = R.drawable.ic_heart) else null, - title = if (hasText) stringResource(id = R.string.component_image_lists) else null - ) - - } - } -} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fe7383a9..70a2c7ac9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -203,8 +203,8 @@ Full screen width - Lists: image - A Floating Action Button (FAB) is an interactive element that enables the user to initiate an immediate action. There are a number of different options for how they can be presented. + Image item + The Image item component is the individual image display used in the Image list module, and it allows to display image lists or media categories. Menus diff --git a/changelog.md b/changelog.md index 1f6f6eb58..cb28e9343 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,12 @@ 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). +## [Unreleased](https://github.com/Orange-OpenSource/ods-android/compare/0.13.0...develop) + +### Added + +- \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) + ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 ### Added diff --git a/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt b/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt similarity index 71% rename from lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt rename to lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt index cfc530cdd..fd523c2d9 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageList.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Icon import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -30,14 +29,18 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.PreviewParameter import com.orange.ods.R import com.orange.ods.compose.component.OdsComponentApi +import com.orange.ods.compose.component.button.OdsIconToggleButton 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 import com.orange.ods.compose.theme.OdsTheme /** * * @param image image display in the ImageList. + * @param checkedIcon specified if icon is currently checkedIcon + * @param onCheckedChange callback to be invoked when this icon is selected * @param modifier to be applied to this ImageList * @param imageContentDescription Optional image content description. * @param icon Optional icon display in front of the test. @@ -48,11 +51,13 @@ import com.orange.ods.compose.theme.OdsTheme @OdsComponentApi fun OdsImageList( image: Painter, + checkedIcon: Boolean, + onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, imageContentDescription: String? = null, icon: Painter? = null, - iconContentDescription: String? = null, - title: String? = null + iconContentDescription: String, + title: String? = null, ) { Box( modifier = modifier @@ -74,20 +79,27 @@ fun OdsImageList( ) { Row( horizontalArrangement = Arrangement.SpaceBetween, - modifier = modifier.padding(top = dimensionResource(id = R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_m)) + verticalAlignment = Alignment.CenterVertically ) { Text( text = it, - modifier.padding(start = dimensionResource(id = R.dimen.spacing_s)), + modifier.padding( + start = dimensionResource(id = R.dimen.spacing_m), + top = dimensionResource(id = R.dimen.spacing_m), + bottom = dimensionResource(id = R.dimen.spacing_m) + ), color = Color.White, style = OdsTheme.typography.subtitle1 ) icon?.let { - Icon( - painter = it, - modifier = modifier.padding(end = dimensionResource(id = R.dimen.spacing_s)), - contentDescription = iconContentDescription, - tint = Color.White + OdsIconToggleButton( + checked = checkedIcon, + onCheckedChange = onCheckedChange, + uncheckedPainter = it, + checkedPainter = it, + iconContentDescription = iconContentDescription, + enabled = true, + displaySurface = OdsDisplaySurface.Dark ) } } @@ -103,14 +115,18 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr OdsImageList( image = painterResource(id = parameter.image), icon = parameter.icon?.let { painterResource(id = it) }, - title = parameter.title + title = parameter.title, + checkedIcon = parameter.checked, + iconContentDescription = "", + onCheckedChange = { parameter.checked } ) } private data class OdsImageListPreviewParameter( val image: Int, val title: String?, - val icon: Int? + val icon: Int?, + val checked: Boolean ) private class OdsImageListPreviewParameterProvider : @@ -123,8 +139,8 @@ private val previewParameterValues: List val icon = R.drawable.ic_check return listOf( - OdsImageListPreviewParameter(image, title = null, icon), - OdsImageListPreviewParameter(image, title, icon = null), - OdsImageListPreviewParameter(image, title, icon) + OdsImageListPreviewParameter(image, title = null, icon, checked = false), + OdsImageListPreviewParameter(image, title, icon = null, checked = false), + OdsImageListPreviewParameter(image, title, icon, checked = true) ) } \ No newline at end of file From 5329c138f2008e975a0e6744bb23d8d8a9c9f8b3 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Wed, 12 Apr 2023 18:17:24 +0200 Subject: [PATCH 003/112] [#473] Add different form to the image item --- .../orange/ods/app/ui/components/Component.kt | 7 +-- .../app/ui/components/ComponentDemoScreen.kt | 2 +- .../imageitem/ComponentImageItem.kt | 46 ++++++++++++++++-- .../ods/app/ui/utilities/DrawableManager.kt | 1 + .../main/res/drawable-hdpi/il_image_item.png | Bin 0 -> 5144 bytes .../drawable-hdpi/il_image_item_generic.png | Bin 0 -> 5008 bytes .../main/res/drawable-xhdpi/il_image_item.png | Bin 0 -> 5144 bytes .../drawable-xhdpi/il_image_item_generic.png | Bin 0 -> 5008 bytes .../main/res/drawable/ic_display_standard.xml | 10 ++++ .../drawable/ic_display_standard_small.xml | 10 ++++ app/src/main/res/values/strings.xml | 5 +- docs/components/ImageItem.md | 32 ++++++++++++ docs/components/ImageItem_docs.md | 4 ++ .../{imagelist => imageitem}/OdsImageItem.kt | 27 +++++----- 14 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/il_image_item.png create mode 100644 app/src/main/res/drawable-hdpi/il_image_item_generic.png create mode 100644 app/src/main/res/drawable-xhdpi/il_image_item.png create mode 100644 app/src/main/res/drawable-xhdpi/il_image_item_generic.png create mode 100644 app/src/main/res/drawable/ic_display_standard.xml create mode 100644 app/src/main/res/drawable/ic_display_standard_small.xml create mode 100644 docs/components/ImageItem.md create mode 100644 docs/components/ImageItem_docs.md rename lib/src/main/java/com/orange/ods/compose/component/{imagelist => imageitem}/OdsImageItem.kt (88%) 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 6395f8c64..b04ff22b3 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 @@ -125,11 +125,12 @@ sealed class Component( composableName = OdsComposable.OdsFloatingActionButton.name ) - object ImageLists : Component( + object ImageItem : Component( R.string.component_image_item, - R.drawable.il_fab, + R.drawable.il_image_item, null, - R.string.component_image_item_description + R.string.component_image_item_description, + composableName = OdsComponent.OdsImageItem.name ) object Lists : Component( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt index 44c9f6cb1..0c496527a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt @@ -39,7 +39,7 @@ fun ComponentDemoScreen(componentId: Long) { Component.Checkboxes -> ComponentCheckboxes() Component.Dialogs -> ComponentDialog() Component.FloatingActionButtons -> ComponentFloatingActionButton() - Component.ImageLists -> ComponentImageItem() + Component.ImageItem -> ComponentImageItem() Component.Lists -> ComponentLists() Component.ModalDrawers -> ComponentModalDrawers() Component.RadioButtons -> ComponentRadioButtons() diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index f1fdfa16d..f2a6865e2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -16,8 +16,11 @@ import androidx.compose.foundation.layout.size 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.remember 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.painterResource @@ -27,7 +30,9 @@ import coil.compose.rememberAsyncImagePainter import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold -import com.orange.ods.compose.component.imagelist.OdsImageList +import com.orange.ods.app.ui.utilities.composable.Subtitle +import com.orange.ods.compose.component.control.OdsSlider +import com.orange.ods.compose.component.imageitem.OdsImageItem import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing @@ -39,6 +44,11 @@ fun ComponentImageItem() { val iconCheckedState = rememberSaveable { mutableStateOf(false) } val recipes = LocalRecipes.current val recipe = rememberSaveable { recipes.random() } + var sliderPosition by remember { mutableStateOf(0f) } + val leftIcon = painterResource(id = R.drawable.ic_display_standard_small) + val leftIconContentDescription = stringResource(id = R.string.component_image_item_small) + val rightIcon = painterResource(id = R.drawable.ic_display_standard) + val rightIconContentDescription = stringResource(id = R.string.component_image_item_large) with(imageItemCustomizationState) { if (!hasText) { @@ -55,13 +65,39 @@ fun ComponentImageItem() { text = stringResource(id = R.string.component_element_icon), trailing = OdsSwitchTrailing(checked = sideIcons, enabled = hasText) ) + Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { + Subtitle(textRes = R.string.component_image_item_sizes) + OdsSlider( + value = sliderPosition, + steps = 1, + valueRange = 0f..20f, + onValueChange = { sliderPosition = it }, + leftIcon = leftIcon, + leftIconContentDescription = leftIconContentDescription, + rightIcon = rightIcon, + rightIconContentDescription = rightIconContentDescription + ) + } }) { Column( - modifier = Modifier - .size(400.dp, 250.dp) - .padding(dimensionResource(id = R.dimen.spacing_m)) + modifier = if (sliderPosition.toInt() == 0) + Modifier + .padding(start = 112.dp, end = 112.dp, top = dimensionResource(id = R.dimen.spacing_m)) + .size(250.dp, 150.dp) + else if (sliderPosition.toInt() == 10) + Modifier + .padding(start = 93.dp, end = 93.dp, top = dimensionResource(id = R.dimen.spacing_m)) + .size(300.dp, 250.dp) + else + Modifier + .padding( + start = dimensionResource(id = R.dimen.spacing_m), + end = dimensionResource(id = R.dimen.spacing_m), + top = dimensionResource(id = R.dimen.spacing_m) + ) + .size(400.dp, 250.dp) ) { - OdsImageList( + OdsImageItem( image = rememberAsyncImagePainter( model = recipe.imageUrl, placeholder = painterResource(id = R.drawable.placeholder), diff --git a/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt b/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt index 17e415ede..32b18eda7 100644 --- a/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt +++ b/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt @@ -41,6 +41,7 @@ object DrawableManager { R.drawable.il_chips_small_generic to R.drawable.il_chips_small, R.drawable.il_dialogs_generic to R.drawable.il_dialogs, R.drawable.il_fab_generic to R.drawable.il_fab, + R.drawable.il_image_item_generic to R.drawable.il_image_item, R.drawable.il_lists_generic to R.drawable.il_lists, R.drawable.il_menus_generic to R.drawable.il_menus, R.drawable.il_navigation_drawers_generic to R.drawable.il_navigation_drawers, diff --git a/app/src/main/res/drawable-hdpi/il_image_item.png b/app/src/main/res/drawable-hdpi/il_image_item.png new file mode 100644 index 0000000000000000000000000000000000000000..af851b4bbd11e410b245afbd028e3535be3aecb5 GIT binary patch literal 5144 zcmd^DXIB$U(+&Y7l+Zy5RiuR8Arv7fE%eZ&8<8SKNJN_S0Mes$LWh79r3i{hQ7MrQ zB5k$ z-3|g$*T{i5U#g-BwlTg2XqpiCO5GTEIU>9*ECA9}8w8+9_65-W8$uNbRR93m3K{?{ zRnz?Is{sBNrMXi<`(GQ-{%_!u0ofq{fD4N-(6rx}J9|mqcr-L*Qozc= zPQ!t|{9(<;(aR7{XFw97VNBkP2c^WRPz@Djdn)`GeTOwA4g}+RjD}sYrKe_4L*o9| zLgJTAMPQMX0P*)uuk~?{9z{NKGTJxtX)YXuQu1jxN7g_fTZ>ex zW--cdsVDM?va>KfJ)NGO{`5w(dsnEOlrFg55(ZRG6E-n&O8h`4{???C}a_g?3TF}7erxTa8YrT65+wN+HE zS6|6n?l8<7vVa>6r&{-AaiJ2nqgpTXZE^&x1{P{F(sV1 zGnQ}d@94bNF6dkHO5p?P>BGDGvIcaw+iNo5be_2)BH{x2iryMnM^a!R{KA1+8+&#y z*3i;eqQpUL1gc+W3d5qt?$FhU#Kd>SLuYKkwwXRM+_t%@v>so?_{6!9jLG~6Z})qS zel`+}Z`E8MT9dYCpU$&+lYJ*mW&2WVsxv&|*NST7w!kU)*F!zyS!uS1i+XiIBh~j( z{n@+kdVb=QIwzz#Ba1R@%J@XcCJWew zvSMMp$YI#ui`A3n?&g@;IsYOO!ft~(Gy{Qfzr z;7g~}U@Nxl$yXE5UVzqVN#USB#M)o4y0~~!?Gux!hNxzeXB1~(V3=!k8I<9S>w0eE zC3N5Rd`t(cG-2jPwtZ`pyfp;fP;E(Ei+9@-3GdV|;WSRLdlHcI7bvdTF`LD?q zSmixC%Fp)34ETv-e71RTm)(q{8uNYpCBT{_q5;33&@J2RXdQu^TSa3sZ24QTQ)|<#+a)mXRM37?-Qou zPjiCpcpBvixhrTSGlLa8)qnj4__p^dL?_qn^+$c}Jze!hy%G;zmW*Rilkth9^&ZCv zv^T50sy93-p{vOF5*;^e7?IBl@+<5oSX@Db zkh=&U-V^0vEql!g76%r@4FzN6DPtxiJH>K5Y;i;HEEk68{`{Lq90{{(H|3On;`-smS}moFc^y=ZTN`F(;Dtx%@%hMehM>xe!q7AA#@7V{d-LymZ7JzPsg!j1u3fnyQE z%5#iKttMnRN6{4U!67><9j;bSKeve-0BryqdgfM8JsZ(qm1W!V{m zdbMD`U0UU2y*@Z>QAa;wu*LCiW3<4(tq4)6)yt8uInHgFSJ~twfXNd!U};+u}TtV zHEdy;qlZgiW0o+DdnQV-n|g*}WZ+e1Ge}qFd7z)s|Et~+1F&#a11N3%Cc z{1`m~FiqKN5y4`C#P=luLSM6`5wVd}U)baT>RX|EF3slCTxZ_X-*5C&(`Y*wB zq>Ws}<;m~H#*n&>X+`3PqZ3mec~29A1!aj$47^gI0CCl{Xp<>MhJ*ga&QN)|5ie|K zpk~mNiRrAYUts$yZe{5wbUmZs`&i*pG(6d5E@U^KYx+Ubi7WB5)Q6RNjTYtVEA@}} zSv4bXVlkoXfgQjMOW4@b=qo!-Oj2H(&ee}WmtPrAWWDOt^2|WLSb@4icq@+_Zr)CQ zNv(*|({=BN$IIOP8+9A|^SW+0QBXY@>xy$~+?)3%D1u*vAGV+}`e-B)69}c{L5D<# z{cHIqLeo``r9m>r#hF;Y55AY@T)tXf;bGOKoU|ge_0I4Wpk!B%(Iu>8N=v)SVw~ou z-l+*8xq_RG&rFhN3ktz{0y`&B1noB^eOfinIfQWnk$2~i&LRx-pje?zr8C_Q=4f)@ zJAF2pw5g;Xy+6)vU|JpkR;p@&3Ep+oMsY`h@23gz<4o!O7zfNU>-zS7n`pc^}%Aad_Rjf*o+8m zr*xnE`$vG&qz8L#f%?2Kbz!aVIwOQKGK)V|4ix}u+Za#t`_}nc5T^Rc(qIFO^_A48M&h-i#01`Z&FM9 z3hW`l!&B~y+!R~U8HdjAh#peS~vMd}J z=QCOM=XmW=1oumyNuer!Ny`$GhIay8DAru!kg=&g^j-W3tc4kOfk6tvtZ7AYjh$>x z>Y2kKU}qofpYdwwaj~)i_3#Jtz9Tc9`77mBNA4MpCd#tjf4>kOr3fPdwE@%m1;we4a{T^`aXdqAjJ?~wONhgVA}J;wQP!nKM%yD zx$EH6(p6~9G?c(w3n}--L!U)u zBsp3Gc2LHW&nHGYBPLGs zFW#ByqQ7t4N498g;K3*LN_yBkz3^WAqO8O^zgc-qc%OWz}rWh`Bf?*--6~1nUD3<_J>Z&iz+2kl91(jcxl;U_ zV0$WQRKD91!c?F@MiJHg}eo^V8zD#DjId_vx3ksH_5Ja{F+uh+K! zIk=ypNY=H|gA>QZCsS2fWUV@3xTDqR z-ek~Bp+{9s?WNZ!uh{0Vxtl(3gCaY+^(fQ58?Oa}Yvq>QAP_zE-@#__ran?IXWxU6 z@1)PaL$+t6LQ}kB`U|&YlUW5lO0muQ=DAB+SLVI!MtjtQmLCM{r4FShlV>MmfAJL4>-9OX9mLz0a#S5;k$!Mh3NUvt^ z>C{l3n>9G5FSpjN8FRfA?)l@?@aOBByRl1@g@vnSZ%-)OPn(}WWXUcO0@POd@Q{b5 zkB@h@f}MXh-3I)?NAFWsqe5+2;okbjP<}y=JwclK&9@`kKz#TZ$C@F3PxA{R)DqUh zG+2ZFjE^t>@)Gd>poD;7SM6@^bG%!7x7#tjG`ZYz#U=J?*8^J?mP0%73o_pzE@mXZ z6-sR2iM`d_PrKOBu;zA(X;GkC-;}B}4|n`;I$0~n?HdhfT)s4qdf7vHpdpnD;Wl*t zlLk+s4)@XlI$$@tG*J;UG_CtLPdpQqAhG)t4(SAO8^ZgL+!4QIfIJc+Ng>l^H;;&o+bX3aMz{`{@sc6;Ns`s2X~DlcP?NiY8^& zR;I&|bPfB{@rF~#x1u6IJl#6R(*s#WWnePn68nveBmU1RCV+R$g#txUS47AyQ^+z< z3S5F(K!R61Ee`YvWC9DgpDPIzeT;zjWu+RXQghUSoUHtU8>w+1PtM2a|Iw1i!Znk( VL4~7MDgUx^2t!MQrfVqde*nQ?0jvN3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/il_image_item_generic.png b/app/src/main/res/drawable-hdpi/il_image_item_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b00d1db5c11620a6aea9f90b1434d98ace2394 GIT binary patch literal 5008 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 z zcWbls&UDkz*)#qH2mW0?6B9SP15xR%xN-YH@l|_8QKNe|0+d@Zm07IW(Fc-yHTGWQ z>-RB$gnFb#xh^dzu?Fb6%CM!N7qqQdxU^`9d;Pg?5A1wP--S599kQ@uvT zxVxLcH9@H~?`h`aUOR3Mz7x)u7o4v!o@frJl7Ptgh5?!}p=n3&S$>6GpfaMjMfUb_ zhS11PtSj5`t3c9A!@T%jwlqy?KuU8yhKmVBTmmeU56*B5@d=(Zn%D;gym^4CmRPKV zij5d*Qa$usAb{6IlUmNcYXf2P5GUdqMHC~TVrCLV+^2*f88EyNP~Wz!xtb8Ra&|NH zy6)vHJk%DD-&OI(K!@ZLU_B5yYorOOg7)l=MO4ko18Wg?p0?R+GAx(ewa&TLjSf2|Sd$7@{fALMUjgGJ>!J4FKa0X#qNrFo5Xa7kBL0a$w*X zXOY-Oezvs?;JYR6Y@~PS0*wiFg%`7Fngu|N|5|D%3=GZjpQuyEYX7=nSUnTnYVF&R F{|EAC4A}qx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/il_image_item.png b/app/src/main/res/drawable-xhdpi/il_image_item.png new file mode 100644 index 0000000000000000000000000000000000000000..af851b4bbd11e410b245afbd028e3535be3aecb5 GIT binary patch literal 5144 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 VL4~7MDgUx^2t!MQrfVqde*nQ?0jvN3 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/il_image_item_generic.png b/app/src/main/res/drawable-xhdpi/il_image_item_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b00d1db5c11620a6aea9f90b1434d98ace2394 GIT binary patch literal 5008 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 z zcWbls&UDkz*)#qH2mW0?6B9SP15xR%xN-YH@l|_8QKNe|0+d@Zm07IW(Fc-yHTGWQ z>-RB$gnFb#xh^dzu?Fb6%CM!N7qqQdxU^`9d;Pg?5A1wP--S599kQ@uvT zxVxLcH9@H~?`h`aUOR3Mz7x)u7o4v!o@frJl7Ptgh5?!}p=n3&S$>6GpfaMjMfUb_ zhS11PtSj5`t3c9A!@T%jwlqy?KuU8yhKmVBTmmeU56*B5@d=(Zn%D;gym^4CmRPKV zij5d*Qa$usAb{6IlUmNcYXf2P5GUdqMHC~TVrCLV+^2*f88EyNP~Wz!xtb8Ra&|NH zy6)vHJk%DD-&OI(K!@ZLU_B5yYorOOg7)l=MO4ko18Wg?p0?R+GAx(ewa&TLjSf2|Sd$7@{fALMUjgGJ>!J4FKa0X#qNrFo5Xa7kBL0a$w*X zXOY-Oezvs?;JYR6Y@~PS0*wiFg%`7Fngu|N|5|D%3=GZjpQuyEYX7=nSUnTnYVF&R F{|EAC4A}qx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_display_standard.xml b/app/src/main/res/drawable/ic_display_standard.xml new file mode 100644 index 000000000..b71fc78d2 --- /dev/null +++ b/app/src/main/res/drawable/ic_display_standard.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_display_standard_small.xml b/app/src/main/res/drawable/ic_display_standard_small.xml new file mode 100644 index 000000000..e789680f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_display_standard_small.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 70a2c7ac9..0d2fbcd3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -204,7 +204,10 @@ Image item - The Image item component is the individual image display used in the Image list module, and it allows to display image lists or media categories. + An Image Item contains primary information which is usually an image. It can also contains secondary information such as a title or an action. + Image item sizes + Small image + Large image Menus diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md new file mode 100644 index 000000000..5e5d8a2f3 --- /dev/null +++ b/docs/components/ImageItem.md @@ -0,0 +1,32 @@ +--- +layout: detail +title: Image item +description: +--- + +--- + +**Page Summary** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Implementation](#implementation) +* [Component specific tokens](#component-specific-tokens) + +--- + +## Specifications references + +- [Design System Manager - Image Item](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/) + +## Implementation + +## Component specific tokens + +_Soon available_ \ No newline at end of file diff --git a/docs/components/ImageItem_docs.md b/docs/components/ImageItem_docs.md new file mode 100644 index 000000000..9ded86b13 --- /dev/null +++ b/docs/components/ImageItem_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: ImageItem.md +--- \ No newline at end of file diff --git a/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt similarity index 88% rename from lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt rename to lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index fd523c2d9..7c26ca2f3 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imagelist/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -8,13 +8,13 @@ * / */ -package com.orange.ods.compose.component.imagelist +package com.orange.ods.compose.component.imageitem import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.Surface import androidx.compose.material.Text @@ -26,7 +26,9 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp import com.orange.ods.R import com.orange.ods.compose.component.OdsComponentApi import com.orange.ods.compose.component.button.OdsIconToggleButton @@ -49,7 +51,7 @@ import com.orange.ods.compose.theme.OdsTheme */ @Composable @OdsComponentApi -fun OdsImageList( +fun OdsImageItem( image: Painter, checkedIcon: Boolean, onCheckedChange: (Boolean) -> Unit, @@ -78,18 +80,18 @@ fun OdsImageList( .fillMaxWidth() ) { Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(48.dp) ) { Text( text = it, - modifier.padding( - start = dimensionResource(id = R.dimen.spacing_m), - top = dimensionResource(id = R.dimen.spacing_m), - bottom = dimensionResource(id = R.dimen.spacing_m) - ), color = Color.White, - style = OdsTheme.typography.subtitle1 + style = OdsTheme.typography.subtitle1, + modifier = Modifier.weight(1f) + .padding(start = dimensionResource(id = R.dimen.spacing_m)), + maxLines = 1, + overflow = TextOverflow.Ellipsis ) icon?.let { OdsIconToggleButton( @@ -98,7 +100,6 @@ fun OdsImageList( uncheckedPainter = it, checkedPainter = it, iconContentDescription = iconContentDescription, - enabled = true, displaySurface = OdsDisplaySurface.Dark ) } @@ -112,7 +113,7 @@ fun OdsImageList( @Composable private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterProvider::class) parameter: OdsImageListPreviewParameter) = Preview { - OdsImageList( + OdsImageItem( image = painterResource(id = parameter.image), icon = parameter.icon?.let { painterResource(id = it) }, title = parameter.title, From b3494a5d448dbbd125c08a92fb2b158aacf0622a Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Fri, 14 Apr 2023 14:24:22 +0200 Subject: [PATCH 004/112] [#473] Add documentation --- .../imageitem/ComponentImageItem.kt | 66 ++++++++++--------- docs/components/ImageItem.md | 17 +++++ .../component/button/OdsIconToggleButton.kt | 2 +- .../component/imageitem/OdsImageItem.kt | 23 +++---- 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index f2a6865e2..06b39f4b7 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -10,7 +10,11 @@ package com.orange.ods.app.ui.components.imageitem +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.ExperimentalMaterialApi @@ -21,6 +25,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -49,6 +54,7 @@ fun ComponentImageItem() { val leftIconContentDescription = stringResource(id = R.string.component_image_item_small) val rightIcon = painterResource(id = R.drawable.ic_display_standard) val rightIconContentDescription = stringResource(id = R.string.component_image_item_large) + val imageItemHeight = 250.dp with(imageItemCustomizationState) { if (!hasText) { @@ -80,39 +86,37 @@ fun ComponentImageItem() { } }) { Column( - modifier = if (sliderPosition.toInt() == 0) - Modifier - .padding(start = 112.dp, end = 112.dp, top = dimensionResource(id = R.dimen.spacing_m)) - .size(250.dp, 150.dp) - else if (sliderPosition.toInt() == 10) - Modifier - .padding(start = 93.dp, end = 93.dp, top = dimensionResource(id = R.dimen.spacing_m)) - .size(300.dp, 250.dp) - else - Modifier - .padding( - start = dimensionResource(id = R.dimen.spacing_m), - end = dimensionResource(id = R.dimen.spacing_m), - top = dimensionResource(id = R.dimen.spacing_m) - ) - .size(400.dp, 250.dp) + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally ) { - OdsImageItem( - image = rememberAsyncImagePainter( - model = recipe.imageUrl, - placeholder = painterResource(id = R.drawable.placeholder), - error = painterResource(id = R.drawable.placeholder) - ), - icon = if (hasSideIcons && !iconCheckedState.value) painterResource(id = R.drawable.ic_heart_outlined) - else if (hasSideIcons && iconCheckedState.value) painterResource(id = R.drawable.ic_heart) else null, - title = if (hasText) recipe.title else null, - checkedIcon = iconCheckedState.value, - iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), - onCheckedChange = { checked -> iconCheckedState.value = checked } - ) + AnimatedVisibility( + visible = true, enter = fadeIn(), + exit = fadeOut() + ) { + OdsImageItem( + image = rememberAsyncImagePainter( + model = recipe.imageUrl, + placeholder = painterResource(id = R.drawable.placeholder), + error = painterResource(id = R.drawable.placeholder) + ), + icon = if (hasSideIcons && !iconCheckedState.value) painterResource(id = R.drawable.ic_heart_outlined) + else if (hasSideIcons && iconCheckedState.value) painterResource(id = R.drawable.ic_heart) else null, + title = if (hasText) recipe.title else null, + modifier = if (sliderPosition.toInt() == 0) Modifier + .size(174.dp, 175.dp) + .padding(dimensionResource(id = R.dimen.spacing_m)) + else if (sliderPosition.toInt() == 20) Modifier + .size(400.dp, imageItemHeight) + .padding(dimensionResource(id = R.dimen.spacing_m)) + else Modifier + .size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) + .padding(dimensionResource(id = R.dimen.spacing_m)), + checkedIcon = iconCheckedState.value, + iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + onCheckedChange = { checked -> iconCheckedState.value = checked } + ) + } } - - } } } \ No newline at end of file diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md index 5e5d8a2f3..4b7eef260 100644 --- a/docs/components/ImageItem.md +++ b/docs/components/ImageItem.md @@ -27,6 +27,23 @@ Please follow [accessibility criteria for development](https://a11y-guidelines.o ## Implementation +> **Jetpack Compose implementation** + +You can use the `OdsImageItem` composable like this: + +```kotlin +OdsImageItem( + image = painterResource(id = R.drawable.placeholder), + checkedIcon = false, + onCheckedChange = { }, + iconContentDescription = "",// Optional + modifier = modifier, + imageContentDescription = "Picture content description", //Optional + icon = painterResource(id = R.drawable.ic_heart), // Optional + title = "Component Image Item" // Optional +) +``` + ## Component specific tokens _Soon available_ \ No newline at end of file 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 3a8b614be..d5d9a5f90 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 @@ -56,8 +56,8 @@ fun OdsIconToggleButton( onCheckedChange: (Boolean) -> Unit, uncheckedPainter: Painter, checkedPainter: Painter, - iconContentDescription: String, modifier: Modifier = Modifier, + iconContentDescription: String?, enabled: Boolean = true, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 7c26ca2f3..c2a6b7468 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -13,6 +13,7 @@ package com.orange.ods.compose.component.imageitem import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -28,7 +29,6 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp import com.orange.ods.R import com.orange.ods.compose.component.OdsComponentApi import com.orange.ods.compose.component.button.OdsIconToggleButton @@ -56,9 +56,9 @@ fun OdsImageItem( checkedIcon: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, + iconContentDescription: String? = null, imageContentDescription: String? = null, icon: Painter? = null, - iconContentDescription: String, title: String? = null, ) { Box( @@ -70,7 +70,7 @@ fun OdsImageItem( contentDescription = imageContentDescription, contentScale = ContentScale.Crop, modifier = Modifier - .fillMaxWidth() + .fillMaxSize() ) title?.let { Surface( @@ -82,14 +82,15 @@ fun OdsImageItem( Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .height(48.dp) + .height(dimensionResource(id = R.dimen.list_single_line_item_height)) ) { Text( text = it, color = Color.White, style = OdsTheme.typography.subtitle1, - modifier = Modifier.weight(1f) - .padding(start = dimensionResource(id = R.dimen.spacing_m)), + modifier = Modifier + .weight(1f) + .padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m)), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -125,9 +126,9 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr private data class OdsImageListPreviewParameter( val image: Int, + val checked: Boolean, val title: String?, - val icon: Int?, - val checked: Boolean + val icon: Int? ) private class OdsImageListPreviewParameterProvider : @@ -140,8 +141,8 @@ private val previewParameterValues: List val icon = R.drawable.ic_check return listOf( - OdsImageListPreviewParameter(image, title = null, icon, checked = false), - OdsImageListPreviewParameter(image, title, icon = null, checked = false), - OdsImageListPreviewParameter(image, title, icon, checked = true) + OdsImageListPreviewParameter(image, title = null, icon = icon, checked = false), + OdsImageListPreviewParameter(image, title = title, icon = null, checked = false), + OdsImageListPreviewParameter(image, title = title, icon = icon, checked = true) ) } \ No newline at end of file From 08bd89a4e2b57d55cfeff81858aca363bf41c8ab Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 11:19:27 +0200 Subject: [PATCH 005/112] [#473] Review : Add another entry in changelog file --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index cb28e9343..2bd84e65a 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) +- \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 From 8181f4d9bf8493b5d369bebecf690553d1dbef25 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 11:32:17 +0200 Subject: [PATCH 006/112] [#473] Review : Update Kdoc --- .../compose/component/imageitem/OdsImageItem.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index c2a6b7468..f3f293257 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -40,14 +40,14 @@ import com.orange.ods.compose.theme.OdsTheme /** * - * @param image image display in the ImageList. - * @param checkedIcon specified if icon is currently checkedIcon - * @param onCheckedChange callback to be invoked when this icon is selected - * @param modifier to be applied to this ImageList - * @param imageContentDescription Optional image content description. - * @param icon Optional icon display in front of the test. - * @param iconContentDescription Optional icon content description.. - * @param title text display in the image + * @param image Image display in the [OdsImageItem]. + * @param checkedIcon Specified if icon is currently checked + * @param onCheckedChange Callback to be invoked when this icon is selected + * @param modifier Modifier to be applied to this [OdsImageItem] + * @param imageContentDescription Optional image content description + * @param icon Optional icon displayed in front of the [OdsImageItem] + * @param iconContentDescription Optional icon content description + * @param title Text displayed in the image */ @Composable @OdsComponentApi From 44175c37562d48b7bf430df6f9e3332377ae7a71 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 13:40:12 +0200 Subject: [PATCH 007/112] [#473] Review : Rename attributs --- .../components/imageitem/ComponentImageItem.kt | 4 ++-- docs/components/ImageItem.md | 4 ++-- .../compose/component/imageitem/OdsImageItem.kt | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 06b39f4b7..b76931916 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -111,9 +111,9 @@ fun ComponentImageItem() { else Modifier .size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) .padding(dimensionResource(id = R.dimen.spacing_m)), - checkedIcon = iconCheckedState.value, + iconChecked = iconCheckedState.value, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), - onCheckedChange = { checked -> iconCheckedState.value = checked } + onIconCheckedChange = { checked -> iconCheckedState.value = checked } ) } } diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md index 4b7eef260..983993955 100644 --- a/docs/components/ImageItem.md +++ b/docs/components/ImageItem.md @@ -34,8 +34,8 @@ You can use the `OdsImageItem` composable like this: ```kotlin OdsImageItem( image = painterResource(id = R.drawable.placeholder), - checkedIcon = false, - onCheckedChange = { }, + iconChecked = false, + onIconCheckedChange = { }, iconContentDescription = "",// Optional modifier = modifier, imageContentDescription = "Picture content description", //Optional diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index f3f293257..12526005a 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -41,8 +41,8 @@ import com.orange.ods.compose.theme.OdsTheme /** * * @param image Image display in the [OdsImageItem]. - * @param checkedIcon Specified if icon is currently checked - * @param onCheckedChange Callback to be invoked when this icon is selected + * @param iconChecked Specified if icon is currently checked + * @param onIconCheckedChange Callback to be invoked when this icon is selected * @param modifier Modifier to be applied to this [OdsImageItem] * @param imageContentDescription Optional image content description * @param icon Optional icon displayed in front of the [OdsImageItem] @@ -53,8 +53,8 @@ import com.orange.ods.compose.theme.OdsTheme @OdsComponentApi fun OdsImageItem( image: Painter, - checkedIcon: Boolean, - onCheckedChange: (Boolean) -> Unit, + iconChecked: Boolean, + onIconCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, iconContentDescription: String? = null, imageContentDescription: String? = null, @@ -96,8 +96,8 @@ fun OdsImageItem( ) icon?.let { OdsIconToggleButton( - checked = checkedIcon, - onCheckedChange = onCheckedChange, + checked = iconChecked, + onCheckedChange = onIconCheckedChange, uncheckedPainter = it, checkedPainter = it, iconContentDescription = iconContentDescription, @@ -118,9 +118,9 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr image = painterResource(id = parameter.image), icon = parameter.icon?.let { painterResource(id = it) }, title = parameter.title, - checkedIcon = parameter.checked, + iconChecked = parameter.checked, iconContentDescription = "", - onCheckedChange = { parameter.checked } + onIconCheckedChange = { parameter.checked } ) } From d60226eb921d32a1978e9f5e6c8070e70808c9dd Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 13:46:11 +0200 Subject: [PATCH 008/112] [#473] Review : change string name --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d2fbcd3d..c017dcd3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -203,8 +203,8 @@ Full screen width - Image item - An Image Item contains primary information which is usually an image. It can also contains secondary information such as a title or an action. + Image Item + An Image Item contains primary information which is usually an image. It can also contain secondary information such as a title or an action. Image item sizes Small image Large image From e42c0f347c8a711ce397cffcb5e4c604b3f92039 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 13:52:13 +0200 Subject: [PATCH 009/112] [#473] Review : remove fillMaxWidth and change apha value --- .../orange/ods/compose/component/imageitem/OdsImageItem.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 12526005a..ead8c764f 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -61,10 +61,7 @@ fun OdsImageItem( icon: Painter? = null, title: String? = null, ) { - Box( - modifier = modifier - .fillMaxWidth() - ) { + Box{ Image( painter = image, contentDescription = imageContentDescription, @@ -74,7 +71,7 @@ fun OdsImageItem( ) title?.let { Surface( - color = Color.Black.copy(alpha = 0.5f), + color = Color.Black.copy(alpha = 0.38f), modifier = Modifier .align(Alignment.BottomStart) .fillMaxWidth() From 6c6d32b6c7b66bb9c64049dc63dd06ee7d68ad6b Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 14:52:24 +0200 Subject: [PATCH 010/112] [#473] Review : change icon to checkedIcon and uncheckedIcon --- .../imageitem/ComponentImageItem.kt | 5 +- docs/components/ImageItem.md | 4 +- .../component/imageitem/OdsImageItem.kt | 76 ++++++++++--------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index b76931916..24cf9dc91 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -99,8 +99,9 @@ fun ComponentImageItem() { placeholder = painterResource(id = R.drawable.placeholder), error = painterResource(id = R.drawable.placeholder) ), - icon = if (hasSideIcons && !iconCheckedState.value) painterResource(id = R.drawable.ic_heart_outlined) - else if (hasSideIcons && iconCheckedState.value) painterResource(id = R.drawable.ic_heart) else null, + uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), + checkedIcon = painterResource(id = R.drawable.ic_heart), + iconSelected = hasSideIcons, title = if (hasText) recipe.title else null, modifier = if (sliderPosition.toInt() == 0) Modifier .size(174.dp, 175.dp) diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md index 983993955..6abf87825 100644 --- a/docs/components/ImageItem.md +++ b/docs/components/ImageItem.md @@ -35,11 +35,13 @@ You can use the `OdsImageItem` composable like this: OdsImageItem( image = painterResource(id = R.drawable.placeholder), iconChecked = false, + iconSelected = true, onIconCheckedChange = { }, iconContentDescription = "",// Optional + checkedIcon = painterResource(id = R.drawable.ic_heart), + uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), modifier = modifier, imageContentDescription = "Picture content description", //Optional - icon = painterResource(id = R.drawable.ic_heart), // Optional title = "Component Image Item" // Optional ) ``` diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index ead8c764f..a769d17f1 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -11,13 +11,13 @@ package com.orange.ods.compose.component.imageitem import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -42,10 +42,12 @@ import com.orange.ods.compose.theme.OdsTheme * * @param image Image display in the [OdsImageItem]. * @param iconChecked Specified if icon is currently checked + * @param iconSelected Specified whether the icon is selected or nor * @param onIconCheckedChange Callback to be invoked when this icon is selected + * @param checkedIcon Optional icon displayed in front of the [OdsImageItem] + * @param uncheckedIcon Optional icon displayed in front of the [OdsImageItem] * @param modifier Modifier to be applied to this [OdsImageItem] * @param imageContentDescription Optional image content description - * @param icon Optional icon displayed in front of the [OdsImageItem] * @param iconContentDescription Optional icon content description * @param title Text displayed in the image */ @@ -54,14 +56,16 @@ import com.orange.ods.compose.theme.OdsTheme fun OdsImageItem( image: Painter, iconChecked: Boolean, + iconSelected: Boolean, onIconCheckedChange: (Boolean) -> Unit, + checkedIcon: Painter, + uncheckedIcon: Painter, modifier: Modifier = Modifier, iconContentDescription: String? = null, imageContentDescription: String? = null, - icon: Painter? = null, title: String? = null, ) { - Box{ + Box { Image( painter = image, contentDescription = imageContentDescription, @@ -70,37 +74,33 @@ fun OdsImageItem( .fillMaxSize() ) title?.let { - Surface( - color = Color.Black.copy(alpha = 0.38f), - modifier = Modifier + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .background(color = Color.Black.copy(alpha = 0.5f)) .align(Alignment.BottomStart) .fillMaxWidth() + .height(dimensionResource(id = R.dimen.list_single_line_item_height)) ) { - Row( - verticalAlignment = Alignment.CenterVertically, + Text( + text = it, + color = Color.White, + style = OdsTheme.typography.subtitle1, modifier = Modifier - .height(dimensionResource(id = R.dimen.list_single_line_item_height)) - ) { - Text( - text = it, - color = Color.White, - style = OdsTheme.typography.subtitle1, - modifier = Modifier - .weight(1f) - .padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m)), - maxLines = 1, - overflow = TextOverflow.Ellipsis + .weight(1f) + .padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m)), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (iconSelected) { + OdsIconToggleButton( + checked = iconChecked, + onCheckedChange = onIconCheckedChange, + uncheckedPainter = uncheckedIcon, + checkedPainter = checkedIcon, + iconContentDescription = iconContentDescription, + displaySurface = OdsDisplaySurface.Dark ) - icon?.let { - OdsIconToggleButton( - checked = iconChecked, - onCheckedChange = onIconCheckedChange, - uncheckedPainter = it, - checkedPainter = it, - iconContentDescription = iconContentDescription, - displaySurface = OdsDisplaySurface.Dark - ) - } } } } @@ -113,7 +113,9 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr Preview { OdsImageItem( image = painterResource(id = parameter.image), - icon = parameter.icon?.let { painterResource(id = it) }, + iconSelected = true, + uncheckedIcon = painterResource(id = parameter.unCheckedIcon), + checkedIcon = painterResource(id = parameter.checkedIcon), title = parameter.title, iconChecked = parameter.checked, iconContentDescription = "", @@ -125,7 +127,8 @@ private data class OdsImageListPreviewParameter( val image: Int, val checked: Boolean, val title: String?, - val icon: Int? + val checkedIcon: Int, + val unCheckedIcon: Int ) private class OdsImageListPreviewParameterProvider : @@ -135,11 +138,12 @@ private val previewParameterValues: List get() { val title = "Subtitle 1" val image = R.drawable.placeholder - val icon = R.drawable.ic_check + val checkedIcon = R.drawable.ic_check + val unCheckedIcon = R.drawable.ic_check return listOf( - OdsImageListPreviewParameter(image, title = null, icon = icon, checked = false), - OdsImageListPreviewParameter(image, title = title, icon = null, checked = false), - OdsImageListPreviewParameter(image, title = title, icon = icon, checked = true) + OdsImageListPreviewParameter(image, title = null, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false), + OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false), + OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = true) ) } \ No newline at end of file From 852a818f6680b5af2cfae80d8480469c8cc6cebd Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 15:01:42 +0200 Subject: [PATCH 011/112] [#473] Review : change sideIcon to iconDisplayed --- .../ui/components/imageitem/ComponentImageItem.kt | 6 +++--- .../imageitem/ImageItemCustomizationState.kt | 12 ++++++------ docs/components/ImageItem.md | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 24cf9dc91..864ad0e2e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -58,7 +58,7 @@ fun ComponentImageItem() { with(imageItemCustomizationState) { if (!hasText) { - sideIcons.value = false + iconDisplayed.value = false } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), @@ -69,7 +69,7 @@ fun ComponentImageItem() { ) OdsListItem( text = stringResource(id = R.string.component_element_icon), - trailing = OdsSwitchTrailing(checked = sideIcons, enabled = hasText) + trailing = OdsSwitchTrailing(checked = iconDisplayed, enabled = hasText) ) Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { Subtitle(textRes = R.string.component_image_item_sizes) @@ -101,7 +101,7 @@ fun ComponentImageItem() { ), uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), checkedIcon = painterResource(id = R.drawable.ic_heart), - iconSelected = hasSideIcons, + iconSelected = hasIcon, title = if (hasText) recipe.title else null, modifier = if (sliderPosition.toInt() == 0) Modifier .size(174.dp, 175.dp) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index 9a3c1ca26..fefc81781 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -18,19 +18,19 @@ import androidx.compose.runtime.saveable.rememberSaveable @Composable fun rememberImageItemCustomizationState( - sideIcons: MutableState = rememberSaveable { mutableStateOf(false) }, + iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) }, textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } ) = - remember(sideIcons, textDisplayed) { - ImageItemCustomizationState(sideIcons, textDisplayed) + remember(iconDisplayed, textDisplayed) { + ImageItemCustomizationState(iconDisplayed, textDisplayed) } class ImageItemCustomizationState( - val sideIcons: MutableState, + val iconDisplayed: MutableState, val textDisplayed: MutableState ) { - val hasSideIcons - get() = sideIcons.value + val hasIcon + get() = iconDisplayed.value val hasText get() = textDisplayed.value diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md index 6abf87825..1a8caeda1 100644 --- a/docs/components/ImageItem.md +++ b/docs/components/ImageItem.md @@ -37,7 +37,7 @@ OdsImageItem( iconChecked = false, iconSelected = true, onIconCheckedChange = { }, - iconContentDescription = "",// Optional + iconContentDescription = "", // Optional checkedIcon = painterResource(id = R.drawable.ic_heart), uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), modifier = modifier, From e6ee23b7ab528f554429ec91b5a1204fc4048bf7 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 15:03:56 +0200 Subject: [PATCH 012/112] [#473] Review : Change padding --- .../ods/app/ui/components/imageitem/ComponentImageItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 864ad0e2e..70fdcdecb 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -71,7 +71,7 @@ fun ComponentImageItem() { text = stringResource(id = R.string.component_element_icon), trailing = OdsSwitchTrailing(checked = iconDisplayed, enabled = hasText) ) - Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) { + Column(modifier = Modifier.padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m))) { Subtitle(textRes = R.string.component_image_item_sizes) OdsSlider( value = sliderPosition, From c3dde10ba5789781e60f1e3a43dc66449376e830 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 15:27:52 +0200 Subject: [PATCH 013/112] [#473] Review : Change value range --- .../imageitem/ComponentImageItem.kt | 20 ++++++++++--------- .../component/imageitem/OdsImageItem.kt | 6 ++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 70fdcdecb..2d79c4aab 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -15,6 +15,8 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.ExperimentalMaterialApi @@ -76,7 +78,7 @@ fun ComponentImageItem() { OdsSlider( value = sliderPosition, steps = 1, - valueRange = 0f..20f, + valueRange = 0f..2f, onValueChange = { sliderPosition = it }, leftIcon = leftIcon, leftIconContentDescription = leftIconContentDescription, @@ -103,14 +105,14 @@ fun ComponentImageItem() { checkedIcon = painterResource(id = R.drawable.ic_heart), iconSelected = hasIcon, title = if (hasText) recipe.title else null, - modifier = if (sliderPosition.toInt() == 0) Modifier - .size(174.dp, 175.dp) - .padding(dimensionResource(id = R.dimen.spacing_m)) - else if (sliderPosition.toInt() == 20) Modifier - .size(400.dp, imageItemHeight) - .padding(dimensionResource(id = R.dimen.spacing_m)) - else Modifier - .size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) + modifier = Modifier + .run { + when (sliderPosition.toInt()) { + 0 -> size(175.dp, 175.dp) + 2 -> fillMaxWidth().height(250.dp) + else -> size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) + } + } .padding(dimensionResource(id = R.dimen.spacing_m)), iconChecked = iconCheckedState.value, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index a769d17f1..1a6fbbeff 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -65,7 +65,9 @@ fun OdsImageItem( imageContentDescription: String? = null, title: String? = null, ) { - Box { + Box( + modifier = modifier.fillMaxWidth() + ) { Image( painter = image, contentDescription = imageContentDescription, @@ -77,7 +79,7 @@ fun OdsImageItem( Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier - .background(color = Color.Black.copy(alpha = 0.5f)) + .background(color = Color.Black.copy(alpha = 0.38f)) .align(Alignment.BottomStart) .fillMaxWidth() .height(dimensionResource(id = R.dimen.list_single_line_item_height)) From 47d7137e28d6637b2c5da5cc3fe9317522e36aef Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 17:33:49 +0200 Subject: [PATCH 014/112] [#473] Review : Add size to preview --- .../imageitem/ComponentImageItem.kt | 2 +- .../component/imageitem/OdsImageItem.kt | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 2d79c4aab..3c3243244 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -109,7 +109,7 @@ fun ComponentImageItem() { .run { when (sliderPosition.toInt()) { 0 -> size(175.dp, 175.dp) - 2 -> fillMaxWidth().height(250.dp) + 2 -> fillMaxWidth().height(imageItemHeight) else -> size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) } } diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 1a6fbbeff..63ee98c31 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -29,6 +30,8 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource 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.OdsComponentApi import com.orange.ods.compose.component.button.OdsIconToggleButton @@ -72,16 +75,15 @@ fun OdsImageItem( painter = image, contentDescription = imageContentDescription, contentScale = ContentScale.Crop, - modifier = Modifier + modifier = modifier .fillMaxSize() ) title?.let { Row( verticalAlignment = Alignment.CenterVertically, - modifier = modifier + modifier = Modifier .background(color = Color.Black.copy(alpha = 0.38f)) .align(Alignment.BottomStart) - .fillMaxWidth() .height(dimensionResource(id = R.dimen.list_single_line_item_height)) ) { Text( @@ -109,6 +111,7 @@ fun OdsImageItem( } } + @UiModePreviews.Default @Composable private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterProvider::class) parameter: OdsImageListPreviewParameter) = @@ -120,6 +123,7 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr checkedIcon = painterResource(id = parameter.checkedIcon), title = parameter.title, iconChecked = parameter.checked, + modifier = Modifier.size(parameter.size), iconContentDescription = "", onIconCheckedChange = { parameter.checked } ) @@ -130,7 +134,8 @@ private data class OdsImageListPreviewParameter( val checked: Boolean, val title: String?, val checkedIcon: Int, - val unCheckedIcon: Int + val unCheckedIcon: Int, + val size: Dp ) private class OdsImageListPreviewParameterProvider : @@ -144,8 +149,8 @@ private val previewParameterValues: List val unCheckedIcon = R.drawable.ic_check return listOf( - OdsImageListPreviewParameter(image, title = null, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false), - OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false), - OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = true) + OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false, size = 150.dp), + OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false, size = 300.dp), + OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = true, size = 400.dp) ) } \ No newline at end of file From 7cb12764ac405c55508f867260a8a88c50d825df Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 24 Apr 2023 18:06:20 +0200 Subject: [PATCH 015/112] [#473] Review : Add size in customization state --- .../imageitem/ComponentImageItem.kt | 13 ++++++++----- .../imageitem/ImageItemCustomizationState.kt | 19 +++++++++++++++++-- .../component/imageitem/OdsImageItem.kt | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 3c3243244..d7d528c6b 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -79,7 +79,10 @@ fun ComponentImageItem() { value = sliderPosition, steps = 1, valueRange = 0f..2f, - onValueChange = { sliderPosition = it }, + onValueChange = { + sliderPosition = it + size.value = ImageItemCustomizationState.Size.fromSliderValue(it) + }, leftIcon = leftIcon, leftIconContentDescription = leftIconContentDescription, rightIcon = rightIcon, @@ -107,10 +110,10 @@ fun ComponentImageItem() { title = if (hasText) recipe.title else null, modifier = Modifier .run { - when (sliderPosition.toInt()) { - 0 -> size(175.dp, 175.dp) - 2 -> fillMaxWidth().height(imageItemHeight) - else -> size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) + when (size.value) { + ImageItemCustomizationState.Size.Small -> size(175.dp, 175.dp) + ImageItemCustomizationState.Size.Large -> fillMaxWidth().height(imageItemHeight) + ImageItemCustomizationState.Size.Medium -> size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) } } .padding(dimensionResource(id = R.dimen.spacing_m)), diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index fefc81781..8c13849ee 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -15,20 +15,35 @@ 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.app.ui.components.floatingactionbuttons.FabCustomizationState @Composable fun rememberImageItemCustomizationState( + size: MutableState = rememberSaveable { mutableStateOf(ImageItemCustomizationState.Size.Small)}, iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) }, textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } ) = - remember(iconDisplayed, textDisplayed) { - ImageItemCustomizationState(iconDisplayed, textDisplayed) + remember(iconDisplayed, textDisplayed, size) { + ImageItemCustomizationState(size, iconDisplayed, textDisplayed) } class ImageItemCustomizationState( + val size: MutableState, val iconDisplayed: MutableState, val textDisplayed: MutableState ) { + enum class Size { + Small, Medium, Large; + companion object { + fun fromSliderValue(sliderValue: Float) = + when (sliderValue) { + 0f -> Small + 1f -> Medium + else -> Large + } + } + } + val hasIcon get() = iconDisplayed.value diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 63ee98c31..2b807befd 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -75,7 +75,7 @@ fun OdsImageItem( painter = image, contentDescription = imageContentDescription, contentScale = ContentScale.Crop, - modifier = modifier + modifier = Modifier .fillMaxSize() ) title?.let { From 217a376f5a0ceb12be0b728e44991a851f6c75d7 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Tue, 25 Apr 2023 13:16:41 +0200 Subject: [PATCH 016/112] [#473] Add Size --- .../imageitem/ComponentImageItem.kt | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index d7d528c6b..e78517d7f 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -10,15 +10,11 @@ package com.orange.ods.app.ui.components.imageitem -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable @@ -94,34 +90,30 @@ fun ComponentImageItem() { modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { - AnimatedVisibility( - visible = true, enter = fadeIn(), - exit = fadeOut() - ) { - OdsImageItem( - image = rememberAsyncImagePainter( - model = recipe.imageUrl, - placeholder = painterResource(id = R.drawable.placeholder), - error = painterResource(id = R.drawable.placeholder) - ), - uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), - checkedIcon = painterResource(id = R.drawable.ic_heart), - iconSelected = hasIcon, - title = if (hasText) recipe.title else null, - modifier = Modifier - .run { - when (size.value) { - ImageItemCustomizationState.Size.Small -> size(175.dp, 175.dp) - ImageItemCustomizationState.Size.Large -> fillMaxWidth().height(imageItemHeight) - ImageItemCustomizationState.Size.Medium -> size(imageItemHeight, dimensionResource(id = R.dimen.card_big_image_height)) - } + + OdsImageItem( + image = rememberAsyncImagePainter( + model = recipe.imageUrl, + placeholder = painterResource(id = R.drawable.placeholder), + error = painterResource(id = R.drawable.placeholder) + ), + uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), + checkedIcon = painterResource(id = R.drawable.ic_heart), + iconSelected = hasIcon, + title = if (hasText) recipe.title else null, + modifier = Modifier + .run { + when (size.value) { + ImageItemCustomizationState.Size.Small -> fillMaxWidth(0.4f).height(175.dp) + ImageItemCustomizationState.Size.Large -> fillMaxWidth().height(imageItemHeight) + ImageItemCustomizationState.Size.Medium -> fillMaxWidth(0.5f).height(dimensionResource(id = R.dimen.card_big_image_height)) } - .padding(dimensionResource(id = R.dimen.spacing_m)), - iconChecked = iconCheckedState.value, - iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), - onIconCheckedChange = { checked -> iconCheckedState.value = checked } - ) - } + } + .padding(dimensionResource(id = R.dimen.spacing_m)), + iconChecked = iconCheckedState.value, + iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + onIconCheckedChange = { checked -> iconCheckedState.value = checked } + ) } } } From 6e7c728ed3f34afb0d6fdb51ecd4510e9a8d6dce Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Wed, 3 May 2023 16:34:26 +0200 Subject: [PATCH 017/112] [#473] Update image item component with type --- .../imageitem/ComponentImageItem.kt | 70 +++---- .../imageitem/ImageItemCustomizationState.kt | 27 ++- .../components/progress/ProgressCircular.kt | 2 +- .../ui/components/progress/ProgressLinear.kt | 2 +- app/src/main/res/values/strings.xml | 4 +- docs/components/ImageItem.md | 1 + .../component/imageitem/OdsImageItem.kt | 198 ++++++++++++++---- 7 files changed, 197 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index e78517d7f..290bd363f 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -12,34 +12,30 @@ package com.orange.ods.app.ui.components.imageitem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width 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.remember import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold import com.orange.ods.app.ui.utilities.composable.Subtitle -import com.orange.ods.compose.component.control.OdsSlider +import com.orange.ods.compose.component.chip.OdsChoiceChip +import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.imageitem.OdsImageItem +import com.orange.ods.compose.component.imageitem.OdsImageItemDisplayTitle import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing - @OptIn(ExperimentalMaterialApi::class) @Composable fun ComponentImageItem() { @@ -47,12 +43,6 @@ fun ComponentImageItem() { val iconCheckedState = rememberSaveable { mutableStateOf(false) } val recipes = LocalRecipes.current val recipe = rememberSaveable { recipes.random() } - var sliderPosition by remember { mutableStateOf(0f) } - val leftIcon = painterResource(id = R.drawable.ic_display_standard_small) - val leftIconContentDescription = stringResource(id = R.string.component_image_item_small) - val rightIcon = painterResource(id = R.drawable.ic_display_standard) - val rightIconContentDescription = stringResource(id = R.string.component_image_item_large) - val imageItemHeight = 250.dp with(imageItemCustomizationState) { if (!hasText) { @@ -61,36 +51,30 @@ fun ComponentImageItem() { ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { - OdsListItem( - text = stringResource(id = R.string.component_element_text), - trailing = OdsSwitchTrailing(checked = textDisplayed) - ) + OdsChoiceChipsFlowRow( + selectedChip = type, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m)), + outlinedChips = true + ) { + Subtitle(textRes = R.string.component_element_type) + OdsChoiceChip(textRes = R.string.component_image_item_overlay_text, value = ImageItemCustomizationState.Type.Overlay) + OdsChoiceChip(textRes = R.string.component_image_item_below_text, value = ImageItemCustomizationState.Type.Below) + OdsChoiceChip(textRes = R.string.component_element_none, value = ImageItemCustomizationState.Type.None) + } OdsListItem( text = stringResource(id = R.string.component_element_icon), trailing = OdsSwitchTrailing(checked = iconDisplayed, enabled = hasText) ) - Column(modifier = Modifier.padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m))) { - Subtitle(textRes = R.string.component_image_item_sizes) - OdsSlider( - value = sliderPosition, - steps = 1, - valueRange = 0f..2f, - onValueChange = { - sliderPosition = it - size.value = ImageItemCustomizationState.Size.fromSliderValue(it) - }, - leftIcon = leftIcon, - leftIconContentDescription = leftIconContentDescription, - rightIcon = rightIcon, - rightIconContentDescription = rightIconContentDescription - ) - } }) { Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier + .fillMaxSize() + .padding( + top = dimensionResource(id = com.orange.ods.R.dimen.spacing_m), + start = dimensionResource(id = com.orange.ods.R.dimen.spacing_m) + ), + horizontalAlignment = Alignment.Start, ) { - OdsImageItem( image = rememberAsyncImagePainter( model = recipe.imageUrl, @@ -102,17 +86,11 @@ fun ComponentImageItem() { iconSelected = hasIcon, title = if (hasText) recipe.title else null, modifier = Modifier - .run { - when (size.value) { - ImageItemCustomizationState.Size.Small -> fillMaxWidth(0.4f).height(175.dp) - ImageItemCustomizationState.Size.Large -> fillMaxWidth().height(imageItemHeight) - ImageItemCustomizationState.Size.Medium -> fillMaxWidth(0.5f).height(dimensionResource(id = R.dimen.card_big_image_height)) - } - } - .padding(dimensionResource(id = R.dimen.spacing_m)), + .width(dimensionResource(id = com.orange.ods.R.dimen.card_big_image_height)), iconChecked = iconCheckedState.value, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), - onIconCheckedChange = { checked -> iconCheckedState.value = checked } + onIconCheckedChange = { checked -> iconCheckedState.value = checked }, + displayTitle = if (isOverlay) OdsImageItemDisplayTitle.Overlay else if (isBelow) OdsImageItemDisplayTitle.Below else OdsImageItemDisplayTitle.None, ) } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index 8c13849ee..0da038527 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -15,35 +15,32 @@ 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.app.ui.components.floatingactionbuttons.FabCustomizationState @Composable fun rememberImageItemCustomizationState( - size: MutableState = rememberSaveable { mutableStateOf(ImageItemCustomizationState.Size.Small)}, + type: MutableState = rememberSaveable { mutableStateOf(ImageItemCustomizationState.Type.Overlay) }, iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) }, textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } ) = - remember(iconDisplayed, textDisplayed, size) { - ImageItemCustomizationState(size, iconDisplayed, textDisplayed) + remember(type, iconDisplayed, textDisplayed) { + ImageItemCustomizationState(type, iconDisplayed, textDisplayed) } class ImageItemCustomizationState( - val size: MutableState, + val type: MutableState, val iconDisplayed: MutableState, val textDisplayed: MutableState ) { - enum class Size { - Small, Medium, Large; - companion object { - fun fromSliderValue(sliderValue: Float) = - when (sliderValue) { - 0f -> Small - 1f -> Medium - else -> Large - } - } + enum class Type { + Overlay, Below, None } + val isOverlay + get() = type.value == Type.Overlay + + val isBelow + get() = type.value == Type.Below + val hasIcon get() = iconDisplayed.value 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 019e111d2..bbb0055e0 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 @@ -64,7 +64,7 @@ fun ProgressCircular() { modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m)), outlinedChips = true ) { - Subtitle(textRes = R.string.component_progress_type) + Subtitle(textRes = R.string.component_element_type) OdsChoiceChip(textRes = R.string.component_progress_determinate, value = ProgressCustomizationState.Type.Determinate) OdsChoiceChip(textRes = R.string.component_progress_indeterminate, value = ProgressCustomizationState.Type.Indeterminate) } 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 2ccb9be7a..e01ce17f1 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 @@ -66,7 +66,7 @@ fun ProgressLinear() { modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m)), outlinedChips = true ) { - Subtitle(textRes = R.string.component_progress_type) + Subtitle(textRes = R.string.component_element_type) OdsChoiceChip(textRes = R.string.component_progress_determinate, value = ProgressCustomizationState.Type.Determinate) OdsChoiceChip(textRes = R.string.component_progress_indeterminate, value = ProgressCustomizationState.Type.Indeterminate) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c017dcd3d..f09ff7841 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -89,6 +89,7 @@ Empty Example Content + Type UI framework Compose XML @@ -208,6 +209,8 @@ Image item sizes Small image Large image + Overlay + Below Menus @@ -261,7 +264,6 @@ Progress Indicators Progress bar and activity indicator are used when a process is taking place to illustrate progress and to reassure users that something is happening, and if possible, how long it will take. Linear - Type Determinate Indeterminate Current value diff --git a/docs/components/ImageItem.md b/docs/components/ImageItem.md index 1a8caeda1..da6716b29 100644 --- a/docs/components/ImageItem.md +++ b/docs/components/ImageItem.md @@ -40,6 +40,7 @@ OdsImageItem( iconContentDescription = "", // Optional checkedIcon = painterResource(id = R.drawable.ic_heart), uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), + displayType = OdsImageItemDisplayTitle.Overlay, modifier = modifier, imageContentDescription = "Picture content description", //Optional title = "Component Image Item" // Optional diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 2b807befd..aa6767010 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -12,8 +12,11 @@ package com.orange.ods.compose.component.imageitem import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -41,14 +44,16 @@ import com.orange.ods.compose.component.utilities.UiModePreviews import com.orange.ods.compose.theme.OdsDisplaySurface import com.orange.ods.compose.theme.OdsTheme + /** * * @param image Image display in the [OdsImageItem]. * @param iconChecked Specified if icon is currently checked * @param iconSelected Specified whether the icon is selected or nor * @param onIconCheckedChange Callback to be invoked when this icon is selected - * @param checkedIcon Optional icon displayed in front of the [OdsImageItem] - * @param uncheckedIcon Optional icon displayed in front of the [OdsImageItem] + * @param checkedIcon Icon displayed in front of the [OdsImageItem] when icon is checked + * @param uncheckedIcon Icon displayed in front of the [OdsImageItem] when icon is unchecked + * @param displayTitle Specified how the title and icon are displayed relative to image * @param modifier Modifier to be applied to this [OdsImageItem] * @param imageContentDescription Optional image content description * @param iconContentDescription Optional icon content description @@ -63,54 +68,135 @@ fun OdsImageItem( onIconCheckedChange: (Boolean) -> Unit, checkedIcon: Painter, uncheckedIcon: Painter, + displayTitle: OdsImageItemDisplayTitle, modifier: Modifier = Modifier, iconContentDescription: String? = null, imageContentDescription: String? = null, title: String? = null, ) { - Box( - modifier = modifier.fillMaxWidth() - ) { - Image( - painter = image, - contentDescription = imageContentDescription, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - ) - title?.let { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background(color = Color.Black.copy(alpha = 0.38f)) - .align(Alignment.BottomStart) - .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + when (displayTitle) { + OdsImageItemDisplayTitle.Overlay -> + Box( + modifier = modifier.aspectRatio(1.0f) ) { - Text( - text = it, - color = Color.White, - style = OdsTheme.typography.subtitle1, + Image( + painter = image, + contentDescription = imageContentDescription, + contentScale = ContentScale.Crop, modifier = Modifier - .weight(1f) - .padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m)), - maxLines = 1, - overflow = TextOverflow.Ellipsis + .fillMaxSize() + ) + title?.let { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .background(color = Color.Black.copy(alpha = 0.38f)) + .align(Alignment.BottomStart) + .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + ) { + OdsImageItemText( + text = it, + iconChecked = iconChecked, + color = Color.White, + onIconCheckedChange = onIconCheckedChange, + uncheckedIcon = uncheckedIcon, + checkedIcon = checkedIcon, + iconContentDescription = iconContentDescription, + iconSelected = iconSelected, + displaySurface = OdsDisplaySurface.Dark, + modifier = Modifier + .weight(1f) + .padding(start = dimensionResource(id = R.dimen.spacing_m), end = dimensionResource(id = R.dimen.spacing_m)), + ) + } + } + } + + OdsImageItemDisplayTitle.Below -> + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center + ) { + Image( + painter = image, + contentDescription = imageContentDescription, + contentScale = ContentScale.Crop, + modifier = Modifier.aspectRatio(1.0f) ) - if (iconSelected) { - OdsIconToggleButton( - checked = iconChecked, - onCheckedChange = onIconCheckedChange, - uncheckedPainter = uncheckedIcon, - checkedPainter = checkedIcon, - iconContentDescription = iconContentDescription, - displaySurface = OdsDisplaySurface.Dark - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + ) { + title?.let { + OdsImageItemText( + text = it, + iconChecked = iconChecked, + color = OdsTheme.colors.onSurface, + onIconCheckedChange = onIconCheckedChange, + uncheckedIcon = uncheckedIcon, + checkedIcon = checkedIcon, + iconContentDescription = iconContentDescription, + iconSelected = iconSelected, + displaySurface = OdsDisplaySurface.Default, + modifier = Modifier + .weight(1f) + .padding(end = dimensionResource(id = R.dimen.spacing_m)), + ) + } } } - } + + OdsImageItemDisplayTitle.None -> + Box( + modifier = modifier.aspectRatio(1.0f) + ) { + Image( + painter = image, + contentDescription = imageContentDescription, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + ) + } } } +enum class OdsImageItemDisplayTitle { + Below, Overlay, None +} + +@Composable +private fun OdsImageItemText( + text: String, + iconChecked: Boolean, + color: Color, + onIconCheckedChange: (Boolean) -> Unit, + uncheckedIcon: Painter, + checkedIcon: Painter, + iconSelected: Boolean, modifier: Modifier, + displaySurface: OdsDisplaySurface, + iconContentDescription: String? +) { + Text( + text = text, + color = color, + modifier = modifier, + style = OdsTheme.typography.subtitle1, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + if (iconSelected) { + OdsIconToggleButton( + checked = iconChecked, + onCheckedChange = onIconCheckedChange, + uncheckedPainter = uncheckedIcon, + checkedPainter = checkedIcon, + iconContentDescription = iconContentDescription, + displaySurface = displaySurface + ) + } +} @UiModePreviews.Default @Composable @@ -125,17 +211,19 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr iconChecked = parameter.checked, modifier = Modifier.size(parameter.size), iconContentDescription = "", - onIconCheckedChange = { parameter.checked } + onIconCheckedChange = { parameter.checked }, + displayTitle = parameter.type ) } private data class OdsImageListPreviewParameter( val image: Int, val checked: Boolean, - val title: String?, + val title: String, val checkedIcon: Int, val unCheckedIcon: Int, - val size: Dp + val size: Dp, + val type: OdsImageItemDisplayTitle ) private class OdsImageListPreviewParameterProvider : @@ -149,8 +237,32 @@ private val previewParameterValues: List val unCheckedIcon = R.drawable.ic_check return listOf( - OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false, size = 150.dp), - OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = false, size = 300.dp), - OdsImageListPreviewParameter(image, title = title, checkedIcon = checkedIcon, unCheckedIcon = unCheckedIcon, checked = true, size = 400.dp) + OdsImageListPreviewParameter( + image, + title = title, + checkedIcon = checkedIcon, + unCheckedIcon = unCheckedIcon, + checked = false, + size = 300.dp, + type = OdsImageItemDisplayTitle.Below + ), + OdsImageListPreviewParameter( + image, + title = title, + checkedIcon = checkedIcon, + unCheckedIcon = unCheckedIcon, + checked = false, + size = 300.dp, + type = OdsImageItemDisplayTitle.Overlay + ), + OdsImageListPreviewParameter( + image, + title = title, + checkedIcon = checkedIcon, + unCheckedIcon = unCheckedIcon, + checked = true, + size = 300.dp, + type = OdsImageItemDisplayTitle.None + ) ) } \ No newline at end of file From 7a6ea62e0749bae14446f9acb145c5369b914de7 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Tue, 9 May 2023 11:00:00 +0200 Subject: [PATCH 018/112] [#473] Add code implementation in image item component --- .../imageitem/ComponentImageItem.kt | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 290bd363f..0e5c145fb 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -28,7 +28,16 @@ import coil.compose.rememberAsyncImagePainter import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold +import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn +import com.orange.ods.app.ui.utilities.composable.CodeParameter +import com.orange.ods.app.ui.utilities.composable.FunctionCallCode +import com.orange.ods.app.ui.utilities.composable.IconPainterValue +import com.orange.ods.app.ui.utilities.composable.LambdaParameter +import com.orange.ods.app.ui.utilities.composable.PredefinedParameter +import com.orange.ods.app.ui.utilities.composable.SimpleParameter +import com.orange.ods.app.ui.utilities.composable.StringRepresentationParameter import com.orange.ods.app.ui.utilities.composable.Subtitle +import com.orange.ods.compose.component.OdsComponent import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.imageitem.OdsImageItem @@ -45,8 +54,11 @@ fun ComponentImageItem() { val recipe = rememberSaveable { recipes.random() } with(imageItemCustomizationState) { - if (!hasText) { + if (type.value == ImageItemCustomizationState.Type.None) { + textDisplayed.value = false iconDisplayed.value = false + } else { + textDisplayed.value = true } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), @@ -92,6 +104,28 @@ fun ComponentImageItem() { onIconCheckedChange = { checked -> iconCheckedState.value = checked }, displayTitle = if (isOverlay) OdsImageItemDisplayTitle.Overlay else if (isBelow) OdsImageItemDisplayTitle.Below else OdsImageItemDisplayTitle.None, ) + CodeImplementationColumn( + modifier = Modifier.padding(end = dimensionResource(id = R.dimen.spacing_m)) + ) { + FunctionCallCode(name = OdsComponent.OdsImageItem.name, exhaustiveParameters = false, parameters = mutableListOf( + ).apply { + if (isOverlay) add( + StringRepresentationParameter( + "displayTitle", + OdsImageItemDisplayTitle.Overlay + ) + ) else if (isBelow) add(StringRepresentationParameter("displayTitle", OdsImageItemDisplayTitle.Below)) else add( + StringRepresentationParameter("displayTitle", OdsImageItemDisplayTitle.None) + ) + if (hasText) add(PredefinedParameter.Title(recipe.title)) + if (hasIcon) add(SimpleParameter("checkedIcon", IconPainterValue)) + if (hasIcon) add(SimpleParameter("uncheckedIcon", IconPainterValue)) + add(PredefinedParameter.Image) + add(PredefinedParameter.Checked(iconCheckedState.value)) + add(PredefinedParameter.Selected(hasIcon)) + add(LambdaParameter("onIconCheckedChange")) + }) + } } } } From 26afd4825d4db24ae686e4e50231c4bd9868f3c8 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Tue, 9 May 2023 18:28:33 +0200 Subject: [PATCH 019/112] [#473] Review : Add another variant of image --- .../main/res/drawable-hdpi/il_image_item.png | Bin 5144 -> 2279 bytes .../drawable-hdpi/il_image_item_generic.png | Bin 5008 -> 2279 bytes .../main/res/drawable-mdpi/il_image_item.png | Bin 0 -> 1323 bytes .../drawable-mdpi/il_image_item_generic.png | Bin 0 -> 1323 bytes .../main/res/drawable-xhdpi/il_image_item.png | Bin 5144 -> 2366 bytes .../drawable-xhdpi/il_image_item_generic.png | Bin 5008 -> 2366 bytes .../res/drawable-xxhdpi/il_image_item.png | Bin 0 -> 3860 bytes .../drawable-xxhdpi/il_image_item_generic.png | Bin 0 -> 3860 bytes .../res/drawable-xxxhdpi/il_image_item.png | Bin 0 -> 5222 bytes .../il_image_item_generic.png | Bin 0 -> 5222 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/res/drawable-mdpi/il_image_item.png create mode 100644 app/src/main/res/drawable-mdpi/il_image_item_generic.png create mode 100644 app/src/main/res/drawable-xxhdpi/il_image_item.png create mode 100644 app/src/main/res/drawable-xxhdpi/il_image_item_generic.png create mode 100644 app/src/main/res/drawable-xxxhdpi/il_image_item.png create mode 100644 app/src/main/res/drawable-xxxhdpi/il_image_item_generic.png diff --git a/app/src/main/res/drawable-hdpi/il_image_item.png b/app/src/main/res/drawable-hdpi/il_image_item.png index af851b4bbd11e410b245afbd028e3535be3aecb5..e4251b73779708638345c8facb5f40ad6c146e7c 100644 GIT binary patch literal 2279 zcmaKudpy$%AIFzSiJ{~aKTD5W2c-?kV_tONz^Vbi-VRl6`N(4L~7=? zR>z%C7)EYcSSB^*UL2Q``>Er3{(7GCJkR;-`~7@g-|y%9=lgrd`2tcwPE8I30x38; z*rP!pDF6i8r7XRB$7v}Hkl!iN!3Y!r1S+HL-}aZ;(YuIfq%G)KFEG0!^qkMTo|&4O zQdd`}(P*``wFCm;+qZ8uH8sP-!+buUOeWjd*q~6T?Ck6g&G)?^&|Y6hdxR@VH2=Q- z+Q?Zz7B@sDo)AB#akbK+s`MyLE*M(6_Vf;Q~}<8JH*#30!}wI}|! z{V%sk>OZEVQY4B2>Yvo3(toWk@2vUntiOo*^IdALM3cS;oP>h6D9fASm|68L`1ufV za-+tW=DzW7!_)lLaDkm>iSH zB@}!qf$P*aDF@4X4a!_0bQ6=P@IKSNL!p?7NEDUS>%ZkQ&73|L$l>x4Q+2kpmxZvK zZDdiR5y8rX0QH$p&H{Q>VXAfL!O)RuP0sF&U?(H6EvDXnbc*x~+&Q+~98AnMJjAVz zqmm-?m%~chl%IA_a;p!4ZQBDLg;xw-Z#<1G?<8&TpR?`q?i=CKPAcr>XJey3bQA4U z@!b}8idIm`xg%JCKJ_HTIQ9cn7-U!xXhg-)Z$ct<+fZjU#x?yH$jiDL0=m>j5AIe( zhrOLKWMiz`w(y24%)xsg-_fFGP>tzM%_rs0 z>Kt9oDXN0qRc7iSJM6pj4*~_Ghf!YeVfggGq^5Ui0Xx0bj*W^Sv;xS?HnNwmAwilk z(qv(43eripd_&NQtF*p!XwFK&f? zalp1D$Ye8lwgf0qll|zf`nYL`u?jn%$5U3x5H!2q;Z2BMGl?|MxqZ+IU`7KETF zTH-_rTH8D%$1cr~!*|fX$bhbUd*(h)+s48}b9fVq3^}c$u#;|MVxo?)E+EOBld5&R z?+}ml{7tAE6tbx1muTeNp&AYdTj;KDAYHvXu5{fgB?|SeF{Lgr6b;FRGD-vf=gW15lb_v9#U zH4!0_v>|lHxzbj=OO_pGUdbx3(N~Atx76U&e@q`z-BOpKhI}40`-YDQ%>?U!IYG~6 zBxx#&*93?8C=@unie+_ifN&<2g)zaHokd=*me1Hxyiy{Zlfimg^Uf?ZO;UKlum81w zrZm6fS{e&uh%p_TV!i%M5WVuLm^;&9 zb^G2o#_^4~aSI+ki|d(i$|Plha~HD37$c2BD2T#{uL9L`xshgAKyRh~%W|u=2k!mr zQ-Rrr9P=wQt&*a*VGv?r052p@!q5gxxFa$h`{}p8&?nd5=u!~v{)eeTUK+vbREnMO zBxF_ZyCMEc3UBVq)pR$s_lGh7!zF}OPq2RrJF?^io`qv!VA0%(kx3}T&j<2Ck|KGh zcXVj_9cRWN6bbhT1g@o*{Ud1f6BdR~_5q)3!mOF30Fo#hiyV$oY3(W!(4cx(-O68h<*9|_bxSnAJhqeAS) zOyP>JKGH@Q`q5+Um}igx@%dhH+Co4??LBl%V6HcV_*+tQSppDjoUH?N>TjeVzBQh}ZO7 r{6>$z;44|?|IqxO1pfC$xbPd3Mze}#;r)|x@t literal 5144 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 VL4~7MDgUx^2t!MQrfVqde*nQ?0jvN3 diff --git a/app/src/main/res/drawable-hdpi/il_image_item_generic.png b/app/src/main/res/drawable-hdpi/il_image_item_generic.png index f9b00d1db5c11620a6aea9f90b1434d98ace2394..e4251b73779708638345c8facb5f40ad6c146e7c 100644 GIT binary patch literal 2279 zcmaKudpy$%AIFzSiJ{~aKTD5W2c-?kV_tONz^Vbi-VRl6`N(4L~7=? zR>z%C7)EYcSSB^*UL2Q``>Er3{(7GCJkR;-`~7@g-|y%9=lgrd`2tcwPE8I30x38; z*rP!pDF6i8r7XRB$7v}Hkl!iN!3Y!r1S+HL-}aZ;(YuIfq%G)KFEG0!^qkMTo|&4O zQdd`}(P*``wFCm;+qZ8uH8sP-!+buUOeWjd*q~6T?Ck6g&G)?^&|Y6hdxR@VH2=Q- z+Q?Zz7B@sDo)AB#akbK+s`MyLE*M(6_Vf;Q~}<8JH*#30!}wI}|! z{V%sk>OZEVQY4B2>Yvo3(toWk@2vUntiOo*^IdALM3cS;oP>h6D9fASm|68L`1ufV za-+tW=DzW7!_)lLaDkm>iSH zB@}!qf$P*aDF@4X4a!_0bQ6=P@IKSNL!p?7NEDUS>%ZkQ&73|L$l>x4Q+2kpmxZvK zZDdiR5y8rX0QH$p&H{Q>VXAfL!O)RuP0sF&U?(H6EvDXnbc*x~+&Q+~98AnMJjAVz zqmm-?m%~chl%IA_a;p!4ZQBDLg;xw-Z#<1G?<8&TpR?`q?i=CKPAcr>XJey3bQA4U z@!b}8idIm`xg%JCKJ_HTIQ9cn7-U!xXhg-)Z$ct<+fZjU#x?yH$jiDL0=m>j5AIe( zhrOLKWMiz`w(y24%)xsg-_fFGP>tzM%_rs0 z>Kt9oDXN0qRc7iSJM6pj4*~_Ghf!YeVfggGq^5Ui0Xx0bj*W^Sv;xS?HnNwmAwilk z(qv(43eripd_&NQtF*p!XwFK&f? zalp1D$Ye8lwgf0qll|zf`nYL`u?jn%$5U3x5H!2q;Z2BMGl?|MxqZ+IU`7KETF zTH-_rTH8D%$1cr~!*|fX$bhbUd*(h)+s48}b9fVq3^}c$u#;|MVxo?)E+EOBld5&R z?+}ml{7tAE6tbx1muTeNp&AYdTj;KDAYHvXu5{fgB?|SeF{Lgr6b;FRGD-vf=gW15lb_v9#U zH4!0_v>|lHxzbj=OO_pGUdbx3(N~Atx76U&e@q`z-BOpKhI}40`-YDQ%>?U!IYG~6 zBxx#&*93?8C=@unie+_ifN&<2g)zaHokd=*me1Hxyiy{Zlfimg^Uf?ZO;UKlum81w zrZm6fS{e&uh%p_TV!i%M5WVuLm^;&9 zb^G2o#_^4~aSI+ki|d(i$|Plha~HD37$c2BD2T#{uL9L`xshgAKyRh~%W|u=2k!mr zQ-Rrr9P=wQt&*a*VGv?r052p@!q5gxxFa$h`{}p8&?nd5=u!~v{)eeTUK+vbREnMO zBxF_ZyCMEc3UBVq)pR$s_lGh7!zF}OPq2RrJF?^io`qv!VA0%(kx3}T&j<2Ck|KGh zcXVj_9cRWN6bbhT1g@o*{Ud1f6BdR~_5q)3!mOF30Fo#hiyV$oY3(W!(4cx(-O68h<*9|_bxSnAJhqeAS) zOyP>JKGH@Q`q5+Um}igx@%dhH+Co4??LBl%V6HcV_*+tQSppDjoUH?N>TjeVzBQh}ZO7 r{6>$z;44|?|IqxO1pfC$xbPd3Mze}#;r)|x@t literal 5008 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 z zcWbls&UDkz*)#qH2mW0?6B9SP15xR%xN-YH@l|_8QKNe|0+d@Zm07IW(Fc-yHTGWQ z>-RB$gnFb#xh^dzu?Fb6%CM!N7qqQdxU^`9d;Pg?5A1wP--S599kQ@uvT zxVxLcH9@H~?`h`aUOR3Mz7x)u7o4v!o@frJl7Ptgh5?!}p=n3&S$>6GpfaMjMfUb_ zhS11PtSj5`t3c9A!@T%jwlqy?KuU8yhKmVBTmmeU56*B5@d=(Zn%D;gym^4CmRPKV zij5d*Qa$usAb{6IlUmNcYXf2P5GUdqMHC~TVrCLV+^2*f88EyNP~Wz!xtb8Ra&|NH zy6)vHJk%DD-&OI(K!@ZLU_B5yYorOOg7)l=MO4ko18Wg?p0?R+GAx(ewa&TLjSf2|Sd$7@{fALMUjgGJ>!J4FKa0X#qNrFo5Xa7kBL0a$w*X zXOY-Oezvs?;JYR6Y@~PS0*wiFg%`7Fngu|N|5|D%3=GZjpQuyEYX7=nSUnTnYVF&R F{|EAC4A}qx diff --git a/app/src/main/res/drawable-mdpi/il_image_item.png b/app/src/main/res/drawable-mdpi/il_image_item.png new file mode 100644 index 0000000000000000000000000000000000000000..5eabee269410853a1a275fb8a0e94f70dc6c1dda GIT binary patch literal 1323 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 Zfmc6dOP7B!)lu9$fcpW03lDev(m#MGPu>6k literal 5144 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 VL4~7MDgUx^2t!MQrfVqde*nQ?0jvN3 diff --git a/app/src/main/res/drawable-xhdpi/il_image_item_generic.png b/app/src/main/res/drawable-xhdpi/il_image_item_generic.png index f9b00d1db5c11620a6aea9f90b1434d98ace2394..734543f95a330aa94259594f34d23af8715784a6 100644 GIT binary patch literal 2366 zcmcIm`#;nBAOEvq6q@+b>$4GK1Woe`^Ho{2^ zS!lvhiHLn%B9}E(_EDxzMsk_6&R_8T;d_30y`RtLFR$n8^?E-Zuh*5s1UD@WV+{ZR zwA>H6cmaSi2mnf_R8%C6^Tz9yBV~dEo`sK z_D^63eO-IW&vlg4q2h40c8+Xi@cK}B+3iD{LqCTYUb~*a_7Ah4l{hyy`&}i^O}uoY zgbB4zk1nuO-Bpx130s_1wCt3OsmgYs^Sb|lF0N{b)}UI*fR-YGBGrExmXZl!n>ol# z(@k`=zLJy!9|vcf)=**O_GE&3V>LiERG|FOAuoWRh7QFGYsz&2X(7A^tiay#k07;> zQ`YT*dUeqFRt%qocERfmSWX{EiS|kkg|e-4=fX0$qD)gG!M6vv+!sGb z8r|eda@WtB;TyK$ZD`?D{@m8kpX%W)U}eBS*aQ7z$;YAA5skDz!Ss$Bnk%*B2>XgL z^gEpSP;^pFD#%la`-XVna)n2vQ50A5$#UB5a#T^rldP;U zbRiyPlIXv4e_q+kN|9Q&9)eCfnKP$v{(gP>W8BgXGc?UBXzareu|BbNl64qoqqE$A zGBsX~`@<)9oFHAYOVXQoppo+DqP!hJyMZpm5&Jb?x|VIQe2j%{GuuKdd{h0==w#V4 z4+YtfT`r&Q+Qsg7dJMDAFIUah)5E-=1^9`=l2DK{`B~yXbZ-e|!};%ho4^v!YmS!R z9X+J}-Q)x|-0|MwSQ5oSQM8#cf1dg>4w5G}!Jy-0m&72SGycAnNGGJiz9wmQpkc(u zGmy3Cvi4C|E6Ck%m+uK(4GB2jk*Zd~P?zrQHyrrhrVP$pKs05&=g04}&ET-!2KeoI zn!X7Svoh1?%TML`Moi0JThsnRZ^jC>YRfxXb}I0M^GD(HLOO++hfA?C)0Xvot86ML@ewJXe(Zlz{p!ms8w03l%v$+cE-p`El_R%mT z|C$5n>YYWmx~;J4?`i;*^!OA9jqAjK*v?eteLMFee3|z%93c+bB|+X6*-47Gx2V6i z6`T#pmil^1v@ukpBOF*sz`#6=qNrSj^hc|Wtr2{Y$9a0obS-IT9!dT=cMSa_mif|E zgv#fxWa_C6zw7W={o+@DNt@&fhn_(l9t}^fDt-kV3o`no`(`zy_Y|--WKZ9(m3bLQ zsYLQY42C|$$wn^M#GcBM{~b$P*oN2T4~|))7POkbyBxADIVevaWkL1QU5Hgl|oQL3BI}eC`yq@P`l=3{JhknaAZP)q+z) zG5qm9a6WZjm2pmhjJR-+8H&!0DLNwA*{FpC0?~MQYF!vep5(e zN!5gzdZ#^?vF6>Vq=y{uuUgjd#rDf}r&1N>0l}9{-Z|W0B0*>(cu*X^i?$CUXi4TH ztG@l_Fx`>DyIq{->HzhdEFNm=VB`b^mdls7nk>E!zKPTjcz5m*Hb}b)P_6iIG3f(x z(|PX0UctGLx4Gh#*p)&{6VmMcFxTr(rjI{m>3X9Jkr-c|~@-(_ObVj^MutFDN|nzx~1c#(o%_!>n%n}K3%k{?Nq z1BMHWg7@40j&ZTY-1X*kISMM8t~j!ms92=vP1BtKVs9WPoF+R9$s|p)!P~%S3T^PS z;O$A&q9J(8yDf35wfD_dMz(T9e@|T{xXKe=ynkj3SYWxoO_?O>v8;J9-xb;0T)*q& zR9cK=#DL054=SkBag0HR!RAWKlGRDCdZsqeZBg;Ij>QjmSUQJe=%PHGktJ#?%ah%? zTf%hCl>?p)_ZNF*#l@QZE~3Sd1Tz>MtRl7XWfC}nlcisz2&&|fR}(`;x}R9gthzXV zQ#jlZ&JnpT3AFhmv5Npr7vgKuHbMqePJ+7e{~p*l#qL+nQ7mc2DZp9#?N Zfmc6dOP7B!)lu9$fcpW03lDev(m#MGPu>6k literal 5008 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 z zcWbls&UDkz*)#qH2mW0?6B9SP15xR%xN-YH@l|_8QKNe|0+d@Zm07IW(Fc-yHTGWQ z>-RB$gnFb#xh^dzu?Fb6%CM!N7qqQdxU^`9d;Pg?5A1wP--S599kQ@uvT zxVxLcH9@H~?`h`aUOR3Mz7x)u7o4v!o@frJl7Ptgh5?!}p=n3&S$>6GpfaMjMfUb_ zhS11PtSj5`t3c9A!@T%jwlqy?KuU8yhKmVBTmmeU56*B5@d=(Zn%D;gym^4CmRPKV zij5d*Qa$usAb{6IlUmNcYXf2P5GUdqMHC~TVrCLV+^2*f88EyNP~Wz!xtb8Ra&|NH zy6)vHJk%DD-&OI(K!@ZLU_B5yYorOOg7)l=MO4ko18Wg?p0?R+GAx(ewa&TLjSf2|Sd$7@{fALMUjgGJ>!J4FKa0X#qNrFo5Xa7kBL0a$w*X zXOY-Oezvs?;JYR6Y@~PS0*wiFg%`7Fngu|N|5|D%3=GZjpQuyEYX7=nSUnTnYVF&R F{|EAC4A}qx diff --git a/app/src/main/res/drawable-xxhdpi/il_image_item.png b/app/src/main/res/drawable-xxhdpi/il_image_item.png new file mode 100644 index 0000000000000000000000000000000000000000..9f9bf2e01891aa6b93e3cec66094790dfcf63527 GIT binary patch literal 3860 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 p4*j1&ed2WKpYr}YRs^LnCwEy( Date: Fri, 26 May 2023 14:18:05 +0200 Subject: [PATCH 020/112] [#473] Review: Minor fixes in ImageItem preview --- .../component/imageitem/OdsImageItem.kt | 19 +++++++------------ .../compose/component/utilities/Preview.kt | 5 +++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index aa6767010..a009ff6db 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -198,18 +198,17 @@ private fun OdsImageItemText( } } -@UiModePreviews.Default +@UiModePreviews.ImageItem @Composable private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterProvider::class) parameter: OdsImageListPreviewParameter) = Preview { OdsImageItem( image = painterResource(id = parameter.image), iconSelected = true, - uncheckedIcon = painterResource(id = parameter.unCheckedIcon), + uncheckedIcon = painterResource(id = parameter.uncheckedIcon), checkedIcon = painterResource(id = parameter.checkedIcon), title = parameter.title, iconChecked = parameter.checked, - modifier = Modifier.size(parameter.size), iconContentDescription = "", onIconCheckedChange = { parameter.checked }, displayTitle = parameter.type @@ -221,8 +220,7 @@ private data class OdsImageListPreviewParameter( val checked: Boolean, val title: String, val checkedIcon: Int, - val unCheckedIcon: Int, - val size: Dp, + val uncheckedIcon: Int, val type: OdsImageItemDisplayTitle ) @@ -234,34 +232,31 @@ private val previewParameterValues: List val title = "Subtitle 1" val image = R.drawable.placeholder val checkedIcon = R.drawable.ic_check - val unCheckedIcon = R.drawable.ic_check + val uncheckedIcon = R.drawable.ic_check return listOf( OdsImageListPreviewParameter( image, title = title, checkedIcon = checkedIcon, - unCheckedIcon = unCheckedIcon, + uncheckedIcon = uncheckedIcon, checked = false, - size = 300.dp, type = OdsImageItemDisplayTitle.Below ), OdsImageListPreviewParameter( image, title = title, checkedIcon = checkedIcon, - unCheckedIcon = unCheckedIcon, + uncheckedIcon = uncheckedIcon, checked = false, - size = 300.dp, type = OdsImageItemDisplayTitle.Overlay ), OdsImageListPreviewParameter( image, title = title, checkedIcon = checkedIcon, - unCheckedIcon = unCheckedIcon, + uncheckedIcon = uncheckedIcon, checked = true, - size = 300.dp, type = OdsImageItemDisplayTitle.None ) ) diff --git a/lib/src/main/java/com/orange/ods/compose/component/utilities/Preview.kt b/lib/src/main/java/com/orange/ods/compose/component/utilities/Preview.kt index b3edac857..065d586e8 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/utilities/Preview.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/utilities/Preview.kt @@ -61,6 +61,7 @@ internal annotation class UiModePreviews { private const val DarkName = "Dark" private const val ButtonWidthDp = 200 private const val ChipWidthDp = 180 + private const val ImageItemSizeDp = 300 private const val TabWidthDp = 100 } @@ -76,6 +77,10 @@ internal annotation class UiModePreviews { @Preview(name = DarkName, uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, widthDp = ChipWidthDp) annotation class Chip + @Preview(name = LightName, widthDp = ImageItemSizeDp, heightDp = ImageItemSizeDp) + @Preview(name = DarkName, uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, widthDp = ImageItemSizeDp, heightDp = ImageItemSizeDp) + annotation class ImageItem + @Preview(name = LightName, widthDp = TabWidthDp) @Preview(name = DarkName, uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, widthDp = TabWidthDp) annotation class Tab From c75713d29231349bcaaf590b5b9ca656c0fa6601 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:22:25 +0200 Subject: [PATCH 021/112] [#473] Remove aspect ratio in ImageItem --- .../imageitem/ComponentImageItem.kt | 11 ++- .../component/imageitem/OdsImageItem.kt | 80 +++++++++---------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 0e5c145fb..0aa86740c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -12,6 +12,7 @@ package com.orange.ods.app.ui.components.imageitem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.ExperimentalMaterialApi @@ -24,6 +25,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes @@ -87,6 +89,12 @@ fun ComponentImageItem() { ), horizontalAlignment = Alignment.Start, ) { + val imageSize = 200.dp + val height = when (type.value) { + ImageItemCustomizationState.Type.Below -> imageSize + dimensionResource(id = R.dimen.image_item_title_height) + ImageItemCustomizationState.Type.Overlay, + ImageItemCustomizationState.Type.None -> imageSize + } OdsImageItem( image = rememberAsyncImagePainter( model = recipe.imageUrl, @@ -98,7 +106,8 @@ fun ComponentImageItem() { iconSelected = hasIcon, title = if (hasText) recipe.title else null, modifier = Modifier - .width(dimensionResource(id = com.orange.ods.R.dimen.card_big_image_height)), + .width(imageSize) + .height(height), iconChecked = iconCheckedState.value, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), onIconCheckedChange = { checked -> iconCheckedState.value = checked }, diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index a009ff6db..341707f20 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -16,12 +16,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -33,8 +31,6 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource 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.OdsComponentApi import com.orange.ods.compose.component.button.OdsIconToggleButton @@ -74,11 +70,9 @@ fun OdsImageItem( imageContentDescription: String? = null, title: String? = null, ) { - when (displayTitle) { - OdsImageItemDisplayTitle.Overlay -> - Box( - modifier = modifier.aspectRatio(1.0f) - ) { + Box(modifier = modifier) { + when (displayTitle) { + OdsImageItemDisplayTitle.Overlay -> { Image( painter = image, contentDescription = imageContentDescription, @@ -112,45 +106,43 @@ fun OdsImageItem( } } - OdsImageItemDisplayTitle.Below -> - Column( - modifier = modifier.fillMaxWidth(), - verticalArrangement = Arrangement.Center - ) { - Image( - painter = image, - contentDescription = imageContentDescription, - contentScale = ContentScale.Crop, - modifier = Modifier.aspectRatio(1.0f) - ) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + OdsImageItemDisplayTitle.Below -> + Column( + verticalArrangement = Arrangement.Center ) { - title?.let { - OdsImageItemText( - text = it, - iconChecked = iconChecked, - color = OdsTheme.colors.onSurface, - onIconCheckedChange = onIconCheckedChange, - uncheckedIcon = uncheckedIcon, - checkedIcon = checkedIcon, - iconContentDescription = iconContentDescription, - iconSelected = iconSelected, - displaySurface = OdsDisplaySurface.Default, - modifier = Modifier - .weight(1f) - .padding(end = dimensionResource(id = R.dimen.spacing_m)), - ) + Image( + modifier = Modifier + .weight(1.0f) + .fillMaxWidth(), + painter = image, + contentDescription = imageContentDescription, + contentScale = ContentScale.Crop, + ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + ) { + title?.let { + OdsImageItemText( + text = it, + iconChecked = iconChecked, + color = OdsTheme.colors.onSurface, + onIconCheckedChange = onIconCheckedChange, + uncheckedIcon = uncheckedIcon, + checkedIcon = checkedIcon, + iconContentDescription = iconContentDescription, + iconSelected = iconSelected, + displaySurface = OdsDisplaySurface.Default, + modifier = Modifier + .weight(1f) + .padding(end = dimensionResource(id = R.dimen.spacing_m)), + ) + } } } - } - OdsImageItemDisplayTitle.None -> - Box( - modifier = modifier.aspectRatio(1.0f) - ) { + OdsImageItemDisplayTitle.None -> Image( painter = image, contentDescription = imageContentDescription, From df3edc123bfcedce38c2d00998128a4da939eefa Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:23:18 +0200 Subject: [PATCH 022/112] [#473] Review: Minor fixes --- .../ods/app/ui/components/imageitem/ComponentImageItem.kt | 4 ++-- .../orange/ods/compose/component/imageitem/OdsImageItem.kt | 4 ++-- lib/src/main/res/values/dimens.xml | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 0aa86740c..5d522e745 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -84,8 +84,8 @@ fun ComponentImageItem() { modifier = Modifier .fillMaxSize() .padding( - top = dimensionResource(id = com.orange.ods.R.dimen.spacing_m), - start = dimensionResource(id = com.orange.ods.R.dimen.spacing_m) + top = dimensionResource(id = R.dimen.screen_horizontal_margin), + start = dimensionResource(id = R.dimen.screen_horizontal_margin) ), horizontalAlignment = Alignment.Start, ) { diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 341707f20..16801ac51 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -45,7 +45,7 @@ import com.orange.ods.compose.theme.OdsTheme * * @param image Image display in the [OdsImageItem]. * @param iconChecked Specified if icon is currently checked - * @param iconSelected Specified whether the icon is selected or nor + * @param iconSelected Specified whether the icon is selected or not * @param onIconCheckedChange Callback to be invoked when this icon is selected * @param checkedIcon Icon displayed in front of the [OdsImageItem] when icon is checked * @param uncheckedIcon Icon displayed in front of the [OdsImageItem] when icon is unchecked @@ -121,7 +121,7 @@ fun OdsImageItem( Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .height(dimensionResource(id = R.dimen.list_single_line_item_height)) + .height(dimensionResource(id = R.dimen.image_item_title_height)) ) { title?.let { OdsImageItemText( diff --git a/lib/src/main/res/values/dimens.xml b/lib/src/main/res/values/dimens.xml index 26b5f2214..e6f48cb06 100644 --- a/lib/src/main/res/values/dimens.xml +++ b/lib/src/main/res/values/dimens.xml @@ -37,6 +37,9 @@ 20dp + + 48dp + 48dp 56dp From 4aa66cde31e36e3bd5d839c034e8414e8661a8d9 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:37:04 +0200 Subject: [PATCH 023/112] [#473] Review: uncheckedIcon and checkedIcon are now optionals in ImageItem --- .../imageitem/ComponentImageItem.kt | 6 ++--- .../component/imageitem/OdsImageItem.kt | 23 ++++++++----------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 5d522e745..3b94357f3 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -101,9 +101,8 @@ fun ComponentImageItem() { placeholder = painterResource(id = R.drawable.placeholder), error = painterResource(id = R.drawable.placeholder) ), - uncheckedIcon = painterResource(id = R.drawable.ic_heart_outlined), - checkedIcon = painterResource(id = R.drawable.ic_heart), - iconSelected = hasIcon, + uncheckedIcon = if (hasIcon) painterResource(id = R.drawable.ic_heart_outlined) else null, + checkedIcon = if (hasIcon) painterResource(id = R.drawable.ic_heart) else null, title = if (hasText) recipe.title else null, modifier = Modifier .width(imageSize) @@ -131,7 +130,6 @@ fun ComponentImageItem() { if (hasIcon) add(SimpleParameter("uncheckedIcon", IconPainterValue)) add(PredefinedParameter.Image) add(PredefinedParameter.Checked(iconCheckedState.value)) - add(PredefinedParameter.Selected(hasIcon)) add(LambdaParameter("onIconCheckedChange")) }) } diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 16801ac51..5161aea17 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -45,12 +45,11 @@ import com.orange.ods.compose.theme.OdsTheme * * @param image Image display in the [OdsImageItem]. * @param iconChecked Specified if icon is currently checked - * @param iconSelected Specified whether the icon is selected or not * @param onIconCheckedChange Callback to be invoked when this icon is selected - * @param checkedIcon Icon displayed in front of the [OdsImageItem] when icon is checked - * @param uncheckedIcon Icon displayed in front of the [OdsImageItem] when icon is unchecked * @param displayTitle Specified how the title and icon are displayed relative to image * @param modifier Modifier to be applied to this [OdsImageItem] + * @param checkedIcon Icon displayed in front of the [OdsImageItem] when icon is checked + * @param uncheckedIcon Icon displayed in front of the [OdsImageItem] when icon is unchecked * @param imageContentDescription Optional image content description * @param iconContentDescription Optional icon content description * @param title Text displayed in the image @@ -60,12 +59,11 @@ import com.orange.ods.compose.theme.OdsTheme fun OdsImageItem( image: Painter, iconChecked: Boolean, - iconSelected: Boolean, onIconCheckedChange: (Boolean) -> Unit, - checkedIcon: Painter, - uncheckedIcon: Painter, displayTitle: OdsImageItemDisplayTitle, modifier: Modifier = Modifier, + checkedIcon: Painter? = null, + uncheckedIcon: Painter? = null, iconContentDescription: String? = null, imageContentDescription: String? = null, title: String? = null, @@ -96,7 +94,6 @@ fun OdsImageItem( uncheckedIcon = uncheckedIcon, checkedIcon = checkedIcon, iconContentDescription = iconContentDescription, - iconSelected = iconSelected, displaySurface = OdsDisplaySurface.Dark, modifier = Modifier .weight(1f) @@ -132,7 +129,6 @@ fun OdsImageItem( uncheckedIcon = uncheckedIcon, checkedIcon = checkedIcon, iconContentDescription = iconContentDescription, - iconSelected = iconSelected, displaySurface = OdsDisplaySurface.Default, modifier = Modifier .weight(1f) @@ -150,7 +146,7 @@ fun OdsImageItem( modifier = Modifier .fillMaxSize() ) - } + } } } @@ -164,9 +160,9 @@ private fun OdsImageItemText( iconChecked: Boolean, color: Color, onIconCheckedChange: (Boolean) -> Unit, - uncheckedIcon: Painter, - checkedIcon: Painter, - iconSelected: Boolean, modifier: Modifier, + uncheckedIcon: Painter?, + checkedIcon: Painter?, + modifier: Modifier, displaySurface: OdsDisplaySurface, iconContentDescription: String? ) { @@ -178,7 +174,7 @@ private fun OdsImageItemText( maxLines = 1, overflow = TextOverflow.Ellipsis ) - if (iconSelected) { + if (uncheckedIcon != null && checkedIcon != null) { OdsIconToggleButton( checked = iconChecked, onCheckedChange = onIconCheckedChange, @@ -196,7 +192,6 @@ private fun PreviewOdsImageList(@PreviewParameter(OdsImageListPreviewParameterPr Preview { OdsImageItem( image = painterResource(id = parameter.image), - iconSelected = true, uncheckedIcon = painterResource(id = parameter.uncheckedIcon), checkedIcon = painterResource(id = parameter.checkedIcon), title = parameter.title, From d32203adf0d50118a7c59b298d0e1dac8bdf3e20 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:39:26 +0200 Subject: [PATCH 024/112] [#473] Review: Rename OdsImageItemDisplayTitle to OdsImageItemTitleType --- .../components/imageitem/ComponentImageItem.kt | 10 +++++----- .../component/imageitem/OdsImageItem.kt | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 3b94357f3..241b2e881 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -43,7 +43,7 @@ import com.orange.ods.compose.component.OdsComponent import com.orange.ods.compose.component.chip.OdsChoiceChip import com.orange.ods.compose.component.chip.OdsChoiceChipsFlowRow import com.orange.ods.compose.component.imageitem.OdsImageItem -import com.orange.ods.compose.component.imageitem.OdsImageItemDisplayTitle +import com.orange.ods.compose.component.imageitem.OdsImageItemTitleType import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing @@ -110,7 +110,7 @@ fun ComponentImageItem() { iconChecked = iconCheckedState.value, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), onIconCheckedChange = { checked -> iconCheckedState.value = checked }, - displayTitle = if (isOverlay) OdsImageItemDisplayTitle.Overlay else if (isBelow) OdsImageItemDisplayTitle.Below else OdsImageItemDisplayTitle.None, + displayTitle = if (isOverlay) OdsImageItemTitleType.Overlay else if (isBelow) OdsImageItemTitleType.Below else OdsImageItemTitleType.None, ) CodeImplementationColumn( modifier = Modifier.padding(end = dimensionResource(id = R.dimen.spacing_m)) @@ -120,10 +120,10 @@ fun ComponentImageItem() { if (isOverlay) add( StringRepresentationParameter( "displayTitle", - OdsImageItemDisplayTitle.Overlay + OdsImageItemTitleType.Overlay ) - ) else if (isBelow) add(StringRepresentationParameter("displayTitle", OdsImageItemDisplayTitle.Below)) else add( - StringRepresentationParameter("displayTitle", OdsImageItemDisplayTitle.None) + ) else if (isBelow) add(StringRepresentationParameter("displayTitle", OdsImageItemTitleType.Below)) else add( + StringRepresentationParameter("displayTitle", OdsImageItemTitleType.None) ) if (hasText) add(PredefinedParameter.Title(recipe.title)) if (hasIcon) add(SimpleParameter("checkedIcon", IconPainterValue)) diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 5161aea17..57aea7af2 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -60,7 +60,7 @@ fun OdsImageItem( image: Painter, iconChecked: Boolean, onIconCheckedChange: (Boolean) -> Unit, - displayTitle: OdsImageItemDisplayTitle, + displayTitle: OdsImageItemTitleType, modifier: Modifier = Modifier, checkedIcon: Painter? = null, uncheckedIcon: Painter? = null, @@ -70,7 +70,7 @@ fun OdsImageItem( ) { Box(modifier = modifier) { when (displayTitle) { - OdsImageItemDisplayTitle.Overlay -> { + OdsImageItemTitleType.Overlay -> { Image( painter = image, contentDescription = imageContentDescription, @@ -103,7 +103,7 @@ fun OdsImageItem( } } - OdsImageItemDisplayTitle.Below -> + OdsImageItemTitleType.Below -> Column( verticalArrangement = Arrangement.Center ) { @@ -138,7 +138,7 @@ fun OdsImageItem( } } - OdsImageItemDisplayTitle.None -> + OdsImageItemTitleType.None -> Image( painter = image, contentDescription = imageContentDescription, @@ -150,7 +150,7 @@ fun OdsImageItem( } } -enum class OdsImageItemDisplayTitle { +enum class OdsImageItemTitleType { Below, Overlay, None } @@ -208,7 +208,7 @@ private data class OdsImageListPreviewParameter( val title: String, val checkedIcon: Int, val uncheckedIcon: Int, - val type: OdsImageItemDisplayTitle + val type: OdsImageItemTitleType ) private class OdsImageListPreviewParameterProvider : @@ -228,7 +228,7 @@ private val previewParameterValues: List checkedIcon = checkedIcon, uncheckedIcon = uncheckedIcon, checked = false, - type = OdsImageItemDisplayTitle.Below + type = OdsImageItemTitleType.Below ), OdsImageListPreviewParameter( image, @@ -236,7 +236,7 @@ private val previewParameterValues: List checkedIcon = checkedIcon, uncheckedIcon = uncheckedIcon, checked = false, - type = OdsImageItemDisplayTitle.Overlay + type = OdsImageItemTitleType.Overlay ), OdsImageListPreviewParameter( image, @@ -244,7 +244,7 @@ private val previewParameterValues: List checkedIcon = checkedIcon, uncheckedIcon = uncheckedIcon, checked = true, - type = OdsImageItemDisplayTitle.None + type = OdsImageItemTitleType.None ) ) } \ No newline at end of file From 427df7cfafa885084c64799dcef407b4f527c030 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:44:47 +0200 Subject: [PATCH 025/112] [#473] Review: Remove textDisplayed in ImageItemCustomizationState --- .../ui/components/imageitem/ComponentImageItem.kt | 3 --- .../imageitem/ImageItemCustomizationState.kt | 13 +++++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 241b2e881..65e6472a2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -57,10 +57,7 @@ fun ComponentImageItem() { with(imageItemCustomizationState) { if (type.value == ImageItemCustomizationState.Type.None) { - textDisplayed.value = false iconDisplayed.value = false - } else { - textDisplayed.value = true } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index 0da038527..a4622b7b2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -19,17 +19,15 @@ import androidx.compose.runtime.saveable.rememberSaveable @Composable fun rememberImageItemCustomizationState( type: MutableState = rememberSaveable { mutableStateOf(ImageItemCustomizationState.Type.Overlay) }, - iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) }, - textDisplayed: MutableState = rememberSaveable { mutableStateOf(true) } + iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) } ) = - remember(type, iconDisplayed, textDisplayed) { - ImageItemCustomizationState(type, iconDisplayed, textDisplayed) + remember(type, iconDisplayed) { + ImageItemCustomizationState(type, iconDisplayed) } class ImageItemCustomizationState( val type: MutableState, - val iconDisplayed: MutableState, - val textDisplayed: MutableState + val iconDisplayed: MutableState ) { enum class Type { Overlay, Below, None @@ -45,6 +43,5 @@ class ImageItemCustomizationState( get() = iconDisplayed.value val hasText - get() = textDisplayed.value - + get() = type.value != OdsImageItemTitleType.None } \ No newline at end of file From d1c8cb0b12927632dd941cfa721dd8b52f4e0f79 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 14:49:01 +0200 Subject: [PATCH 026/112] [#473] Review: Use OdsImageItemTitleType instead of ImageItemCustomizationState.Type --- .../imageitem/ComponentImageItem.kt | 23 +++++++------------ .../imageitem/ImageItemCustomizationState.kt | 12 ++++------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 65e6472a2..68cfb0fa9 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -56,7 +56,7 @@ fun ComponentImageItem() { val recipe = rememberSaveable { recipes.random() } with(imageItemCustomizationState) { - if (type.value == ImageItemCustomizationState.Type.None) { + if (type.value == OdsImageItemTitleType.None) { iconDisplayed.value = false } ComponentCustomizationBottomSheetScaffold( @@ -68,9 +68,9 @@ fun ComponentImageItem() { outlinedChips = true ) { Subtitle(textRes = R.string.component_element_type) - OdsChoiceChip(textRes = R.string.component_image_item_overlay_text, value = ImageItemCustomizationState.Type.Overlay) - OdsChoiceChip(textRes = R.string.component_image_item_below_text, value = ImageItemCustomizationState.Type.Below) - OdsChoiceChip(textRes = R.string.component_element_none, value = ImageItemCustomizationState.Type.None) + OdsChoiceChip(textRes = R.string.component_image_item_overlay_text, value = OdsImageItemTitleType.Overlay) + OdsChoiceChip(textRes = R.string.component_image_item_below_text, value = OdsImageItemTitleType.Below) + OdsChoiceChip(textRes = R.string.component_element_none, value = OdsImageItemTitleType.None) } OdsListItem( text = stringResource(id = R.string.component_element_icon), @@ -88,9 +88,9 @@ fun ComponentImageItem() { ) { val imageSize = 200.dp val height = when (type.value) { - ImageItemCustomizationState.Type.Below -> imageSize + dimensionResource(id = R.dimen.image_item_title_height) - ImageItemCustomizationState.Type.Overlay, - ImageItemCustomizationState.Type.None -> imageSize + OdsImageItemTitleType.Below -> imageSize + dimensionResource(id = R.dimen.image_item_title_height) + OdsImageItemTitleType.Overlay, + OdsImageItemTitleType.None -> imageSize } OdsImageItem( image = rememberAsyncImagePainter( @@ -114,14 +114,7 @@ fun ComponentImageItem() { ) { FunctionCallCode(name = OdsComponent.OdsImageItem.name, exhaustiveParameters = false, parameters = mutableListOf( ).apply { - if (isOverlay) add( - StringRepresentationParameter( - "displayTitle", - OdsImageItemTitleType.Overlay - ) - ) else if (isBelow) add(StringRepresentationParameter("displayTitle", OdsImageItemTitleType.Below)) else add( - StringRepresentationParameter("displayTitle", OdsImageItemTitleType.None) - ) + add(StringRepresentationParameter("displayTitle", type.value)) if (hasText) add(PredefinedParameter.Title(recipe.title)) if (hasIcon) add(SimpleParameter("checkedIcon", IconPainterValue)) if (hasIcon) add(SimpleParameter("uncheckedIcon", IconPainterValue)) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt index a4622b7b2..782bb212c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ImageItemCustomizationState.kt @@ -15,10 +15,11 @@ 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.imageitem.OdsImageItemTitleType @Composable fun rememberImageItemCustomizationState( - type: MutableState = rememberSaveable { mutableStateOf(ImageItemCustomizationState.Type.Overlay) }, + type: MutableState = rememberSaveable { mutableStateOf(OdsImageItemTitleType.Overlay) }, iconDisplayed: MutableState = rememberSaveable { mutableStateOf(false) } ) = remember(type, iconDisplayed) { @@ -26,18 +27,15 @@ fun rememberImageItemCustomizationState( } class ImageItemCustomizationState( - val type: MutableState, + val type: MutableState, val iconDisplayed: MutableState ) { - enum class Type { - Overlay, Below, None - } val isOverlay - get() = type.value == Type.Overlay + get() = type.value == OdsImageItemTitleType.Overlay val isBelow - get() = type.value == Type.Below + get() = type.value == OdsImageItemTitleType.Below val hasIcon get() = iconDisplayed.value From a2ec6140e67eff4447aaaa9e3c8376bc48166cc3 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 15:32:20 +0200 Subject: [PATCH 027/112] [#473] Fix build errors following rebase --- .../orange/ods/app/ui/components/Component.kt | 2 +- .../imageitem/ComponentImageItem.kt | 29 +++++++++---------- .../component/imageitem/OdsImageItem.kt | 4 +-- 3 files changed, 16 insertions(+), 19 deletions(-) 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 b04ff22b3..22268b5f2 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 @@ -130,7 +130,7 @@ sealed class Component( R.drawable.il_image_item, null, R.string.component_image_item_description, - composableName = OdsComponent.OdsImageItem.name + composableName = OdsComposable.OdsImageItem.name ) object Lists : Component( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index 68cfb0fa9..ca43209eb 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -31,15 +31,10 @@ import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn -import com.orange.ods.app.ui.utilities.composable.CodeParameter import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.app.ui.utilities.composable.IconPainterValue -import com.orange.ods.app.ui.utilities.composable.LambdaParameter -import com.orange.ods.app.ui.utilities.composable.PredefinedParameter -import com.orange.ods.app.ui.utilities.composable.SimpleParameter -import com.orange.ods.app.ui.utilities.composable.StringRepresentationParameter import com.orange.ods.app.ui.utilities.composable.Subtitle -import com.orange.ods.compose.component.OdsComponent +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.imageitem.OdsImageItem @@ -112,16 +107,18 @@ fun ComponentImageItem() { CodeImplementationColumn( modifier = Modifier.padding(end = dimensionResource(id = R.dimen.spacing_m)) ) { - FunctionCallCode(name = OdsComponent.OdsImageItem.name, exhaustiveParameters = false, parameters = mutableListOf( - ).apply { - add(StringRepresentationParameter("displayTitle", type.value)) - if (hasText) add(PredefinedParameter.Title(recipe.title)) - if (hasIcon) add(SimpleParameter("checkedIcon", IconPainterValue)) - if (hasIcon) add(SimpleParameter("uncheckedIcon", IconPainterValue)) - add(PredefinedParameter.Image) - add(PredefinedParameter.Checked(iconCheckedState.value)) - add(LambdaParameter("onIconCheckedChange")) - }) + FunctionCallCode( + name = OdsComposable.OdsImageItem.name, + exhaustiveParameters = false, + parameters = { + stringRepresentation("displayTitle", type.value) + if (hasText) title(recipe.title) + if (hasIcon) simple("checkedIcon", IconPainterValue) + if (hasIcon) simple("uncheckedIcon", IconPainterValue) + image() + checked(iconCheckedState.value) + lambda("onIconCheckedChange") + }) } } } diff --git a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt index 57aea7af2..cba04d0d4 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/imageitem/OdsImageItem.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import com.orange.ods.R -import com.orange.ods.compose.component.OdsComponentApi +import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.button.OdsIconToggleButton import com.orange.ods.compose.component.utilities.BasicPreviewParameterProvider import com.orange.ods.compose.component.utilities.Preview @@ -55,7 +55,7 @@ import com.orange.ods.compose.theme.OdsTheme * @param title Text displayed in the image */ @Composable -@OdsComponentApi +@OdsComposable fun OdsImageItem( image: Painter, iconChecked: Boolean, From ba397839fe7df35902d879c69c41209320125eee Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 26 May 2023 15:38:18 +0200 Subject: [PATCH 028/112] [#473] Review: Minor fixes in ImageItem demo --- .../components/imageitem/ComponentImageItem.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt index ca43209eb..7ea36ef3e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/imageitem/ComponentImageItem.kt @@ -18,8 +18,10 @@ import androidx.compose.foundation.layout.width 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource @@ -46,7 +48,7 @@ import com.orange.ods.compose.component.list.OdsSwitchTrailing @Composable fun ComponentImageItem() { val imageItemCustomizationState = rememberImageItemCustomizationState() - val iconCheckedState = rememberSaveable { mutableStateOf(false) } + var iconChecked by rememberSaveable { mutableStateOf(false) } val recipes = LocalRecipes.current val recipe = rememberSaveable { recipes.random() } @@ -54,6 +56,9 @@ fun ComponentImageItem() { if (type.value == OdsImageItemTitleType.None) { iconDisplayed.value = false } + if (!hasIcon) { + iconChecked = false + } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { @@ -99,9 +104,9 @@ fun ComponentImageItem() { modifier = Modifier .width(imageSize) .height(height), - iconChecked = iconCheckedState.value, + iconChecked = iconChecked, iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), - onIconCheckedChange = { checked -> iconCheckedState.value = checked }, + onIconCheckedChange = { checked -> iconChecked = checked }, displayTitle = if (isOverlay) OdsImageItemTitleType.Overlay else if (isBelow) OdsImageItemTitleType.Below else OdsImageItemTitleType.None, ) CodeImplementationColumn( @@ -113,11 +118,11 @@ fun ComponentImageItem() { parameters = { stringRepresentation("displayTitle", type.value) if (hasText) title(recipe.title) - if (hasIcon) simple("checkedIcon", IconPainterValue) - if (hasIcon) simple("uncheckedIcon", IconPainterValue) image() - checked(iconCheckedState.value) + checked(iconChecked) lambda("onIconCheckedChange") + if (hasIcon) simple("checkedIcon", IconPainterValue) + if (hasIcon) simple("uncheckedIcon", IconPainterValue) }) } } From 28623fc81fe819e3534b1d6a2fff14a4014b459f Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Fri, 2 Jun 2023 16:46:52 +0200 Subject: [PATCH 029/112] [#473] Update ImageItem thumbnail --- .../main/res/drawable-hdpi/il_image_item.png | Bin 2279 -> 13394 bytes .../drawable-hdpi/il_image_item_generic.png | Bin 2279 -> 13394 bytes .../main/res/drawable-mdpi/il_image_item.png | Bin 1323 -> 7974 bytes .../drawable-mdpi/il_image_item_generic.png | Bin 1323 -> 7974 bytes .../main/res/drawable-xhdpi/il_image_item.png | Bin 2366 -> 17364 bytes .../drawable-xhdpi/il_image_item_generic.png | Bin 2366 -> 17364 bytes .../res/drawable-xxhdpi/il_image_item.png | Bin 3860 -> 30009 bytes .../drawable-xxhdpi/il_image_item_generic.png | Bin 3860 -> 30009 bytes .../res/drawable-xxxhdpi/il_image_item.png | Bin 5222 -> 12907 bytes .../il_image_item_generic.png | Bin 5222 -> 12907 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/main/res/drawable-hdpi/il_image_item.png b/app/src/main/res/drawable-hdpi/il_image_item.png index e4251b73779708638345c8facb5f40ad6c146e7c..aacfc75cb34994b0593fc28ca1abdda100d0271b 100644 GIT binary patch literal 13394 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 r{6>$z;44|?|IqxO1pfC$xbPd3Mze}#;r)|x@t diff --git a/app/src/main/res/drawable-hdpi/il_image_item_generic.png b/app/src/main/res/drawable-hdpi/il_image_item_generic.png index e4251b73779708638345c8facb5f40ad6c146e7c..aacfc75cb34994b0593fc28ca1abdda100d0271b 100644 GIT binary patch literal 13394 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 r{6>$z;44|?|IqxO1pfC$xbPd3Mze}#;r)|x@t diff --git a/app/src/main/res/drawable-mdpi/il_image_item.png b/app/src/main/res/drawable-mdpi/il_image_item.png index 5eabee269410853a1a275fb8a0e94f70dc6c1dda..65fa879e5f30416562908378bd5b778703100b33 100644 GIT binary patch literal 7974 zcmeHsXH-*L+h7uc1f(R0fYc;_pj1U6^bmo7bOi*2&;&$!?@c0I%0&eM=_-OWK~Rcx zL5d(nkY1!pkxq~@C*1qqZ|2XenYHH6to0=;Is5GMl)az*^b@6{g{PxoqXB_HbZV+f zw?H6pHZcAMI|KY%AJJ6-7qI&+JPuU+m17CGAzB-%*=TBlE(2p2h!V^Qq9B6+Ul5oL zM0q*}fo_7?{~g}~3;Y8E0f8dyK+u0+jDah8C`A7I_ezlk{x6DIkpDn~v$H7vGX{Ml z<7K#)1^^do7gZy75J-fF`~&;ERPX{w8QJL>dKhYINLx8OidfupwzL-UadaVLfnxTadLA1XIsDqMafS@ zuZW0={!1G`l_k$gYrEN51Dwh9<*&$|LjEsv|L~C&B{Tn@7W4N`PiKLx%G1b-{wp?l znhS(!DiDa{yPA@so)38Sg?+oV{!!bIc$|3dSImU1!{s)yrfZ^n7ZA7$x&mx4HP-E7 zB~=)dj;59UnR5k!SJ@)_{5_9EKNO6ReM`N2K#xPStwt)@p-=ZU>FvqM-iMwI*&g{G zDgX8Io*$W%w!1(4OLp$s2Ywf?UpGpw+Z_w1WB1|7C!~Mp8(t-9m@auZ$|%5Mc2vR)%n&Yq@mcfrZ4)u zVBhj!p>39gLH13^XqB_JjQ5&`ySux7>fra-b2>zu+R(zcT#$-|4F6aB89q5T%STdC zkO)q%6x_8Nt3Q6Huy*& z(cSohd5+bFlqByfrLC7n#;b6dAw2T(|T6uA^YaTw((j&?T%8!GRqNy$>kb(jyi^yK;=#(NUX# zVhX)DlBb9*+5C-S()D?p=M#>hXT78~y^C9#ZExT3HcvbWmZ_&GtwD z*{?f^)eV( zsZA@qh7E%W&1=+*i5tguBI5{2*ik_J9PJUE<@X?+ z$)f8&MT1)Qx+Nz~u8N*h&C&!ERpNcPwb51Ze58cKZ~{C>WG8YFT(>)7Z?`qyt!`rj zp{|Gm-0!RJr9wQ229&p>VTb-ho})J@`H!F`DZNc9WvYw5i&aQy zXbVKEn?%e$3@0xUiztXMdUh9!3?E`OV6S9hYPHFJs0d-q2g0PDs98}}2*~oC+gyhb zu6a8gE;yr{%BDL^7`#ECN@U!Uwz6=FnzFYr`S9J#OE`B z;heGDm=QvlJK1Ii?tWp&dZ!M-iRa^)AT7P)id3A})M0O9pvLN0!~@5E!_v#ty}45R zQ(^s|{;Z|##jsZ_fA87f)7TluViT*&B@5iJ#$ ztSi-h7VTa3_^rD)48uS0qO$T;AQT?6zdg=Ist~~T$ZlznJp?n?Zjlw$I_ojJ6verl z=AQvu5&xcLVCx1wpp0}3PO+bnz7e=GRRq^84($5)0ytf-%9xj_S0ORfaQ$idhk0;K zqh{l=5kSoEc_hM&_;oH~b%P%XsQNr<7zLoKjP{GdpoRhs1@Cp6OCe0V=W~$Lx{wp# zKsQCRlc1I#w<{e-Rf3#*Z&3DC@IsKT6&$SC0kql zWUfD#MyJ6b@*%OU7!X1AF-vduL4*qtR2Gn`Zy(vW)uWL4NWh?6IQ-?Lmh1U3%cX<$ zOG-ydvg>HTE5#H|G)nKCr7Bqyp`owKvLNQ)w*_pRv|6Yvqpn;C7k-+uVfz?Rb9>lv|stu0*)x*o@2QIXIc^w6hfuXhI!it6UGG;j7 zH(a(&EPrK`HYbL_%LeH!R!80&ec1f67W|>jK(>{u7bk;c>SU}eJ7>+1<{HPjQh%^q z=v%yOy6!g=z{%sZ!8#YWJr*M?#jMck;TafT`|DlF_zxgM>~U zN=oFP%cQaW>>zD@uuIY)Kgl1nM~-gd<1{gi^L8$5=@;>>sejX_MHgPRP(#;stBH|B2t6A3q(y@+p z`v*4<3yLFl*?2Zeof3chwo4|%ugrTd{Kbu z5?xn_k6&_JQm|~&VZkXK>uP^$dp&8hAw3yEZBZrU)H2JOonmIPB@k)>SZK4(RV@Z^ zx^{q{)AkFd7kZOL%Gx~RxHiQo9uazWa5TZ#g!_q2YVkLo9KtuA%ijz4Ur4(WY7Gtv z1`8Im^z0=<3hU}qt1ngFB{V!?ghjNM31Dvj&hJztY+vH_5@%6bPRGyCrR!$Gz-m-XG1qwmrUraztM6JrM7X?y&rr$rNP zzQdm8`>wEJ`UMnzUy0wKt1-s3LTZ<4W;5!8(3aO5Zw0=dG;+%{nsbbJQ8BlM_LYU(T3aszZ;*QZ(^#6aOZTY4HocXSr{SvezQ7k?Ub|| z&vP9J4>|6J*!;CYJ%w$)LT^tO(fP;IrBXIMy8maGFk~;8F^4wsOjV9s`8d;l+}*RTc2^ai;gVE_V5 z_4=njeW(bIzkZ-q8pS{OJi(Hkvh>meezbfWjhUigkQF=W;}^y;hfY_+ z`Qk9+r9HkdmW@jkC4c6xPXsJXfT=hV%#cG~s2r^)$z0u%^`!3YviLxuJ)4IV_z5Nd zUqw}~GLKelr6vIHo1rB&_vEnScTRf)kdSeyDja-^?*lydyuWfQM!6;^89NA_IOaBa zZ@sE!4gwRwRHQH3L01EJ%QL%irA!g{1BJtBhNXjFCEAk*->dq*@Moq|(y>n-t$Fu{ zG03M(5^sK|mD{k-^v}zE-Qb;$DWOp~Sj_EzZkzF(^MIT%0x4U15Rh~%_+UttXP{3} z80ca8>MkqMC#gR;}zshJ-#U~MGF?U=X9|f zjf%ABvc&gmV-cyx66pnOQ%DkJPc3CLE*I*DtF=iV@|#t%VvXbZnlypft3R9*eQgyd zWplimr?B71@NR2fzV6l0?x-kd(Ejw6vSF2zX;93i8@C!!<9jQ6!EzJC~(lA#mN_EJHFXTW01rb#@ewm#T~a(R7pZXFJu) zHwSE_&4P8ScsYp|J~L524sq5|(oJF*5k1LeB@WCtE5D6a7XNUmLcs6w>hPLt=c_ia zzdwf+Feo9skZ_(zKR({8w?#Et%s^ekHQwQS!b#B)+(AABlgO|H*{v>dU)a*CG^d;1 zA2$F+o2RXx(`q;63PHL6cI6 z=zDG6TYzH6P}ML;BW#&zFB(70xc6Z;PF-@$r5VfIx(}$0KA^n?st;@hAegV$?Z4+K zoRE5!KC?S_X))nDaFpSVLHo_zUt{1~Tr6e*t|RZPJ8Sz}jjlR;PN&M*s;WP7oIoUT zp>AAbEfAEx&mk0-_K|zd%C-#tFd`wbrv}h3*$VuROFg;q@~IhtyFXf%#b=)VxGPj* zv93$y=QvQe47YsbI8RK?*B=jAS+CB>FCjw8ze)?f@aR*EBG6Bd*q3MqAM|JT0Sj8KaGm|kg+MDXUq#|tNS`G? zB?9y*j&}xemz&y}9INCYZyJUA-GCTRPwcdj}+^ zJq)riCgG)GZwD1B9*O^Ct4mz0b9u}~SH}x|NJ)=?v(0N5erWK9Y?Q@kk?T6M*&+GD z+31JK&W9%oFdFqLM235hoVUDbOSX{E!tvOGeDZ>>NX^Q-F@IQStdBnTo3p;RC7N(^ z+p zrU$d!Cn^5wOi5DwenKl8r54TM0d;0XRc+CjxpA|TgP|=(rx}-{Hicu?gH`9?G!}W} zEf!M`-Het6F2TX!sHu%M?jHKuNhkf3_83&-pY?hL1%}owIg_*Wx$`%CE!yGyJvYBK z(}r>nzFOiH69W6=n*X{wp$Biso>A(-%*K)^>6tk2-7^qB5m|1fk{kti1lO6f9C6ai z`2p+;U1L3WK5ag&?P0s&DGT~Tc0Zpao8HFeUgT1&pbI?@Kyb56Qwju zgl~|Y-bWVJh1ub2MbL0QbM5**;?=Ln6;pu)?4i@{E%x=j4UJ4a?WnjJ0QSyDGUxaE{AFm#LkQb+Kg)vATTXjv>R ze;70s2;kS1M*=QtnJ4_-W{I|t3E$4ahq;>pCCTx2n{Y;djioZ@#xHuI0dqS{9V#yK zVBob%ha_|{5znP|$1Kou?b2neXH~}TA0WRcXVKTdL@0`e>aoj)xo{1P=~2|L?SM#p z>gx=@%RMZv1aG8s%R@q^{vFe&+{Z_Ycm-**d5g{XgCn2-)kroA^5x3mxcI`ASBKmq zc#tz<&j4z#8SWCO7dluu&@pa=Yl_MNZi+q z)B#-#W;QZ$`@%;ux=0l8vy9m}z=goIm<<~!P}Z#OWgdwocxpX0prY098!)hkZOhzl zp~82L(f2UYF`_vzok>5CPq=VUgZd2@!7b%s3(N}foK}0YMeXJ3q55M_>D7w<&6v4m z!YfYe8C72d6LGOmU9`IpoI8P1Y26QC0FPAI-L3`_>T)`+DgBTGHQxy)KxH%&B{D}~ zQ!dS}>kn zw-%mKyGdO{4pa?Q>&;I!k67UVk%Y?Mc zkVq|EBAjto!GEQ!ZF5WJN^&!V0wRZ+_c>68vC|fnU0sWlS!*AqDKi3+M(Ik}GQ-bS zakXKqrET7f5=9e%w&@x(^^F(n0sLo$<;0iIp_M;M)dRiF&8=H2mlaF8xpabK{3fme z2W2-ie@7}b2(Ru8RWuLN%eY`!Otg?l7i($`ps#ywDNWNVRyF7J+C!2~Y6@SZ<}ms+Mrj=5ktpV-PU;l*ec)M|=diC;pBRn@SYouu|HnOj^^hxO)~w}om4%$)o2L!TtbhL39g zITX-UIHi{f#6IsxWf&!{nMx5UmfYM?Y01FvC-9ax*Slrw##=8d0u^e8#I^a#sYhAT)c!m>Q?cc-hJK^a4~SF(5(B0 z6ObG^_gx<{Ny_xa1>~DO4VB1FZZ$aptMa)<&pXwXrYXj5SNt4QMonu*Ug zUxXJL5{frc>oAqyNjD@iCIJbLWcZrw-V*7PUQQE-&FD%j^+9beNN>32 z3*F3e7;gH)V@~@GQNT3o;L;T=px`bSd9_5>c5xLj31wzvhZ$anxSS>3FR`H9KKPRLXc(wUsqKu(1%fJ|HGJK3F+reza9G{Ctk53LccA< z_SR|Vm=)+8U*l4^ecCy0U2w+6Dd$w3{Ed^$eW0o5jumg$1q8#iysO;|-wN zncWb7f7&{ZRm39LC^FPe`_1OEfF<}GY6ze9oB!#d{U5NJnsA-n_+_#@e8sO!Q&EPh zmBtow){#+=i&{tpi!hFj+nZ1fp}XQs30ePvA_~;;EI>R(lrK2P7*w zdGTyXhW`GY>OS$Q5UA+y-Z2!Pd~1yA(t6(1_*#a(1W=+4w)AP@Uxl$e0s@G`lroK zJK%Kk?lv5f@qsm?2c|z)Et3~>Q4`3$J!PsneZ*OmQcDB)Il*we1lapGlwxpzl!{K~ue)x!KQ5%^UNI5?-KjD9B^3X2J z)2VmbM20Q+Bf?wXZFVXip-SvxcEcLVY!GIV#FD50!J|MMUUSt@M};83KLv^0!&c=# z=jKSGhtJ$GqyGF1-nhnO>)?yhopH|c0COSbI`438!_Km%94db63QPy0U%AUIEfLT}48cvt#*3Uk$a9=C8g~ zr&IXGQe<`#V0&G9Yj4KXG3ZCix&hnD{d$vMz#N{RQlyCx5^^O$Opl)81;L(qID^JNx_ZLR zf#&M|$1bFtD7yA4!$SFX>ElpKfJT{Y%Gn&=LfQ8fN_S~*P1l?GP9A9<+_fx_6QqA@ zs>&WEIh85^+E!AN;M|=`m({SA3pG-{v(hdeH3ziGYy`CFM^$IV7-DkR0WYxW{KY=t z5fXbl_J*ntGHQ)-;^PQf%*l2%J_w`H!d)DjA6VpQZMg6nO!atZy5Z^Y>Z>SuBT`o! zq$?xlBLM0m?OjrtWp&OBauF8PLNJEi6NidPO>SsiSmAb5Q>@-^>a_g97sqya*uz_> zCZ@rc!uMZeA#Py3f=9)DgZ9|*{(E&ixKlGz365kf&>4_Q1=rw)CXMFw4MHcz!N*v1 zw(FTn`nh|O=C!8!?&cwVC6g+Bt(Z?NhZi*LhBs43QEBqR;%)&T5DOWM)l1{lFIScZ zrweu@oV64=4*6u9C*PfXQM2ayIRJOLfgI^<9$5P$Y?;F-n)>~j(GhX1Ukx9eYyCdR zq2~kaTsH6k*U(y&%He*ae{ZY!=kN7TdKhYINLx8OidfupwzL-UadaVLfnxTadLA1XIsDqMafS@ zuZW0={!1G`l_k$gYrEN51Dwh9<*&$|LjEsv|L~C&B{Tn@7W4N`PiKLx%G1b-{wp?l znhS(!DiDa{yPA@so)38Sg?+oV{!!bIc$|3dSImU1!{s)yrfZ^n7ZA7$x&mx4HP-E7 zB~=)dj;59UnR5k!SJ@)_{5_9EKNO6ReM`N2K#xPStwt)@p-=ZU>FvqM-iMwI*&g{G zDgX8Io*$W%w!1(4OLp$s2Ywf?UpGpw+Z_w1WB1|7C!~Mp8(t-9m@auZ$|%5Mc2vR)%n&Yq@mcfrZ4)u zVBhj!p>39gLH13^XqB_JjQ5&`ySux7>fra-b2>zu+R(zcT#$-|4F6aB89q5T%STdC zkO)q%6x_8Nt3Q6Huy*& z(cSohd5+bFlqByfrLC7n#;b6dAw2T(|T6uA^YaTw((j&?T%8!GRqNy$>kb(jyi^yK;=#(NUX# zVhX)DlBb9*+5C-S()D?p=M#>hXT78~y^C9#ZExT3HcvbWmZ_&GtwD z*{?f^)eV( zsZA@qh7E%W&1=+*i5tguBI5{2*ik_J9PJUE<@X?+ z$)f8&MT1)Qx+Nz~u8N*h&C&!ERpNcPwb51Ze58cKZ~{C>WG8YFT(>)7Z?`qyt!`rj zp{|Gm-0!RJr9wQ229&p>VTb-ho})J@`H!F`DZNc9WvYw5i&aQy zXbVKEn?%e$3@0xUiztXMdUh9!3?E`OV6S9hYPHFJs0d-q2g0PDs98}}2*~oC+gyhb zu6a8gE;yr{%BDL^7`#ECN@U!Uwz6=FnzFYr`S9J#OE`B z;heGDm=QvlJK1Ii?tWp&dZ!M-iRa^)AT7P)id3A})M0O9pvLN0!~@5E!_v#ty}45R zQ(^s|{;Z|##jsZ_fA87f)7TluViT*&B@5iJ#$ ztSi-h7VTa3_^rD)48uS0qO$T;AQT?6zdg=Ist~~T$ZlznJp?n?Zjlw$I_ojJ6verl z=AQvu5&xcLVCx1wpp0}3PO+bnz7e=GRRq^84($5)0ytf-%9xj_S0ORfaQ$idhk0;K zqh{l=5kSoEc_hM&_;oH~b%P%XsQNr<7zLoKjP{GdpoRhs1@Cp6OCe0V=W~$Lx{wp# zKsQCRlc1I#w<{e-Rf3#*Z&3DC@IsKT6&$SC0kql zWUfD#MyJ6b@*%OU7!X1AF-vduL4*qtR2Gn`Zy(vW)uWL4NWh?6IQ-?Lmh1U3%cX<$ zOG-ydvg>HTE5#H|G)nKCr7Bqyp`owKvLNQ)w*_pRv|6Yvqpn;C7k-+uVfz?Rb9>lv|stu0*)x*o@2QIXIc^w6hfuXhI!it6UGG;j7 zH(a(&EPrK`HYbL_%LeH!R!80&ec1f67W|>jK(>{u7bk;c>SU}eJ7>+1<{HPjQh%^q z=v%yOy6!g=z{%sZ!8#YWJr*M?#jMck;TafT`|DlF_zxgM>~U zN=oFP%cQaW>>zD@uuIY)Kgl1nM~-gd<1{gi^L8$5=@;>>sejX_MHgPRP(#;stBH|B2t6A3q(y@+p z`v*4<3yLFl*?2Zeof3chwo4|%ugrTd{Kbu z5?xn_k6&_JQm|~&VZkXK>uP^$dp&8hAw3yEZBZrU)H2JOonmIPB@k)>SZK4(RV@Z^ zx^{q{)AkFd7kZOL%Gx~RxHiQo9uazWa5TZ#g!_q2YVkLo9KtuA%ijz4Ur4(WY7Gtv z1`8Im^z0=<3hU}qt1ngFB{V!?ghjNM31Dvj&hJztY+vH_5@%6bPRGyCrR!$Gz-m-XG1qwmrUraztM6JrM7X?y&rr$rNP zzQdm8`>wEJ`UMnzUy0wKt1-s3LTZ<4W;5!8(3aO5Zw0=dG;+%{nsbbJQ8BlM_LYU(T3aszZ;*QZ(^#6aOZTY4HocXSr{SvezQ7k?Ub|| z&vP9J4>|6J*!;CYJ%w$)LT^tO(fP;IrBXIMy8maGFk~;8F^4wsOjV9s`8d;l+}*RTc2^ai;gVE_V5 z_4=njeW(bIzkZ-q8pS{OJi(Hkvh>meezbfWjhUigkQF=W;}^y;hfY_+ z`Qk9+r9HkdmW@jkC4c6xPXsJXfT=hV%#cG~s2r^)$z0u%^`!3YviLxuJ)4IV_z5Nd zUqw}~GLKelr6vIHo1rB&_vEnScTRf)kdSeyDja-^?*lydyuWfQM!6;^89NA_IOaBa zZ@sE!4gwRwRHQH3L01EJ%QL%irA!g{1BJtBhNXjFCEAk*->dq*@Moq|(y>n-t$Fu{ zG03M(5^sK|mD{k-^v}zE-Qb;$DWOp~Sj_EzZkzF(^MIT%0x4U15Rh~%_+UttXP{3} z80ca8>MkqMC#gR;}zshJ-#U~MGF?U=X9|f zjf%ABvc&gmV-cyx66pnOQ%DkJPc3CLE*I*DtF=iV@|#t%VvXbZnlypft3R9*eQgyd zWplimr?B71@NR2fzV6l0?x-kd(Ejw6vSF2zX;93i8@C!!<9jQ6!EzJC~(lA#mN_EJHFXTW01rb#@ewm#T~a(R7pZXFJu) zHwSE_&4P8ScsYp|J~L524sq5|(oJF*5k1LeB@WCtE5D6a7XNUmLcs6w>hPLt=c_ia zzdwf+Feo9skZ_(zKR({8w?#Et%s^ekHQwQS!b#B)+(AABlgO|H*{v>dU)a*CG^d;1 zA2$F+o2RXx(`q;63PHL6cI6 z=zDG6TYzH6P}ML;BW#&zFB(70xc6Z;PF-@$r5VfIx(}$0KA^n?st;@hAegV$?Z4+K zoRE5!KC?S_X))nDaFpSVLHo_zUt{1~Tr6e*t|RZPJ8Sz}jjlR;PN&M*s;WP7oIoUT zp>AAbEfAEx&mk0-_K|zd%C-#tFd`wbrv}h3*$VuROFg;q@~IhtyFXf%#b=)VxGPj* zv93$y=QvQe47YsbI8RK?*B=jAS+CB>FCjw8ze)?f@aR*EBG6Bd*q3MqAM|JT0Sj8KaGm|kg+MDXUq#|tNS`G? zB?9y*j&}xemz&y}9INCYZyJUA-GCTRPwcdj}+^ zJq)riCgG)GZwD1B9*O^Ct4mz0b9u}~SH}x|NJ)=?v(0N5erWK9Y?Q@kk?T6M*&+GD z+31JK&W9%oFdFqLM235hoVUDbOSX{E!tvOGeDZ>>NX^Q-F@IQStdBnTo3p;RC7N(^ z+p zrU$d!Cn^5wOi5DwenKl8r54TM0d;0XRc+CjxpA|TgP|=(rx}-{Hicu?gH`9?G!}W} zEf!M`-Het6F2TX!sHu%M?jHKuNhkf3_83&-pY?hL1%}owIg_*Wx$`%CE!yGyJvYBK z(}r>nzFOiH69W6=n*X{wp$Biso>A(-%*K)^>6tk2-7^qB5m|1fk{kti1lO6f9C6ai z`2p+;U1L3WK5ag&?P0s&DGT~Tc0Zpao8HFeUgT1&pbI?@Kyb56Qwju zgl~|Y-bWVJh1ub2MbL0QbM5**;?=Ln6;pu)?4i@{E%x=j4UJ4a?WnjJ0QSyDGUxaE{AFm#LkQb+Kg)vATTXjv>R ze;70s2;kS1M*=QtnJ4_-W{I|t3E$4ahq;>pCCTx2n{Y;djioZ@#xHuI0dqS{9V#yK zVBob%ha_|{5znP|$1Kou?b2neXH~}TA0WRcXVKTdL@0`e>aoj)xo{1P=~2|L?SM#p z>gx=@%RMZv1aG8s%R@q^{vFe&+{Z_Ycm-**d5g{XgCn2-)kroA^5x3mxcI`ASBKmq zc#tz<&j4z#8SWCO7dluu&@pa=Yl_MNZi+q z)B#-#W;QZ$`@%;ux=0l8vy9m}z=goIm<<~!P}Z#OWgdwocxpX0prY098!)hkZOhzl zp~82L(f2UYF`_vzok>5CPq=VUgZd2@!7b%s3(N}foK}0YMeXJ3q55M_>D7w<&6v4m z!YfYe8C72d6LGOmU9`IpoI8P1Y26QC0FPAI-L3`_>T)`+DgBTGHQxy)KxH%&B{D}~ zQ!dS}>kn zw-%mKyGdO{4pa?Q>&;I!k67UVk%Y?Mc zkVq|EBAjto!GEQ!ZF5WJN^&!V0wRZ+_c>68vC|fnU0sWlS!*AqDKi3+M(Ik}GQ-bS zakXKqrET7f5=9e%w&@x(^^F(n0sLo$<;0iIp_M;M)dRiF&8=H2mlaF8xpabK{3fme z2W2-ie@7}b2(Ru8RWuLN%eY`!Otg?l7i($`ps#ywDNWNVRyF7J+C!2~Y6@SZ<}ms+Mrj=5ktpV-PU;l*ec)M|=diC;pBRn@SYouu|HnOj^^hxO)~w}om4%$)o2L!TtbhL39g zITX-UIHi{f#6IsxWf&!{nMx5UmfYM?Y01FvC-9ax*Slrw##=8d0u^e8#I^a#sYhAT)c!m>Q?cc-hJK^a4~SF(5(B0 z6ObG^_gx<{Ny_xa1>~DO4VB1FZZ$aptMa)<&pXwXrYXj5SNt4QMonu*Ug zUxXJL5{frc>oAqyNjD@iCIJbLWcZrw-V*7PUQQE-&FD%j^+9beNN>32 z3*F3e7;gH)V@~@GQNT3o;L;T=px`bSd9_5>c5xLj31wzvhZ$anxSS>3FR`H9KKPRLXc(wUsqKu(1%fJ|HGJK3F+reza9G{Ctk53LccA< z_SR|Vm=)+8U*l4^ecCy0U2w+6Dd$w3{Ed^$eW0o5jumg$1q8#iysO;|-wN zncWb7f7&{ZRm39LC^FPe`_1OEfF<}GY6ze9oB!#d{U5NJnsA-n_+_#@e8sO!Q&EPh zmBtow){#+=i&{tpi!hFj+nZ1fp}XQs30ePvA_~;;EI>R(lrK2P7*w zdGTyXhW`GY>OS$Q5UA+y-Z2!Pd~1yA(t6(1_*#a(1W=+4w)AP@Uxl$e0s@G`lroK zJK%Kk?lv5f@qsm?2c|z)Et3~>Q4`3$J!PsneZ*OmQcDB)Il*we1lapGlwxpzl!{K~ue)x!KQ5%^UNI5?-KjD9B^3X2J z)2VmbM20Q+Bf?wXZFVXip-SvxcEcLVY!GIV#FD50!J|MMUUSt@M};83KLv^0!&c=# z=jKSGhtJ$GqyGF1-nhnO>)?yhopH|c0COSbI`438!_Km%94db63QPy0U%AUIEfLT}48cvt#*3Uk$a9=C8g~ zr&IXGQe<`#V0&G9Yj4KXG3ZCix&hnD{d$vMz#N{RQlyCx5^^O$Opl)81;L(qID^JNx_ZLR zf#&M|$1bFtD7yA4!$SFX>ElpKfJT{Y%Gn&=LfQ8fN_S~*P1l?GP9A9<+_fx_6QqA@ zs>&WEIh85^+E!AN;M|=`m({SA3pG-{v(hdeH3ziGYy`CFM^$IV7-DkR0WYxW{KY=t z5fXbl_J*ntGHQ)-;^PQf%*l2%J_w`H!d)DjA6VpQZMg6nO!atZy5Z^Y>Z>SuBT`o! zq$?xlBLM0m?OjrtWp&OBauF8PLNJEi6NidPO>SsiSmAb5Q>@-^>a_g97sqya*uz_> zCZ@rc!uMZeA#Py3f=9)DgZ9|*{(E&ixKlGz365kf&>4_Q1=rw)CXMFw4MHcz!N*v1 zw(FTn`nh|O=C!8!?&cwVC6g+Bt(Z?NhZi*LhBs43QEBqR;%)&T5DOWM)l1{lFIScZ zrweu@oV64=4*6u9C*PfXQM2ayIRJOLfgI^<9$5P$Y?;F-n)>~j(GhX1Ukx9eYyCdR zq2~kaTsH6k*U(y&%He*ae{ZY!=kN7T^vWEy+TkW#%OZHtD`(RK#B~-TTRQ5ePF)dHYk`cya z9VOe0Eeu(P@3~da$M5$ae7~q6fo2~q3k@CA1^l6* zJxQ}~_nC%9jh5rzXI)zU-+f>-H20io;J^Erf=}pC2>R#WPr7W{|IL^U`_r3FC7bTg zGi?quPGV4W4SXw8EO_igWT=n>neZ7SW^di+0)w|DJ3E%B6ddM z01}D3&L@p?`mV(dp~#@V}Kje12~W zY)}+>BYIv$O!VKm!O%-ktE{%SvprZDnqT4krQM$YpSIuYToQ#A-$Tr=o$j`RttuS2 zB>FGg6b`Uke%(()qeP>6MOi<9c5WoNgvVcb|Kh+%)7;CE+NP1Y+HO;qbGMjOiubXF zNGj;uRHD8+arGH*_EX7Aec9VN&lr_?*z_N&993mjc%A)>myPE|PXr>+xH7l)D8IY^ zkVjM8{H8sY4%;DNiM z`583%FO+r3l7(yvE>rW1;Enu922>@XF9r0ebsNwfx!uZ%kC7fVZl1m>S`_=2%(uS! zLzJu{7AG~NGQWiKLdcX{F`;=Lg~uCNS_~;g%paQn9j4?l+fJ_Jz6atRMNks|aqGG}hXXvBlr{g@XEO&wdae_~08@=fH z&qdCxBgl%Aux+8HlSQkl;jfJ`<{aNQvMVJO&MJ)w!nR|)_z}qN1mJiQs=fF4BBvP8 zlv<{9WREXOWkE!xJ~2GB$BUI7K}4DMvIzjH{fQy^ z0NHA$GQV8q?&#`kph?rHfZ@+npjDHRK}Azl@ZP`E(_s3+ftJo3VB5REIa=j{bIucx z*ZtkI`y^<(V3BiX&jOM>NQMhMrGM?&lPNcnp(amIKWfhcd2o16-V1OH|F$W)f~J1o zI}i3Q@E_CuV;Uq}|4G_^>iNr3IH_5e6-Fcg!+}vaP-0eB);``>s7HAoM?d)L5{5!1 z7zL&)pc_MlJY8K`p#b3pETa2`Za+786yGQQRNima7&Z19Lt6a$4i8+Wx6bkHsjmk`N$?Y%-+KA zB$kV}zg~<9}h>yW8J7{t2 zO;C3#Z3%leA7b4)1txP>y!dIaP~2ZKx>>gxIxUPdAjTd8v>prpaUMJ-2|m(GS)Chl ztY|d9NEQ{)nU|gZVw%vF4ESMO8;-1Y6XyU6jopW_mrNeI-5_UfnRYPf*<)=(-bc3Q zTJ>W~hi{xN7lzcrGV=souFeBw~~)Cr#uO*6ta17f-u zctS!Z!2<@*6B%2x%+qqYXLaPp&$iA-DcA17!RS0bB!ecdCv%T?oBkUNdK{~(xyRMy z@r~^_$ai$shG=~p?y6w;Pyc#M`egsQNFNOhE-M0{!z((2EC5-U(k}(})NqX~e6X)U zekB}!Arjr$4jp+TIC7M(960jV-(lJZ=8pF5 z%JFZX%?r>4@2!_CV9*~%R-HvMeA4xFVcOHi3IGA?d6WCUG!7bzF##hT3Z-lA>}k6X z9QOUEC0G9E!(R)5kyU+ZgL|;b>nlJ8u4ovZ`4=`QStr4_vPnXeyjo}{Dwx}f(}5s> zXRNC|xl6?~(7HaVLLUr}sd2UI{*BUI z0yvK$qGX2yi?jf-s&KV>8Q*E0#MZ~$*NbdhV-McRD5<(V{=ua;PZKXQm9D4gNQ8Yp z`?ep#Hy9BBM=fSApm@QFN5L2`GAhO?Y&cQP9m20^$8<1%zR$`>Laok^m_g=Yuo2}- zO=my*i(gMK&?-Ci2k8d`b}rT{MQEwu-W^snznA?=i@%+_qdq0`&BCeR*-Fybn@D`E z`%n>+NWFDv80EwEFSOMe4up*^qks{d$BCmi7}|xp=0}fup-FV#kL|c{ zIS>ns59#NRaeP;X+*CQ2067?t3l78LnAhg4$XaG2Fm~1tm0%i-%050;+rb3ukL*=# ztO^2T$4%VDLkR?<=+DhZFRMSZ=9hvzxeos~c#E zg6xXjc3(;*4VXyGs(DxKx#uE|46aDAyG; z>XcJ5m9i@(zN$cwbv+9O!J0{kkDNqrba(YTwT5~A%{p-KkW>MyE6jiWU61cz(l~W&F(>}#r%(~pAeZ$V+OKy=fnjQhFq9b zuD+Lmw&!aA)^))ZN`qhM;o`ROWdBs-RDt~awfvC~VH<)-21?yR4G2!ySuO@H*-?WP zi{-LiK$^Vp40o*UuLiNYYW8y441|hH-qlk=P~IolejK@ z;x0E5Jeo*7l!Q0tX8Wa1c|yS6vG>h`*(#2~)?>0jgwn6I7|;bR(kH9 zOvNBjFc+pFMXBC zQVy!g1iJsi_!dBa8i#{Pkp7f2oPC;(>NmkROl39+)YXdAO?PdlUEHVLy1PjX9`rRd z`?CFOzPH`|rDkN;fIJ!7ctiqqVlaC)EOpc4?`!9Gjm#J$AdmG%2z1^a2hL{O89KGb za_VKVwI4394ETD-A+j;hso(;1a1TMG^CA1^-;h|d-LRHfc(n{EOtviWv1q|L?;-L; zZJ}E-)s5JK-mY$%m~Y+KT_QUM=Hp(HAbSKH!Rz*YEO%MVS?pG42lN!DRtGy|a)pN+ z6ivigtKElc*VhEgQ-mQ$8LkuzPHuJOPAX)CTwsjyjF)8$=D*(KDvRY(wA@+MPMJ_u zj(p>^xD3y})b8wOR_k^oBClTpI#ybkJD_#2hn1uZwsnr_Z0C0o&on9bqSXbWm7;g5 z8LZoSQMGctBqX!xt$ounP7&Lptdhc>>P?xA-Bpf~Lf}dBwhr(Cl1T^7{`1|#Glf&F zLI!9R>ifsXq%W1W5YCul!Yv%Or{~W(r`Vb5&hjuSJMfGsWV@|D0P5MvI-kJty)IBn znQKF-q|CH&)AD5{^GIA-S5R*utI?=s=n?u9UZr05JyB*f0tz&;G2`VwHW`y z%u~`oEymf*956g9mzF+qQ<#lLvwXT_49Xl|{*oS5u#jp($!@4tdgR7*Ta8we-(1ll zPFa1NoxPtRL3v^Yn<3|4n^*QLMxA2=tQnGaxd!ox8^!~dUwlnR(aB~=L|X>-|8^QsO5Z5fT$k_d%rE9 z@O$9JUWC4YC^H))4(co>fMkey5TFh9a}^LPMd+VA{qH-Fqd4dip!xgV9C*k5=ba9i z^UT*>6WOnCU8ADoobZQGB{=+aD`cXB1Rjl5hEg{KaIVcIehdP0tbfRTdU@CEQ(5@m zOnrRJy^BS;kR#R3dQ{sNuBSiiGYGhIFkfDGo9fD#5ZooSn;V$2>lvkmfa)95kRzpx zMIJWXfB01T{(+bF>Zq@Gj`vGkVLYHIta&fHN5rP!24ruQ!uF+~BoJE&-hdun9hGjn z!s}}&5h|S;EAvbgj>)WVj}RE%xy!MvR1O$oLFBHLjO=? z&noaZc2SCe$)rzWNX3jvVxq5UHO+@dD7PdhtpqKeyvSlt?n8&coRvd;Dw) z{)U+SdKN-XgnZL0;Y!;3^4KE|*f7F74qlEtJ-xlT;7OVB$}N4qOGUxMHHiF!fPATq zn}UG+hSx4p*(#>6H!p0QB;lOEiPjL{iBcjf=77z@XTSRZmiQV0pXfm>l)-c{+UcaU zmI%8JVHoTL|=}M5%fa|1m{g45Kttx&Shbt%H@#ua%7DBc_}H#YPGU2 z$YP@$2wzPH6Y z`uf-3S;clwo%0uC&fOBqRbW({NX?FVv9zwTN$#1-owIk~r}b@6Up>F^)Q&51$d39d ztH!=MrmA0I?zB=i8s;-s{N&^jq?kJF-eIn?wB^b;bje_qqc&>m=Z|&l-#A^lWuw{Z z3lj9ttIjgjNYG*3>QLnVpjsQA$f4F#!6hwM{Tu`hUs6?2Y2TNlvVj%NAAD)w5i-z_ z#EWIEK1NA-(ba3p=dRt$2uD&ll@F6t+DT z*8e^sOwO=MW0O}Yn+Pe@rWKj7q*00f;c*M!o2Z56HABb{Ymb|Djd|h)FW!NPhpVS~ zs~OYRceoStG}B0n`m0sOiS4q1T@=|s%i`{vgXv?*Qs1ScmrNWU>Xw^W_H;#UeDMUZ z*285c?fsxdze!u=6ZdiRkmTTWVZ(B%kmS`|cMuS3qqf(l2s>demz(xk^hGjvL@0MQ44fBo`4STeJ}mCe*gG#bTD` zB55$8KtZS^&r+TWUYPysr+UHSm#fp?5L5L7_T90LZH9SiJKMvJZ<&|)DZyryaSPLAJN+#rA)*6G07XuXG= znA78MMBlS6;}-&NyU$!~0U?hP6NuA}`8^N-9kUr2l&?PHz`fOx-cW@w2!dAm<88<% zB^_W;7M^>`Q^dn8O#9sx=xg;Akf&9>UO*2l2>rgOY~`5w=3{po363rr;YvHe0&PvnAygs}ecDVwjEJbq_Kz?Kr3=Mpc5^!FGIX7-MB(GQnzHo32 za}vT3+xN~=wYq0HnV;KyeAqopsXTTB*{z9b@Gvwh1@wu@!PrkYCXGR<5-8`PBPmzf z`-CAuEP(*9PJ8@rFxYPl{O9-~olCSZ>amYB>mVD0ue5EA%4SzQO8>5VcsTvF+&Hx~ z5bVhc>?w?^5M0qphZe;B<$lX957UKLpa6Yl2I!w?nzgtfN*n9FH!=t+$eznbkfMf9U zVHbef{9Mf{u;nq=RY_h2pfZIkt=Bk}77yp&|K0b!4ch~0G2#6bC{yNOq1#Xv$4sj`%bISS_ zgle5$4*|&)RG>^I%irEY_9O3;UgAoE0Z`sJ(djcFlK+LLVCped>m}}e`!Xjuk46B_ zP&HG1X*3u^rn9r(KQL+s^~94f4(x6RluPGpuL&k+$B%!*>_1f?m4aEB+VLMd(@s`Cqq-d+HTd4VlDZ{C(uu#rC^$PY9-_?|qk8VC7;b)L+ z;b(%H$xZIx)SzkihLVe;!3(9Jdu9;!xVe{djeSQ_qev`HS+g7@W>8SdAlP^lMEoo# zE?sq+jy4F1f8%^AGP09d?RZfKNJ`%8wsxC?UHf5~rw>0bhV0;#9I+Z#Oa| zu4oeVExg-!C?&W8br|k>oPy~HdTj)MU18}lX)?{TtPA8gf`(Z6wEBwqBy({rMJ#!t z->-+h2%410GFk8$u1rGbZ8gm|7H^F0S2RY?+Hw{9%@3P!XKV1*&$(doXp%F$zFVPszg07W5EY~!-4G@;ZvvtL)VO6bmGm` zi9l~mz~o~@!Zvn$gG`Q5=Q_Hubg6!-B>sAAMsZnVQ4p1EWknrV+_t5RuC>%18c4PZ z4NOeQ?WDIi=~9reU5PmUD!cL91HmH2^?1d?xMmQG;#=R;H!}BkK=yu5(vOwN?iz1C zY{cu`L3~vfb#_Dc0keRSkLus5(~XpVvtnuN`X_vR*4Fopx0Hl5jAI!Sd)=_#kUO4F zalK8pI)+Jw{X1{TLo;zN3pXh~qCIl^WpZ=L#%C_-=hOFeiMX)sWxT7^=3ryXL|^f3 z`*pWK*v#5~u8@kL5wfKbZqSarGI7?1o18%D(Pn(9xN56tII)qLF0dAj{TaPXIAo8t zA9-cz#6IE}i(A=*E?&{|K3T5rA@A5BF3fG6Z7 zCCHp~^1fFA&taDA$i5Ky4PF}+$DG<>McGeI!Lc}aOIHF6xHe$r;B(BZUEyWd#e2i5 zZsr5gbG`o2OfPWrO$#lgDS(VX01?tk`T8xmXfiIwZF7W39l_V3hwAWe_*=iIy+hY_ ztjd&GQi_ZWJOkcwkSa39EBjD|LAyzL3lIP|_S=%w!(TtxC+;N_*(kv-sbz}My>`yW zt4ZAYlMg#{GNpa<4P{xD%=_Iuc&-L7y7gAHuTDXWJ=cQW&GEz6y0Fc>Lq`4ccDLvO+Jcg)VHx9w$w$vmFb(xp4^xg8Q%}TbIY5j!^}M z@)sl|R+f^nuY!8hM)j@A9Sps0N?EZWyKOLddC?c|#6TMJwHs4<8`4)d<$XK94L2?_ zZnh~Z;j?%9m^58~=l8Qe?N)guv;w=P0xi45ja%OkItK2cjvv2){mu&)4Qx{FB(3>^&3L_g#xm|VN_@_XNNX36D`WFf|j1Fh3zwSDrqb%l;G$H)b;C>58=^ulUw zZpi4~1IJ6_>?m{|r_i;ycnNpk*wViu307ffc{-W7jZ9~%8;F!K&Wsl?c$hv}1^4gd zed4bL3I8N?-{wus^bLP1-f?w`y*lqy-LQ8@gnvv&oXupc2hVJRY!a1^GH{ zWa|wN_Y5LqJNb4_y?c#Ft6{{DUglE5hG~xOS}ooH4Lr6NFF-gc(*sE~m@bC1`e)5U zzOiN%gr$2I5>?x*7gJr!3d^x42p@+JZDMyeu~UwAA4=nuJ+;i5sH;Q8%UyDw3d|GB zbuEj9=RbbZoDaJuK3_gNFbP~P#V@zBBv~(U zDSaR}*P*IC<460p-c+@-d2l|ysnC^_IF*o?ZPMQ1*x*-qkz%iu;n#XvF(?%=n#Um-UuPVJ*jJB9VFc{$l&zT_3kqY+KMho7JH1W?0&LP_G+nJwj$wmWp^X{ z&$vU|T!G&o=Tzv`Y7VeVIwDdD1O6h$#O7ds&IoU&$@2Bo@|45u_DxIav~Hnvy!p0? zt}!pmLCxNaP^|Ia5>Bx`q|AjlPB}w@;@piP@uo+(>za86`BHOy z)WMr+ zv8%3FDJ(H06R51>jt|;(j@d20=B#zD*5ONwN~rg2M*5;5Uzv)_cQ(q)wdp!FR9dvx zugCsXNNCd-4x2@QZHESAq7 z1twk#N!QN&fE5u+$`36%1PipHPqn9?p7RU~ra((joK+cB7?0zCHRW^h)Y{oh@?1~i zD+wl-*7Yk+o3sbLJ~&|;<*75;FzDKoo=8qFS(cfC#N{R^s)a z*RdU&3tL?aC7}~Wp}E@`8RCl`HbxmY6Tdws*Rb4ee3{VFsmU_mNG{8fJLQ*qkfaLa z<$oDWkgC;?UBn6L%-1LJR9$8_POhlb0RggobGk^fJ5Dl$2X23NEVyMVxF~CTCMy%m zez{#WxP5y$sNzs=>BMG8G9fTzTsqX20)dm3yZoD3?|J4nvUb+9x-JOkD~$T!z5DPXXYyg*z-`Wo=KAxFHv>iIfm%n4gT5n=e?Xl?tee^89(%^90A=-Y19i6{#~h zI#2Y-;su{F7S&FelwEzR;SuBoK&7GL4_3vS`-}U^qe^G%;F+T68FhouqqW0qBL4R~ zJ{@J5ZVxi4>zin5AvYD_LMA2f*A+KDZ%8)09PuW9`9SGSGQXZkEN`#5IfJ{^{bA!! z{|o>Rc4+c!saaj4mJ{Lwz#k5vEGRXltPcSmHId5Pb!jc@lWE0kz+o)c;X-HBtxR{i z?HNxy`?H%Xu70t_a}a-isI0?@DBJO9rQB>E`ReViO}{#>qN7FIu^>qu!cxu9jbdk6s{{ON14f z{3xU~T~f@oQrP|xXH-WF4dyiW5i$WnEEWwbJCgd|63+{GsfMiLm1!2H>*;(c#K!W{ zvR^drm6bEY0lT;DE$B^B(?9lx=P2x)|AZ|@a+PcY`1m|%us72%ENd*$8+hDk(U$s_ zMFnz#&M8N z8%@$96WP=P1ne~m8C_e_Sm=q=d4HqBid&0YZX>BiZW{Z5Vr&<-qb$cm7yUa)3Q(5A z5yT2zei;uSm8R@txgoCZvp$hbP>?wr`_Vc0IKc3%Amp1Zz-lwJ&-RSz zs%S^_BpO+oSY_#IrEr*+ca*bkc(2u0%iHi*1&zNQGp(uYT$;GXv4CC(ChLiPqAxY@ zMd7bVC( zH`kMJRe!4;@om4tB8D`h9Tr42({OjPRnggyk$(5{x#Gt2##r@?(1E)_#Zc8km%b80 zR!?~d+SuKFEQa1ire(L=p1c-6VZCHBnoN{!-;^*B7nE$=kr=QVs*SOC=ojBL+Mpd{&=tlD7{`?zXTaRWy}`+XBBn&-V}q2 zz1LT<#xhKa#2Vaqg5EyQS3f?G0Q4=3yrJM4bS_6KXUbwB$Otv$UWfNz@liAHmCv~Z zgZG14KU2ZL3Fuz&GA#NN!j7J=>VV_Y$AzQz=lpZYh)*wzQ)!;8PNwG6y_yilgKK7v z!|%~ddBbDYi`6ya zPrD8xydR_K*Vf&5)Xz;p2Ce+6X*Bzj#J=F%A-jGa#p;s;b1TXQ0o!uujI%#%!bK#P zsQZDqLnRu8t@(!;Sj6?3oyFNGH_a zY50jrHavSuejyN#qYmTnbc?#JebN%@GMa(}?nGScoMV!Lx}1bW^`K3`ipea9E7uI5 zys#ysa+U8{XHbD42reNFpE+;A|9AU>sdAxIM88fD8D&?D-)?7i`ZyA zUta(1fk1wGsie7GQ~I_a06m6To5NG2GaWOoxx-K@eIwUuBbRh9?B~5=?(4DoYs9cH z$^b63Hgq_6p_$^0f-(%n#Gf(5g3CIqDQISeZD8SZ6fDZB3$<~#J3DnYW}wU6&}Hec zDm5{o7QvdoJy(w`fS?vzFbdf?#v+f$&AK;-c5eV2ewVvoQjXo|ZWB_c?_~cnnMW6? zPXEdz6Y^(97AS*eEl|ki61KQMOvk?Vw6b%q`f6gu(k?Q)p5n|ONg19Va|~N`EItlu zlkw2~+5Tq$9NgL)qypa zpOV3)WYG$t&Buj*D883cOxnl~+sMa%k|vs4gsx9Osn(<~Jp{nv>3gpi!>(?Ab|{6a z)%fxJt5ZRC*Tlx*gWoh8womm3f&SReA# zeqLrlydJ`0=aXR+>NpMA6EZIw_Whudn}pz6Fm^2%?_M`zI5*r-m1%G4CP#!atDZ22 zsEz*dK@jo(17p{5Xj>jbD$?~Wjr=3WVN&U)=t1O>eCVc&)%5xTD)=~0c^3`vB%6y@ zn+|xci&3=3PFSe1GbLIL$~9W_V;A2t6+`xulJVVpXf~=!Xu$E*czfqmZb?Yi^-ztK z@@-Oi(czFWI!fIAMh|=Arhwcy$b#>d{;6AS^fW3ikJN_>XAwcdZW*00&XfO6xG(=* z?rzQfHZWAw8CPK*C2MwI43o&t?dKdK)25mv*o{SM7tA;H=-))zPoiK~%Nhon{5>go z(#?e%;0S(@23^Zb$*CI;IsW3i1S-qg$g`mQsCHtd$xXsTb=}0wy z2Z_v^`=8y>SPEPh0c8bxHQxKHhP5`IMT?N;P|D*E_bW#G!@f<#Sq-r+q?3};Y|e%x zb;ncGu`hNc=n~{`6BpZ2W-8s;1(4YNEDA@i7%-JXSVJ*e15rPbWJV$ziX((0e==W` zc-ji3YbT#{GlTU2_jSD4l5g$?yNsrY|J~qs z9HTl`(krFJjZ(Z`)<&O8N0KnbKeyRL+FjObOv%E-qYAw_S4YTqo#^r_jJLmy8NPJ7 zb|GwMlZ@DEH)?t5C(>#?^KSUt)+f(UA9U;Mzw!slLh1q-TX=vn?ZErx6($ryyaRWb ztDgewdGsSvs7|lzoIX1;S$RX?m`sg@<6>qbpH4yrfU=7z$S25(9{Wl(Uv~n>X`$Mk zJ{}3#Lfd)fWb1_9{Pq$ZQc6W)n{yUdG-& z?3`R3x=Z6~d*_Ugy{f!-Yh&jvdumjcI9XDa-Ci-Xcu-pTf^S)_H3>O>spWmQ9RwZF}-m+{;nhAJCh-n2vUG{kpiZc$?nlG z9+@!kxG!w@iL%-DU<*XIBesK)q0sgeBRdZ~G00xb{M1C%CC6rF*;Zkbu!bthbBdel z&8&I!o}befdPcoZTDDkeZGOO4mS?>ewvmu{!dP66o^vIW|5@TLY?!$cT2Wiv;+zI* zJh{!97$pUORPniy{~?Hy=8j&B&#uys&TCj;MlKt7F)Q_RG7GBX{RJsgT!!_na6ihzt`NKa$h^-+4czUX(!aHIdGjkz!vaL-oUZ0)O5F61@54N zgrKL)h#2MuL+X7GJOe1IIwr>5CszLr%m*&&tKs6y4y*2_sZb7!2=8Q|l(yDDw;RxU z&}E()!W;4D3JNrJ%Yh7lzeU-b-|wN03@Wkz_6H9^ptzM%)^Py}8UOTB{&@b+-4>Lm zTL3uemqGov70Dn&|7*$JQPUaV2CPYhVEykDFsPbj&y=uckoycGOq|>;ZTYoOIP&`m zi0a=9XZr(0v(RWBxmzLis}<7u_GKz}zcZ;9AKd*;VX+w8A1D+5S5P;J_%js%<8F z9|U)Gen zK_!jPxoRSmiuZ{ZKvk%qkj~Kn+-A9;ET9fGM?PX%1Hqt!98wIIU3(9z@pKDfP|4=r z_Z6E~iaIVNDJ{d;hwZLS&sO22>YX#K?!#rdAF=0*x5gPkwJDBqa(cJfz!$O9VfkD; zbq*urw(+>MBP89rLP1RI0XwgFG2g{8<;b+y)f^XACzb52&u4cWjDe`^sT5p>`S@(N zrL-Zpe>bS|4apBHwH*@%uPY4BJp=mOfAq}_ZddYUadI~RQ$ZlK7cm<2z>W3(DWbE{$m=# z>HjfF<7FvA)6mccK>rs2q!ji^HaO`a64@ N^{UpDG8OBH{{u=kw?zN| literal 2366 zcmcIm`#;nBAOEvq6q@+b>$4GK1Woe`^Ho{2^ zS!lvhiHLn%B9}E(_EDxzMsk_6&R_8T;d_30y`RtLFR$n8^?E-Zuh*5s1UD@WV+{ZR zwA>H6cmaSi2mnf_R8%C6^Tz9yBV~dEo`sK z_D^63eO-IW&vlg4q2h40c8+Xi@cK}B+3iD{LqCTYUb~*a_7Ah4l{hyy`&}i^O}uoY zgbB4zk1nuO-Bpx130s_1wCt3OsmgYs^Sb|lF0N{b)}UI*fR-YGBGrExmXZl!n>ol# z(@k`=zLJy!9|vcf)=**O_GE&3V>LiERG|FOAuoWRh7QFGYsz&2X(7A^tiay#k07;> zQ`YT*dUeqFRt%qocERfmSWX{EiS|kkg|e-4=fX0$qD)gG!M6vv+!sGb z8r|eda@WtB;TyK$ZD`?D{@m8kpX%W)U}eBS*aQ7z$;YAA5skDz!Ss$Bnk%*B2>XgL z^gEpSP;^pFD#%la`-XVna)n2vQ50A5$#UB5a#T^rldP;U zbRiyPlIXv4e_q+kN|9Q&9)eCfnKP$v{(gP>W8BgXGc?UBXzareu|BbNl64qoqqE$A zGBsX~`@<)9oFHAYOVXQoppo+DqP!hJyMZpm5&Jb?x|VIQe2j%{GuuKdd{h0==w#V4 z4+YtfT`r&Q+Qsg7dJMDAFIUah)5E-=1^9`=l2DK{`B~yXbZ-e|!};%ho4^v!YmS!R z9X+J}-Q)x|-0|MwSQ5oSQM8#cf1dg>4w5G}!Jy-0m&72SGycAnNGGJiz9wmQpkc(u zGmy3Cvi4C|E6Ck%m+uK(4GB2jk*Zd~P?zrQHyrrhrVP$pKs05&=g04}&ET-!2KeoI zn!X7Svoh1?%TML`Moi0JThsnRZ^jC>YRfxXb}I0M^GD(HLOO++hfA?C)0Xvot86ML@ewJXe(Zlz{p!ms8w03l%v$+cE-p`El_R%mT z|C$5n>YYWmx~;J4?`i;*^!OA9jqAjK*v?eteLMFee3|z%93c+bB|+X6*-47Gx2V6i z6`T#pmil^1v@ukpBOF*sz`#6=qNrSj^hc|Wtr2{Y$9a0obS-IT9!dT=cMSa_mif|E zgv#fxWa_C6zw7W={o+@DNt@&fhn_(l9t}^fDt-kV3o`no`(`zy_Y|--WKZ9(m3bLQ zsYLQY42C|$$wn^M#GcBM{~b$P*oN2T4~|))7POkbyBxADIVevaWkL1QU5Hgl|oQL3BI}eC`yq@P`l=3{JhknaAZP)q+z) zG5qm9a6WZjm2pmhjJR-+8H&!0DLNwA*{FpC0?~MQYF!vep5(e zN!5gzdZ#^?vF6>Vq=y{uuUgjd#rDf}r&1N>0l}9{-Z|W0B0*>(cu*X^i?$CUXi4TH ztG@l_Fx`>DyIq{->HzhdEFNm=VB`b^mdls7nk>E!zKPTjcz5m*Hb}b)P_6iIG3f(x z(|PX0UctGLx4Gh#*p)&{6VmMcFxTr(rjI{m>3X9Jkr-c|~@-(_ObVj^MutFDN|nzx~1c#(o%_!>n%n}K3%k{?Nq z1BMHWg7@40j&ZTY-1X*kISMM8t~j!ms92=vP1BtKVs9WPoF+R9$s|p)!P~%S3T^PS z;O$A&q9J(8yDf35wfD_dMz(T9e@|T{xXKe=ynkj3SYWxoO_?O>v8;J9-xb;0T)*q& zR9cK=#DL054=SkBag0HR!RAWKlGRDCdZsqeZBg;Ij>QjmSUQJe=%PHGktJ#?%ah%? zTf%hCl>?p)_ZNF*#l@QZE~3Sd1Tz>MtRl7XWfC}nlcisz2&&|fR}(`;x}R9gthzXV zQ#jlZ&JnpT3AFhmv5Npr7vgKuHbMqePJ+7e{~p*l#qL+nQ7mc2DZp9#?N Zfmc6dOP7B!)lu9$fcpW03lDev(m#MGPu>6k diff --git a/app/src/main/res/drawable-xhdpi/il_image_item_generic.png b/app/src/main/res/drawable-xhdpi/il_image_item_generic.png index 734543f95a330aa94259594f34d23af8715784a6..e9470abb5a17b5fdba7db43912adcb62298fb259 100644 GIT binary patch literal 17364 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 N^{UpDG8OBH{{u=kw?zN| literal 2366 zcmcIm`#;nBAOEvq6q@+b>$4GK1Woe`^Ho{2^ zS!lvhiHLn%B9}E(_EDxzMsk_6&R_8T;d_30y`RtLFR$n8^?E-Zuh*5s1UD@WV+{ZR zwA>H6cmaSi2mnf_R8%C6^Tz9yBV~dEo`sK z_D^63eO-IW&vlg4q2h40c8+Xi@cK}B+3iD{LqCTYUb~*a_7Ah4l{hyy`&}i^O}uoY zgbB4zk1nuO-Bpx130s_1wCt3OsmgYs^Sb|lF0N{b)}UI*fR-YGBGrExmXZl!n>ol# z(@k`=zLJy!9|vcf)=**O_GE&3V>LiERG|FOAuoWRh7QFGYsz&2X(7A^tiay#k07;> zQ`YT*dUeqFRt%qocERfmSWX{EiS|kkg|e-4=fX0$qD)gG!M6vv+!sGb z8r|eda@WtB;TyK$ZD`?D{@m8kpX%W)U}eBS*aQ7z$;YAA5skDz!Ss$Bnk%*B2>XgL z^gEpSP;^pFD#%la`-XVna)n2vQ50A5$#UB5a#T^rldP;U zbRiyPlIXv4e_q+kN|9Q&9)eCfnKP$v{(gP>W8BgXGc?UBXzareu|BbNl64qoqqE$A zGBsX~`@<)9oFHAYOVXQoppo+DqP!hJyMZpm5&Jb?x|VIQe2j%{GuuKdd{h0==w#V4 z4+YtfT`r&Q+Qsg7dJMDAFIUah)5E-=1^9`=l2DK{`B~yXbZ-e|!};%ho4^v!YmS!R z9X+J}-Q)x|-0|MwSQ5oSQM8#cf1dg>4w5G}!Jy-0m&72SGycAnNGGJiz9wmQpkc(u zGmy3Cvi4C|E6Ck%m+uK(4GB2jk*Zd~P?zrQHyrrhrVP$pKs05&=g04}&ET-!2KeoI zn!X7Svoh1?%TML`Moi0JThsnRZ^jC>YRfxXb}I0M^GD(HLOO++hfA?C)0Xvot86ML@ewJXe(Zlz{p!ms8w03l%v$+cE-p`El_R%mT z|C$5n>YYWmx~;J4?`i;*^!OA9jqAjK*v?eteLMFee3|z%93c+bB|+X6*-47Gx2V6i z6`T#pmil^1v@ukpBOF*sz`#6=qNrSj^hc|Wtr2{Y$9a0obS-IT9!dT=cMSa_mif|E zgv#fxWa_C6zw7W={o+@DNt@&fhn_(l9t}^fDt-kV3o`no`(`zy_Y|--WKZ9(m3bLQ zsYLQY42C|$$wn^M#GcBM{~b$P*oN2T4~|))7POkbyBxADIVevaWkL1QU5Hgl|oQL3BI}eC`yq@P`l=3{JhknaAZP)q+z) zG5qm9a6WZjm2pmhjJR-+8H&!0DLNwA*{FpC0?~MQYF!vep5(e zN!5gzdZ#^?vF6>Vq=y{uuUgjd#rDf}r&1N>0l}9{-Z|W0B0*>(cu*X^i?$CUXi4TH ztG@l_Fx`>DyIq{->HzhdEFNm=VB`b^mdls7nk>E!zKPTjcz5m*Hb}b)P_6iIG3f(x z(|PX0UctGLx4Gh#*p)&{6VmMcFxTr(rjI{m>3X9Jkr-c|~@-(_ObVj^MutFDN|nzx~1c#(o%_!>n%n}K3%k{?Nq z1BMHWg7@40j&ZTY-1X*kISMM8t~j!ms92=vP1BtKVs9WPoF+R9$s|p)!P~%S3T^PS z;O$A&q9J(8yDf35wfD_dMz(T9e@|T{xXKe=ynkj3SYWxoO_?O>v8;J9-xb;0T)*q& zR9cK=#DL054=SkBag0HR!RAWKlGRDCdZsqeZBg;Ij>QjmSUQJe=%PHGktJ#?%ah%? zTf%hCl>?p)_ZNF*#l@QZE~3Sd1Tz>MtRl7XWfC}nlcisz2&&|fR}(`;x}R9gthzXV zQ#jlZ&JnpT3AFhmv5Npr7vgKuHbMqePJ+7e{~p*l#qL+nQ7mc2DZp9#?N Zfmc6dOP7B!)lu9$fcpW03lDev(m#MGPu>6k diff --git a/app/src/main/res/drawable-xxhdpi/il_image_item.png b/app/src/main/res/drawable-xxhdpi/il_image_item.png index 9f9bf2e01891aa6b93e3cec66094790dfcf63527..4744eaa4a5d8d6b2816cc3f54571e99f1dc4424f 100644 GIT binary patch literal 30009 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 ObLEou#X=S9kpBnfmR1S? literal 3860 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 ObLEou#X=S9kpBnfmR1S? literal 3860 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 literal 5222 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 literal 5222 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 p4*j1&ed2WKpYr}YRs^LnCwEy( Date: Wed, 7 Jun 2023 13:49:46 +0200 Subject: [PATCH 030/112] [#545] Replace demo item saver by OdsExposedDropdownMenuItemSaver class in the library --- .../components/menus/MenuExposedDropdown.kt | 23 ++++--------------- .../component/menu/OdsExposedDropdownMenu.kt | 17 +++++++++----- 2 files changed, 15 insertions(+), 25 deletions(-) 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 88157e331..050a90ee0 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 @@ -17,14 +17,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.rememberBottomSheetScaffoldState -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.saveable.mapSaver +import androidx.compose.runtime.* 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 @@ -41,6 +35,7 @@ import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing import com.orange.ods.compose.component.menu.OdsExposedDropdownMenu import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItem +import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItemSaver @OptIn(ExperimentalMaterialApi::class) @Composable @@ -58,20 +53,10 @@ fun MenuExposedDropdown() { } var items by remember { mutableStateOf(dropdownItems) } - val itemSaver = run { - val itemKey = "item" - mapSaver( - save = { - mapOf(itemKey to it.label) - }, - restore = { - OdsExposedDropdownMenuItem(it[itemKey] as String) - } - ) - } with(customizationState) { - val selectedItem: MutableState = rememberSaveable(stateSaver = itemSaver) { mutableStateOf(dropdownItems.first()) } + val selectedItem: MutableState = + rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(dropdownItems.first()) } if (hasIcons) { items = dropdownItems selectedItem.value = dropdownItems.first { selectedItem.value.label == it.label } 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 ccd8170d2..909609ebd 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 @@ -13,12 +13,9 @@ package com.orange.ods.compose.component.menu import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExposedDropdownMenuBox -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.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.SaverScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource @@ -90,6 +87,14 @@ fun OdsExposedDropdownMenu( data class OdsExposedDropdownMenuItem(val label: String, val icon: Painter? = null) +/** + * This Saver implementation converts OdsExposedDropdownMenuItem object which we don't know how to save to String which we can save. + */ +class OdsExposedDropdownMenuItemSaver : Saver { + override fun restore(value: String) = OdsExposedDropdownMenuItem(label = value) + override fun SaverScope.save(value: OdsExposedDropdownMenuItem) = value.label +} + /** * Note: Please use Android Studio preview interactive mode to see the OdsExposedDropdownMenu preview cause expanded is a target state. */ From b30fde2a50912c18180a5030a013e631cf14900b Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 13:50:22 +0200 Subject: [PATCH 031/112] [#545] Update documentation to mention OdsExposedDropdownMenuItemSaver --- docs/components/Menus.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/components/Menus.md b/docs/components/Menus.md index ad2784cf6..640779a75 100644 --- a/docs/components/Menus.md +++ b/docs/components/Menus.md @@ -11,8 +11,8 @@ description: Menus appear from a button, action, or other control. It contains a * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) - * [Dropdown menu](#dropdown-menu) - * [Exposed dropdown menu](#exposed-dropdown-menu) + * [Dropdown menu](#dropdown-menu) + * [Exposed dropdown menu](#exposed-dropdown-menu) * [Component specific tokens](#component-specific-tokens) --- @@ -43,22 +43,22 @@ The library offers an `OdsDropdownMenu` container composable in which you can ad var menuExpanded by remember { mutableStateOf(false) } OdsDropdownMenu( - expanded = menuExpanded, - onDismissRequest = { menuExpanded = false }, + expanded = menuExpanded, + onDismissRequest = { menuExpanded = false }, offset = DpOffset(x = (-100).dp, y = (-10).dp) ) { OdsDropdownMenuItem( text = "Summer salad", icon = painterResource(id = R.drawable.ic_salad), - onClick = { + onClick = { // Do something - } + } ) OdsDivider() // Allow to add a divider between the 2 items OdsDropdownMenuItem( text = "Brocoli soup", icon = painterResource(id = R.drawable.ic_soup), - onClick = { + onClick = { // Do something } ) @@ -85,7 +85,8 @@ val items = listOf( OdsExposedDropdownMenuItem("Map", painterResource(id = android.R.drawable.ic_dialog_map)), OdsExposedDropdownMenuItem("Dialer", painterResource(id = android.R.drawable.ic_dialog_dialer)), ) -val selectedItem = remember { mutableStateOf(items.first()) } +val selectedItem = + rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(items.first()) } OdsExposedDropdownMenu( label = "Dropdown menu label", @@ -98,6 +99,8 @@ OdsExposedDropdownMenu( ) ``` +Note that ODS library provides an `OdsExposedDropdownMenuItemSaver` that allows you to store and restore an `OdsExposedDropdownMenuItem`. + > **XML implementation** *Not available yet* From 24be58aeace2ff813a23dc1d4152a6adc4606864 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 13:52:17 +0200 Subject: [PATCH 032/112] [#545] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 2bd84e65a..5e5a04dee 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) +- \[Lib\] Add `OdsExposedDropdownMenuItemSaver` class to allow to save and restore `OdsExposedDropdownMenuItem` ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 From 8d99471d811885748f30e02e25b7fa9b4bff367a Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:03:59 +0200 Subject: [PATCH 033/112] [#545] Review: Use parcelize annotation on OdsExposedDropdownMenuItem instead of a custom saver --- .../app/ui/components/menus/MenuDropdown.kt | 6 +--- .../components/menus/MenuExposedDropdown.kt | 9 ++---- docs/components/Menus.md | 11 +++---- lib/build.gradle.kts | 1 + .../component/menu/OdsExposedDropdownMenu.kt | 29 +++++++------------ 5 files changed, 20 insertions(+), 36 deletions(-) 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 f00a9658d..936b83b5e 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 @@ -20,12 +20,8 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext 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 050a90ee0..0692d4e33 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 @@ -22,7 +22,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier 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.domain.recipes.LocalRecipes @@ -35,7 +34,6 @@ import com.orange.ods.compose.component.list.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing import com.orange.ods.compose.component.menu.OdsExposedDropdownMenu import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItem -import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItemSaver @OptIn(ExperimentalMaterialApi::class) @Composable @@ -46,7 +44,7 @@ fun MenuExposedDropdown() { val recipes = LocalRecipes.current.take(4) val dropdownItems = recipes.map { recipe -> - OdsExposedDropdownMenuItem(label = recipe.title, icon = recipe.iconResId?.let { painterResource(id = it) }) + OdsExposedDropdownMenuItem(label = recipe.title, iconResId = recipe.iconResId) } val textOnlyDropdownItems = recipes.map { recipe -> OdsExposedDropdownMenuItem(label = recipe.title) @@ -55,8 +53,7 @@ fun MenuExposedDropdown() { var items by remember { mutableStateOf(dropdownItems) } with(customizationState) { - val selectedItem: MutableState = - rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(dropdownItems.first()) } + val selectedItem: MutableState = rememberSaveable { mutableStateOf(dropdownItems.first()) } if (hasIcons) { items = dropdownItems selectedItem.value = dropdownItems.first { selectedItem.value.label == it.label } @@ -107,7 +104,7 @@ fun MenuExposedDropdown() { items.forEach { item -> classInstance(OdsExposedDropdownMenuItem::class.java) { string("label", item.label) - if (hasIcons) icon() + if (hasIcons) simple("iconResId", "") } } } diff --git a/docs/components/Menus.md b/docs/components/Menus.md index 640779a75..ec5c65dcd 100644 --- a/docs/components/Menus.md +++ b/docs/components/Menus.md @@ -81,12 +81,11 @@ To display an exposed dropdown menu, you can use the `OdsExposedDropdownMenu` co ```kotlin val items = listOf( - OdsExposedDropdownMenuItem("Email", painterResource(id = android.R.drawable.ic_dialog_email)), - OdsExposedDropdownMenuItem("Map", painterResource(id = android.R.drawable.ic_dialog_map)), - OdsExposedDropdownMenuItem("Dialer", painterResource(id = android.R.drawable.ic_dialog_dialer)), + 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(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(items.first()) } +val selectedItem = rememberSaveable() { mutableStateOf(items.first()) } OdsExposedDropdownMenu( label = "Dropdown menu label", @@ -99,8 +98,6 @@ OdsExposedDropdownMenu( ) ``` -Note that ODS library provides an `OdsExposedDropdownMenuItemSaver` that allows you to store and restore an `OdsExposedDropdownMenuItem`. - > **XML implementation** *Not available yet* diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 035405a55..7df646b8d 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -17,6 +17,7 @@ plugins { id("kotlin-android") id("github") id("maven-central-publish") + id("kotlin-parcelize") } /** 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 909609ebd..2fd1cb6a2 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 @@ -10,14 +10,13 @@ package com.orange.ods.compose.component.menu +import android.os.Parcelable +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExposedDropdownMenuBox import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.SaverScope import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.PreviewParameter import com.orange.ods.compose.component.OdsComposable @@ -26,6 +25,7 @@ import com.orange.ods.compose.component.textfield.OdsTextField 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 kotlinx.parcelize.Parcelize /** * ODS menus. @@ -64,7 +64,7 @@ fun OdsExposedDropdownMenu( onValueChange = {}, readOnly = true, label = label, - leadingIcon = selectedItem.value.icon, + leadingIcon = selectedItem.value.iconResId?.let { painterResource(id = it) }, trailing = OdsExposedDropdownMenuTrailing(expanded = if (enabled) expanded else false, enabled = enabled), enabled = enabled ) @@ -74,7 +74,7 @@ fun OdsExposedDropdownMenu( onDismissRequest = { expanded = false }, content = { items.forEach { item -> - OdsDropdownMenuItem(text = item.label, icon = item.icon, onClick = { + OdsDropdownMenuItem(text = item.label, icon = item.iconResId?.let { painterResource(id = it) }, onClick = { selectedItem.value = item expanded = false onItemSelectionChange(item) @@ -85,15 +85,8 @@ fun OdsExposedDropdownMenu( } } -data class OdsExposedDropdownMenuItem(val label: String, val icon: Painter? = null) - -/** - * This Saver implementation converts OdsExposedDropdownMenuItem object which we don't know how to save to String which we can save. - */ -class OdsExposedDropdownMenuItemSaver : Saver { - override fun restore(value: String) = OdsExposedDropdownMenuItem(label = value) - override fun SaverScope.save(value: OdsExposedDropdownMenuItem) = value.label -} +@Parcelize +data class OdsExposedDropdownMenuItem(val label: String, @DrawableRes val iconResId: Int? = null) : Parcelable /** * Note: Please use Android Studio preview interactive mode to see the OdsExposedDropdownMenu preview cause expanded is a target state. @@ -102,10 +95,10 @@ class OdsExposedDropdownMenuItemSaver : Saver Date: Tue, 20 Jun 2023 17:05:50 +0200 Subject: [PATCH 034/112] [#545] Update changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 5e5a04dee..4f0fa8fa1 100644 --- a/changelog.md +++ b/changelog.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) -- \[Lib\] Add `OdsExposedDropdownMenuItemSaver` class to allow to save and restore `OdsExposedDropdownMenuItem` ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) +- \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 From 440a6a58add42cb81758e54f1a2ad3e239026204 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 15:44:58 +0200 Subject: [PATCH 035/112] [#546] Add frameworks icons --- app/src/main/res/drawable/ic_compose.xml | 4 ++++ app/src/main/res/drawable/ic_xml.xml | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 app/src/main/res/drawable/ic_compose.xml create mode 100644 app/src/main/res/drawable/ic_xml.xml diff --git a/app/src/main/res/drawable/ic_compose.xml b/app/src/main/res/drawable/ic_compose.xml new file mode 100644 index 000000000..52727a80b --- /dev/null +++ b/app/src/main/res/drawable/ic_compose.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_xml.xml b/app/src/main/res/drawable/ic_xml.xml new file mode 100644 index 000000000..a02a73509 --- /dev/null +++ b/app/src/main/res/drawable/ic_xml.xml @@ -0,0 +1,4 @@ + + + From 95e8b0dfba4853b2112432d2223f085e9d3e77b1 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 15:45:40 +0200 Subject: [PATCH 036/112] [#546] Use an exposed dropdown menu to switch the UI framework --- .../java/com/orange/ods/app/ui/UiFramework.kt | 11 ++++- .../ui/components/banners/ComponentBanners.kt | 2 - .../ComponentCustomizationBottomSheet.kt | 40 +--------------- .../components/utilities/ViewDataBinding.kt | 46 +++++++++++++++---- .../composable/CodeImplementation.kt | 39 ++++++++++++++-- app/src/main/res/values/strings.xml | 7 +-- 6 files changed, 86 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt index 708305361..80543b5f0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt +++ b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt @@ -10,6 +10,9 @@ package com.orange.ods.app.ui +import android.content.Context +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -18,13 +21,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.databinding.ViewDataBinding +import com.orange.ods.app.R import com.orange.ods.app.ui.components.utilities.ViewDataBinding val LocalUiFramework = staticCompositionLocalOf> { error("CompositionLocal LocalUiFramework not present") } -enum class UiFramework { - Compose, Xml +enum class UiFramework(val iconResId: Int, val labelResId: Int) { + Compose(R.drawable.ic_compose, R.string.code_implementation_compose), + Xml(R.drawable.ic_xml, R.string.code_implementation_xml) } @Composable 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 ddec9003c..c11b3b511 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 @@ -33,7 +33,6 @@ import com.orange.ods.app.domain.recipes.LocalRecipes import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.utilities.ComponentCountRow import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold -import com.orange.ods.app.ui.components.utilities.ComponentCustomizationUiFramework import com.orange.ods.app.ui.components.utilities.clickOnElement import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn @@ -54,7 +53,6 @@ fun ComponentBanners() { ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), bottomSheetContent = { - ComponentCustomizationUiFramework() ComponentCountRow( title = stringResource(id = R.string.component_banner_text_lines_count), count = textLinesCount, 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 dc93f9d5f..4c19d2fc7 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 @@ -14,40 +14,23 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.BottomSheetScaffoldState -import androidx.compose.material.BottomSheetState -import androidx.compose.material.BottomSheetValue -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FabPosition -import androidx.compose.material.Icon -import androidx.compose.material.SnackbarHost -import androidx.compose.material.SnackbarHostState +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope 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 import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalUiFramework -import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.utilities.composable.OnResumeEffect -import com.orange.ods.app.ui.utilities.composable.Subtitle import com.orange.ods.compose.component.bottomsheet.OdsBottomSheetScaffold -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.theme.OdsTheme import kotlinx.coroutines.CancellationException @@ -127,25 +110,6 @@ fun ComponentCustomizationBottomSheetScaffold( } } -@Composable -fun ComponentCustomizationUiFramework() { - Subtitle(textRes = R.string.component_ui_framework, horizontalPadding = true) - val uiFramework = LocalUiFramework.current - OdsChoiceChipsFlowRow( - selectedChip = uiFramework, - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), - outlinedChips = true - ) { - UiFramework.values().forEach { uiFramework -> - val textRes = when (uiFramework) { - UiFramework.Compose -> R.string.component_ui_framework_compose - UiFramework.Xml -> R.string.component_ui_framework_xml - } - OdsChoiceChip(textRes = textRes, value = uiFramework) - } - } -} - @OptIn(ExperimentalMaterialApi::class) private fun tryExpandBottomSheet(coroutineScope: CoroutineScope, bottomSheetState: BottomSheetState, retryCount: Int = 0) { coroutineScope.launch { diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index f12642bf5..07532a134 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -12,11 +12,24 @@ package com.orange.ods.app.ui.components.utilities import android.view.LayoutInflater import android.view.ViewGroup +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.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import androidx.databinding.ViewDataBinding +import com.orange.ods.app.R +import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn +import com.orange.ods.app.ui.utilities.composable.TechnicalText +import com.orange.ods.compose.text.OdsTextBody1 @Composable inline fun ViewDataBinding(noinline bind: T.() -> Unit) { @@ -24,14 +37,27 @@ inline fun ViewDataBinding(noinline bind: T.() -> val layoutInflater = LayoutInflater.from(LocalContext.current) val parameters = listOf(layoutInflater, null, false) val binding = remember { inflateMethod.invoke(null, *parameters.toTypedArray()) as T } - AndroidView( - factory = { - binding.bind() - binding.executePendingBindings() - binding.root - }, - update = { - binding.bind() - binding.executePendingBindings() - }) + Column { + AndroidView( + modifier = Modifier.background(Color.Red), + factory = { + binding.bind() + binding.executePendingBindings() + binding.root + }, + update = { + binding.bind() + binding.executePendingBindings() + }) + + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + ) { + TechnicalText( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.soon_available)) + } + } } 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 4d5c39aa1..d50652950 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 @@ -12,14 +12,23 @@ package com.orange.ods.app.ui.utilities.composable import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +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.semantics.semantics import com.orange.ods.app.R +import com.orange.ods.app.ui.LocalUiFramework +import com.orange.ods.app.ui.UiFramework +import com.orange.ods.compose.component.menu.OdsExposedDropdownMenu +import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItem +import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItemSaver import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.utilities.extension.orElse @@ -118,8 +127,7 @@ fun CodeImplementationColumn( vertical = dimensionResource(id = R.dimen.spacing_s) ) ) { - Subtitle(textRes = R.string.code_implementation) - Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.spacing_s))) + UiFrameworkChoice() if (contentBackground) { CodeBackgroundColumn(content) } else { @@ -128,6 +136,29 @@ fun CodeImplementationColumn( } } +@Composable +private fun UiFrameworkChoice() { + val context = LocalContext.current + val currentUiFramework = LocalUiFramework.current + var selectedIndex = 0 + val uiFrameworks = UiFramework.values().mapIndexed { index, uiFramework -> + if (uiFramework == currentUiFramework.value) selectedIndex = index + OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), icon = painterResource(id = uiFramework.iconResId)) + } + + val selectedUiFramework: MutableState = + rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(uiFrameworks[selectedIndex]) } + + OdsExposedDropdownMenu( + label = stringResource(id = R.string.code_implementation), + items = uiFrameworks, + selectedItem = selectedUiFramework, + onItemSelectionChange = { selectedItem -> + currentUiFramework.value = UiFramework.values().first { context.getString(it.labelResId) == selectedItem.label } + }, + ) +} + @Composable fun CodeBackgroundColumn(content: @Composable () -> Unit) { Column( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f09ff7841..f93d4394e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,7 +10,11 @@ Orange Design System + Code implementation + XML + Jetpack Compose + Soon available… Guidelines @@ -90,9 +94,6 @@ Example Content Type - UI framework - Compose - XML App bars: top From 1df9aa29efabc440fe28620ebbc983c72b307dc1 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 16:40:00 +0200 Subject: [PATCH 037/112] [#546] Disable framework dropdown if the XML version of the component is not available --- .../app/ui/components/banners/ComponentBanners.kt | 3 ++- .../app/ui/components/utilities/ViewDataBinding.kt | 8 ++++---- .../ui/utilities/composable/CodeImplementation.kt | 13 +++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) 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 c11b3b511..319476fb6 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 @@ -107,7 +107,8 @@ fun ComponentBanners() { ) CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( name = OdsComposable.OdsBanner.name, diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index 07532a134..00a114ab0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -16,7 +16,6 @@ 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.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -29,7 +28,6 @@ import androidx.databinding.ViewDataBinding import com.orange.ods.app.R import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.TechnicalText -import com.orange.ods.compose.text.OdsTextBody1 @Composable inline fun ViewDataBinding(noinline bind: T.() -> Unit) { @@ -51,13 +49,15 @@ inline fun ViewDataBinding(noinline bind: T.() -> }) CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true, ) { TechnicalText( modifier = Modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.spacing_xs)), - text = stringResource(id = R.string.soon_available)) + text = stringResource(id = R.string.soon_available) + ) } } } 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 d50652950..ddb08740d 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 @@ -119,6 +119,7 @@ private sealed class PredefinedParameter { @Composable fun CodeImplementationColumn( modifier: Modifier = Modifier, + xmlAvailable: Boolean = false, // Temporary parameter: Indicates whether the XML version of the component is available contentBackground: Boolean = true, content: @Composable () -> Unit ) { @@ -127,7 +128,7 @@ fun CodeImplementationColumn( vertical = dimensionResource(id = R.dimen.spacing_s) ) ) { - UiFrameworkChoice() + UiFrameworkChoice(xmlAvailable) if (contentBackground) { CodeBackgroundColumn(content) } else { @@ -137,17 +138,16 @@ fun CodeImplementationColumn( } @Composable -private fun UiFrameworkChoice() { +private fun UiFrameworkChoice(xmlAvailable: Boolean) { val context = LocalContext.current val currentUiFramework = LocalUiFramework.current - var selectedIndex = 0 + var selectedFramework = 0 val uiFrameworks = UiFramework.values().mapIndexed { index, uiFramework -> - if (uiFramework == currentUiFramework.value) selectedIndex = index + if (uiFramework == currentUiFramework.value) selectedFramework = index OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), icon = painterResource(id = uiFramework.iconResId)) } - val selectedUiFramework: MutableState = - rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(uiFrameworks[selectedIndex]) } + rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(if (xmlAvailable) uiFrameworks[selectedFramework] else uiFrameworks.first()) } OdsExposedDropdownMenu( label = stringResource(id = R.string.code_implementation), @@ -156,6 +156,7 @@ private fun UiFrameworkChoice() { onItemSelectionChange = { selectedItem -> currentUiFramework.value = UiFramework.values().first { context.getString(it.labelResId) == selectedItem.label } }, + enabled = xmlAvailable ) } From 11848f6befc3310b1a5e6ee3903410df4b1d57d3 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 16:45:29 +0200 Subject: [PATCH 038/112] [#546] Update changelog --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index 4f0fa8fa1..6f0be9dde 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) +### Changed + +- \[App\] Use an exposed dropdown menu to switch between XML and Compose implementation ([#546](https://github.com/Orange-OpenSource/ods-android/issues/546)) + ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 ### Added From 4267ea12f922612139a0a2247af50fcbf0ec9147 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 7 Jun 2023 16:46:25 +0200 Subject: [PATCH 039/112] [#546] Remove center alignment to avoid lost space --- .../components/bottomnavigation/ComponentBottomNavigation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 27d1eec0e..963bb55bf 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 @@ -70,8 +70,8 @@ fun ComponentBottomNavigation() { Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Center + .padding(top = dimensionResource(id = R.dimen.screen_vertical_margin)) + .verticalScroll(rememberScrollState()) ) { OdsBottomNavigation { navigationItems.take(selectedNavigationItemCount.value).forEach { navigationItem -> From 541e37a6eb67609a7826e215577c1df680300352 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:13:34 +0200 Subject: [PATCH 040/112] [#546] Use resource identifier on OdsExposedDropdownMenuItem use --- .../ods/app/ui/utilities/composable/CodeImplementation.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 ddb08740d..3efb89000 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 @@ -28,7 +28,6 @@ import com.orange.ods.app.ui.LocalUiFramework import com.orange.ods.app.ui.UiFramework import com.orange.ods.compose.component.menu.OdsExposedDropdownMenu import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItem -import com.orange.ods.compose.component.menu.OdsExposedDropdownMenuItemSaver import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.utilities.extension.orElse @@ -144,10 +143,10 @@ private fun UiFrameworkChoice(xmlAvailable: Boolean) { var selectedFramework = 0 val uiFrameworks = UiFramework.values().mapIndexed { index, uiFramework -> if (uiFramework == currentUiFramework.value) selectedFramework = index - OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), icon = painterResource(id = uiFramework.iconResId)) + OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), iconResId = uiFramework.iconResId) } val selectedUiFramework: MutableState = - rememberSaveable(stateSaver = OdsExposedDropdownMenuItemSaver()) { mutableStateOf(if (xmlAvailable) uiFrameworks[selectedFramework] else uiFrameworks.first()) } + rememberSaveable() { mutableStateOf(if (xmlAvailable) uiFrameworks[selectedFramework] else uiFrameworks.first()) } OdsExposedDropdownMenu( label = stringResource(id = R.string.code_implementation), From 684752f2321a0a1dd72f61d043968c60a4a41740 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:17:16 +0200 Subject: [PATCH 041/112] [#546] Review: Remove useless background color on AndroidView --- .../orange/ods/app/ui/components/utilities/ViewDataBinding.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index 00a114ab0..b46c1d4d0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -37,7 +37,6 @@ inline fun ViewDataBinding(noinline bind: T.() -> val binding = remember { inflateMethod.invoke(null, *parameters.toTypedArray()) as T } Column { AndroidView( - modifier = Modifier.background(Color.Red), factory = { binding.bind() binding.executePendingBindings() From c828c30c03f9eb1a3a38758fa2d843f3c0a4656e Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:24:31 +0200 Subject: [PATCH 042/112] [#546] Review: Rename variables for clarity --- .../app/ui/utilities/composable/CodeImplementation.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 3efb89000..2475ca061 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 @@ -20,7 +20,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier 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.semantics.semantics import com.orange.ods.app.R @@ -140,17 +139,17 @@ fun CodeImplementationColumn( private fun UiFrameworkChoice(xmlAvailable: Boolean) { val context = LocalContext.current val currentUiFramework = LocalUiFramework.current - var selectedFramework = 0 - val uiFrameworks = UiFramework.values().mapIndexed { index, uiFramework -> - if (uiFramework == currentUiFramework.value) selectedFramework = index + var selectedUiFrameworkIndex = 0 + val uiFrameworkItems = UiFramework.values().mapIndexed { index, uiFramework -> + if (uiFramework == currentUiFramework.value) selectedUiFrameworkIndex = index OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), iconResId = uiFramework.iconResId) } val selectedUiFramework: MutableState = - rememberSaveable() { mutableStateOf(if (xmlAvailable) uiFrameworks[selectedFramework] else uiFrameworks.first()) } + rememberSaveable() { mutableStateOf(if (xmlAvailable) uiFrameworkItems[selectedUiFrameworkIndex] else uiFrameworkItems.first()) } OdsExposedDropdownMenu( label = stringResource(id = R.string.code_implementation), - items = uiFrameworks, + items = uiFrameworkItems, selectedItem = selectedUiFramework, onItemSelectionChange = { selectedItem -> currentUiFramework.value = UiFramework.values().first { context.getString(it.labelResId) == selectedItem.label } From 3e90a7151c3cfd1bb071f5134cae198894b88b92 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:25:48 +0200 Subject: [PATCH 043/112] [#546] Review: Remove useless type --- .../ods/app/ui/utilities/composable/CodeImplementation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 2475ca061..6404500e2 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 @@ -14,7 +14,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier @@ -144,8 +143,7 @@ private fun UiFrameworkChoice(xmlAvailable: Boolean) { if (uiFramework == currentUiFramework.value) selectedUiFrameworkIndex = index OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), iconResId = uiFramework.iconResId) } - val selectedUiFramework: MutableState = - rememberSaveable() { mutableStateOf(if (xmlAvailable) uiFrameworkItems[selectedUiFrameworkIndex] else uiFrameworkItems.first()) } + val selectedUiFramework = rememberSaveable { mutableStateOf(if (xmlAvailable) uiFrameworkItems[selectedUiFrameworkIndex] else uiFrameworkItems.first()) } OdsExposedDropdownMenu( label = stringResource(id = R.string.code_implementation), From 8857ddc0545c18b739e9df4493d1bba04a42f0f9 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 17:43:28 +0200 Subject: [PATCH 044/112] [#546] Review: Move xml code implementation from ViewDataBinding to CodeImplementationColumn --- .../ui/components/banners/ComponentBanners.kt | 36 +++++++++------- .../components/utilities/ViewDataBinding.kt | 43 +++++-------------- .../composable/CodeImplementation.kt | 20 +++++++-- 3 files changed, 47 insertions(+), 52 deletions(-) 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 319476fb6..5249bcd69 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 @@ -12,6 +12,7 @@ package com.orange.ods.app.ui.components.banners import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -30,6 +31,7 @@ import coil.request.ImageRequest import com.orange.ods.app.R import com.orange.ods.app.databinding.OdsBannerBinding import com.orange.ods.app.domain.recipes.LocalRecipes +import com.orange.ods.app.ui.LocalUiFramework import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.utilities.ComponentCountRow import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold @@ -37,6 +39,7 @@ import com.orange.ods.app.ui.components.utilities.clickOnElement import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode +import com.orange.ods.app.ui.utilities.composable.TechnicalText import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.banner.OdsBanner import com.orange.ods.compose.component.list.OdsListItem @@ -105,22 +108,6 @@ fun ComponentBanners() { onButton1Click = onButton1Click, onButton2Click = onButton2Click, ) - - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), - xmlAvailable = true - ) { - FunctionCallCode( - name = OdsComposable.OdsBanner.name, - exhaustiveParameters = false, - parameters = { - string("message", if (hasTwoTextLines) recipe.description else recipe.title) - button1Text(context.getString(R.string.component_banner_dismiss)) - if (hasImage) image() - if (hasButton2) button2Text(context.getString(R.string.component_banner_detail)) - } - ) - } }, xml = { this.message = message @@ -141,6 +128,23 @@ fun ComponentBanners() { } } ) + + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true + ) { + FunctionCallCode( + name = OdsComposable.OdsBanner.name, + exhaustiveParameters = false, + parameters = { + string("message", if (hasTwoTextLines) recipe.description else recipe.title) + button1Text(context.getString(R.string.component_banner_dismiss)) + if (hasImage) image() + if (hasButton2) button2Text(context.getString(R.string.component_banner_detail)) + } + ) + } + } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index b46c1d4d0..585be7ac7 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -12,22 +12,11 @@ package com.orange.ods.app.ui.components.utilities import android.view.LayoutInflater import android.view.ViewGroup -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.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import androidx.databinding.ViewDataBinding -import com.orange.ods.app.R -import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn -import com.orange.ods.app.ui.utilities.composable.TechnicalText @Composable inline fun ViewDataBinding(noinline bind: T.() -> Unit) { @@ -35,28 +24,16 @@ inline fun ViewDataBinding(noinline bind: T.() -> val layoutInflater = LayoutInflater.from(LocalContext.current) val parameters = listOf(layoutInflater, null, false) val binding = remember { inflateMethod.invoke(null, *parameters.toTypedArray()) as T } - Column { - AndroidView( - factory = { - binding.bind() - binding.executePendingBindings() - binding.root - }, - update = { - binding.bind() - binding.executePendingBindings() - }) - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), - xmlAvailable = true, - ) { - TechnicalText( - modifier = Modifier - .fillMaxWidth() - .padding(dimensionResource(id = R.dimen.spacing_xs)), - text = stringResource(id = R.string.soon_available) - ) + AndroidView( + factory = { + binding.bind() + binding.executePendingBindings() + binding.root + }, + update = { + binding.bind() + binding.executePendingBindings() } - } + ) } 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 6404500e2..94e37bb7f 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 @@ -12,6 +12,7 @@ package com.orange.ods.app.ui.utilities.composable 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.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -120,16 +121,29 @@ fun CodeImplementationColumn( contentBackground: Boolean = true, content: @Composable () -> Unit ) { + val currentUiFramework = LocalUiFramework.current + Column( modifier = modifier.padding( vertical = dimensionResource(id = R.dimen.spacing_s) ) ) { UiFrameworkChoice(xmlAvailable) - if (contentBackground) { - CodeBackgroundColumn(content) + if (currentUiFramework.value == UiFramework.Compose) { + if (contentBackground) { + CodeBackgroundColumn(content) + } else { + content() + } } else { - content() + CodeBackgroundColumn { + TechnicalText( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.soon_available) + ) + } } } } From 04b00bc76905baf771b8fc44b258ced665747a4d Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Mon, 12 Jun 2023 17:39:10 +0200 Subject: [PATCH 045/112] [#528] Add OdsButton and OdsOutlinedButton in LibXML --- .../ui/components/buttons/ButtonsContained.kt | 85 ++++++++++++------- .../ui/components/buttons/ButtonsOutlined.kt | 64 +++++++++----- app/src/main/res/layout/ods_button.xml | 30 +++++++ .../main/res/layout/ods_outlined_button.xml | 30 +++++++ changelog.md | 2 + .../ods/xml/component/button/OdsButton.kt | 45 ++++++++++ .../xml/component/button/OdsOutlinedButton.kt | 42 +++++++++ lib-xml/src/main/res/values/attrs.xml | 10 +++ 8 files changed, 253 insertions(+), 55 deletions(-) create mode 100644 app/src/main/res/layout/ods_button.xml create mode 100644 app/src/main/res/layout/ods_outlined_button.xml create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt index a0251fa66..f94ed194e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt @@ -10,6 +10,9 @@ package com.orange.ods.app.ui.components.buttons +import android.app.ActionBar +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -18,10 +21,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +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.databinding.OdsButtonBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.app.ui.utilities.composable.Title @@ -40,43 +46,56 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - with(buttonStyle.value) { - if (buttonStyle.value in listOf(OdsButtonStyle.FunctionalNegative, OdsButtonStyle.FunctionalPositive)) { - Title( - textRes = if (this == OdsButtonStyle.FunctionalNegative) R.string.component_button_style_functional_negative else R.string.component_button_style_functional_positive, - horizontalPadding = true - ) - } - } - - ContainedButton(style = buttonStyle.value, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) + val context = LocalContext.current + val text = stringResource(if (isEnabled) R.string.component_state_enabled else R.string.component_state_disabled) + val icon = R.drawable.ic_coffee + UiFramework( + compose = { + with(buttonStyle.value) { + if (buttonStyle.value in listOf(OdsButtonStyle.FunctionalNegative, OdsButtonStyle.FunctionalPositive)) { + Title( + textRes = if (this == OdsButtonStyle.FunctionalNegative) R.string.component_button_style_functional_negative else R.string.component_button_style_functional_positive, + horizontalPadding = true + ) + } + } + ContainedButton(style = buttonStyle.value, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) - Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) + Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) - InvertedBackgroundColumn { - ContainedButton( - style = buttonStyle.value, - leadingIcon = hasLeadingIcon, - enabled = isEnabled, - fullScreenWidth = hasFullScreenWidth, - displaySurface = displaySurface - ) - } + InvertedBackgroundColumn { + ContainedButton( + style = buttonStyle.value, + leadingIcon = hasLeadingIcon, + enabled = isEnabled, + fullScreenWidth = hasFullScreenWidth, + displaySurface = displaySurface + ) + } - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) - ) { - FunctionCallCode( - name = OdsComposable.OdsButton.name, - exhaustiveParameters = false, - parameters = { - simple("style", buttonStyle.value.fullName) - if (hasFullScreenWidth) fillMaxWidth() - if (hasLeadingIcon) icon() - if (!isEnabled) enabled(false) + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + ) { + FunctionCallCode( + name = OdsComposable.OdsButton.name, + exhaustiveParameters = false, + parameters = { + simple("style", buttonStyle.value.fullName) + if (hasFullScreenWidth) fillMaxWidth() + if (hasLeadingIcon) icon() + if (!isEnabled) enabled(false) + } + ) } - ) - } + }, xml = { + this.text = text + button.style = buttonStyle.value + button.icon = if (hasLeadingIcon) AppCompatResources.getDrawable(context, icon) else null + button.isEnabled = isEnabled + val width = if (hasFullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT + button.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) + } + ) } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt index 3eaed2bf5..8668517a2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt @@ -10,6 +10,9 @@ package com.orange.ods.app.ui.components.buttons +import android.app.ActionBar.LayoutParams +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -18,10 +21,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +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.databinding.OdsOutlinedButtonBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.compose.OdsComposable @@ -36,31 +42,45 @@ fun ButtonsOutlined(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - OutlinedButton(leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) + val context = LocalContext.current + val text = stringResource(if (isEnabled) R.string.component_state_enabled else R.string.component_state_disabled) + val icon = R.drawable.ic_coffee + UiFramework( + compose = { + OutlinedButton(leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) - Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) + Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) - InvertedBackgroundColumn { - OutlinedButton( - leadingIcon = hasLeadingIcon, - enabled = isEnabled, - fullScreenWidth = hasFullScreenWidth, - displaySurface = displaySurface - ) - } + InvertedBackgroundColumn { + OutlinedButton( + leadingIcon = hasLeadingIcon, + enabled = isEnabled, + fullScreenWidth = hasFullScreenWidth, + displaySurface = displaySurface + ) + } - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) - ) { - FunctionCallCode( - name = OdsComposable.OdsOutlinedButton.name, - exhaustiveParameters = false, - parameters = { - if (hasFullScreenWidth) fillMaxWidth() - if (hasLeadingIcon) icon() - if (!isEnabled) enabled(false) - }) - } + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + ) { + FunctionCallCode( + name = OdsComposable.OdsOutlinedButton.name, + exhaustiveParameters = false, + parameters = { + if (hasFullScreenWidth) fillMaxWidth() + if (hasLeadingIcon) icon() + if (!isEnabled) enabled(false) + }) + } + }, xml = { + this.text = text + outlinedbutton.icon = if (hasLeadingIcon) AppCompatResources.getDrawable(context, icon) else null + outlinedbutton.isEnabled = isEnabled + val width = if (hasFullScreenWidth) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT + outlinedbutton.layoutParams = ViewGroup.LayoutParams(width, LayoutParams.WRAP_CONTENT) + } + + ) } } } diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml new file mode 100644 index 000000000..81acf6fed --- /dev/null +++ b/app/src/main/res/layout/ods_button.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_outlined_button.xml b/app/src/main/res/layout/ods_outlined_button.xml new file mode 100644 index 000000000..912c4d0af --- /dev/null +++ b/app/src/main/res/layout/ods_outlined_button.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/changelog.md b/changelog.md index 6f0be9dde..dc6a3b1de 100644 --- a/changelog.md +++ b/changelog.md @@ -10,8 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) +- \[App\] Display XML version in `ButtonsContained` and `ButtonsOutlined` ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) +- \[LibXml\] Add `OdsButton` and `OdsOutlinedButton` views ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) ### Changed diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt new file mode 100644 index 000000000..ca74af104 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt @@ -0,0 +1,45 @@ +/* + * + * 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.button + +import android.content.Context +import android.graphics.drawable.Drawable +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.google.accompanist.drawablepainter.rememberDrawablePainter +import com.orange.ods.compose.component.button.OdsButton +import com.orange.ods.compose.component.button.OdsButtonStyle +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + +class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var text by mutableStateOf("") + var onClick by mutableStateOf({}) + var icon by mutableStateOf(null) + var style by mutableStateOf(OdsButtonStyle.Default) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + OdsButton( + text = text, + onClick = onClick, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, + enabled = isEnabled, + style = style, + displaySurface = displaySurface + ) + } +} \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt new file mode 100644 index 000000000..53c3a7a52 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt @@ -0,0 +1,42 @@ +/* + * + * 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.button + +import android.content.Context +import android.graphics.drawable.Drawable +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.google.accompanist.drawablepainter.rememberDrawablePainter +import com.orange.ods.compose.component.button.OdsOutlinedButton +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + +class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var text by mutableStateOf("") + var onClick by mutableStateOf({}) + var icon by mutableStateOf(null) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + OdsOutlinedButton( + text = text, + onClick = onClick, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, + enabled = isEnabled, + displaySurface = displaySurface + ) + } +} \ No newline at end of file diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 7096f380d..d104bac4c 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -9,6 +9,8 @@ --> + + @@ -16,4 +18,12 @@ + + + + + + + + From d30d660ed5680da30ad9fe898312f01c7c808b3e Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Wed, 14 Jun 2023 16:48:22 +0200 Subject: [PATCH 046/112] [#528] Enhance xml part --- .../java/com/orange/ods/app/ui/UiFramework.kt | 17 ++- .../ui/components/buttons/ButtonsContained.kt | 126 ++++++++++-------- .../ui/components/buttons/ButtonsOutlined.kt | 92 ++++++------- app/src/main/res/layout/ods_button.xml | 5 +- .../main/res/layout/ods_outlined_button.xml | 5 +- 5 files changed, 128 insertions(+), 117 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt index 80543b5f0..69e9e3d3d 100644 --- a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt +++ b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt @@ -10,9 +10,7 @@ package com.orange.ods.app.ui -import android.content.Context -import androidx.compose.foundation.layout.Column -import androidx.compose.material.Text +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -21,8 +19,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.Modifier import androidx.databinding.ViewDataBinding import com.orange.ods.app.R import com.orange.ods.app.ui.components.utilities.ViewDataBinding @@ -35,7 +32,7 @@ enum class UiFramework(val iconResId: Int, val labelResId: Int) { } @Composable -inline fun UiFramework(compose: @Composable () -> Unit, noinline xml: T.() -> Unit) { +inline fun UiFramework(compose: @Composable () -> Unit, noinline xml: T.() -> Unit, modifier: Modifier = Modifier) { val uiFramework = LocalUiFramework.current // Reset current UI framework to Compose when displaying the content // shouldResetUiFramework is used to avoid calling LaunchedEffect on configuration changes (for instance on device rotation) @@ -46,8 +43,10 @@ inline fun UiFramework(compose: @Composable () -> uiFramework.value = UiFramework.Compose } } - when (uiFramework.value) { - UiFramework.Compose -> compose() - UiFramework.Xml -> ViewDataBinding(bind = xml) + Box(modifier) { + when (uiFramework.value) { + UiFramework.Compose -> compose() + UiFramework.Xml -> ViewDataBinding(bind = xml) + } } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt index f94ed194e..d9b346479 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt @@ -46,78 +46,92 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val context = LocalContext.current - val text = stringResource(if (isEnabled) R.string.component_state_enabled else R.string.component_state_disabled) - val icon = R.drawable.ic_coffee - UiFramework( - compose = { - with(buttonStyle.value) { - if (buttonStyle.value in listOf(OdsButtonStyle.FunctionalNegative, OdsButtonStyle.FunctionalPositive)) { - Title( - textRes = if (this == OdsButtonStyle.FunctionalNegative) R.string.component_button_style_functional_negative else R.string.component_button_style_functional_positive, - horizontalPadding = true - ) - } - } - ContainedButton(style = buttonStyle.value, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(R.dimen.spacing_m)) + .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } - Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) + with(buttonStyle.value) { + if (buttonStyle.value in listOf(OdsButtonStyle.FunctionalNegative, OdsButtonStyle.FunctionalPositive)) { + Title( + textRes = if (this == OdsButtonStyle.FunctionalNegative) R.string.component_button_style_functional_negative else R.string.component_button_style_functional_positive, + horizontalPadding = true + ) + } + } + ContainedButton( + style = buttonStyle.value, + modifier = modifier, + leadingIcon = hasLeadingIcon, + enabled = isEnabled, + fullScreenWidth = hasFullScreenWidth + ) - InvertedBackgroundColumn { - ContainedButton( - style = buttonStyle.value, - leadingIcon = hasLeadingIcon, - enabled = isEnabled, - fullScreenWidth = hasFullScreenWidth, - displaySurface = displaySurface - ) - } + Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) + + InvertedBackgroundColumn { + ContainedButton( + style = buttonStyle.value, + modifier = modifier, + leadingIcon = hasLeadingIcon, + enabled = isEnabled, + fullScreenWidth = hasFullScreenWidth, + displaySurface = displaySurface + ) + } - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) - ) { - FunctionCallCode( - name = OdsComposable.OdsButton.name, - exhaustiveParameters = false, - parameters = { - simple("style", buttonStyle.value.fullName) - if (hasFullScreenWidth) fillMaxWidth() - if (hasLeadingIcon) icon() - if (!isEnabled) enabled(false) - } - ) + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true + ) { + FunctionCallCode( + name = OdsComposable.OdsButton.name, + exhaustiveParameters = false, + parameters = { + simple("style", buttonStyle.value.fullName) + if (hasFullScreenWidth) fillMaxWidth() + if (hasLeadingIcon) icon() + if (!isEnabled) enabled(false) } - }, xml = { - this.text = text - button.style = buttonStyle.value - button.icon = if (hasLeadingIcon) AppCompatResources.getDrawable(context, icon) else null - button.isEnabled = isEnabled - val width = if (hasFullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT - button.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) - } - ) + ) + } } } } - @Composable private fun ContainedButton( style: OdsButtonStyle, + modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { - val modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m)) + val context = LocalContext.current + val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) + val iconId = R.drawable.ic_coffee - OdsButton( - modifier = if (fullScreenWidth) modifier.fillMaxWidth() else modifier, - icon = if (leadingIcon) painterResource(id = R.drawable.ic_coffee) else null, - text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled), - onClick = {}, - enabled = enabled, - style = style, - displaySurface = displaySurface + UiFramework( + modifier = modifier, + compose = { + OdsButton( + modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, + icon = if (leadingIcon) painterResource(id = iconId) else null, + text = text, + onClick = {}, + enabled = enabled, + style = style, + displaySurface = displaySurface + ) + }, xml = { + this.text = text + button.style = style + button.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + button.isEnabled = enabled + val width = if (fullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT + button.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) + button.displaySurface = displaySurface + } ) } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt index 8668517a2..7f505fbb2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt @@ -42,66 +42,66 @@ fun ButtonsOutlined(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val context = LocalContext.current - val text = stringResource(if (isEnabled) R.string.component_state_enabled else R.string.component_state_disabled) - val icon = R.drawable.ic_coffee - UiFramework( - compose = { - OutlinedButton(leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(R.dimen.spacing_m)) + .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } - Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) + OutlinedButton(modifier, hasLeadingIcon, isEnabled, hasFullScreenWidth) - InvertedBackgroundColumn { - OutlinedButton( - leadingIcon = hasLeadingIcon, - enabled = isEnabled, - fullScreenWidth = hasFullScreenWidth, - displaySurface = displaySurface - ) - } + Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) - CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) - ) { - FunctionCallCode( - name = OdsComposable.OdsOutlinedButton.name, - exhaustiveParameters = false, - parameters = { - if (hasFullScreenWidth) fillMaxWidth() - if (hasLeadingIcon) icon() - if (!isEnabled) enabled(false) - }) - } - }, xml = { - this.text = text - outlinedbutton.icon = if (hasLeadingIcon) AppCompatResources.getDrawable(context, icon) else null - outlinedbutton.isEnabled = isEnabled - val width = if (hasFullScreenWidth) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT - outlinedbutton.layoutParams = ViewGroup.LayoutParams(width, LayoutParams.WRAP_CONTENT) - } + InvertedBackgroundColumn { + OutlinedButton(modifier, hasLeadingIcon, isEnabled, hasFullScreenWidth, displaySurface = displaySurface) + } - ) + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true + ) { + FunctionCallCode( + name = OdsComposable.OdsOutlinedButton.name, + exhaustiveParameters = false, + parameters = { + if (hasFullScreenWidth) fillMaxWidth() + if (hasLeadingIcon) icon() + if (!isEnabled) enabled(false) + }) + } } } } @Composable private fun OutlinedButton( + modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, - displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default + displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default, ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) + val context = LocalContext.current + val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) + val iconId = R.drawable.ic_coffee - OdsOutlinedButton( - modifier = if (fullScreenWidth) modifier.fillMaxWidth() else modifier, - text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled), - onClick = {}, - icon = if (leadingIcon) painterResource(id = R.drawable.ic_coffee) else null, - enabled = enabled, - displaySurface = displaySurface + UiFramework( + modifier = modifier, + compose = { + OdsOutlinedButton( + modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, + text = text, + onClick = {}, + icon = if (leadingIcon) painterResource(id = iconId) else null, + enabled = enabled, + displaySurface = displaySurface + ) + }, xml = { + this.text = text + outlinedbutton.displaySurface = displaySurface + outlinedbutton.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + outlinedbutton.isEnabled = enabled + val width = if (fullScreenWidth) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT + outlinedbutton.layoutParams = ViewGroup.LayoutParams(width, LayoutParams.WRAP_CONTENT) + } ) } \ No newline at end of file diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml index 81acf6fed..961f8bb99 100644 --- a/app/src/main/res/layout/ods_button.xml +++ b/app/src/main/res/layout/ods_button.xml @@ -21,9 +21,8 @@ + android:layout_height="match_parent" + android:layout_width="match_parent"> diff --git a/app/src/main/res/layout/ods_outlined_button.xml b/app/src/main/res/layout/ods_outlined_button.xml index 912c4d0af..ddd9775f4 100644 --- a/app/src/main/res/layout/ods_outlined_button.xml +++ b/app/src/main/res/layout/ods_outlined_button.xml @@ -21,9 +21,8 @@ + android:layout_height="match_parent" + android:layout_width="match_parent"> From 80e606ea2a5b1276d2553149980430eb8fa841f5 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 15:28:22 +0200 Subject: [PATCH 047/112] [#528] Modify contained and outlined buttons for XML display by providing all attributes in the XML file --- .../java/com/orange/ods/app/ui/UiFramework.kt | 10 ++- .../ui/components/buttons/ButtonsContained.kt | 61 ++++++++----------- .../ui/components/buttons/ButtonsOutlined.kt | 57 ++++++++--------- .../components/utilities/ViewDataBinding.kt | 2 + app/src/main/res/layout/ods_banner.xml | 1 + app/src/main/res/layout/ods_button.xml | 48 +++++++++++++-- .../main/res/layout/ods_outlined_button.xml | 41 +++++++++++-- .../ods/xml/component/button/OdsButton.kt | 21 ++++++- .../xml/component/button/OdsOutlinedButton.kt | 20 +++++- lib-xml/src/main/res/values/attrs.xml | 21 +++++++ .../ods/compose/component/button/OdsButton.kt | 8 +++ .../ods/compose/theme/OdsDisplaySurface.kt | 10 ++- 12 files changed, 209 insertions(+), 91 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt index 69e9e3d3d..4bbec825d 100644 --- a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt +++ b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt @@ -32,7 +32,7 @@ enum class UiFramework(val iconResId: Int, val labelResId: Int) { } @Composable -inline fun UiFramework(compose: @Composable () -> Unit, noinline xml: T.() -> Unit, modifier: Modifier = Modifier) { +inline fun UiFramework(compose: @Composable () -> Unit, noinline xml: T.() -> Unit) { val uiFramework = LocalUiFramework.current // Reset current UI framework to Compose when displaying the content // shouldResetUiFramework is used to avoid calling LaunchedEffect on configuration changes (for instance on device rotation) @@ -43,10 +43,8 @@ inline fun UiFramework(compose: @Composable () -> uiFramework.value = UiFramework.Compose } } - Box(modifier) { - when (uiFramework.value) { - UiFramework.Compose -> compose() - UiFramework.Xml -> ViewDataBinding(bind = xml) - } + when (uiFramework.value) { + UiFramework.Compose -> compose() + UiFramework.Xml -> ViewDataBinding(bind = xml) } } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt index d9b346479..bf2654c95 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt @@ -10,13 +10,9 @@ package com.orange.ods.app.ui.components.buttons -import android.app.ActionBar -import android.view.ViewGroup +import android.widget.RelativeLayout import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -46,10 +42,6 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) - .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } with(buttonStyle.value) { if (buttonStyle.value in listOf(OdsButtonStyle.FunctionalNegative, OdsButtonStyle.FunctionalPositive)) { @@ -61,7 +53,6 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { } ContainedButton( style = buttonStyle.value, - modifier = modifier, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth @@ -72,7 +63,6 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { InvertedBackgroundColumn { ContainedButton( style = buttonStyle.value, - modifier = modifier, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth, @@ -102,7 +92,6 @@ fun ButtonsContained(customizationState: ButtonCustomizationState) { @Composable private fun ContainedButton( style: OdsButtonStyle, - modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, @@ -112,26 +101,28 @@ private fun ContainedButton( val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) val iconId = R.drawable.ic_coffee - UiFramework( - modifier = modifier, - compose = { - OdsButton( - modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, - icon = if (leadingIcon) painterResource(id = iconId) else null, - text = text, - onClick = {}, - enabled = enabled, - style = style, - displaySurface = displaySurface - ) - }, xml = { - this.text = text - button.style = style - button.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null - button.isEnabled = enabled - val width = if (fullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT - button.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) - button.displaySurface = displaySurface - } - ) + Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { + UiFramework( + compose = { + OdsButton( + modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, + icon = if (leadingIcon) painterResource(id = iconId) else null, + text = text, + onClick = {}, + enabled = enabled, + style = style, + displaySurface = displaySurface + ) + }, xml = { + this.text = text + this.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + this.enabled = enabled + this.style = style + this.displaySurface = displaySurface + + val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT + odsButton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) + } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt index 7f505fbb2..3a6d0c9be 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt @@ -12,11 +12,9 @@ package com.orange.ods.app.ui.components.buttons import android.app.ActionBar.LayoutParams import android.view.ViewGroup +import android.widget.RelativeLayout import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -42,17 +40,13 @@ fun ButtonsOutlined(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) - .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } - OutlinedButton(modifier, hasLeadingIcon, isEnabled, hasFullScreenWidth) + OutlinedButton(hasLeadingIcon, isEnabled, hasFullScreenWidth) Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn { - OutlinedButton(modifier, hasLeadingIcon, isEnabled, hasFullScreenWidth, displaySurface = displaySurface) + OutlinedButton(hasLeadingIcon, isEnabled, hasFullScreenWidth, displaySurface = displaySurface) } CodeImplementationColumn( @@ -74,7 +68,6 @@ fun ButtonsOutlined(customizationState: ButtonCustomizationState) { @Composable private fun OutlinedButton( - modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, @@ -84,24 +77,26 @@ private fun OutlinedButton( val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) val iconId = R.drawable.ic_coffee - UiFramework( - modifier = modifier, - compose = { - OdsOutlinedButton( - modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, - text = text, - onClick = {}, - icon = if (leadingIcon) painterResource(id = iconId) else null, - enabled = enabled, - displaySurface = displaySurface - ) - }, xml = { - this.text = text - outlinedbutton.displaySurface = displaySurface - outlinedbutton.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null - outlinedbutton.isEnabled = enabled - val width = if (fullScreenWidth) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT - outlinedbutton.layoutParams = ViewGroup.LayoutParams(width, LayoutParams.WRAP_CONTENT) - } - ) + Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { + UiFramework( + compose = { + OdsOutlinedButton( + modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, + text = text, + onClick = {}, + icon = if (leadingIcon) painterResource(id = iconId) else null, + enabled = enabled, + displaySurface = displaySurface + ) + }, xml = { + this.text = text + this.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + this.enabled = enabled + this.displaySurface = displaySurface + + val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT + odsOutlinedButton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) + } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index 585be7ac7..f4728b1cd 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -12,8 +12,10 @@ package com.orange.ods.app.ui.components.utilities import android.view.LayoutInflater import android.view.ViewGroup +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.databinding.ViewDataBinding diff --git a/app/src/main/res/layout/ods_banner.xml b/app/src/main/res/layout/ods_banner.xml index 41ce6536c..565566c4c 100644 --- a/app/src/main/res/layout/ods_banner.xml +++ b/app/src/main/res/layout/ods_banner.xml @@ -36,6 +36,7 @@ android:id="@+id/banner" android:layout_width="match_parent" android:layout_height="wrap_content" + app:image="@drawable/ic_add" app:message="@{message}" app:button1Text="@{button1Text}" app:button2Text="@{button2Text}" diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml index 961f8bb99..de6225fd3 100644 --- a/app/src/main/res/layout/ods_button.xml +++ b/app/src/main/res/layout/ods_button.xml @@ -16,14 +16,50 @@ name="text" type="String" /> + + + + + + + + - + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_outlined_button.xml b/app/src/main/res/layout/ods_outlined_button.xml index ddd9775f4..2682b6415 100644 --- a/app/src/main/res/layout/ods_outlined_button.xml +++ b/app/src/main/res/layout/ods_outlined_button.xml @@ -16,14 +16,43 @@ name="text" type="String" /> + + + + + - + + + + + + - + \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt index ca74af104..6f83a515c 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt @@ -13,33 +13,48 @@ package com.orange.ods.xml.component.button import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsButton import com.orange.ods.compose.component.button.OdsButtonStyle import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + var leadingIcon by mutableStateOf(null) var text by mutableStateOf("") - var onClick by mutableStateOf({}) - var icon by mutableStateOf(null) var style by mutableStateOf(OdsButtonStyle.Default) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + var onClick by mutableStateOf({}) + + init { + context.withStyledAttributes(attrs, R.styleable.OdsButton) { + text = getString(R.styleable.OdsButton_text).orEmpty() + leadingIcon = getResourceIdOrNull(R.styleable.OdsButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + style = OdsButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsButton_style, 0)) + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsButton_displaySurface, 0)) + } + } @Composable override fun OdsContent() { OdsButton( text = text, onClick = onClick, - icon = icon?.let { rememberDrawablePainter(drawable = it) }, + icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, style = style, displaySurface = displaySurface ) } + } \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt index 53c3a7a52..1021ef7cb 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt @@ -13,30 +13,44 @@ package com.orange.ods.xml.component.button import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsOutlinedButton import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var text by mutableStateOf("") + var leadingIcon by mutableStateOf(null) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + var onClick by mutableStateOf({}) - var icon by mutableStateOf(null) - var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + init { + context.withStyledAttributes(attrs, R.styleable.OdsOutlinedButton) { + text = getString(R.styleable.OdsOutlinedButton_text).orEmpty() + leadingIcon = getResourceIdOrNull(R.styleable.OdsOutlinedButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsOutlinedButton_displaySurface, 0)) + } + } @Composable override fun OdsContent() { OdsOutlinedButton( text = text, onClick = onClick, - icon = icon?.let { rememberDrawablePainter(drawable = it) }, + icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, displaySurface = displaySurface ) } + } \ No newline at end of file diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index d104bac4c..c64abd650 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -9,8 +9,16 @@ --> + + + + + + + + @@ -18,12 +26,25 @@ + + + + + + + + + + + + + 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 b03eeafa3..bbf511445 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 @@ -38,6 +38,14 @@ import com.orange.ods.utilities.extension.enable enum class OdsButtonStyle { Default, Primary, FunctionalPositive, FunctionalNegative; + companion object { + /** + * @return [OdsButtonStyle] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ + fun fromXmlAttrValue(xmlId: Int): OdsButtonStyle = OdsButtonStyle.values()[xmlId] + } + @Composable internal fun getColors(displaySurface: OdsDisplaySurface): ButtonColors { return when (this) { diff --git a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt index fac44ad70..19aa88102 100644 --- a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt +++ b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt @@ -29,5 +29,13 @@ enum class OdsDisplaySurface { /** * The element is displayed on a light background even if the device system is set in dark theme. */ - Light + Light; + + companion object { + /** + * @return [OdsDisplaySurface] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ + fun fromXmlAttrValue(xmlId: Int): OdsDisplaySurface = OdsDisplaySurface.values()[xmlId] + } } \ No newline at end of file From 1c4162444e68e9ad181d150be388d7bc885067f0 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 20 Jun 2023 15:37:04 +0200 Subject: [PATCH 048/112] [#528] Modify XML banner component to match with other XML components with a test component for hard values --- .../ui/components/banners/ComponentBanners.kt | 10 +++--- app/src/main/res/layout/ods_banner.xml | 34 +++++++++++++------ 2 files changed, 29 insertions(+), 15 deletions(-) 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 5249bcd69..e8e3a3c84 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 @@ -113,18 +113,18 @@ fun ComponentBanners() { this.message = message this.button1Text = button1Text this.button2Text = button2Text - banner.onButton1Click = onButton1Click - banner.onButton2Click = onButton1Click + odsBanner.onButton1Click = onButton1Click + odsBanner.onButton2Click = onButton1Click if (hasImage) { - banner.image = AppCompatResources.getDrawable(context, placeholderResId) + odsBanner.image = AppCompatResources.getDrawable(context, placeholderResId) val request = ImageRequest.Builder(context) .data(recipe.imageUrl) .error(errorPlaceholderResId) - .target { banner.image = it } + .target { odsBanner.image = it } .build() context.imageLoader.enqueue(request) } else { - banner.image = null + odsBanner.image = null } } ) diff --git a/app/src/main/res/layout/ods_banner.xml b/app/src/main/res/layout/ods_banner.xml index 565566c4c..42136d42d 100644 --- a/app/src/main/res/layout/ods_banner.xml +++ b/app/src/main/res/layout/ods_banner.xml @@ -32,16 +32,30 @@ type="String" /> - - - + android:layout_height="wrap_content"> + + + + + + + From df528494d656f514705905b124dd1af997be5f70 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 27 Jun 2023 17:45:05 +0200 Subject: [PATCH 049/112] [#528] Review: Use FrameLayout instead of RelativeLayout in XML --- .../app/ui/components/buttons/ButtonsContained.kt | 14 ++++++++++---- .../app/ui/components/buttons/ButtonsOutlined.kt | 14 +++++++++----- app/src/main/res/layout/ods_banner.xml | 4 ++-- app/src/main/res/layout/ods_button.xml | 4 ++-- app/src/main/res/layout/ods_outlined_button.xml | 4 ++-- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt index bf2654c95..e0c2276e5 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsContained.kt @@ -10,9 +10,14 @@ package com.orange.ods.app.ui.components.buttons -import android.widget.RelativeLayout +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -120,8 +125,9 @@ private fun ContainedButton( this.style = style this.displaySurface = displaySurface - val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT - odsButton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) + root.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val width = if (fullScreenWidth) FrameLayout.LayoutParams.MATCH_PARENT else FrameLayout.LayoutParams.WRAP_CONTENT + odsButton.layoutParams = FrameLayout.LayoutParams(width, FrameLayout.LayoutParams.WRAP_CONTENT) } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt index 3a6d0c9be..25dd1e58d 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt @@ -10,11 +10,14 @@ package com.orange.ods.app.ui.components.buttons -import android.app.ActionBar.LayoutParams import android.view.ViewGroup -import android.widget.RelativeLayout +import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable @@ -94,8 +97,9 @@ private fun OutlinedButton( this.enabled = enabled this.displaySurface = displaySurface - val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT - odsOutlinedButton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) + root.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val width = if (fullScreenWidth) FrameLayout.LayoutParams.MATCH_PARENT else FrameLayout.LayoutParams.WRAP_CONTENT + odsOutlinedButton.layoutParams = FrameLayout.LayoutParams(width, FrameLayout.LayoutParams.WRAP_CONTENT) } ) } diff --git a/app/src/main/res/layout/ods_banner.xml b/app/src/main/res/layout/ods_banner.xml index 42136d42d..535b8ba3b 100644 --- a/app/src/main/res/layout/ods_banner.xml +++ b/app/src/main/res/layout/ods_banner.xml @@ -32,7 +32,7 @@ type="String" /> - @@ -56,6 +56,6 @@ app:button2Text="Button 2" app:imageContentDescription="Content description of the image" /> - + diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml index de6225fd3..c117aac7f 100644 --- a/app/src/main/res/layout/ods_button.xml +++ b/app/src/main/res/layout/ods_button.xml @@ -34,7 +34,7 @@ - @@ -60,6 +60,6 @@ app:style="functional_positive" app:displaySurface="standard" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_outlined_button.xml b/app/src/main/res/layout/ods_outlined_button.xml index 2682b6415..cd3137b41 100644 --- a/app/src/main/res/layout/ods_outlined_button.xml +++ b/app/src/main/res/layout/ods_outlined_button.xml @@ -29,7 +29,7 @@ type="com.orange.ods.compose.theme.OdsDisplaySurface" /> - @@ -53,6 +53,6 @@ app:enabled="true" app:displaySurface="standard" /> - + \ No newline at end of file From fcc42d55b34b93847f3304c560656c2a23b5f84f Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 27 Jun 2023 17:46:05 +0200 Subject: [PATCH 050/112] [#528] Review: Test XML components are now gone instead of invisible --- app/src/main/res/layout/ods_banner.xml | 2 +- app/src/main/res/layout/ods_button.xml | 2 +- app/src/main/res/layout/ods_outlined_button.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/ods_banner.xml b/app/src/main/res/layout/ods_banner.xml index 535b8ba3b..d1ae230a7 100644 --- a/app/src/main/res/layout/ods_banner.xml +++ b/app/src/main/res/layout/ods_banner.xml @@ -48,7 +48,7 @@ Date: Tue, 27 Jun 2023 17:50:04 +0200 Subject: [PATCH 051/112] [#528] Review: Replace leadingIcon XML attribute with icon --- app/src/main/res/layout/ods_button.xml | 4 ++-- app/src/main/res/layout/ods_outlined_button.xml | 4 ++-- .../java/com/orange/ods/xml/component/button/OdsButton.kt | 8 ++++---- .../orange/ods/xml/component/button/OdsOutlinedButton.kt | 6 +++--- lib-xml/src/main/res/values/attrs.xml | 5 ++--- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml index 740c63397..e95b6a314 100644 --- a/app/src/main/res/layout/ods_button.xml +++ b/app/src/main/res/layout/ods_button.xml @@ -42,7 +42,7 @@ android:id="@+id/ods_button" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@{icon}" + app:icon="@{icon}" app:text="@{text}" app:enabled="@{enabled}" app:style="@{style}" @@ -54,7 +54,7 @@ android:visibility="gone" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@drawable/ic_add" + app:icon="@drawable/ic_add" app:text="Button" app:enabled="true" app:style="functional_positive" diff --git a/app/src/main/res/layout/ods_outlined_button.xml b/app/src/main/res/layout/ods_outlined_button.xml index 981eae5e4..0312fde40 100644 --- a/app/src/main/res/layout/ods_outlined_button.xml +++ b/app/src/main/res/layout/ods_outlined_button.xml @@ -37,7 +37,7 @@ android:id="@+id/ods_outlined_button" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@{icon}" + app:icon="@{icon}" app:text="@{text}" app:enabled="@{enabled}" app:displaySurface="@{displaySurface}" /> @@ -48,7 +48,7 @@ android:visibility="gone" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@drawable/ic_add" + app:icon="@drawable/ic_add" app:text="Button" app:enabled="true" app:displaySurface="standard" /> diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt index 6f83a515c..a228cb805 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt @@ -29,17 +29,17 @@ import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - var leadingIcon by mutableStateOf(null) + var icon by mutableStateOf(null) var text by mutableStateOf("") var style by mutableStateOf(OdsButtonStyle.Default) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) - + var onClick by mutableStateOf({}) init { context.withStyledAttributes(attrs, R.styleable.OdsButton) { text = getString(R.styleable.OdsButton_text).orEmpty() - leadingIcon = getResourceIdOrNull(R.styleable.OdsButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + icon = getResourceIdOrNull(R.styleable.OdsButton_icon)?.let { AppCompatResources.getDrawable(context, it) } style = OdsButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsButton_style, 0)) displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsButton_displaySurface, 0)) } @@ -50,7 +50,7 @@ class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? OdsButton( text = text, onClick = onClick, - icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, style = style, displaySurface = displaySurface diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt index 1021ef7cb..930984b7c 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt @@ -29,7 +29,7 @@ import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var text by mutableStateOf("") - var leadingIcon by mutableStateOf(null) + var icon by mutableStateOf(null) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) var onClick by mutableStateOf({}) @@ -37,7 +37,7 @@ class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: Attri init { context.withStyledAttributes(attrs, R.styleable.OdsOutlinedButton) { text = getString(R.styleable.OdsOutlinedButton_text).orEmpty() - leadingIcon = getResourceIdOrNull(R.styleable.OdsOutlinedButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + icon = getResourceIdOrNull(R.styleable.OdsOutlinedButton_icon)?.let { AppCompatResources.getDrawable(context, it) } displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsOutlinedButton_displaySurface, 0)) } } @@ -47,7 +47,7 @@ class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: Attri OdsOutlinedButton( text = text, onClick = onClick, - icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, displaySurface = displaySurface ) diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index c64abd650..2dc307504 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -12,7 +12,6 @@ - @@ -28,7 +27,7 @@ - + @@ -41,7 +40,7 @@ - + From dfe3497788208b03a9d11af83967a9d0b59547c1 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 27 Jun 2023 18:09:45 +0200 Subject: [PATCH 052/112] [#528] Review: Move fromXmlAttrValue methods in lib-xml --- .../ods/xml/component/button/OdsButton.kt | 1 + .../xml/component/button/OdsOutlinedButton.kt | 1 + .../xml/utilities/extension/OdsButtonStyle.kt | 19 +++++++++++++++++++ .../extension/OdsDisplaySurfaceExt.kt | 19 +++++++++++++++++++ .../ods/compose/component/button/OdsButton.kt | 8 +------- .../ods/compose/theme/OdsDisplaySurface.kt | 10 ++-------- 6 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsButtonStyle.kt create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsDisplaySurfaceExt.kt diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt index a228cb805..10f499ffd 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt @@ -25,6 +25,7 @@ import com.orange.ods.compose.component.button.OdsButtonStyle import com.orange.ods.compose.theme.OdsDisplaySurface import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt index 930984b7c..78c0fa692 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsOutlinedButton.kt @@ -24,6 +24,7 @@ import com.orange.ods.compose.component.button.OdsOutlinedButton import com.orange.ods.compose.theme.OdsDisplaySurface import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsOutlinedButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { diff --git a/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsButtonStyle.kt b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsButtonStyle.kt new file mode 100644 index 000000000..3a1bf2141 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsButtonStyle.kt @@ -0,0 +1,19 @@ +/* + * + * 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.utilities.extension + +import com.orange.ods.compose.component.button.OdsButtonStyle + +/** + * @return [OdsButtonStyle] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ +fun OdsButtonStyle.Companion.fromXmlAttrValue(xmlId: Int): OdsButtonStyle = OdsButtonStyle.values()[xmlId] \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsDisplaySurfaceExt.kt b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsDisplaySurfaceExt.kt new file mode 100644 index 000000000..8d72c9571 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsDisplaySurfaceExt.kt @@ -0,0 +1,19 @@ +/* + * + * 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.utilities.extension + +import com.orange.ods.compose.theme.OdsDisplaySurface + +/** + * @return [OdsDisplaySurface] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ +fun OdsDisplaySurface.Companion.fromXmlAttrValue(xmlId: Int): OdsDisplaySurface = OdsDisplaySurface.values()[xmlId] \ No newline at end of file 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 bbf511445..fb8859b59 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 @@ -38,13 +38,7 @@ import com.orange.ods.utilities.extension.enable enum class OdsButtonStyle { Default, Primary, FunctionalPositive, FunctionalNegative; - companion object { - /** - * @return [OdsButtonStyle] associated to the provided [xmlId] - * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml - */ - fun fromXmlAttrValue(xmlId: Int): OdsButtonStyle = OdsButtonStyle.values()[xmlId] - } + companion object @Composable internal fun getColors(displaySurface: OdsDisplaySurface): ButtonColors { diff --git a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt index 19aa88102..a6de7b963 100644 --- a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt +++ b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt @@ -31,11 +31,5 @@ enum class OdsDisplaySurface { */ Light; - companion object { - /** - * @return [OdsDisplaySurface] associated to the provided [xmlId] - * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml - */ - fun fromXmlAttrValue(xmlId: Int): OdsDisplaySurface = OdsDisplaySurface.values()[xmlId] - } -} \ No newline at end of file + companion object +} From 022f7b3fdf8c0a62ca3ee5db05441a74fedc5ffe Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 27 Jun 2023 18:09:57 +0200 Subject: [PATCH 053/112] [#528] Review: Minor fixes --- app/src/main/java/com/orange/ods/app/ui/UiFramework.kt | 2 -- .../orange/ods/app/ui/components/buttons/ButtonsOutlined.kt | 4 ++-- .../orange/ods/app/ui/components/utilities/ViewDataBinding.kt | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt index 4bbec825d..ec32306ad 100644 --- a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt +++ b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt @@ -10,7 +10,6 @@ package com.orange.ods.app.ui -import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -19,7 +18,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.Modifier import androidx.databinding.ViewDataBinding import com.orange.ods.app.R import com.orange.ods.app.ui.components.utilities.ViewDataBinding diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt index 25dd1e58d..370f13845 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsOutlined.kt @@ -44,12 +44,12 @@ fun ButtonsOutlined(customizationState: ButtonCustomizationState) { .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - OutlinedButton(hasLeadingIcon, isEnabled, hasFullScreenWidth) + OutlinedButton(leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn { - OutlinedButton(hasLeadingIcon, isEnabled, hasFullScreenWidth, displaySurface = displaySurface) + OutlinedButton(leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth, displaySurface = displaySurface) } CodeImplementationColumn( diff --git a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt index f4728b1cd..585be7ac7 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/utilities/ViewDataBinding.kt @@ -12,10 +12,8 @@ package com.orange.ods.app.ui.components.utilities import android.view.LayoutInflater import android.view.ViewGroup -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.databinding.ViewDataBinding From b468a84fabc006670efc3324b7224625971a61ed Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Thu, 29 Jun 2023 16:20:38 +0200 Subject: [PATCH 054/112] [#561] Fix a bug where selected UI framework in dropdown menu did not correspond to code implementation --- .../java/com/orange/ods/app/ui/UiFramework.kt | 17 ++---------- .../composable/CodeImplementation.kt | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt index ec32306ad..fd0f70e80 100644 --- a/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt +++ b/app/src/main/java/com/orange/ods/app/ui/UiFramework.kt @@ -11,12 +11,8 @@ package com.orange.ods.app.ui import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.databinding.ViewDataBinding import com.orange.ods.app.R @@ -31,17 +27,8 @@ enum class UiFramework(val iconResId: Int, val labelResId: Int) { @Composable inline fun UiFramework(compose: @Composable () -> Unit, noinline xml: T.() -> Unit) { - val uiFramework = LocalUiFramework.current - // Reset current UI framework to Compose when displaying the content - // shouldResetUiFramework is used to avoid calling LaunchedEffect on configuration changes (for instance on device rotation) - var shouldResetUiFramework by rememberSaveable { mutableStateOf(true) } - if (shouldResetUiFramework) { - LaunchedEffect(Unit) { - shouldResetUiFramework = false - uiFramework.value = UiFramework.Compose - } - } - when (uiFramework.value) { + val uiFramework by LocalUiFramework.current + when (uiFramework) { UiFramework.Compose -> compose() UiFramework.Xml -> ViewDataBinding(bind = xml) } 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 94e37bb7f..120fd4c1c 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 @@ -15,8 +15,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -121,7 +124,16 @@ fun CodeImplementationColumn( contentBackground: Boolean = true, content: @Composable () -> Unit ) { - val currentUiFramework = LocalUiFramework.current + var currentUiFramework by LocalUiFramework.current + // Reset current UI framework to Compose when displaying the content + // shouldResetUiFramework is used to avoid calling LaunchedEffect on configuration changes (for instance on device rotation) + var shouldResetUiFramework by rememberSaveable { mutableStateOf(true) } + if (shouldResetUiFramework) { + LaunchedEffect(Unit) { + shouldResetUiFramework = false + currentUiFramework = UiFramework.Compose + } + } Column( modifier = modifier.padding( @@ -129,7 +141,7 @@ fun CodeImplementationColumn( ) ) { UiFrameworkChoice(xmlAvailable) - if (currentUiFramework.value == UiFramework.Compose) { + if (currentUiFramework == UiFramework.Compose) { if (contentBackground) { CodeBackgroundColumn(content) } else { @@ -152,12 +164,14 @@ fun CodeImplementationColumn( private fun UiFrameworkChoice(xmlAvailable: Boolean) { val context = LocalContext.current val currentUiFramework = LocalUiFramework.current - var selectedUiFrameworkIndex = 0 - val uiFrameworkItems = UiFramework.values().mapIndexed { index, uiFramework -> - if (uiFramework == currentUiFramework.value) selectedUiFrameworkIndex = index + val uiFrameworkItems = UiFramework.values().map { uiFramework -> OdsExposedDropdownMenuItem(label = stringResource(id = uiFramework.labelResId), iconResId = uiFramework.iconResId) } - val selectedUiFramework = rememberSaveable { mutableStateOf(if (xmlAvailable) uiFrameworkItems[selectedUiFrameworkIndex] else uiFrameworkItems.first()) } + val selectedUiFramework = rememberSaveable(currentUiFramework.value) { + val selectedUiFramework = if (xmlAvailable) currentUiFramework.value else UiFramework.Compose + val selectedUiFrameworkIndex = UiFramework.values().indexOf(selectedUiFramework) + mutableStateOf(uiFrameworkItems[selectedUiFrameworkIndex]) + } OdsExposedDropdownMenu( label = stringResource(id = R.string.code_implementation), From 5dbe8fd0c003e7b3e639d1f9abbd3581d0f1ac53 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Thu, 15 Jun 2023 14:55:02 +0200 Subject: [PATCH 055/112] [#529] Add OdsTextButton --- .../app/ui/components/buttons/ButtonsText.kt | 62 ++++++++++++++----- app/src/main/res/layout/ods_text_button.xml | 27 ++++++++ .../ods/xml/component/button/OdsTextButton.kt | 45 ++++++++++++++ lib-xml/src/main/res/values/attrs.xml | 4 ++ 4 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 app/src/main/res/layout/ods_text_button.xml create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt index e23670d90..e08eba8f2 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt @@ -10,6 +10,10 @@ package com.orange.ods.app.ui.components.buttons +import android.app.ActionBar +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -18,10 +22,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +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.databinding.OdsTextButtonBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.app.ui.utilities.composable.Title @@ -39,18 +46,29 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(R.dimen.spacing_m)) + .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } Title( textRes = if (textButtonStyle.value == OdsTextButtonStyle.Default) R.string.component_button_style_default else R.string.component_button_style_primary, horizontalPadding = true ) - TextButton(style = textButtonStyle.value, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth) + TextButton( + style = textButtonStyle.value, + modifier = modifier, + leadingIcon = hasLeadingIcon, + enabled = isEnabled, + fullScreenWidth = hasFullScreenWidth + ) Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn { TextButton( style = textButtonStyle.value, + modifier = modifier, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth, @@ -59,7 +77,8 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { } CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( name = OdsComposable.OdsTextButton.name, @@ -79,22 +98,37 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { @Composable private fun TextButton( style: OdsTextButtonStyle, + modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) + val context = LocalContext.current + val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) + val iconId = R.drawable.ic_coffee - OdsTextButton( - modifier = if (fullScreenWidth) modifier.fillMaxWidth() else modifier, - icon = if (leadingIcon) painterResource(id = R.drawable.ic_coffee) else null, - text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled), - onClick = {}, - enabled = enabled, - style = style, - displaySurface = displaySurface - ) + Box(modifier = modifier) { + UiFramework( + compose = { + OdsTextButton( + modifier = if (fullScreenWidth) Modifier.fillMaxWidth() else Modifier, + icon = if (leadingIcon) painterResource(id = iconId) else null, + text = text, + onClick = {}, + enabled = enabled, + style = style, + displaySurface = displaySurface + ) + }, xml = { + this.text = text + textbutton.style = style + textbutton.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + textbutton.isEnabled = enabled + val width = if (fullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT + textbutton.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) + textbutton.displaySurface = displaySurface + } + ) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/ods_text_button.xml b/app/src/main/res/layout/ods_text_button.xml new file mode 100644 index 000000000..114fabaf4 --- /dev/null +++ b/app/src/main/res/layout/ods_text_button.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt new file mode 100644 index 000000000..f239c14f5 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt @@ -0,0 +1,45 @@ +/* + * + * 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.button + +import android.content.Context +import android.graphics.drawable.Drawable +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.google.accompanist.drawablepainter.rememberDrawablePainter +import com.orange.ods.compose.component.button.OdsTextButton +import com.orange.ods.compose.component.button.OdsTextButtonStyle +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + +class OdsTextButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var text by mutableStateOf("") + var onClick by mutableStateOf({}) + var icon by mutableStateOf(null) + var style by mutableStateOf(OdsTextButtonStyle.Default) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + OdsTextButton( + text = text, + onClick = onClick, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, + enabled = isEnabled, + style = style, + displaySurface = displaySurface + ) + } +} \ No newline at end of file diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 2dc307504..981d72df9 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -46,4 +46,8 @@ + + + + From 7ee8d2139bd944bc2e7c0f31d8637d9687a6e5aa Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Thu, 15 Jun 2023 19:59:41 +0200 Subject: [PATCH 056/112] [#529] Add iconToggleButton xml --- .../components/buttons/icons/ButtonsIcon.kt | 56 ++++++++++++++---- .../buttons/icons/ButtonsIconToggle.kt | 58 ++++++++++++++++--- app/src/main/res/layout/ods_icon_button.xml | 24 ++++++++ .../res/layout/ods_icon_toogle_button.xml | 23 ++++++++ app/src/main/res/layout/ods_text_button.xml | 2 +- .../ods/xml/component/button/OdsIconButton.kt | 42 ++++++++++++++ .../component/button/OdsIconToogleButton.kt | 48 +++++++++++++++ lib-xml/src/main/res/values/attrs.xml | 12 +++- 8 files changed, 241 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/layout/ods_icon_button.xml create mode 100644 app/src/main/res/layout/ods_icon_toogle_button.xml create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt index 637906d07..87ec72f1c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt @@ -10,7 +10,9 @@ package com.orange.ods.app.ui.components.buttons.icons +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -26,16 +28,17 @@ 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.OdsIconButtonBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.buttons.InvertedBackgroundColumn 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.button.OdsIconButton +import com.orange.ods.compose.theme.OdsDisplaySurface @Composable fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { - val context = LocalContext.current with(customizationState) { Column( @@ -43,14 +46,16 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(R.dimen.spacing_m)) + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - OdsIconButton( - onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) }, - painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource(id = R.string.component_button_icon_search_desc), + IconButton( + modifier = modifier, enabled = isEnabled ) } @@ -58,20 +63,19 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn(horizontalAlignment = Alignment.CenterHorizontally) { - OdsIconButton( - onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) }, - painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource(id = R.string.component_button_icon_search_desc), + IconButton( + modifier = modifier, enabled = isEnabled, displaySurface = displaySurface ) } CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( - name = OdsComposable.OdsIconButton.name, + name = com.orange.ods.compose.OdsComposable.OdsIconButton.name, exhaustiveParameters = false, parameters = { painter() @@ -82,3 +86,31 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { } } } + +@Composable +private fun IconButton( + modifier: Modifier, + enabled: Boolean, + displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default +) { + val context = LocalContext.current + val iconId = R.drawable.ic_search + + Box(modifier = modifier) { + UiFramework( + compose = { + OdsIconButton( + onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) }, + painter = painterResource(id = R.drawable.ic_search), + contentDescription = stringResource(id = R.string.component_button_icon_search_desc), + enabled = enabled, + displaySurface = displaySurface + ) + }, xml = { + iconbutton.icon = AppCompatResources.getDrawable(context, iconId) + iconbutton.isEnabled = enabled + iconbutton.displaySurface = displaySurface + } + ) + } +} diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt index 6678d2c26..1c930214b 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt @@ -10,7 +10,9 @@ package com.orange.ods.app.ui.components.buttons.icons +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -23,16 +25,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +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.databinding.OdsIconToogleButtonBinding +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.buttons.InvertedBackgroundColumn import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode import com.orange.ods.app.ui.utilities.composable.IconPainterValue import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.button.OdsIconToggleButton +import com.orange.ods.compose.theme.OdsDisplaySurface @Composable fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { @@ -44,15 +50,16 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + .padding(top = dimensionResource(R.dimen.spacing_m)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - OdsIconToggleButton( + IconToogleButton( checked = buttonCheckedState.value, - uncheckedPainter = painterResource(id = R.drawable.ic_heart_outlined), - checkedPainter = painterResource(id = R.drawable.ic_heart), - iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + modifier = modifier, onCheckedChange = { checked -> buttonCheckedState.value = checked }, enabled = isEnabled ) @@ -61,19 +68,18 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn(horizontalAlignment = Alignment.CenterHorizontally) { - OdsIconToggleButton( + IconToogleButton( checked = buttonCheckedState.value, - uncheckedPainter = painterResource(id = R.drawable.ic_heart_outlined), - checkedPainter = painterResource(id = R.drawable.ic_heart), - iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), onCheckedChange = { checked -> buttonCheckedState.value = checked }, + modifier = modifier, enabled = isEnabled, displaySurface = displaySurface ) } CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( name = OdsComposable.OdsIconToggleButton.name, @@ -89,3 +95,37 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { } } } + +@Composable +private fun IconToogleButton( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier, + enabled: Boolean, + displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default +) { + val context = LocalContext.current + val uncheckedPainterId = R.drawable.ic_heart_outlined + val checkedPainterId = R.drawable.ic_heart + + Box(modifier = modifier) { + UiFramework( + compose = { + OdsIconToggleButton( + checked = checked, + uncheckedPainter = painterResource(id = uncheckedPainterId), + checkedPainter = painterResource(id = checkedPainterId), + iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + onCheckedChange = onCheckedChange, + enabled = enabled, + displaySurface = displaySurface + ) + }, xml = { + icontooglebutton.checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) + icontooglebutton.uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) + icontooglebutton.isEnabled = enabled + icontooglebutton.displaySurface = displaySurface + } + ) + } +} diff --git a/app/src/main/res/layout/ods_icon_button.xml b/app/src/main/res/layout/ods_icon_button.xml new file mode 100644 index 000000000..a0447d269 --- /dev/null +++ b/app/src/main/res/layout/ods_icon_button.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toogle_button.xml b/app/src/main/res/layout/ods_icon_toogle_button.xml new file mode 100644 index 000000000..e16ed73dc --- /dev/null +++ b/app/src/main/res/layout/ods_icon_toogle_button.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_text_button.xml b/app/src/main/res/layout/ods_text_button.xml index 114fabaf4..3da3e33ed 100644 --- a/app/src/main/res/layout/ods_text_button.xml +++ b/app/src/main/res/layout/ods_text_button.xml @@ -22,6 +22,6 @@ android:id="@+id/textbutton" app:text="@{text}" android:layout_height="match_parent" - android:layout_width="match_parent"> + android:layout_width="match_parent" /> \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt new file mode 100644 index 000000000..0c7e2ec5f --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt @@ -0,0 +1,42 @@ +/* + * + * 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.button + +import android.content.Context +import android.graphics.drawable.Drawable +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.google.accompanist.drawablepainter.rememberDrawablePainter +import com.orange.ods.compose.component.button.OdsIconButton +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + +class OdsIconButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var onClick by mutableStateOf({}) + var icon by mutableStateOf(null) + var iconContentDescription by mutableStateOf("") + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + OdsIconButton( + onClick = onClick, + painter = rememberDrawablePainter(drawable = icon), + contentDescription = iconContentDescription, + enabled = isEnabled, + displaySurface = displaySurface + ) + } +} \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt new file mode 100644 index 000000000..152ed2a65 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.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.xml.component.button + +import android.content.Context +import android.graphics.drawable.Drawable +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.google.accompanist.drawablepainter.rememberDrawablePainter +import com.orange.ods.compose.component.button.OdsIconToggleButton +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + + +class OdsIconToogleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var onClick by mutableStateOf({}) + var checkedPainter by mutableStateOf(null) + var uncheckedPainter by mutableStateOf(null) + var iconContentDescription by mutableStateOf("") + var checked by mutableStateOf(false) + private var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + OdsIconToggleButton( + checked = checked, + onCheckedChange = onCheckedChange, + uncheckedPainter = rememberDrawablePainter(drawable = uncheckedPainter), + checkedPainter = rememberDrawablePainter(drawable = checkedPainter), + iconContentDescription = iconContentDescription, + enabled = isEnabled, + displaySurface = displaySurface + ) + } +} \ No newline at end of file diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 981d72df9..56eb36551 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -17,7 +17,7 @@ - + @@ -38,7 +38,15 @@ - + + + + + + + + + From e38fcb13037d566b49ebd28432d92c9f32262316 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Fri, 16 Jun 2023 17:33:01 +0200 Subject: [PATCH 057/112] [#529] Add Ods Buttons Icons --- .../buttons/icons/ButtonsIconToggle.kt | 16 +++---- .../buttons/icons/ButtonsIconToggleGroup.kt | 35 ++++++++++++---- .../layout/ods_icon_toggle_buttons_group.xml | 23 ++++++++++ .../res/layout/ods_icon_toogle_button.xml | 6 +-- changelog.md | 2 + ...ToogleButton.kt => OdsIconToggleButton.kt} | 7 ++-- .../button/OdsIconToggleButtonsRow.kt | 42 +++++++++++++++++++ 7 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/layout/ods_icon_toggle_buttons_group.xml rename lib-xml/src/main/java/com/orange/ods/xml/component/button/{OdsIconToogleButton.kt => OdsIconToggleButton.kt} (90%) create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt index 1c930214b..63d6388bd 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt @@ -57,7 +57,7 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - IconToogleButton( + IconToggleButton( checked = buttonCheckedState.value, modifier = modifier, onCheckedChange = { checked -> buttonCheckedState.value = checked }, @@ -68,7 +68,7 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { Spacer(modifier = Modifier.padding(top = dimensionResource(R.dimen.spacing_s))) InvertedBackgroundColumn(horizontalAlignment = Alignment.CenterHorizontally) { - IconToogleButton( + IconToggleButton( checked = buttonCheckedState.value, onCheckedChange = { checked -> buttonCheckedState.value = checked }, modifier = modifier, @@ -97,7 +97,7 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { } @Composable -private fun IconToogleButton( +private fun IconToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier, @@ -121,10 +121,12 @@ private fun IconToogleButton( displaySurface = displaySurface ) }, xml = { - icontooglebutton.checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) - icontooglebutton.uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) - icontooglebutton.isEnabled = enabled - icontooglebutton.displaySurface = displaySurface + icontogglebutton.checked = checked + icontogglebutton.checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) + icontogglebutton.uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) + icontogglebutton.isEnabled = enabled + icontogglebutton.displaySurface = displaySurface + icontogglebutton.onCheckedChange = onCheckedChange } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt index ff1f6faf8..9db7514f4 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt @@ -11,6 +11,7 @@ package com.orange.ods.app.ui.components.buttons.icons import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -27,7 +28,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import com.orange.ods.app.R +import com.orange.ods.app.databinding.OdsIconToggleButtonsGroupBinding import com.orange.ods.app.domain.recipes.LocalRecipes +import com.orange.ods.app.ui.UiFramework import com.orange.ods.app.ui.components.buttons.InvertedBackgroundColumn import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode @@ -51,9 +54,13 @@ fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { + val modifier = Modifier + .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) + ToggleButtonsRow( iconToggleButtons = iconToggleButtons, selectedIndex = selectedIndex, + modifier = modifier, onSelectedIndexChange = { index -> selectedIndex = index }, toggleCount = toggleCount.value ) @@ -65,13 +72,15 @@ fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { iconToggleButtons = iconToggleButtons, selectedIndex = selectedIndex, onSelectedIndexChange = { index -> selectedIndex = index }, + modifier = modifier, toggleCount = toggleCount.value, displaySurface = displaySurface ) } CodeImplementationColumn( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)) + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + xmlAvailable = true ) { FunctionCallCode( name = OdsComposable.OdsIconToggleButtonsRow.name, @@ -99,6 +108,7 @@ private fun ToggleButtonsRow( selectedIndex: Int, onSelectedIndexChange: (Int) -> Unit, toggleCount: Int, + modifier: Modifier, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { Row( @@ -108,11 +118,22 @@ private fun ToggleButtonsRow( .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)), horizontalArrangement = Arrangement.Center ) { - OdsIconToggleButtonsRow( - iconToggleButtons = iconToggleButtons.take(toggleCount), - selectedIndex = selectedIndex, - onSelectedIndexChange = onSelectedIndexChange, - displaySurface = displaySurface - ) + Box(modifier = modifier) { + UiFramework( + compose = { + OdsIconToggleButtonsRow( + iconToggleButtons = iconToggleButtons.take(toggleCount), + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, + displaySurface = displaySurface + ) + }, xml = { + icontogglebuttonsrow.iconToggleButtons = iconToggleButtons.take(toggleCount) + icontogglebuttonsrow.selectedIndex = selectedIndex + icontogglebuttonsrow.onSelectedIndexChange = onSelectedIndexChange + icontogglebuttonsrow.displaySurface = displaySurface + } + ) + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml new file mode 100644 index 000000000..b18feb247 --- /dev/null +++ b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toogle_button.xml b/app/src/main/res/layout/ods_icon_toogle_button.xml index e16ed73dc..21cd108c8 100644 --- a/app/src/main/res/layout/ods_icon_toogle_button.xml +++ b/app/src/main/res/layout/ods_icon_toogle_button.xml @@ -13,11 +13,11 @@ - - + \ No newline at end of file diff --git a/changelog.md b/changelog.md index dc6a3b1de..2cf8e2d5d 100644 --- a/changelog.md +++ b/changelog.md @@ -11,9 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[App\] Display XML version in `ButtonsContained` and `ButtonsOutlined` ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) +- \[App\] Display XML version in `ButtonsText`, `ButtonsIcon`, `ButtonsIconToggle` and `ButtonsIconToggleGroup` ([#529](https://github.com/Orange-OpenSource/ods-android/issues/529)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) - \[LibXml\] Add `OdsButton` and `OdsOutlinedButton` views ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) +- \[LibXml\] Add `OdsTextButton`, `OdsIconButton`, `OdsIconToggleButton` and `OdsIconToggleButtonsRow` views ([#529](https://github.com/Orange-OpenSource/ods-android/issues/529)) ### Changed diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt similarity index 90% rename from lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt rename to lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt index 152ed2a65..139c23708 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToogleButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt @@ -23,14 +23,13 @@ import com.orange.ods.compose.theme.OdsDisplaySurface import com.orange.ods.xml.component.OdsAbstractComposeView -class OdsIconToogleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - - var onClick by mutableStateOf({}) +class OdsIconToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + var checkedPainter by mutableStateOf(null) var uncheckedPainter by mutableStateOf(null) var iconContentDescription by mutableStateOf("") var checked by mutableStateOf(false) - private var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) + var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) @Composable diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt new file mode 100644 index 000000000..96d88a74c --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt @@ -0,0 +1,42 @@ +/* + * + * 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.button + +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.button.OdsIconToggleButtonsRow +import com.orange.ods.compose.component.button.OdsIconToggleButtonsRowItem +import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.component.OdsAbstractComposeView + +class OdsIconToggleButtonsRow @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { + + var iconToggleButtons by mutableStateOf?>(null) + var selectedIndex by mutableStateOf(0) + var onSelectedIndexChange by mutableStateOf<(Int) -> Unit>({}) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + @Composable + override fun OdsContent() { + iconToggleButtons?.let { + OdsIconToggleButtonsRow( + iconToggleButtons = it, + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, + displaySurface = displaySurface + ) + } + } +} \ No newline at end of file From d56cd0c713f9dae5b10b318b5585384a4b7f7894 Mon Sep 17 00:00:00 2001 From: Doline Touko Date: Wed, 21 Jun 2023 18:56:44 +0200 Subject: [PATCH 058/112] [#529] Modify odsbutton --- .../app/ui/components/buttons/ButtonsText.kt | 24 +++----- .../components/buttons/icons/ButtonsIcon.kt | 19 +++--- .../buttons/icons/ButtonsIconToggle.kt | 24 ++++---- .../buttons/icons/ButtonsIconToggleGroup.kt | 38 +++++------- app/src/main/res/layout/ods_icon_button.xml | 47 +++++++++++++-- .../layout/ods_icon_toggle_buttons_group.xml | 36 +++++++++-- .../res/layout/ods_icon_toogle_button.xml | 60 +++++++++++++++++-- app/src/main/res/layout/ods_text_button.xml | 48 +++++++++++++-- .../ods/xml/component/button/OdsIconButton.kt | 21 +++++-- .../component/button/OdsIconToggleButton.kt | 26 ++++++-- .../button/OdsIconToggleButtonsRow.kt | 12 +++- .../ods/xml/component/button/OdsTextButton.kt | 18 +++++- lib-xml/src/main/res/values/attrs.xml | 31 ++++++---- .../compose/component/button/OdsTextButton.kt | 12 +++- 14 files changed, 304 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt index e08eba8f2..e3fc41c7a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt @@ -10,8 +10,7 @@ package com.orange.ods.app.ui.components.buttons -import android.app.ActionBar -import android.view.ViewGroup +import android.widget.RelativeLayout import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -46,10 +45,6 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) - .let { if (hasFullScreenWidth) it.fillMaxWidth() else it } Title( textRes = if (textButtonStyle.value == OdsTextButtonStyle.Default) R.string.component_button_style_default else R.string.component_button_style_primary, horizontalPadding = true @@ -57,7 +52,6 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { TextButton( style = textButtonStyle.value, - modifier = modifier, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth @@ -68,7 +62,6 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { InvertedBackgroundColumn { TextButton( style = textButtonStyle.value, - modifier = modifier, leadingIcon = hasLeadingIcon, enabled = isEnabled, fullScreenWidth = hasFullScreenWidth, @@ -98,7 +91,6 @@ fun ButtonsText(customizationState: ButtonCustomizationState) { @Composable private fun TextButton( style: OdsTextButtonStyle, - modifier: Modifier, leadingIcon: Boolean, enabled: Boolean, fullScreenWidth: Boolean, @@ -108,7 +100,7 @@ private fun TextButton( val text = stringResource(if (enabled) R.string.component_state_enabled else R.string.component_state_disabled) val iconId = R.drawable.ic_coffee - Box(modifier = modifier) { + Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { UiFramework( compose = { OdsTextButton( @@ -122,12 +114,12 @@ private fun TextButton( ) }, xml = { this.text = text - textbutton.style = style - textbutton.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null - textbutton.isEnabled = enabled - val width = if (fullScreenWidth) ActionBar.LayoutParams.MATCH_PARENT else ActionBar.LayoutParams.WRAP_CONTENT - textbutton.layoutParams = ViewGroup.LayoutParams(width, ActionBar.LayoutParams.WRAP_CONTENT) - textbutton.displaySurface = displaySurface + this.icon = if (leadingIcon) AppCompatResources.getDrawable(context, iconId) else null + this.enabled = enabled + this.style = style + this.displaySurface = displaySurface + val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT + odsTextbutton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt index 87ec72f1c..c6284c626 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt @@ -46,16 +46,11 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) - Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { IconButton( - modifier = modifier, enabled = isEnabled ) } @@ -64,7 +59,6 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { InvertedBackgroundColumn(horizontalAlignment = Alignment.CenterHorizontally) { IconButton( - modifier = modifier, enabled = isEnabled, displaySurface = displaySurface ) @@ -89,27 +83,28 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { @Composable private fun IconButton( - modifier: Modifier, enabled: Boolean, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { val context = LocalContext.current val iconId = R.drawable.ic_search + val contentDescription = stringResource(id = R.string.component_button_icon_search_desc) - Box(modifier = modifier) { + Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { UiFramework( compose = { OdsIconButton( onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) }, painter = painterResource(id = R.drawable.ic_search), - contentDescription = stringResource(id = R.string.component_button_icon_search_desc), + contentDescription = contentDescription, enabled = enabled, displaySurface = displaySurface ) }, xml = { - iconbutton.icon = AppCompatResources.getDrawable(context, iconId) - iconbutton.isEnabled = enabled - iconbutton.displaySurface = displaySurface + icon = AppCompatResources.getDrawable(context, iconId) + this.enabled = enabled + this.displaySurface = displaySurface + iconContentDescription = contentDescription } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt index 63d6388bd..335075fc0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt @@ -50,16 +50,12 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - .padding(top = dimensionResource(R.dimen.spacing_m)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { IconToggleButton( checked = buttonCheckedState.value, - modifier = modifier, onCheckedChange = { checked -> buttonCheckedState.value = checked }, enabled = isEnabled ) @@ -71,7 +67,6 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { IconToggleButton( checked = buttonCheckedState.value, onCheckedChange = { checked -> buttonCheckedState.value = checked }, - modifier = modifier, enabled = isEnabled, displaySurface = displaySurface ) @@ -100,33 +95,34 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { private fun IconToggleButton( checked: Boolean, onCheckedChange: (Boolean) -> Unit, - modifier: Modifier, enabled: Boolean, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { val context = LocalContext.current val uncheckedPainterId = R.drawable.ic_heart_outlined val checkedPainterId = R.drawable.ic_heart + val iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc) - Box(modifier = modifier) { + Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { UiFramework( compose = { OdsIconToggleButton( checked = checked, uncheckedPainter = painterResource(id = uncheckedPainterId), checkedPainter = painterResource(id = checkedPainterId), - iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc), + iconContentDescription = iconContentDescription, onCheckedChange = onCheckedChange, enabled = enabled, displaySurface = displaySurface ) }, xml = { - icontogglebutton.checked = checked - icontogglebutton.checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) - icontogglebutton.uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) - icontogglebutton.isEnabled = enabled - icontogglebutton.displaySurface = displaySurface - icontogglebutton.onCheckedChange = onCheckedChange + this.checked = checked + checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) + uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) + iconDescription = iconContentDescription + this.enabled = enabled + this.displaySurface = displaySurface + odsIconToggleButton.onCheckedChange = onCheckedChange } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt index 9db7514f4..cf5e4afbe 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt @@ -11,7 +11,6 @@ package com.orange.ods.app.ui.components.buttons.icons import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -54,13 +53,10 @@ fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { - val modifier = Modifier - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) ToggleButtonsRow( iconToggleButtons = iconToggleButtons, selectedIndex = selectedIndex, - modifier = modifier, onSelectedIndexChange = { index -> selectedIndex = index }, toggleCount = toggleCount.value ) @@ -72,7 +68,6 @@ fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { iconToggleButtons = iconToggleButtons, selectedIndex = selectedIndex, onSelectedIndexChange = { index -> selectedIndex = index }, - modifier = modifier, toggleCount = toggleCount.value, displaySurface = displaySurface ) @@ -108,7 +103,6 @@ private fun ToggleButtonsRow( selectedIndex: Int, onSelectedIndexChange: (Int) -> Unit, toggleCount: Int, - modifier: Modifier, displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { Row( @@ -118,22 +112,20 @@ private fun ToggleButtonsRow( .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)), horizontalArrangement = Arrangement.Center ) { - Box(modifier = modifier) { - UiFramework( - compose = { - OdsIconToggleButtonsRow( - iconToggleButtons = iconToggleButtons.take(toggleCount), - selectedIndex = selectedIndex, - onSelectedIndexChange = onSelectedIndexChange, - displaySurface = displaySurface - ) - }, xml = { - icontogglebuttonsrow.iconToggleButtons = iconToggleButtons.take(toggleCount) - icontogglebuttonsrow.selectedIndex = selectedIndex - icontogglebuttonsrow.onSelectedIndexChange = onSelectedIndexChange - icontogglebuttonsrow.displaySurface = displaySurface - } - ) - } + UiFramework( + compose = { + OdsIconToggleButtonsRow( + iconToggleButtons = iconToggleButtons.take(toggleCount), + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, + displaySurface = displaySurface + ) + }, xml = { + this.odsIconToggleButtonsRow.iconToggleButtons = iconToggleButtons.take(toggleCount) + this.selectedIndex = selectedIndex + this.displaySurface = displaySurface + this.odsIconToggleButtonsRow.onSelectedIndexChange = onSelectedIndexChange + } + ) } } \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_button.xml b/app/src/main/res/layout/ods_icon_button.xml index a0447d269..d7821a534 100644 --- a/app/src/main/res/layout/ods_icon_button.xml +++ b/app/src/main/res/layout/ods_icon_button.xml @@ -7,18 +7,53 @@ ~ * https://opensource.org/licenses/MIT. ~ */ --> - + + + + + + + + - + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml index b18feb247..024fc6fb6 100644 --- a/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml +++ b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml @@ -7,17 +7,41 @@ ~ * https://opensource.org/licenses/MIT. ~ */ --> - + + + + + - + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toogle_button.xml b/app/src/main/res/layout/ods_icon_toogle_button.xml index 21cd108c8..2ab54e7ba 100644 --- a/app/src/main/res/layout/ods_icon_toogle_button.xml +++ b/app/src/main/res/layout/ods_icon_toogle_button.xml @@ -7,17 +7,65 @@ ~ * https://opensource.org/licenses/MIT. ~ */ --> - + + + + + + + + + + + + + - + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_text_button.xml b/app/src/main/res/layout/ods_text_button.xml index 3da3e33ed..13c759c4c 100644 --- a/app/src/main/res/layout/ods_text_button.xml +++ b/app/src/main/res/layout/ods_text_button.xml @@ -16,12 +16,50 @@ name="text" type="String" /> + + + + + + + + - + + + + + + + + \ No newline at end of file diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt index 0c7e2ec5f..49715c2c6 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt @@ -13,27 +13,40 @@ package com.orange.ods.xml.component.button import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsIconButton import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue +import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsIconButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var onClick by mutableStateOf({}) - var icon by mutableStateOf(null) - var iconContentDescription by mutableStateOf("") - var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + var leadingIcon by mutableStateOf(null) + var iconContentDescription by mutableStateOf("") + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + init { + context.withStyledAttributes(attrs, R.styleable.OdsIconButton) { + iconContentDescription = getString(R.styleable.OdsIconButton_iconContentDescription).orEmpty() + leadingIcon = getResourceIdOrNull(R.styleable.OdsIconButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsIconButton_displaySurface, 0)) + } + } @Composable override fun OdsContent() { OdsIconButton( onClick = onClick, - painter = rememberDrawablePainter(drawable = icon), + painter = rememberDrawablePainter(drawable = leadingIcon), contentDescription = iconContentDescription, enabled = isEnabled, displaySurface = displaySurface diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt index 139c23708..f5c8a79bc 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt @@ -13,29 +13,43 @@ package com.orange.ods.xml.component.button import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsIconToggleButton import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView - +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue +import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsIconToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - + var checkedPainter by mutableStateOf(null) var uncheckedPainter by mutableStateOf(null) - var iconContentDescription by mutableStateOf("") - var checked by mutableStateOf(false) + var iconContentDescription by mutableStateOf("") + var selectedPainter by mutableStateOf(false) var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) - var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + init { + context.withStyledAttributes(attrs, R.styleable.OdsIconToggleButton) { + selectedPainter = getBoolean(R.styleable.OdsIconToggleButton_selectedPainter, false) + iconContentDescription = getString(R.styleable.OdsIconToggleButton_iconContentDescription).orEmpty() + checkedPainter = getResourceIdOrNull(R.styleable.OdsIconToggleButton_checkedPainter)?.let { AppCompatResources.getDrawable(context, it) } + uncheckedPainter = getResourceIdOrNull(R.styleable.OdsIconToggleButton_uncheckedPainter)?.let { AppCompatResources.getDrawable(context, it) } + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsIconToggleButton_displaySurface, 0)) + } + } @Composable override fun OdsContent() { OdsIconToggleButton( - checked = checked, + checked = selectedPainter, onCheckedChange = onCheckedChange, uncheckedPainter = rememberDrawablePainter(drawable = uncheckedPainter), checkedPainter = rememberDrawablePainter(drawable = checkedPainter), diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt index 96d88a74c..a345cff37 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt @@ -16,17 +16,27 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.orange.ods.compose.component.button.OdsIconToggleButtonsRow import com.orange.ods.compose.component.button.OdsIconToggleButtonsRowItem import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue class OdsIconToggleButtonsRow @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var iconToggleButtons by mutableStateOf?>(null) var selectedIndex by mutableStateOf(0) var onSelectedIndexChange by mutableStateOf<(Int) -> Unit>({}) - var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + + init { + context.withStyledAttributes(attrs, R.styleable.OdsIconToggleButtonsRow) { + selectedIndex = getInt(R.styleable.OdsIconToggleButtonsRow_selectedIndex, 0) + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsIconToggleButtonsRow_displaySurface, 0)) + } + } @Composable override fun OdsContent() { diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt index f239c14f5..babe15a47 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt @@ -13,30 +13,44 @@ package com.orange.ods.xml.component.button import android.content.Context import android.graphics.drawable.Drawable import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.content.withStyledAttributes import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsTextButton import com.orange.ods.compose.component.button.OdsTextButtonStyle import com.orange.ods.compose.theme.OdsDisplaySurface +import com.orange.ods.xml.R import com.orange.ods.xml.component.OdsAbstractComposeView +import com.orange.ods.xml.utilities.extension.fromXmlAttrValue +import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsTextButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var text by mutableStateOf("") var onClick by mutableStateOf({}) - var icon by mutableStateOf(null) + var leadingIcon by mutableStateOf(null) var style by mutableStateOf(OdsTextButtonStyle.Default) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) + init { + context.withStyledAttributes(attrs, R.styleable.OdsTextButton) { + text = getString(R.styleable.OdsTextButton_text).orEmpty() + leadingIcon = getResourceIdOrNull(R.styleable.OdsTextButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + style = OdsTextButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsTextButton_style, 0)) + displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsTextButton_displaySurface, 0)) + } + } + @Composable override fun OdsContent() { OdsTextButton( text = text, onClick = onClick, - icon = icon?.let { rememberDrawablePainter(drawable = it) }, + icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, style = style, displaySurface = displaySurface diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 56eb36551..9c8d7426c 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -17,6 +17,12 @@ + + + + + + @@ -25,27 +31,30 @@ - - - - - - - + - + + - + + + + + + + + + @@ -53,9 +62,11 @@ - + + + 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 2ad752050..9b74d3966 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 @@ -11,6 +11,7 @@ package com.orange.ods.compose.component.button import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.material.TextButton @@ -38,7 +39,16 @@ import com.orange.ods.utilities.extension.enable * Specifying an [OdsTextButtonStyle] allow to display a button with specific colors. */ enum class OdsTextButtonStyle { - Default, Primary + Default, Primary; + + companion object { + /** + * @return [OdsTextButtonStyle] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ + fun fromXmlAttrValue(xmlId: Int): OdsTextButtonStyle = OdsTextButtonStyle.values()[xmlId] + } + } /** From d8f5db3624f9031384df77f5a05486383fee99c6 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Mon, 3 Jul 2023 11:49:53 +0200 Subject: [PATCH 059/112] [#529] Review: Refactor XML attributes of ODS buttons --- .../buttons/icons/ButtonsIconToggle.kt | 12 ++++---- app/src/main/res/layout/ods_button.xml | 4 +-- app/src/main/res/layout/ods_icon_button.xml | 4 +-- .../res/layout/ods_icon_toogle_button.xml | 16 +++++------ app/src/main/res/layout/ods_text_button.xml | 8 +++--- lib-xml/build.gradle.kts | 3 ++ .../ods/xml/component/button/OdsButton.kt | 14 ++++++++-- .../ods/xml/component/button/OdsIconButton.kt | 8 +++--- .../component/button/OdsIconToggleButton.kt | 18 ++++++------ .../ods/xml/component/button/OdsTextButton.kt | 20 +++++++++---- .../extension/OdsTextButtonStyleExt.kt | 19 +++++++++++++ lib-xml/src/main/res/values/attrs.xml | 28 ++++++++++--------- .../compose/component/button/OdsTextButton.kt | 10 +------ 13 files changed, 100 insertions(+), 64 deletions(-) create mode 100644 lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsTextButtonStyleExt.kt diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt index 335075fc0..69207c883 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt @@ -99,8 +99,8 @@ private fun IconToggleButton( displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { val context = LocalContext.current - val uncheckedPainterId = R.drawable.ic_heart_outlined - val checkedPainterId = R.drawable.ic_heart + val uncheckedIconResId = R.drawable.ic_heart_outlined + val checkedIconResId = R.drawable.ic_heart val iconContentDescription = stringResource(id = R.string.component_button_icon_toggle_favorite_icon_desc) Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { @@ -108,8 +108,8 @@ private fun IconToggleButton( compose = { OdsIconToggleButton( checked = checked, - uncheckedPainter = painterResource(id = uncheckedPainterId), - checkedPainter = painterResource(id = checkedPainterId), + uncheckedPainter = painterResource(id = uncheckedIconResId), + checkedPainter = painterResource(id = checkedIconResId), iconContentDescription = iconContentDescription, onCheckedChange = onCheckedChange, enabled = enabled, @@ -117,8 +117,8 @@ private fun IconToggleButton( ) }, xml = { this.checked = checked - checkedPainter = AppCompatResources.getDrawable(context, checkedPainterId) - uncheckedPainter = AppCompatResources.getDrawable(context, uncheckedPainterId) + checkedIcon = AppCompatResources.getDrawable(context, checkedIconResId) + uncheckedIcon = AppCompatResources.getDrawable(context, uncheckedIconResId) iconDescription = iconContentDescription this.enabled = enabled this.displaySurface = displaySurface diff --git a/app/src/main/res/layout/ods_button.xml b/app/src/main/res/layout/ods_button.xml index e95b6a314..4daf404d4 100644 --- a/app/src/main/res/layout/ods_button.xml +++ b/app/src/main/res/layout/ods_button.xml @@ -45,7 +45,7 @@ app:icon="@{icon}" app:text="@{text}" app:enabled="@{enabled}" - app:style="@{style}" + app:odsButtonStyle="@{style}" app:displaySurface="@{displaySurface}" /> @@ -57,7 +57,7 @@ app:icon="@drawable/ic_add" app:text="Button" app:enabled="true" - app:style="functional_positive" + app:odsButtonStyle="standard" app:displaySurface="standard" /> diff --git a/app/src/main/res/layout/ods_icon_button.xml b/app/src/main/res/layout/ods_icon_button.xml index d7821a534..b5053fdfd 100644 --- a/app/src/main/res/layout/ods_icon_button.xml +++ b/app/src/main/res/layout/ods_icon_button.xml @@ -38,7 +38,7 @@ android:id="@+id/ods_icon_button" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@{icon}" + app:icon="@{icon}" app:enabled="@{enabled}" app:iconContentDescription="@{iconContentDescription}" app:displaySurface="@{displaySurface}" /> @@ -49,7 +49,7 @@ android:visibility="gone" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@drawable/ic_add" + app:icon="@drawable/ic_add" app:iconContentDescription="Button" app:enabled="true" app:displaySurface="standard" /> diff --git a/app/src/main/res/layout/ods_icon_toogle_button.xml b/app/src/main/res/layout/ods_icon_toogle_button.xml index 2ab54e7ba..4cae5bd90 100644 --- a/app/src/main/res/layout/ods_icon_toogle_button.xml +++ b/app/src/main/res/layout/ods_icon_toogle_button.xml @@ -17,11 +17,11 @@ type="Boolean" /> @@ -60,9 +60,9 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" app:enabled="true" - app:selectedPainter="true" - app:uncheckedPainter="@drawable/ic_add" - app:checkedPainter="@drawable/ic_add" + app:uncheckedIcon="@drawable/ic_add" + app:checkedIcon="@drawable/ic_add" + app:checked="true" app:iconContentDescription="Button" app:displaySurface="standard" /> diff --git a/app/src/main/res/layout/ods_text_button.xml b/app/src/main/res/layout/ods_text_button.xml index 13c759c4c..4b2ecf59b 100644 --- a/app/src/main/res/layout/ods_text_button.xml +++ b/app/src/main/res/layout/ods_text_button.xml @@ -42,10 +42,10 @@ android:id="@+id/ods_textbutton" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@{icon}" + app:icon="@{icon}" app:text="@{text}" app:enabled="@{enabled}" - app:style="@{style}" + app:textButtonStyle="@{style}" app:displaySurface="@{displaySurface}" /> @@ -54,10 +54,10 @@ android:visibility="gone" android:layout_height="wrap_content" android:layout_width="wrap_content" - app:leadingIcon="@drawable/ic_add" + app:icon="@drawable/ic_add" app:text="Button" app:enabled="true" - app:style="primary" + app:textButtonStyle="primary" app:displaySurface="standard" /> diff --git a/lib-xml/build.gradle.kts b/lib-xml/build.gradle.kts index b72c74af4..db6c95801 100644 --- a/lib-xml/build.gradle.kts +++ b/lib-xml/build.gradle.kts @@ -14,6 +14,7 @@ import com.orange.ods.gradle.Versions plugins { id("com.android.library") id("kotlin-android") + id("kotlin-kapt") } android { @@ -45,6 +46,8 @@ android { buildFeatures { compose = true + viewBinding = true + dataBinding = true } composeOptions { diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt index 10f499ffd..46352d10b 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsButton.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.content.withStyledAttributes +import androidx.databinding.BindingAdapter import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsButton import com.orange.ods.compose.component.button.OdsButtonStyle @@ -41,7 +42,7 @@ class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? context.withStyledAttributes(attrs, R.styleable.OdsButton) { text = getString(R.styleable.OdsButton_text).orEmpty() icon = getResourceIdOrNull(R.styleable.OdsButton_icon)?.let { AppCompatResources.getDrawable(context, it) } - style = OdsButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsButton_style, 0)) + style = OdsButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsButton_odsButtonStyle, 0)) displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsButton_displaySurface, 0)) } } @@ -58,4 +59,13 @@ class OdsButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? ) } -} \ No newline at end of file +} + +internal object OdsButtonBindingAdapter { + + @JvmStatic + @BindingAdapter("odsButtonStyle") + fun OdsButton.setOdsButtonStyle(style: OdsButtonStyle) { + this.style = style + } +} diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt index 49715c2c6..76f418987 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconButton.kt @@ -30,14 +30,14 @@ import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsIconButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { var onClick by mutableStateOf({}) - var leadingIcon by mutableStateOf(null) + var icon by mutableStateOf(null) var iconContentDescription by mutableStateOf("") var displaySurface by mutableStateOf(OdsDisplaySurface.Default) init { context.withStyledAttributes(attrs, R.styleable.OdsIconButton) { iconContentDescription = getString(R.styleable.OdsIconButton_iconContentDescription).orEmpty() - leadingIcon = getResourceIdOrNull(R.styleable.OdsIconButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } + icon = getResourceIdOrNull(R.styleable.OdsIconButton_icon)?.let { AppCompatResources.getDrawable(context, it) } displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsIconButton_displaySurface, 0)) } } @@ -46,10 +46,10 @@ class OdsIconButton @JvmOverloads constructor(context: Context, attrs: Attribute override fun OdsContent() { OdsIconButton( onClick = onClick, - painter = rememberDrawablePainter(drawable = leadingIcon), + painter = rememberDrawablePainter(drawable = icon), contentDescription = iconContentDescription, enabled = isEnabled, displaySurface = displaySurface ) } -} \ No newline at end of file +} diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt index f5c8a79bc..280feb86e 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt @@ -29,19 +29,19 @@ import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsIconToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - var checkedPainter by mutableStateOf(null) - var uncheckedPainter by mutableStateOf(null) var iconContentDescription by mutableStateOf("") - var selectedPainter by mutableStateOf(false) + var checked by mutableStateOf(false) + var checkedIcon by mutableStateOf(null) + var uncheckedIcon by mutableStateOf(null) var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) init { context.withStyledAttributes(attrs, R.styleable.OdsIconToggleButton) { - selectedPainter = getBoolean(R.styleable.OdsIconToggleButton_selectedPainter, false) + checked = getBoolean(R.styleable.OdsIconToggleButton_checked, false) iconContentDescription = getString(R.styleable.OdsIconToggleButton_iconContentDescription).orEmpty() - checkedPainter = getResourceIdOrNull(R.styleable.OdsIconToggleButton_checkedPainter)?.let { AppCompatResources.getDrawable(context, it) } - uncheckedPainter = getResourceIdOrNull(R.styleable.OdsIconToggleButton_uncheckedPainter)?.let { AppCompatResources.getDrawable(context, it) } + checkedIcon = getResourceIdOrNull(R.styleable.OdsIconToggleButton_checkedIcon)?.let { AppCompatResources.getDrawable(context, it) } + uncheckedIcon = getResourceIdOrNull(R.styleable.OdsIconToggleButton_uncheckedIcon)?.let { AppCompatResources.getDrawable(context, it) } displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsIconToggleButton_displaySurface, 0)) } } @@ -49,10 +49,10 @@ class OdsIconToggleButton @JvmOverloads constructor(context: Context, attrs: Att @Composable override fun OdsContent() { OdsIconToggleButton( - checked = selectedPainter, + checked = checked, onCheckedChange = onCheckedChange, - uncheckedPainter = rememberDrawablePainter(drawable = uncheckedPainter), - checkedPainter = rememberDrawablePainter(drawable = checkedPainter), + uncheckedPainter = rememberDrawablePainter(drawable = uncheckedIcon), + checkedPainter = rememberDrawablePainter(drawable = checkedIcon), iconContentDescription = iconContentDescription, enabled = isEnabled, displaySurface = displaySurface diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt index babe15a47..54cfcc33f 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsTextButton.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.content.withStyledAttributes +import androidx.databinding.BindingAdapter import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.orange.ods.compose.component.button.OdsTextButton import com.orange.ods.compose.component.button.OdsTextButtonStyle @@ -32,15 +33,15 @@ class OdsTextButton @JvmOverloads constructor(context: Context, attrs: Attribute var text by mutableStateOf("") var onClick by mutableStateOf({}) - var leadingIcon by mutableStateOf(null) + var icon by mutableStateOf(null) var style by mutableStateOf(OdsTextButtonStyle.Default) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) init { context.withStyledAttributes(attrs, R.styleable.OdsTextButton) { text = getString(R.styleable.OdsTextButton_text).orEmpty() - leadingIcon = getResourceIdOrNull(R.styleable.OdsTextButton_leadingIcon)?.let { AppCompatResources.getDrawable(context, it) } - style = OdsTextButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsTextButton_style, 0)) + icon = getResourceIdOrNull(R.styleable.OdsTextButton_icon)?.let { AppCompatResources.getDrawable(context, it) } + style = OdsTextButtonStyle.fromXmlAttrValue(getInteger(R.styleable.OdsTextButton_textButtonStyle, 0)) displaySurface = OdsDisplaySurface.fromXmlAttrValue(getInteger(R.styleable.OdsTextButton_displaySurface, 0)) } } @@ -50,10 +51,19 @@ class OdsTextButton @JvmOverloads constructor(context: Context, attrs: Attribute OdsTextButton( text = text, onClick = onClick, - icon = leadingIcon?.let { rememberDrawablePainter(drawable = it) }, + icon = icon?.let { rememberDrawablePainter(drawable = it) }, enabled = isEnabled, style = style, displaySurface = displaySurface ) } -} \ No newline at end of file +} + +internal object OdsTextButtonBindingAdapter { + + @JvmStatic + @BindingAdapter("textButtonStyle") + fun OdsTextButton.setTextButtonStyle(style: OdsTextButtonStyle) { + this.style = style + } +} diff --git a/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsTextButtonStyleExt.kt b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsTextButtonStyleExt.kt new file mode 100644 index 000000000..147bd5d57 --- /dev/null +++ b/lib-xml/src/main/java/com/orange/ods/xml/utilities/extension/OdsTextButtonStyleExt.kt @@ -0,0 +1,19 @@ +/* + * + * 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.utilities.extension + +import com.orange.ods.compose.component.button.OdsTextButtonStyle + +/** + * @return [OdsTextButtonStyle] associated to the provided [xmlId] + * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml + */ +fun OdsTextButtonStyle.Companion.fromXmlAttrValue(xmlId: Int): OdsTextButtonStyle = OdsTextButtonStyle.values()[xmlId] diff --git a/lib-xml/src/main/res/values/attrs.xml b/lib-xml/src/main/res/values/attrs.xml index 9c8d7426c..15f860f63 100644 --- a/lib-xml/src/main/res/values/attrs.xml +++ b/lib-xml/src/main/res/values/attrs.xml @@ -17,12 +17,6 @@ - - - - - - @@ -35,19 +29,24 @@ - + + + + + + - + - - - + + + @@ -64,9 +63,12 @@ - + - + + + + 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 9b74d3966..03922023a 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 @@ -11,7 +11,6 @@ package com.orange.ods.compose.component.button import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.material.TextButton @@ -41,14 +40,7 @@ import com.orange.ods.utilities.extension.enable enum class OdsTextButtonStyle { Default, Primary; - companion object { - /** - * @return [OdsTextButtonStyle] associated to the provided [xmlId] - * BE CAREFUL: If the enum values change you have to update associated XML attributes in the lib-xml - */ - fun fromXmlAttrValue(xmlId: Int): OdsTextButtonStyle = OdsTextButtonStyle.values()[xmlId] - } - + companion object } /** From 0e9059be485646ac40ff4f287e0343b0ad0d9fe3 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Mon, 3 Jul 2023 15:55:35 +0200 Subject: [PATCH 060/112] [#529] Review: Replace RelativeLayout with FrameLayout in XML --- .../orange/ods/app/ui/components/buttons/ButtonsText.kt | 8 +++++--- app/src/main/res/layout/ods_icon_button.xml | 4 ++-- app/src/main/res/layout/ods_icon_toggle_buttons_group.xml | 4 ++-- app/src/main/res/layout/ods_icon_toogle_button.xml | 4 ++-- app/src/main/res/layout/ods_text_button.xml | 4 ++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt index e3fc41c7a..005ab4f37 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/ButtonsText.kt @@ -10,7 +10,8 @@ package com.orange.ods.app.ui.components.buttons -import android.widget.RelativeLayout +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -118,8 +119,9 @@ private fun TextButton( this.enabled = enabled this.style = style this.displaySurface = displaySurface - val width = if (fullScreenWidth) RelativeLayout.LayoutParams.MATCH_PARENT else RelativeLayout.LayoutParams.WRAP_CONTENT - odsTextbutton.layoutParams = RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT) + root.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val width = if (fullScreenWidth) FrameLayout.LayoutParams.MATCH_PARENT else FrameLayout.LayoutParams.WRAP_CONTENT + odsTextbutton.layoutParams = FrameLayout.LayoutParams(width, FrameLayout.LayoutParams.WRAP_CONTENT) } ) } diff --git a/app/src/main/res/layout/ods_icon_button.xml b/app/src/main/res/layout/ods_icon_button.xml index b5053fdfd..34e41ce69 100644 --- a/app/src/main/res/layout/ods_icon_button.xml +++ b/app/src/main/res/layout/ods_icon_button.xml @@ -30,7 +30,7 @@ - @@ -54,6 +54,6 @@ app:enabled="true" app:displaySurface="standard" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml index 024fc6fb6..f73d8fa3a 100644 --- a/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml +++ b/app/src/main/res/layout/ods_icon_toggle_buttons_group.xml @@ -22,7 +22,7 @@ - @@ -42,6 +42,6 @@ app:selectedIndex="0" app:displaySurface="standard" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_icon_toogle_button.xml b/app/src/main/res/layout/ods_icon_toogle_button.xml index 4cae5bd90..0b800fe3a 100644 --- a/app/src/main/res/layout/ods_icon_toogle_button.xml +++ b/app/src/main/res/layout/ods_icon_toogle_button.xml @@ -38,7 +38,7 @@ - @@ -66,6 +66,6 @@ app:iconContentDescription="Button" app:displaySurface="standard" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/ods_text_button.xml b/app/src/main/res/layout/ods_text_button.xml index 4b2ecf59b..d5da15721 100644 --- a/app/src/main/res/layout/ods_text_button.xml +++ b/app/src/main/res/layout/ods_text_button.xml @@ -34,7 +34,7 @@ - @@ -60,6 +60,6 @@ app:textButtonStyle="primary" app:displaySurface="standard" /> - + \ No newline at end of file From f9f25a5c25e6e1417c18ce1c102a7b4cf7682fda Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Mon, 3 Jul 2023 15:56:18 +0200 Subject: [PATCH 061/112] [#529] Review: Minor refactors and fixes --- .../components/buttons/icons/ButtonsIcon.kt | 10 +++++++--- .../buttons/icons/ButtonsIconToggle.kt | 6 ++++-- .../buttons/icons/ButtonsIconToggleGroup.kt | 5 +++-- .../component/button/OdsIconToggleButton.kt | 4 ++-- .../button/OdsIconToggleButtonsRow.kt | 20 +++++++++---------- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt index c6284c626..b2b120f52 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIcon.kt @@ -34,6 +34,7 @@ import com.orange.ods.app.ui.components.buttons.InvertedBackgroundColumn 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.button.OdsIconButton import com.orange.ods.compose.theme.OdsDisplaySurface @@ -69,7 +70,7 @@ fun ButtonsIcon(customizationState: ButtonIconCustomizationState) { xmlAvailable = true ) { FunctionCallCode( - name = com.orange.ods.compose.OdsComposable.OdsIconButton.name, + name = OdsComposable.OdsIconButton.name, exhaustiveParameters = false, parameters = { painter() @@ -89,22 +90,25 @@ private fun IconButton( val context = LocalContext.current val iconId = R.drawable.ic_search val contentDescription = stringResource(id = R.string.component_button_icon_search_desc) + val onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) } Box(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin), vertical = dimensionResource(R.dimen.spacing_m))) { UiFramework( compose = { OdsIconButton( - onClick = { clickOnElement(context, context.getString(R.string.component_button_icon)) }, + onClick = onClick, painter = painterResource(id = R.drawable.ic_search), contentDescription = contentDescription, enabled = enabled, displaySurface = displaySurface ) - }, xml = { + }, + xml = { icon = AppCompatResources.getDrawable(context, iconId) this.enabled = enabled this.displaySurface = displaySurface iconContentDescription = contentDescription + odsIconButton.onClick = onClick } ) } diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt index 69207c883..774e262b0 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggle.kt @@ -50,13 +50,15 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { .verticalScroll(rememberScrollState()) .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) ) { + val onCheckedChange: (Boolean) -> Unit = { checked -> buttonCheckedState.value = checked } + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { IconToggleButton( checked = buttonCheckedState.value, - onCheckedChange = { checked -> buttonCheckedState.value = checked }, + onCheckedChange = onCheckedChange, enabled = isEnabled ) } @@ -66,7 +68,7 @@ fun ButtonsIconToggle(customizationState: ButtonIconCustomizationState) { InvertedBackgroundColumn(horizontalAlignment = Alignment.CenterHorizontally) { IconToggleButton( checked = buttonCheckedState.value, - onCheckedChange = { checked -> buttonCheckedState.value = checked }, + onCheckedChange = onCheckedChange, enabled = isEnabled, displaySurface = displaySurface ) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt index cf5e4afbe..e03f7d602 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt @@ -112,16 +112,17 @@ private fun ToggleButtonsRow( .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)), horizontalArrangement = Arrangement.Center ) { + val buttons = iconToggleButtons.take(toggleCount) UiFramework( compose = { OdsIconToggleButtonsRow( - iconToggleButtons = iconToggleButtons.take(toggleCount), + iconToggleButtons = buttons, selectedIndex = selectedIndex, onSelectedIndexChange = onSelectedIndexChange, displaySurface = displaySurface ) }, xml = { - this.odsIconToggleButtonsRow.iconToggleButtons = iconToggleButtons.take(toggleCount) + this.odsIconToggleButtonsRow.iconToggleButtons = buttons this.selectedIndex = selectedIndex this.displaySurface = displaySurface this.odsIconToggleButtonsRow.onSelectedIndexChange = onSelectedIndexChange diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt index 280feb86e..df7ed1807 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButton.kt @@ -29,10 +29,10 @@ import com.orange.ods.xml.utilities.extension.getResourceIdOrNull class OdsIconToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - var iconContentDescription by mutableStateOf("") - var checked by mutableStateOf(false) var checkedIcon by mutableStateOf(null) var uncheckedIcon by mutableStateOf(null) + var iconContentDescription by mutableStateOf(null) + var checked by mutableStateOf(false) var onCheckedChange by mutableStateOf<(Boolean) -> Unit>({}) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) diff --git a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt index a345cff37..cc887b6d5 100644 --- a/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt +++ b/lib-xml/src/main/java/com/orange/ods/xml/component/button/OdsIconToggleButtonsRow.kt @@ -26,8 +26,8 @@ import com.orange.ods.xml.utilities.extension.fromXmlAttrValue class OdsIconToggleButtonsRow @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : OdsAbstractComposeView(context, attrs) { - var iconToggleButtons by mutableStateOf?>(null) - var selectedIndex by mutableStateOf(0) + var iconToggleButtons by mutableStateOf>(emptyList()) + var selectedIndex by mutableStateOf(0) var onSelectedIndexChange by mutableStateOf<(Int) -> Unit>({}) var displaySurface by mutableStateOf(OdsDisplaySurface.Default) @@ -40,13 +40,11 @@ class OdsIconToggleButtonsRow @JvmOverloads constructor(context: Context, attrs: @Composable override fun OdsContent() { - iconToggleButtons?.let { - OdsIconToggleButtonsRow( - iconToggleButtons = it, - selectedIndex = selectedIndex, - onSelectedIndexChange = onSelectedIndexChange, - displaySurface = displaySurface - ) - } + OdsIconToggleButtonsRow( + iconToggleButtons = iconToggleButtons, + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, + displaySurface = displaySurface + ) } -} \ No newline at end of file +} From 27079caa9ae3ea866ebf41181323ddb60158a73f Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 4 Jul 2023 15:51:10 +0200 Subject: [PATCH 062/112] [#563] Add badges to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e01bac058..76ab1291a 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,17 @@ ## Table of contents +- [Status](#status) - [Content](#content) - [Bugs and feature requests](#bugs-and-feature-requests) - [Contributing](#contributing) - [Copyright and license](#copyright-and-license) +## Status + +[![Build](https://img.shields.io/github/actions/workflow/status/Orange-OpenSource/ods-android/android-build.yml?logo=github)](https://github.com/Orange-OpenSource/ods-android/actions/workflows/android-build.yml?query=branch%3Amain) +[![Maven Central](https://img.shields.io/maven-central/v/com.orange.ods.android/ods-lib?logo=apachemaven)](https://search.maven.org/search?q=com.orange.ods.android) + ## Content This repository contains the Orange Design System Android library that provides Orange Android components, but also a demo application showcasing these different components. From 764de728e7fc9724518561e3fe8b784f66127719 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Tue, 20 Jun 2023 18:35:28 +0200 Subject: [PATCH 063/112] [#329] Toggle icon buttons group can now be disabled with the customization bottom sheet --- .../buttons/icons/ButtonsIconToggleGroup.kt | 2 +- .../buttons/icons/ComponentButtonsIcons.kt | 9 ++-- changelog.md | 6 +++ .../ods/compose/component/button/OdsButton.kt | 30 +++---------- .../component/button/OdsButtonsCommon.kt | 8 +--- .../compose/component/button/OdsIconButton.kt | 9 +--- .../component/button/OdsIconToggleButton.kt | 6 +-- .../button/OdsIconToggleButtonsRow.kt | 44 +++++++++---------- .../component/button/OdsOutlinedButton.kt | 13 +----- .../compose/component/button/OdsTextButton.kt | 25 ++--------- .../com/orange/ods/compose/text/OdsTexts.kt | 7 +-- .../ods/compose/theme/OdsDisplaySurface.kt | 21 ++++++++- 12 files changed, 67 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt index e03f7d602..29c5f6208 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt @@ -42,7 +42,7 @@ import com.orange.ods.compose.theme.OdsDisplaySurface fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { val iconToggleButtons = LocalRecipes.current.distinctBy { it.iconResId }.filter { it.iconResId != null }.take(ButtonIconCustomizationState.MaxToggleCount).map { recipe -> - OdsIconToggleButtonsRowItem(painterResource(id = recipe.iconResId!!), recipe.title) + OdsIconToggleButtonsRowItem(painterResource(id = recipe.iconResId!!), recipe.title, customizationState.enabled.value) } var selectedIndex by rememberSaveable { mutableStateOf(0) } 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 cd21e853f..0b44b9884 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 @@ -43,12 +43,11 @@ fun ComponentButtonsIcons(variant: Variant) { minCount = ButtonIconCustomizationState.MinToggleCount, maxCount = ButtonIconCustomizationState.MaxToggleCount ) - } else { - OdsListItem( - text = stringResource(id = R.string.component_state_enabled), - trailing = OdsSwitchTrailing(checked = enabled) - ) } + OdsListItem( + text = stringResource(id = R.string.component_state_enabled), + trailing = OdsSwitchTrailing(checked = enabled) + ) }) { when (variant) { diff --git a/changelog.md b/changelog.md index 2cf8e2d5d..5990d1208 100644 --- a/changelog.md +++ b/changelog.md @@ -14,12 +14,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[App\] Display XML version in `ButtonsText`, `ButtonsIcon`, `ButtonsIconToggle` and `ButtonsIconToggleGroup` ([#529](https://github.com/Orange-OpenSource/ods-android/issues/529)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) +- \[Lib\] Add `themeColors` and `rippleTheme` properties to `OdsDisplaySurface` ([#329](https://github.com/Orange-OpenSource/ods-android/issues/329)) - \[LibXml\] Add `OdsButton` and `OdsOutlinedButton` views ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) - \[LibXml\] Add `OdsTextButton`, `OdsIconButton`, `OdsIconToggleButton` and `OdsIconToggleButtonsRow` views ([#529](https://github.com/Orange-OpenSource/ods-android/issues/529)) ### Changed - \[App\] Use an exposed dropdown menu to switch between XML and Compose implementation ([#546](https://github.com/Orange-OpenSource/ods-android/issues/546)) +- \[App\] Toggle icon buttons group can now be disabled with the customization bottom sheet ([#329](https://github.com/Orange-OpenSource/ods-android/issues/329)) + +### Fixed + +- \[Lib\] Fix a bug where `enabled` property of `OdsIconToggleButtonsRowItem` has no effect ([#329](https://github.com/Orange-OpenSource/ods-android/issues/329)) ## [0.13.0](https://github.com/Orange-OpenSource/ods-android/compare/0.12.0...0.13.0) - 2023-06-01 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 fb8859b59..179eaf920 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 @@ -98,32 +98,16 @@ fun OdsButton( @Composable private fun odsDefaultButtonColors(displaySurface: OdsDisplaySurface) = ButtonDefaults.buttonColors( - backgroundColor = when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onSurface - }, - contentColor = when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.surface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.surface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.surface - }, + backgroundColor = displaySurface.themeColors.onSurface, + contentColor = displaySurface.themeColors.surface, disabledBackgroundColor = disabledButtonBackgroundColor(displaySurface), disabledContentColor = disabledButtonContentColor(displaySurface), ) @Composable private fun odsPrimaryButtonColors(displaySurface: OdsDisplaySurface) = ButtonDefaults.buttonColors( - backgroundColor = when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.primary - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.primary - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.primary - }, - contentColor = when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onPrimary - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onPrimary - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onPrimary - }, + backgroundColor = displaySurface.themeColors.primary, + contentColor = displaySurface.themeColors.onPrimary, disabledBackgroundColor = disabledButtonBackgroundColor(displaySurface), disabledContentColor = disabledButtonContentColor(displaySurface), ) @@ -145,11 +129,7 @@ private fun odsNegativeButtonColors(displaySurface: OdsDisplaySurface) = ButtonD ) @Composable -private fun disabledButtonColors(displaySurface: OdsDisplaySurface) = when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onSurface -} +private fun disabledButtonColors(displaySurface: OdsDisplaySurface) = displaySurface.themeColors.onSurface @Composable private fun disabledButtonBackgroundColor(displaySurface: OdsDisplaySurface) = disabledButtonColors(displaySurface = displaySurface).copy(alpha = 0.12f) diff --git a/lib/src/main/java/com/orange/ods/compose/component/button/OdsButtonsCommon.kt b/lib/src/main/java/com/orange/ods/compose/component/button/OdsButtonsCommon.kt index 5d91a4c86..3c4980484 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/button/OdsButtonsCommon.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/button/OdsButtonsCommon.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import com.orange.ods.compose.theme.OdsDisplaySurface -import com.orange.ods.compose.theme.OdsTheme /** * The icon displayed in every type of buttons @@ -36,9 +35,4 @@ internal fun ButtonIcon(painter: Painter) { } @Composable -internal fun iconButtonTintColor(displaySurface: OdsDisplaySurface) = - when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onSurface - } \ No newline at end of file +internal fun iconButtonTintColor(displaySurface: OdsDisplaySurface) = displaySurface.themeColors.onSurface 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 190399137..0bcb27b8e 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 @@ -23,10 +23,7 @@ import androidx.compose.ui.res.painterResource import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews -import com.orange.ods.compose.theme.OdsDarkRippleTheme import com.orange.ods.compose.theme.OdsDisplaySurface -import com.orange.ods.compose.theme.OdsLightRippleTheme -import com.orange.ods.compose.theme.OdsRippleTheme import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.utilities.extension.enable @@ -58,11 +55,7 @@ fun OdsIconButton( displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { CompositionLocalProvider( - LocalRippleTheme provides when (displaySurface) { - OdsDisplaySurface.Default -> OdsRippleTheme - OdsDisplaySurface.Light -> OdsLightRippleTheme - OdsDisplaySurface.Dark -> OdsDarkRippleTheme - } + LocalRippleTheme provides displaySurface.rippleTheme ) { OdsIconButton( onClick = onClick, 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 d5d9a5f90..1450040e5 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 @@ -62,11 +62,7 @@ fun OdsIconToggleButton( displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { CompositionLocalProvider( - LocalRippleTheme provides when (displaySurface) { - OdsDisplaySurface.Default -> OdsRippleTheme - OdsDisplaySurface.Light -> OdsLightRippleTheme - OdsDisplaySurface.Dark -> OdsDarkRippleTheme - } + LocalRippleTheme provides displaySurface.rippleTheme ) { IconToggleButton( checked = checked, 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 fb025c079..0c88a152d 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 @@ -38,7 +38,7 @@ import com.orange.ods.compose.component.utilities.DisabledInteractionSource import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews import com.orange.ods.compose.theme.OdsDisplaySurface -import com.orange.ods.compose.theme.OdsTheme +import com.orange.ods.utilities.extension.enable /** * ODS Buttons. @@ -106,43 +106,41 @@ private fun IconToggleButtonsRowItem( displaySurface: OdsDisplaySurface, onClick: (Int) -> Unit ) { - val iconTint by animateColorAsState(buttonToggleIconColor(displaySurface, selected)) - val backgroundAlpha by animateFloatAsState(if (selected) 0.12f else 0f) + val iconTint = buttonToggleIconColor(displaySurface, selected, iconToggleButton.enabled) + val backgroundAlpha = if (selected && iconToggleButton.enabled) 0.12f else 0f Icon( modifier = Modifier - .background(color = buttonToggleBackgroundColor(displaySurface).copy(alpha = backgroundAlpha)) + .background( + color = buttonToggleBackgroundColor(displaySurface) + .copy(alpha = if (iconToggleButton.enabled) animateFloatAsState(backgroundAlpha).value else backgroundAlpha) + ) .padding(12.dp) - .clickable(interactionSource = remember { DisabledInteractionSource() }, indication = null) { onClick(index) }, + .run { + if (iconToggleButton.enabled) { + clickable(interactionSource = remember { DisabledInteractionSource() }, indication = null) { onClick(index) } + } else { + this + } + }, painter = iconToggleButton.icon, contentDescription = iconToggleButton.iconDescription, - tint = iconTint + tint = if (iconToggleButton.enabled) animateColorAsState(iconTint).value else iconTint ) } @Composable -private fun buttonToggleIconColor(displaySurface: OdsDisplaySurface, checked: Boolean) = - when (displaySurface) { - OdsDisplaySurface.Default -> if (checked) OdsTheme.colors.primary else OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> if (checked) OdsTheme.darkThemeColors.primary else OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> if (checked) OdsTheme.lightThemeColors.primary else OdsTheme.lightThemeColors.onSurface +private fun buttonToggleIconColor(displaySurface: OdsDisplaySurface, selected: Boolean, enabled: Boolean) = + with(displaySurface.themeColors) { + if (selected && enabled) primary else onSurface.enable(enabled = enabled) } @Composable private fun buttonToggleBackgroundColor(displaySurface: OdsDisplaySurface) = - when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.primary - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.primary - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.primary - } + displaySurface.themeColors.primary @Composable -private fun buttonToggleBorderColor(displaySurface: OdsDisplaySurface) = - when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onSurface - }.copy(alpha = 0.12f) +private fun buttonToggleBorderColor(displaySurface: OdsDisplaySurface) = displaySurface.themeColors.onSurface.copy(alpha = 0.12f) @UiModePreviews.Default @@ -151,7 +149,7 @@ private fun PreviewOdsIconToggleButtonsGroupRow() = Preview { val iconToggleButtons = listOf( OdsIconToggleButtonsRowItem(painterResource(id = android.R.drawable.ic_dialog_dialer), "Today"), OdsIconToggleButtonsRowItem(painterResource(id = android.R.drawable.ic_dialog_email), "Day"), - OdsIconToggleButtonsRowItem(painterResource(id = android.R.drawable.ic_dialog_alert), "Month") + OdsIconToggleButtonsRowItem(painterResource(id = android.R.drawable.ic_dialog_alert), "Month", false) ) var selectedIndex by remember { mutableStateOf(0) } 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 e92b04dd9..270240821 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 @@ -59,11 +59,7 @@ fun OdsOutlinedButton( displaySurface: OdsDisplaySurface = OdsDisplaySurface.Default ) { CompositionLocalProvider( - LocalRippleTheme provides when (displaySurface) { - OdsDisplaySurface.Default -> OdsRippleTheme - OdsDisplaySurface.Light -> OdsLightRippleTheme - OdsDisplaySurface.Dark -> OdsDarkRippleTheme - } + LocalRippleTheme provides displaySurface.rippleTheme ) { OutlinedButton( onClick = onClick, @@ -92,12 +88,7 @@ fun OdsOutlinedButton( } @Composable -private fun OdsColors.buttonOutlinedColor(displaySurface: OdsDisplaySurface) = - when (displaySurface) { - OdsDisplaySurface.Default -> OdsTheme.colors.onSurface - OdsDisplaySurface.Dark -> OdsTheme.darkThemeColors.onSurface - OdsDisplaySurface.Light -> OdsTheme.lightThemeColors.onSurface - } +private fun OdsColors.buttonOutlinedColor(displaySurface: OdsDisplaySurface) = displaySurface.themeColors.onSurface @Composable private fun OdsColors.buttonOutlinedDisabledColor(displaySurface: OdsDisplaySurface) = buttonOutlinedColor(displaySurface).enable(enabled = false) 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 03922023a..707b10826 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 @@ -25,11 +25,8 @@ import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.component.utilities.EnumPreviewParameterProvider import com.orange.ods.compose.component.utilities.Preview import com.orange.ods.compose.component.utilities.UiModePreviews -import com.orange.ods.compose.theme.OdsDarkRippleTheme import com.orange.ods.compose.theme.OdsDisplaySurface -import com.orange.ods.compose.theme.OdsLightRippleTheme import com.orange.ods.compose.theme.OdsPrimaryRippleTheme -import com.orange.ods.compose.theme.OdsRippleTheme import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.theme.colors.OdsColors import com.orange.ods.utilities.extension.enable @@ -73,11 +70,7 @@ fun OdsTextButton( CompositionLocalProvider( LocalRippleTheme provides when (style) { OdsTextButtonStyle.Primary -> OdsPrimaryRippleTheme - OdsTextButtonStyle.Default -> when (displaySurface) { - OdsDisplaySurface.Default -> OdsRippleTheme - OdsDisplaySurface.Light -> OdsLightRippleTheme - OdsDisplaySurface.Dark -> OdsDarkRippleTheme - } + OdsTextButtonStyle.Default -> displaySurface.rippleTheme } ) { TextButton( @@ -99,19 +92,9 @@ fun OdsTextButton( @Composable private fun OdsColors.buttonTextColor(displaySurface: OdsDisplaySurface, style: OdsTextButtonStyle) = - when (displaySurface) { - OdsDisplaySurface.Default -> when (style) { - OdsTextButtonStyle.Primary -> OdsTheme.colors.primary - OdsTextButtonStyle.Default -> OdsTheme.colors.onSurface - } - OdsDisplaySurface.Dark -> when (style) { - OdsTextButtonStyle.Primary -> OdsTheme.darkThemeColors.primary - OdsTextButtonStyle.Default -> OdsTheme.darkThemeColors.onSurface - } - OdsDisplaySurface.Light -> when (style) { - OdsTextButtonStyle.Primary -> OdsTheme.lightThemeColors.primary - OdsTextButtonStyle.Default -> OdsTheme.lightThemeColors.onSurface - } + when (style) { + OdsTextButtonStyle.Primary -> displaySurface.themeColors.primary + OdsTextButtonStyle.Default -> displaySurface.themeColors.onSurface } @Composable diff --git a/lib/src/main/java/com/orange/ods/compose/text/OdsTexts.kt b/lib/src/main/java/com/orange/ods/compose/text/OdsTexts.kt index b11e3d217..1b6128097 100644 --- a/lib/src/main/java/com/orange/ods/compose/text/OdsTexts.kt +++ b/lib/src/main/java/com/orange/ods/compose/text/OdsTexts.kt @@ -72,11 +72,6 @@ fun OdsTextOverline(text: String, modifier: Modifier = Modifier, displaySurface: @Composable private fun OdsText(text: String, textStyle: TextStyle, modifier: Modifier, displaySurface: OdsDisplaySurface, enabled: Boolean) { - val color = when (displaySurface) { - OdsDisplaySurface.Default -> if (enabled) OdsTheme.colors.onSurface else OdsTheme.colors.onSurface.enable(enabled = false) - OdsDisplaySurface.Dark -> if (enabled) OdsTheme.darkThemeColors.onSurface else OdsTheme.darkThemeColors.onSurface.enable(enabled = false) - OdsDisplaySurface.Light -> if (enabled) OdsTheme.lightThemeColors.onSurface else OdsTheme.lightThemeColors.onSurface.enable(enabled = false) - } - + val color = displaySurface.themeColors.onSurface.enable(enabled = enabled) Text(text = text, style = textStyle, color = color, modifier = modifier) } \ No newline at end of file diff --git a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt index a6de7b963..ed902847b 100644 --- a/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt +++ b/lib/src/main/java/com/orange/ods/compose/theme/OdsDisplaySurface.kt @@ -10,6 +10,10 @@ package com.orange.ods.compose.theme +import androidx.compose.material.ripple.RippleTheme +import androidx.compose.runtime.Composable +import com.orange.ods.theme.colors.OdsColors + /** * Allow to force elements appearance to be displayed on light or dark surface. * @@ -30,6 +34,21 @@ enum class OdsDisplaySurface { * The element is displayed on a light background even if the device system is set in dark theme. */ Light; - + companion object + + val themeColors: OdsColors + @Composable + get() = when (this) { + Default -> OdsTheme.colors + Dark -> OdsTheme.darkThemeColors + Light -> OdsTheme.lightThemeColors + } + + val rippleTheme: RippleTheme + get() = when (this) { + Default -> OdsRippleTheme + Light -> OdsLightRippleTheme + Dark -> OdsDarkRippleTheme + } } From ec50812d4a0abf5c81f7343081399d8d9a409fb4 Mon Sep 17 00:00:00 2001 From: Florent Maitre Date: Mon, 10 Jul 2023 15:36:56 +0200 Subject: [PATCH 064/112] [#329] Review: Add enabled parameter of OdsIconToggleButtonsRowItem to code implementation and documentation --- .../app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt | 1 + docs/components/Buttons.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt index 29c5f6208..b2f2ca8be 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/buttons/icons/ButtonsIconToggleGroup.kt @@ -86,6 +86,7 @@ fun ButtonsIconToggleGroup(customizationState: ButtonIconCustomizationState) { classInstance(OdsIconToggleButtonsRowItem::class.java) { painter() string("iconDescription", "icon description") + if (!isEnabled) enabled(false) } } } diff --git a/docs/components/Buttons.md b/docs/components/Buttons.md index 492f70f35..e4ad3dd9c 100644 --- a/docs/components/Buttons.md +++ b/docs/components/Buttons.md @@ -248,7 +248,7 @@ OdsIconToggleButtonsRow( iconToggleButtons = listOf( OdsIconToggleButtonsRowItem(painterResource(id = R.drawable.ic_restaurant), "Restaurant"), OdsIconToggleButtonsRowItem(painterResource(id = R.drawable.ic_cooking_pot), "Cooking pot"), - OdsIconToggleButtonsRowItem(painterResource(id = R.drawable.ic_coffee), "Coffee") + OdsIconToggleButtonsRowItem(painterResource(id = R.drawable.ic_coffee), "Coffee", enabled = false) ), selectedIndex = 0, onSelectedIndexChange = { From d9a3f8a964088eecec177f738a796c3f4ee45e41 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 6 Mar 2023 17:19:08 +0100 Subject: [PATCH 065/112] Add top app bar extended variant --- .../orange/ods/app/ui/components/Component.kt | 3 ++- .../ui/components/ComponentVariantScreen.kt | 3 ++- .../appbars/top/ComponentTopAppBarExtended.kt | 18 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt 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 22268b5f2..f7062ed7b 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 @@ -37,7 +37,7 @@ sealed class Component( R.drawable.il_app_bars_top, R.drawable.il_app_bars_top_small, R.string.component_app_bars_top_description, - variants = listOf(Variant.AppBarsTopRegular), + variants = listOf(Variant.AppBarsTopRegular, Variant.AppBarsTopExtended), imageAlignment = Alignment.TopCenter ) @@ -233,6 +233,7 @@ sealed class Variant( val id: Long = Variant::class.sealedSubclasses.indexOf(this::class).toLong() object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name) + object AppBarsTopExtended : Variant(R.string.component_app_bars_top_extended, OdsComposable.OdsTopAppBar.name) object ButtonsPrimary : Variant(R.string.component_buttons_highest_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Primary.name}") object ButtonsDefault : Variant(R.string.component_buttons_high_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Default.name}") diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt index c8c3099d6..337951e84 100644 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt +++ b/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBar +import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBarExtended import com.orange.ods.app.ui.components.buttons.ComponentButtons import com.orange.ods.app.ui.components.buttons.icons.ComponentButtonsIcons import com.orange.ods.app.ui.components.cards.ComponentCard @@ -32,7 +33,7 @@ fun ComponentVariantScreen(variantId: Long) { variant?.let { LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) when (component) { - Component.AppBarsTop -> ComponentTopAppBar() + Component.AppBarsTop -> if (variant == Variant.AppBarsTopExtended) ComponentTopAppBarExtended() else ComponentTopAppBar() Component.Buttons -> ComponentButtons(variant = variant) Component.ButtonsIcons -> ComponentButtonsIcons(variant = variant) Component.Cards -> ComponentCard(variant = variant) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt new file mode 100644 index 000000000..05a1031a9 --- /dev/null +++ b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt @@ -0,0 +1,18 @@ +/* + * + * 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.components.appbars.top + +import androidx.compose.runtime.Composable + +@Composable +fun ComponentTopAppBarExtended() { + +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f93d4394e..b3f4fcad1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ App bars: top The app bar is a key component and organizes titling, navigation and action buttons. Regular + Extended Navigation icon Overflow menu Actions count From cce2f6a5563fbe7da9efb8c71d562948fe90bb87 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Thu, 8 Jun 2023 10:29:16 +0200 Subject: [PATCH 066/112] Add compose material 3 dependency --- app/build.gradle.kts | 1 + buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt | 1 + buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4e578dfb..06a437848 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -119,6 +119,7 @@ dependencies { implementation(Dependencies.composeUi) implementation(Dependencies.lifecycleViewModelKtx) implementation(Dependencies.composeMaterial) + implementation(Dependencies.composeMaterial3) implementation(Dependencies.composeUiToolingPreview) implementation(Dependencies.lifecycleRuntimeKtx) implementation(Dependencies.activityCompose) diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt index 6f4858073..25c2f2110 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Dependencies.kt @@ -24,6 +24,7 @@ object Dependencies { const val coil = "io.coil-kt:coil:${Versions.coil}" const val coilCompose = "io.coil-kt:coil-compose:${Versions.coil}" const val composeMaterial = "androidx.compose.material:material:${Versions.compose}" + const val composeMaterial3 = "androidx.compose.material3:material3:${Versions.composeM3}" const val composeUi = "androidx.compose.ui:ui:${Versions.compose}" const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}" const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose}" diff --git a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt index 58fd1db65..52e4612da 100644 --- a/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt +++ b/buildSrc/src/main/kotlin/com/orange/ods/gradle/Versions.kt @@ -22,6 +22,7 @@ object Versions { const val appCompat = "1.5.1" const val browser = "1.4.0" const val compose = "1.3.1" //TODO: When upgrading, see TODO in OdsOutlinedTextField.kt + const val composeM3 = "1.0.1" const val coil = "2.2.2" const val constraintLayoutCompose = "1.0.1" const val core = "1.9.0" From f10b9413e935bf005e92ff512396eb6b66070578 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Fri, 9 Jun 2023 14:28:32 +0200 Subject: [PATCH 067/112] Set no elevation to top app bar when in dark mode --- .../main/java/com/orange/ods/app/ui/MainScreen.kt | 12 +++--------- .../ods/compose/component/appbar/top/OdsTopAppBar.kt | 3 ++- 2 files changed, 5 insertions(+), 10 deletions(-) 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 2ff4713f5..177384aeb 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 @@ -21,20 +21,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.AppBarDefaults import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry @@ -109,7 +103,7 @@ fun MainScreen(themeConfigurations: Set, mainView Scaffold( backgroundColor = OdsTheme.colors.background, topBar = { - Surface(elevation = AppBarDefaults.TopAppBarElevation) { + Surface(elevation = if (isSystemInDarkTheme()) 0.dp else AppBarDefaults.TopAppBarElevation) { Column { SystemBarsColorSideEffect() MainTopAppBar( 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 d1d6fbafd..804f1175c 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,6 +10,7 @@ package com.orange.ods.compose.component.appbar.top +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row @@ -85,7 +86,7 @@ fun OdsTopAppBar( actions = actions, backgroundColor = OdsTheme.colors.component.topAppBar.barBackground, contentColor = OdsTheme.colors.component.topAppBar.barContent, - elevation = if (elevated) AppBarDefaults.TopAppBarElevation else 0.dp + elevation = if (elevated && !isSystemInDarkTheme()) AppBarDefaults.TopAppBarElevation else 0.dp ) } From 962038130e8788ec4f1a9fdf7fda1c9d3a52f186 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Fri, 9 Jun 2023 14:38:16 +0200 Subject: [PATCH 068/112] Remove titleRes parameter from MainTopAppBar composable cause we can get it by using provided state --- app/src/main/java/com/orange/ods/app/ui/MainScreen.kt | 1 - app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) 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 177384aeb..c7d843f1c 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 @@ -107,7 +107,6 @@ fun MainScreen(themeConfigurations: Set, mainView Column { SystemBarsColorSideEffect() MainTopAppBar( - titleRes = mainState.topAppBarState.titleRes.value, shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar, state = mainState.topAppBarState, upPress = mainState::upPress, 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 index c33fbca6c..1b2bc1c3a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -36,7 +36,6 @@ import com.orange.ods.compose.component.textfield.search.OdsSearchTextField @Composable fun MainTopAppBar( - titleRes: Int, shouldShowUpNavigationIcon: Boolean, state: MainTopAppBarState, upPress: () -> Unit, @@ -44,7 +43,7 @@ fun MainTopAppBar( onSearchActionClick: () -> Unit ) { OdsTopAppBar( - title = stringResource(id = titleRes), + title = stringResource(id = state.titleRes.value), navigationIcon = if (shouldShowUpNavigationIcon && state.isNavigationIconEnabled) { { Icon( From e71dd5b733d37ac05404ea7f01d2f5eb2f1e39b0 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Fri, 9 Jun 2023 15:04:20 +0200 Subject: [PATCH 069/112] Move theme switch management in the MainTopAppBar --- .../java/com/orange/ods/app/ui/MainScreen.kt | 60 +------------- .../java/com/orange/ods/app/ui/MainState.kt | 4 +- .../com/orange/ods/app/ui/MainTopAppBar.kt | 83 ++++++++++++++++--- .../ui/{MainThemeState.kt => ThemeState.kt} | 12 +-- .../ods/app/ui/components/chips/Chip.kt | 4 +- .../ods/app/ui/components/chips/ChipFilter.kt | 4 +- .../spacing/GuidelineSpacingScreen.kt | 4 +- .../ods/app/ui/utilities/DrawableManager.kt | 4 +- 8 files changed, 89 insertions(+), 86 deletions(-) rename app/src/main/java/com/orange/ods/app/ui/{MainThemeState.kt => ThemeState.kt} (83%) 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 c7d843f1c..3d57916a5 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 @@ -13,7 +13,6 @@ package com.orange.ods.app.ui import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,11 +24,8 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder @@ -49,9 +45,6 @@ import com.orange.ods.app.ui.guidelines.addGuidelinesGraph import com.orange.ods.app.ui.search.SearchScreen import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled import com.orange.ods.app.ui.utilities.extension.isOrange -import com.orange.ods.compose.component.list.OdsListItem -import com.orange.ods.compose.component.list.OdsRadioButtonTrailing -import com.orange.ods.compose.text.OdsTextH6 import com.orange.ods.compose.theme.OdsTheme import com.orange.ods.theme.OdsThemeConfigurationContract import com.orange.ods.utilities.extension.orElse @@ -62,7 +55,7 @@ import com.orange.ods.xml.utilities.extension.xml fun MainScreen(themeConfigurations: Set, mainViewModel: MainViewModel = viewModel()) { val isSystemInDarkTheme = isSystemInDarkTheme() val mainState = rememberMainState( - themeState = rememberMainThemeState( + themeState = rememberThemeState( currentThemeConfiguration = rememberSaveable { mutableStateOf( getCurrentThemeConfiguration( @@ -88,14 +81,12 @@ fun MainScreen(themeConfigurations: Set, mainView CompositionLocalProvider( LocalConfiguration provides configuration, LocalMainTopAppBarManager provides mainState.topAppBarState, - LocalMainThemeManager provides mainState.themeState, + LocalThemeManager provides mainState.themeState, LocalOdsGuideline provides mainState.themeState.currentThemeConfiguration.guideline, LocalRecipes provides mainViewModel.recipes, LocalCategories provides mainViewModel.categories, LocalUiFramework provides mainState.uiFramework ) { - var changeThemeDialogVisible by remember { mutableStateOf(false) } - OdsTheme( themeConfiguration = mainState.themeState.currentThemeConfiguration, darkThemeEnabled = configuration.isDarkModeEnabled @@ -108,9 +99,8 @@ fun MainScreen(themeConfigurations: Set, mainView SystemBarsColorSideEffect() MainTopAppBar( shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar, - state = mainState.topAppBarState, + topAppBarState = mainState.topAppBarState, upPress = mainState::upPress, - onChangeThemeActionClick = { changeThemeDialogVisible = true }, onSearchActionClick = { mainState.navController.navigate(MainDestinations.SearchRoute) } @@ -139,18 +129,6 @@ fun MainScreen(themeConfigurations: Set, mainView mainNavGraph(navigateToElement = mainState::navigateToElement, searchedText = mainState.topAppBarState.searchedText) } } - - if (changeThemeDialogVisible) { - ChangeThemeDialog( - themeState = mainState.themeState, - dismissDialog = { - changeThemeDialogVisible = false - }, - onThemeSelected = { - mainViewModel.storeUserThemeName(mainState.themeState.currentThemeConfiguration.name) - } - ) - } } } } @@ -163,38 +141,6 @@ private fun getCurrentThemeConfiguration(storedUserThemeName: String?, themeConf .orElse { themeConfigurations.first() } } -@Composable -private fun ChangeThemeDialog(themeState: MainThemeState, dismissDialog: () -> Unit, onThemeSelected: () -> Unit) { - val selectedRadio = rememberSaveable { mutableStateOf(themeState.currentThemeConfiguration.name) } - - Dialog(onDismissRequest = dismissDialog) { - Column(modifier = Modifier.background(OdsTheme.colors.surface)) { - OdsTextH6( - text = stringResource(R.string.top_app_bar_action_change_theme_desc), - modifier = Modifier - .padding(top = dimensionResource(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s)) - .padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin)) - ) - themeState.themeConfigurations.forEach { themeConfiguration -> - OdsListItem( - text = themeConfiguration.name, - trailing = OdsRadioButtonTrailing( - selectedRadio = selectedRadio, - currentRadio = themeConfiguration.name, - onClick = { - if (themeConfiguration != themeState.currentThemeConfiguration) { - themeState.currentThemeConfiguration = themeConfiguration - onThemeSelected() - } - dismissDialog() - } - ) - ) - } - } - } -} - @Composable private fun SystemBarsColorSideEffect() { val systemUiController = rememberSystemUiController() 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 f164c6b02..f15bf6456 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 @@ -47,7 +47,7 @@ object MainDestinations { @Composable fun rememberMainState( - themeState: MainThemeState, + themeState: ThemeState, navController: NavHostController = rememberNavController(), topAppBarState: MainTopAppBarState = rememberMainTopAppBarState(), uiFramework: MutableState = rememberSaveable { mutableStateOf(UiFramework.Compose) } @@ -57,7 +57,7 @@ fun rememberMainState( } class MainState( - val themeState: MainThemeState, + val themeState: ThemeState, val navController: NavHostController, val topAppBarState: MainTopAppBarState, val uiFramework: MutableState 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 index 1b2bc1c3a..b6bc31a7c 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -10,20 +10,25 @@ 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.Icon import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember +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.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 @@ -31,20 +36,27 @@ import com.orange.ods.app.ui.utilities.extension.isDarkModeEnabled import com.orange.ods.compose.component.appbar.top.OdsTopAppBar import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuBox +import com.orange.ods.compose.component.list.OdsListItem +import com.orange.ods.compose.component.list.OdsRadioButtonTrailing import com.orange.ods.compose.component.menu.OdsDropdownMenuItem import com.orange.ods.compose.component.textfield.search.OdsSearchTextField +import com.orange.ods.compose.text.OdsTextH6 +import com.orange.ods.compose.theme.OdsTheme @Composable fun MainTopAppBar( shouldShowUpNavigationIcon: Boolean, - state: MainTopAppBarState, + topAppBarState: MainTopAppBarState, upPress: () -> Unit, - onChangeThemeActionClick: () -> Unit, - onSearchActionClick: () -> Unit + onSearchActionClick: () -> Unit, + mainViewModel: MainViewModel = viewModel() ) { + val themeManager = LocalThemeManager.current + var changeThemeDialogVisible by remember { mutableStateOf(false) } + OdsTopAppBar( - title = stringResource(id = state.titleRes.value), - navigationIcon = if (shouldShowUpNavigationIcon && state.isNavigationIconEnabled) { + title = stringResource(id = topAppBarState.titleRes.value), + navigationIcon = if (shouldShowUpNavigationIcon && topAppBarState.isNavigationIconEnabled) { { Icon( imageVector = Icons.Filled.ArrowBack, @@ -54,12 +66,12 @@ fun MainTopAppBar( } else null, onNavigationIconClick = upPress, actions = { - if (state.titleRes.value == R.string.navigation_item_search) { + if (topAppBarState.titleRes.value == R.string.navigation_item_search) { val focusRequester = remember { FocusRequester() } OdsSearchTextField( - value = state.searchedText.value, + value = topAppBarState.searchedText.value, onValueChange = { value -> - state.searchedText.value = value + topAppBarState.searchedText.value = value }, placeholder = stringResource(id = R.string.search_text_field_hint), modifier = Modifier @@ -70,11 +82,24 @@ fun MainTopAppBar( focusRequester.requestFocus() } } else { - TopAppBarActions(state, onSearchActionClick, onChangeThemeActionClick) + TopAppBarActions(topAppBarState, onSearchActionClick) { changeThemeDialogVisible = true } } }, 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 @@ -102,7 +127,7 @@ private fun TopAppBarChangeThemeActionButton(onClick: () -> Unit) { @Composable private fun TopAppBarChangeModeActionButton() { val configuration = LocalConfiguration.current - val mainThemeManager = LocalMainThemeManager.current + val mainThemeManager = LocalThemeManager.current val painterRes = if (configuration.isDarkModeEnabled) R.drawable.ic_ui_light_mode else R.drawable.ic_ui_dark_mode val iconDesc = @@ -148,3 +173,35 @@ private fun TopAppBarCustomActionButton(action: TopAppBarConfiguration.Action.Cu 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(R.dimen.spacing_m), bottom = dimensionResource(id = R.dimen.spacing_s)) + .padding(horizontal = dimensionResource(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/MainThemeState.kt b/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt similarity index 83% rename from app/src/main/java/com/orange/ods/app/ui/MainThemeState.kt rename to app/src/main/java/com/orange/ods/app/ui/ThemeState.kt index 9245cd3e5..7d9727e6e 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainThemeState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/ThemeState.kt @@ -19,10 +19,10 @@ import androidx.compose.runtime.staticCompositionLocalOf import com.orange.ods.theme.OdsThemeConfigurationContract import com.orange.ods.theme.guideline.OdsGuideline -val LocalMainThemeManager = staticCompositionLocalOf { error("CompositionLocal LocalMainThemeManager not present") } +val LocalThemeManager = staticCompositionLocalOf { error("CompositionLocal LocalMainThemeManager not present") } val LocalOdsGuideline = staticCompositionLocalOf { error("CompositionLocal LocalOdsGuideline not present") } -interface MainThemeManager { +interface ThemeManager { val themeConfigurations: List @@ -33,20 +33,20 @@ interface MainThemeManager { } @Composable -fun rememberMainThemeState( +fun rememberThemeState( themeConfigurations: List, currentThemeConfiguration: MutableState, darkModeEnabled: MutableState, ) = remember(themeConfigurations, currentThemeConfiguration, darkModeEnabled) { - MainThemeState(themeConfigurations, currentThemeConfiguration, darkModeEnabled) + ThemeState(themeConfigurations, currentThemeConfiguration, darkModeEnabled) } -class MainThemeState( +class ThemeState( override val themeConfigurations: List, currentThemeConfiguration: MutableState, darkModeEnabled: MutableState -) : MainThemeManager { +) : ThemeManager { // ---------------------------------------------------------- // Theme state source of truth 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 812a22952..66f5725c4 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 @@ -28,7 +28,7 @@ import androidx.compose.ui.res.stringResource import coil.compose.rememberAsyncImagePainter import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes -import com.orange.ods.app.ui.LocalMainThemeManager +import com.orange.ods.app.ui.LocalThemeManager import com.orange.ods.app.ui.components.Variant import com.orange.ods.app.ui.components.chips.ChipCustomizationState.ChipType import com.orange.ods.app.ui.components.chips.ChipCustomizationState.LeadingElement @@ -104,7 +104,7 @@ fun ChipTypeDemo(chipType: ChipType, content: @Composable () -> Unit) { @Composable private fun Chip(chipCustomizationState: ChipCustomizationState) { val context = LocalContext.current - val outlinedChips = LocalMainThemeManager.current.currentThemeConfiguration.componentsConfiguration.chipStyle == ComponentStyle.Outlined + val outlinedChips = LocalThemeManager.current.currentThemeConfiguration.componentsConfiguration.chipStyle == ComponentStyle.Outlined val cancelCrossLabel = stringResource(id = R.string.component_element_cancel_cross) val recipes = LocalRecipes.current.take(4) 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 02a390057..9ddba3fc4 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 @@ -28,7 +28,7 @@ import coil.compose.rememberAsyncImagePainter import com.google.accompanist.flowlayout.FlowRow import com.orange.ods.app.R import com.orange.ods.app.domain.recipes.LocalRecipes -import com.orange.ods.app.ui.LocalMainThemeManager +import com.orange.ods.app.ui.LocalThemeManager import com.orange.ods.app.ui.components.utilities.ComponentCustomizationBottomSheetScaffold import com.orange.ods.app.ui.utilities.DrawableManager import com.orange.ods.app.ui.utilities.composable.CodeImplementationColumn @@ -49,7 +49,7 @@ fun ChipFilter() { val chipCustomizationState = rememberChipCustomizationState(chipType = rememberSaveable { mutableStateOf(ChipCustomizationState.ChipType.Filter) }) val recipes = LocalRecipes.current val recipe = rememberSaveable { recipes.filter { it.ingredients.count() >= 3 }.random() } - val outlinedChips = LocalMainThemeManager.current.currentThemeConfiguration.componentsConfiguration.chipStyle == ComponentStyle.Outlined + val outlinedChips = LocalThemeManager.current.currentThemeConfiguration.componentsConfiguration.chipStyle == ComponentStyle.Outlined with(chipCustomizationState) { ComponentCustomizationBottomSheetScaffold( 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 0df9f01b5..466088373 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 @@ -30,7 +30,7 @@ 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.LocalMainThemeManager +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 @@ -89,7 +89,7 @@ fun GuidelineSpacingImage(spacing: Spacing) { 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 = LocalMainThemeManager.current.currentThemeConfiguration.isOrange + val isOrangeTheme = LocalThemeManager.current.currentThemeConfiguration.isOrange Canvas( modifier = Modifier diff --git a/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt b/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt index 32b18eda7..f01350458 100644 --- a/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt +++ b/app/src/main/java/com/orange/ods/app/ui/utilities/DrawableManager.kt @@ -14,7 +14,7 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import com.orange.ods.app.R -import com.orange.ods.app.ui.LocalMainThemeManager +import com.orange.ods.app.ui.LocalThemeManager import com.orange.ods.app.ui.utilities.extension.isOrange import com.orange.ods.utilities.extension.orElse @@ -62,7 +62,7 @@ object DrawableManager { @Composable fun getDrawableResIdForCurrentTheme(@DrawableRes resId: Int): Int { - val isOrangeTheme = LocalMainThemeManager.current.currentThemeConfiguration.isOrange + val isOrangeTheme = LocalThemeManager.current.currentThemeConfiguration.isOrange val currentThemeResId = if (isOrangeTheme) orangeResIdByGenericResId[resId] else genericResIdByOrangeResId[resId] return currentThemeResId.orElse { resId } From c84fd063234330970de3044cf8a5833c6ec2b8e6 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 14 Jun 2023 09:58:30 +0200 Subject: [PATCH 070/112] Simplify management of components and variants screen display. Add extended attribute to top app bar state. --- .../java/com/orange/ods/app/ui/MainScreen.kt | 9 +- .../orange/ods/app/ui/MainTopAppBarState.kt | 30 ++-- .../orange/ods/app/ui/components/Component.kt | 130 +++++++++++++----- .../app/ui/components/ComponentDemoScreen.kt | 53 ------- .../ui/components/ComponentVariantScreen.kt | 48 ------- .../app/ui/components/ComponentsNavGraph.kt | 26 +++- 6 files changed, 142 insertions(+), 154 deletions(-) delete mode 100644 app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt delete mode 100644 app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt 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 3d57916a5..390064502 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,7 +14,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.AppBarDefaults @@ -124,10 +123,10 @@ fun MainScreen(themeConfigurations: Set, mainView } } ) { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { - NavHost(mainState.navController, startDestination = MainDestinations.HomeRoute) { - mainNavGraph(navigateToElement = mainState::navigateToElement, searchedText = mainState.topAppBarState.searchedText) - } + NavHost( + navController = mainState.navController, startDestination = MainDestinations.HomeRoute, modifier = Modifier.padding(innerPadding) + ) { + mainNavGraph(navigateToElement = mainState::navigateToElement, searchedText = mainState.topAppBarState.searchedText) } } } 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 index ce5b9fad6..d235fa4e1 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -12,12 +12,8 @@ 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.* 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 kotlinx.parcelize.Parcelize @@ -27,6 +23,8 @@ val LocalMainTopAppBarManager = staticCompositionLocalOf { interface MainTopAppBarManager { fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) fun updateTopAppBarTitle(titleRes: Int) + fun setExtended(extended: Boolean) + fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) fun clearTopAppBarTabs() @@ -40,10 +38,11 @@ fun rememberMainTopAppBarState( actions: MutableState> = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.actions) }, navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.isNavigationIconEnabled) }, searchedText: MutableState = remember { mutableStateOf(TextFieldValue("")) }, + extended: MutableState = rememberSaveable { mutableStateOf(false) }, tabsState: MainTabsState = rememberMainTabsState(), ) = - remember(titleRes, actions, searchedText, navigationIconEnabled, tabsState) { - MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, tabsState) + remember(titleRes, actions, searchedText, navigationIconEnabled, extended, tabsState) { + MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, extended, tabsState) } @@ -52,11 +51,13 @@ class MainTopAppBarState( val actions: MutableState>, var searchedText: MutableState, private val navigationIconEnabled: MutableState, + private var extended: MutableState, val tabsState: MainTabsState, ) : MainTopAppBarManager { companion object { val DefaultConfiguration = TopAppBarConfiguration( + isExtended = false, isNavigationIconEnabled = true, actions = listOf(TopAppBarConfiguration.Action.Theme, TopAppBarConfiguration.Action.Mode) ) @@ -66,10 +67,18 @@ class MainTopAppBarState( // TopAppBar state source of truth // ---------------------------------------------------------- + val isExtended: Boolean + get() = extended.value + val isNavigationIconEnabled: Boolean get() = navigationIconEnabled.value + override fun setExtended(extended: Boolean) { + this.extended.value = extended + } + override fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) { + extended.value = topAppBarConfiguration.isExtended navigationIconEnabled.value = topAppBarConfiguration.isNavigationIconEnabled actions.value = topAppBarConfiguration.actions } @@ -93,6 +102,7 @@ class MainTopAppBarState( } data class TopAppBarConfiguration constructor( + val isExtended: Boolean, val isNavigationIconEnabled: Boolean, val actions: List ) { @@ -117,10 +127,12 @@ data class TopAppBarConfiguration constructor( class Builder { + private var isExtended = false private var isNavigationIconEnabled = true - private var actions = mutableListOf() + fun extended(value: Boolean) = apply { isExtended = value } + fun navigationIconEnabled(enabled: Boolean) = apply { isNavigationIconEnabled = enabled } fun actions(builderAction: MutableList.() -> Unit) = apply { actions.builderAction() } @@ -135,7 +147,7 @@ data class TopAppBarConfiguration constructor( actions { add(action) } } - fun build() = TopAppBarConfiguration(isNavigationIconEnabled, actions) + fun build() = TopAppBarConfiguration(isExtended, isNavigationIconEnabled, actions) } fun newBuilder() = Builder().apply { 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 f7062ed7b..3a3128e94 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 @@ -12,8 +12,29 @@ package com.orange.ods.app.ui.components import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import com.orange.ods.app.R +import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBar +import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBarExtended +import com.orange.ods.app.ui.components.banners.ComponentBanners +import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation +import com.orange.ods.app.ui.components.buttons.ComponentButtons +import com.orange.ods.app.ui.components.cards.ComponentCard +import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes +import com.orange.ods.app.ui.components.chips.Chip +import com.orange.ods.app.ui.components.dialogs.ComponentDialog +import com.orange.ods.app.ui.components.floatingactionbuttons.ComponentFloatingActionButton +import com.orange.ods.app.ui.components.lists.ComponentLists +import com.orange.ods.app.ui.components.menus.ComponentMenu +import com.orange.ods.app.ui.components.progress.ComponentProgress +import com.orange.ods.app.ui.components.radiobuttons.ComponentRadioButtons +import com.orange.ods.app.ui.components.sheets.ComponentSheetsBottom +import com.orange.ods.app.ui.components.sliders.ComponentSliders +import com.orange.ods.app.ui.components.snackbars.ComponentSnackbars +import com.orange.ods.app.ui.components.switches.ComponentSwitches +import com.orange.ods.app.ui.components.tabs.ComponentTabs +import com.orange.ods.app.ui.components.textfields.ComponentTextField import com.orange.ods.compose.OdsComposable import com.orange.ods.compose.component.button.OdsButtonStyle @@ -24,6 +45,7 @@ sealed class Component( @StringRes val descriptionRes: Int, val variants: List = emptyList(), val composableName: String? = null, + val screenContent: @Composable (() -> Unit)? = null, val imageAlignment: Alignment = Alignment.Center, ) { companion object { @@ -47,6 +69,7 @@ sealed class Component( null, R.string.component_banners_description, composableName = OdsComposable.OdsBanner.name, + screenContent = { ComponentBanners() } ) object BottomNavigation : @@ -56,6 +79,7 @@ sealed class Component( null, R.string.component_bottom_navigation_description, composableName = OdsComposable.OdsBottomNavigation.name, + screenContent = { ComponentBottomNavigation() }, imageAlignment = Alignment.TopCenter ) @@ -98,7 +122,8 @@ sealed class Component( R.drawable.il_checkboxes, null, R.string.component_checkboxes_description, - composableName = OdsComposable.OdsCheckbox.name + composableName = OdsComposable.OdsCheckbox.name, + screenContent = { ComponentCheckboxes() } ) object Chips : Component( @@ -114,7 +139,8 @@ sealed class Component( R.drawable.il_dialogs, null, R.string.component_dialogs_description, - composableName = OdsComposable.OdsAlertDialog.name + composableName = OdsComposable.OdsAlertDialog.name, + screenContent = { ComponentDialog() } ) object FloatingActionButtons : Component( @@ -122,7 +148,8 @@ sealed class Component( R.drawable.il_fab, null, R.string.component_floating_action_buttons_description, - composableName = OdsComposable.OdsFloatingActionButton.name + composableName = OdsComposable.OdsFloatingActionButton.name, + screenContent = { ComponentFloatingActionButton() } ) object ImageItem : Component( @@ -139,6 +166,7 @@ sealed class Component( null, R.string.component_lists_description, composableName = OdsComposable.OdsListItem.name, + screenContent = { ComponentLists() }, imageAlignment = Alignment.BottomCenter ) @@ -170,7 +198,8 @@ sealed class Component( R.drawable.il_radio_buttons, null, R.string.component_radio_buttons_description, - composableName = OdsComposable.OdsRadioButton.name + composableName = OdsComposable.OdsRadioButton.name, + screenContent = { ComponentRadioButtons() } ) object SheetsBottom : Component( @@ -178,7 +207,8 @@ sealed class Component( R.drawable.il_bottom_sheet, null, R.string.component_sheet_bottom_description, - composableName = OdsComposable.OdsBottomSheetScaffold.name + composableName = OdsComposable.OdsBottomSheetScaffold.name, + screenContent = { ComponentSheetsBottom() } ) object Sliders : Component( @@ -187,6 +217,7 @@ sealed class Component( null, R.string.component_sliders_description, composableName = OdsComposable.OdsSlider.name, + screenContent = { ComponentSliders() }, imageAlignment = Alignment.CenterEnd ) @@ -195,7 +226,8 @@ sealed class Component( R.drawable.il_snackbars, R.drawable.il_snackbars_small, R.string.component_snackbars_description, - composableName = OdsComposable.OdsSnackbar.name + composableName = OdsComposable.OdsSnackbar.name, + screenContent = { ComponentSnackbars() } ) object Switches : Component( @@ -203,7 +235,8 @@ sealed class Component( R.drawable.il_switches, R.drawable.il_switches_small, R.string.component_switches_description, - composableName = OdsComposable.OdsSwitch.name + composableName = OdsComposable.OdsSwitch.name, + screenContent = { ComponentSwitches() } ) object TextFields : Component( @@ -229,43 +262,70 @@ val components = Component::class.sealedSubclasses.mapNotNull { it.objectInstanc sealed class Variant( @StringRes val titleRes: Int, val composableName: String, + val screenContent: @Composable () -> Unit, + val extendedTopAppBar: Boolean = false ) { val id: Long = Variant::class.sealedSubclasses.indexOf(this::class).toLong() - object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name) - object AppBarsTopExtended : Variant(R.string.component_app_bars_top_extended, OdsComposable.OdsTopAppBar.name) + object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name, { ComponentTopAppBar() }) + object AppBarsTopExtended : Variant(R.string.component_app_bars_top_extended, OdsComposable.OdsTopAppBar.name, { ComponentTopAppBarExtended() }, true) - object ButtonsPrimary : Variant(R.string.component_buttons_highest_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Primary.name}") - object ButtonsDefault : Variant(R.string.component_buttons_high_emphasis, "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Default.name}") - object ButtonsOutlined : Variant(R.string.component_buttons_medium_emphasis, OdsComposable.OdsOutlinedButton.name) - object ButtonsText : Variant(R.string.component_buttons_low_emphasis, OdsComposable.OdsTextButton.name) - object ButtonsFunctional : Variant(R.string.component_buttons_functional, "${OdsComposable.OdsButton.name} with a functional style") + object ButtonsPrimary : Variant( + R.string.component_buttons_highest_emphasis, + "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Primary.name}", + { ComponentButtons(variant = ButtonsPrimary) }) - object ButtonsIcon : Variant(R.string.component_buttons_icon, OdsComposable.OdsIconButton.name) - object ButtonsIconToggle : Variant(R.string.component_buttons_icon_toggle, OdsComposable.OdsIconToggleButton.name) - object ButtonsIconToggleGroup : Variant(R.string.component_buttons_icon_toggle_group, OdsComposable.OdsIconToggleButtonsRow.name) + object ButtonsDefault : Variant( + R.string.component_buttons_high_emphasis, + "${OdsComposable.OdsButton.name} with ${OdsButtonStyle.Default.name}", + { ComponentButtons(variant = ButtonsDefault) }) - object CardVerticalImageFirst : Variant(R.string.component_card_vertical_image_first, OdsComposable.OdsVerticalImageFirstCard.name) - object CardVerticalHeaderFirst : Variant(R.string.component_card_vertical_header_first, OdsComposable.OdsVerticalHeaderFirstCard.name) - object CardSmall : Variant(R.string.component_card_small, OdsComposable.OdsSmallCard.name) - object CardHorizontal : Variant(R.string.component_card_horizontal, OdsComposable.OdsHorizontalCard.name) + object ButtonsOutlined : + Variant(R.string.component_buttons_medium_emphasis, OdsComposable.OdsOutlinedButton.name, { ComponentButtons(variant = ButtonsOutlined) }) - object ChipAction : Variant(R.string.component_chip_action, OdsComposable.OdsChip.name) - object ChipChoice : Variant(R.string.component_chip_choice, OdsComposable.OdsChip.name) - object ChipInput : Variant(R.string.component_chip_input, OdsComposable.OdsChip.name) - object ChipFilter : Variant(R.string.component_chip_filter, OdsComposable.OdsFilterChip.name) + object ButtonsText : Variant(R.string.component_buttons_low_emphasis, OdsComposable.OdsTextButton.name, { ComponentButtons(variant = ButtonsText) }) + object ButtonsFunctional : Variant( + R.string.component_buttons_functional, + "${OdsComposable.OdsButton.name} with a functional style", + { ComponentButtons(variant = ButtonsFunctional) }) - object DropdownMenu : Variant(R.string.component_menu_dropdown, OdsComposable.OdsDropdownMenu.name) - object ExposedDropdownMenu : Variant(R.string.component_menu_exposed_dropdown, OdsComposable.OdsExposedDropdownMenu.name) + object ButtonsIcon : Variant(R.string.component_buttons_icon, OdsComposable.OdsIconButton.name, { ComponentButtons(variant = ButtonsIcon) }) + object ButtonsIconToggle : + Variant(R.string.component_buttons_icon_toggle, OdsComposable.OdsIconToggleButton.name, { ComponentButtons(variant = ButtonsIconToggle) }) - object ProgressLinear : Variant(R.string.component_progress_linear, OdsComposable.OdsLinearProgressIndicator.name) - object ProgressCircular : Variant(R.string.component_progress_circular, OdsComposable.OdsCircularProgressIndicator.name) + object ButtonsIconToggleGroup : + Variant(R.string.component_buttons_icon_toggle_group, OdsComposable.OdsIconToggleButtonsRow.name, { ComponentButtons(variant = ButtonsIconToggle) }) - object TextField : Variant(R.string.component_text_field_text, OdsComposable.OdsTextField.name) - object TextFieldPassword : Variant(R.string.component_text_field_password, OdsComposable.OdsPasswordTextField.name) + object CardVerticalImageFirst : + Variant(R.string.component_card_vertical_image_first, OdsComposable.OdsVerticalImageFirstCard.name, { ComponentCard(variant = CardVerticalImageFirst) }) - object TabsFixed : Variant(R.string.component_tabs_fixed, OdsComposable.OdsTabRow.name) - object TabsScrollable : Variant(R.string.component_tabs_scrollable, OdsComposable.OdsScrollableTabRow.name) -} + object CardVerticalHeaderFirst : Variant( + R.string.component_card_vertical_header_first, + OdsComposable.OdsVerticalHeaderFirstCard.name, + { ComponentCard(variant = CardVerticalHeaderFirst) }) + + object CardSmall : Variant(R.string.component_card_small, OdsComposable.OdsSmallCard.name, { ComponentCard(variant = CardSmall) }) + object CardHorizontal : Variant(R.string.component_card_horizontal, OdsComposable.OdsHorizontalCard.name, { ComponentCard(variant = CardHorizontal) }) + + object ChipAction : Variant(R.string.component_chip_action, OdsComposable.OdsChip.name, { Chip(variant = ChipAction) }) + object ChipChoice : Variant(R.string.component_chip_choice, OdsComposable.OdsChip.name, { Chip(variant = ChipChoice) }) + object ChipInput : Variant(R.string.component_chip_input, OdsComposable.OdsChip.name, { Chip(variant = ChipInput) }) + object ChipFilter : Variant(R.string.component_chip_filter, OdsComposable.OdsFilterChip.name, { Chip(variant = ChipFilter) }) + + object DropdownMenu : Variant(R.string.component_menu_dropdown, OdsComposable.OdsDropdownMenu.name, { ComponentMenu(variant = DropdownMenu) }) + object ExposedDropdownMenu : + Variant(R.string.component_menu_exposed_dropdown, OdsComposable.OdsExposedDropdownMenu.name, { ComponentMenu(variant = ExposedDropdownMenu) }) + + object ProgressLinear : + Variant(R.string.component_progress_linear, OdsComposable.OdsLinearProgressIndicator.name, { ComponentProgress(variant = ProgressLinear) }) + + object ProgressCircular : + Variant(R.string.component_progress_circular, OdsComposable.OdsCircularProgressIndicator.name, { ComponentProgress(variant = ProgressCircular) }) + + object TextField : Variant(R.string.component_text_field_text, OdsComposable.OdsTextField.name, { ComponentTextField(variant = TextField) }) + object TextFieldPassword : + Variant(R.string.component_text_field_password, OdsComposable.OdsPasswordTextField.name, { ComponentTextField(variant = TextFieldPassword) }) -val variants = Variant::class.sealedSubclasses.mapNotNull { it.objectInstance } + object TabsFixed : Variant(R.string.component_tabs_fixed, OdsComposable.OdsTabRow.name, { ComponentTabs(variant = TabsFixed) }) + object TabsScrollable : Variant(R.string.component_tabs_scrollable, OdsComposable.OdsScrollableTabRow.name, { ComponentTabs(variant = TabsScrollable) }) +} \ No newline at end of file diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt deleted file mode 100644 index 0c496527a..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentDemoScreen.kt +++ /dev/null @@ -1,53 +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.components - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.components.banners.ComponentBanners -import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation -import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes -import com.orange.ods.app.ui.components.dialogs.ComponentDialog -import com.orange.ods.app.ui.components.floatingactionbuttons.ComponentFloatingActionButton -import com.orange.ods.app.ui.components.imageitem.ComponentImageItem -import com.orange.ods.app.ui.components.lists.ComponentLists -import com.orange.ods.app.ui.components.navigationdrawers.ComponentModalDrawers -import com.orange.ods.app.ui.components.radiobuttons.ComponentRadioButtons -import com.orange.ods.app.ui.components.sheets.ComponentSheetsBottom -import com.orange.ods.app.ui.components.sliders.ComponentSliders -import com.orange.ods.app.ui.components.snackbars.ComponentSnackbars -import com.orange.ods.app.ui.components.switches.ComponentSwitches - -@Composable -fun ComponentDemoScreen(componentId: Long) { - val component = remember { components.firstOrNull { it.id == componentId } } - - component?.let { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) - when (component) { - Component.Banners -> ComponentBanners() - Component.BottomNavigation -> ComponentBottomNavigation() - Component.Checkboxes -> ComponentCheckboxes() - Component.Dialogs -> ComponentDialog() - Component.FloatingActionButtons -> ComponentFloatingActionButton() - Component.ImageItem -> ComponentImageItem() - Component.Lists -> ComponentLists() - Component.ModalDrawers -> ComponentModalDrawers() - Component.RadioButtons -> ComponentRadioButtons() - Component.SheetsBottom -> ComponentSheetsBottom() - Component.Sliders -> ComponentSliders() - Component.Snackbars -> ComponentSnackbars() - Component.Switches -> ComponentSwitches() - else -> {} - } - } -} diff --git a/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt b/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt deleted file mode 100644 index 337951e84..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/components/ComponentVariantScreen.kt +++ /dev/null @@ -1,48 +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.components - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import com.orange.ods.app.ui.LocalMainTopAppBarManager -import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBar -import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBarExtended -import com.orange.ods.app.ui.components.buttons.ComponentButtons -import com.orange.ods.app.ui.components.buttons.icons.ComponentButtonsIcons -import com.orange.ods.app.ui.components.cards.ComponentCard -import com.orange.ods.app.ui.components.chips.Chip -import com.orange.ods.app.ui.components.chips.ChipFilter -import com.orange.ods.app.ui.components.menus.ComponentMenu -import com.orange.ods.app.ui.components.progress.ComponentProgress -import com.orange.ods.app.ui.components.tabs.ComponentTabs -import com.orange.ods.app.ui.components.textfields.ComponentTextField - -@Composable -fun ComponentVariantScreen(variantId: Long) { - val component = remember { components.firstOrNull { component -> component.variants.any { variant -> variant.id == variantId } } } - val variant = remember { components.flatMap { it.variants }.firstOrNull { it.id == variantId } } - - variant?.let { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) - when (component) { - Component.AppBarsTop -> if (variant == Variant.AppBarsTopExtended) ComponentTopAppBarExtended() else ComponentTopAppBar() - Component.Buttons -> ComponentButtons(variant = variant) - Component.ButtonsIcons -> ComponentButtonsIcons(variant = variant) - Component.Cards -> ComponentCard(variant = variant) - Component.Chips -> if (variant == Variant.ChipFilter) ChipFilter() else Chip(variant = variant) - Component.Menus -> ComponentMenu(variant = variant) - Component.Progress -> ComponentProgress(variant = variant) - Component.TextFields -> ComponentTextField(variant = variant) - Component.Tabs -> ComponentTabs(variant) - else -> {} - } - } -} 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 fe5acde32..5d98e1320 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 @@ -10,6 +10,7 @@ package com.orange.ods.app.ui.components +import androidx.compose.runtime.remember import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType @@ -39,19 +40,36 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac "${MainDestinations.ComponentVariantRoute}/{${MainDestinations.ComponentVariantIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentVariantIdKey) { type = NavType.LongType }) ) { from -> + LocalMainTopAppBarManager.current.reset() + val arguments = requireNotNull(from.arguments) val variantId = arguments.getLong(MainDestinations.ComponentVariantIdKey) - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) - ComponentVariantScreen(variantId = variantId) + val variant = remember { components.flatMap { it.variants }.firstOrNull { it.id == variantId } } + + variant?.let { + with(LocalMainTopAppBarManager.current) { + updateTopAppBarTitle(variant.titleRes) + setExtended(variant.extendedTopAppBar) + } + + variant.screenContent() + } } composable( "${MainDestinations.ComponentDemoRoute}/{${MainDestinations.ComponentIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) ) { from -> + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + val arguments = requireNotNull(from.arguments) val componentId = arguments.getLong(MainDestinations.ComponentIdKey) - LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) - ComponentDemoScreen(componentId = componentId) + val component = remember { components.firstOrNull { it.id == componentId } } + + component?.let { + LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) + component.screenContent?.let { it() } + } + } } \ No newline at end of file From d21123326200de753365bc7326b21330ab8f3155 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 14 Jun 2023 09:59:13 +0200 Subject: [PATCH 071/112] Add a large top app bar in the lib and display it in the demo --- app/build.gradle.kts | 2 - .../com/orange/ods/app/ui/MainTopAppBar.kt | 90 ++++++++++++------- lib/build.gradle.kts | 5 +- .../appbar/top/OdsExtendedTopAppBar.kt | 49 ++++++++++ 4 files changed, 109 insertions(+), 37 deletions(-) create mode 100644 lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 06a437848..9b72cca65 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -118,8 +118,6 @@ dependencies { implementation(Dependencies.material) implementation(Dependencies.composeUi) implementation(Dependencies.lifecycleViewModelKtx) - implementation(Dependencies.composeMaterial) - implementation(Dependencies.composeMaterial3) implementation(Dependencies.composeUiToolingPreview) implementation(Dependencies.lifecycleRuntimeKtx) implementation(Dependencies.activityCompose) 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 index b6bc31a7c..5d7faf79f 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -12,11 +12,13 @@ package com.orange.ods.app.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.Icon import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier @@ -27,12 +29,14 @@ 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.OdsExtendedTopAppBar import com.orange.ods.compose.component.appbar.top.OdsTopAppBar import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuBox @@ -43,6 +47,7 @@ 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, @@ -54,39 +59,40 @@ fun MainTopAppBar( val themeManager = LocalThemeManager.current var changeThemeDialogVisible by remember { mutableStateOf(false) } - OdsTopAppBar( - title = stringResource(id = topAppBarState.titleRes.value), - navigationIcon = if (shouldShowUpNavigationIcon && topAppBarState.isNavigationIconEnabled) { - { - Icon( - imageVector = Icons.Filled.ArrowBack, - contentDescription = stringResource(id = R.string.top_app_bar_back_icon_desc) - ) - } - } else null, - onNavigationIconClick = upPress, - actions = { - if (topAppBarState.titleRes.value == R.string.navigation_item_search) { - val focusRequester = remember { FocusRequester() } - OdsSearchTextField( - value = topAppBarState.searchedText.value, - onValueChange = { value -> - topAppBarState.searchedText.value = value - }, - placeholder = stringResource(id = R.string.search_text_field_hint), - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - } else { - TopAppBarActions(topAppBarState, onSearchActionClick) { changeThemeDialogVisible = true } - } - }, - elevated = false // elevation is managed in [MainScreen] cause of tabs - ) + val title = stringResource(id = topAppBarState.titleRes.value) + val navigationIcon: (@Composable () -> Unit)? = if (shouldShowUpNavigationIcon && topAppBarState.isNavigationIconEnabled) { + { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource(id = R.string.top_app_bar_back_icon_desc) + ) + } + } else null + + val actions: @Composable RowScope.() -> Unit = { + if (topAppBarState.titleRes.value == R.string.navigation_item_search) { + TopAppBarSearchAction(searchedText = topAppBarState.searchedText) + } else { + TopAppBarActions(topAppBarState, onSearchActionClick) { changeThemeDialogVisible = true } + } + } + + if (topAppBarState.isExtended) { + OdsExtendedTopAppBar( + title = title, + navigationIcon = navigationIcon ?: { }, + onNavigationIconClick = upPress, + actions = actions + ) + } else { + OdsTopAppBar( + title = title, + navigationIcon = navigationIcon, + onNavigationIconClick = upPress, + actions = actions, + elevated = false // elevation is managed in [MainScreen] cause of tabs + ) + } if (changeThemeDialogVisible) { ChangeThemeDialog( @@ -99,7 +105,25 @@ fun MainTopAppBar( } ) } +} + +@Composable +private fun TopAppBarSearchAction(searchedText: MutableState) { + 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 diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 7df646b8d..6293e0f1e 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -73,7 +73,9 @@ android { dependencies { api(project(":theme-contract")) api(project(":theme-orange")) - + api(Dependencies.composeMaterial) + api(Dependencies.composeMaterial3) + implementation(Dependencies.kotlinStdlibJdk8) implementation(Dependencies.kotlinReflect) compileOnly(project(":composable-processor")) @@ -81,7 +83,6 @@ dependencies { implementation(Dependencies.accompanistFlowLayout) implementation(Dependencies.appCompat) implementation(Dependencies.composeUi) - implementation(Dependencies.composeMaterial) implementation(Dependencies.composeUiTooling) implementation(Dependencies.composeUiToolingPreview) implementation(Dependencies.coreKtx) diff --git a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt new file mode 100644 index 000000000..4f21d2b68 --- /dev/null +++ b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt @@ -0,0 +1,49 @@ +/* + * + * 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.compose.component.appbar.top + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.orange.ods.compose.component.OdsComposable +import com.orange.ods.compose.theme.OdsTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@OdsComposable +fun OdsExtendedTopAppBar( + title: String, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, + onNavigationIconClick: () -> Unit = {}, + actions: @Composable RowScope.() -> Unit = {}, + scrollBehavior: TopAppBarScrollBehavior? = null +) { + val contentColor = OdsTheme.colors.component.topAppBar.barContent + LargeTopAppBar( + title = { Text(text = title) }, + modifier = modifier, + navigationIcon = { + IconButton(onClick = onNavigationIconClick) { + navigationIcon() + } + }, + actions = actions, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = OdsTheme.colors.component.topAppBar.barBackground, + navigationIconContentColor = contentColor, + titleContentColor = contentColor, + actionIconContentColor = contentColor + ), + scrollBehavior = scrollBehavior + ) +} \ No newline at end of file From 3bd9abc94d508acf33abd998e88cd0d1d0f3f61c Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 14 Jun 2023 10:59:31 +0200 Subject: [PATCH 072/112] Fix modal drawer and image item component demo --- .../main/java/com/orange/ods/app/ui/components/Component.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 3a3128e94..996029268 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 @@ -25,8 +25,10 @@ import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes import com.orange.ods.app.ui.components.chips.Chip import com.orange.ods.app.ui.components.dialogs.ComponentDialog import com.orange.ods.app.ui.components.floatingactionbuttons.ComponentFloatingActionButton +import com.orange.ods.app.ui.components.imageitem.ComponentImageItem import com.orange.ods.app.ui.components.lists.ComponentLists import com.orange.ods.app.ui.components.menus.ComponentMenu +import com.orange.ods.app.ui.components.navigationdrawers.ComponentModalDrawers import com.orange.ods.app.ui.components.progress.ComponentProgress import com.orange.ods.app.ui.components.radiobuttons.ComponentRadioButtons import com.orange.ods.app.ui.components.sheets.ComponentSheetsBottom @@ -157,7 +159,8 @@ sealed class Component( R.drawable.il_image_item, null, R.string.component_image_item_description, - composableName = OdsComposable.OdsImageItem.name + composableName = OdsComposable.OdsImageItem.name, + screenContent = { ComponentImageItem() } ) object Lists : Component( @@ -183,6 +186,7 @@ sealed class Component( R.drawable.il_navigation_drawers, null, R.string.component_modal_drawers_description, + screenContent = { ComponentModalDrawers() } ) object Progress : Component( From f1e93b63b6e25558401046e6af7b68a008be4650 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 26 Jun 2023 12:18:58 +0200 Subject: [PATCH 073/112] Rename extended top app bar into large top app bar and add its customization through bottom sheet --- .../com/orange/ods/app/ui/MainTopAppBar.kt | 6 ++-- .../orange/ods/app/ui/MainTopAppBarState.kt | 22 ++++++------ .../orange/ods/app/ui/components/Component.kt | 12 ++++--- .../app/ui/components/ComponentsNavGraph.kt | 2 +- .../appbars/top/ComponentTopAppBar.kt | 34 ++++++++++++++++--- .../appbars/top/ComponentTopAppBarExtended.kt | 18 ---------- .../top/TopAppBarCustomizationState.kt | 15 ++++++-- app/src/main/res/values/strings.xml | 10 ++++-- ...endedTopAppBar.kt => OdsLargeTopAppBar.kt} | 17 ++++++++-- 9 files changed, 88 insertions(+), 48 deletions(-) delete mode 100644 app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt rename lib/src/main/java/com/orange/ods/compose/component/appbar/top/{OdsExtendedTopAppBar.kt => OdsLargeTopAppBar.kt} (72%) 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 index 5d7faf79f..95dc9b65a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -36,7 +36,7 @@ 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.OdsExtendedTopAppBar +import com.orange.ods.compose.component.appbar.top.OdsLargeTopAppBar import com.orange.ods.compose.component.appbar.top.OdsTopAppBar import com.orange.ods.compose.component.appbar.top.OdsTopAppBarActionButton import com.orange.ods.compose.component.appbar.top.OdsTopAppBarOverflowMenuBox @@ -77,8 +77,8 @@ fun MainTopAppBar( } } - if (topAppBarState.isExtended) { - OdsExtendedTopAppBar( + if (topAppBarState.isLarge) { + OdsLargeTopAppBar( title = title, navigationIcon = navigationIcon ?: { }, onNavigationIconClick = upPress, 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 index d235fa4e1..eb6803d26 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -23,7 +23,9 @@ val LocalMainTopAppBarManager = staticCompositionLocalOf { interface MainTopAppBarManager { fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) fun updateTopAppBarTitle(titleRes: Int) - fun setExtended(extended: Boolean) + + /** If set to true, a large top app bar will be displayed **/ + fun setLargeTopAppBar(value: Boolean) fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) fun clearTopAppBarTabs() @@ -51,7 +53,7 @@ class MainTopAppBarState( val actions: MutableState>, var searchedText: MutableState, private val navigationIconEnabled: MutableState, - private var extended: MutableState, + private var large: MutableState, val tabsState: MainTabsState, ) : MainTopAppBarManager { @@ -67,18 +69,18 @@ class MainTopAppBarState( // TopAppBar state source of truth // ---------------------------------------------------------- - val isExtended: Boolean - get() = extended.value + val isLarge: Boolean + get() = large.value val isNavigationIconEnabled: Boolean get() = navigationIconEnabled.value - override fun setExtended(extended: Boolean) { - this.extended.value = extended + override fun setLargeTopAppBar(value: Boolean) { + this.large.value = value } override fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) { - extended.value = topAppBarConfiguration.isExtended + large.value = topAppBarConfiguration.isExtended navigationIconEnabled.value = topAppBarConfiguration.isNavigationIconEnabled actions.value = topAppBarConfiguration.actions } @@ -127,11 +129,11 @@ data class TopAppBarConfiguration constructor( class Builder { - private var isExtended = false + private var isLarge = false private var isNavigationIconEnabled = true private var actions = mutableListOf() - fun extended(value: Boolean) = apply { isExtended = value } + fun large(value: Boolean) = apply { isLarge = value } fun navigationIconEnabled(enabled: Boolean) = apply { isNavigationIconEnabled = enabled } @@ -147,7 +149,7 @@ data class TopAppBarConfiguration constructor( actions { add(action) } } - fun build() = TopAppBarConfiguration(isExtended, isNavigationIconEnabled, actions) + fun build() = TopAppBarConfiguration(isLarge, isNavigationIconEnabled, actions) } fun newBuilder() = Builder().apply { 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 996029268..fdc354220 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 @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import com.orange.ods.app.R import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBar -import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBarExtended import com.orange.ods.app.ui.components.banners.ComponentBanners import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation import com.orange.ods.app.ui.components.buttons.ComponentButtons @@ -61,7 +60,7 @@ sealed class Component( R.drawable.il_app_bars_top, R.drawable.il_app_bars_top_small, R.string.component_app_bars_top_description, - variants = listOf(Variant.AppBarsTopRegular, Variant.AppBarsTopExtended), + variants = listOf(Variant.AppBarsTopRegular, Variant.AppBarsTopLarge), imageAlignment = Alignment.TopCenter ) @@ -267,12 +266,15 @@ sealed class Variant( @StringRes val titleRes: Int, val composableName: String, val screenContent: @Composable () -> Unit, - val extendedTopAppBar: Boolean = false + val largeTopAppBar: Boolean = false ) { val id: Long = Variant::class.sealedSubclasses.indexOf(this::class).toLong() - object AppBarsTopRegular : Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name, { ComponentTopAppBar() }) - object AppBarsTopExtended : Variant(R.string.component_app_bars_top_extended, OdsComposable.OdsTopAppBar.name, { ComponentTopAppBarExtended() }, true) + object AppBarsTopRegular : + Variant(R.string.component_app_bars_top_regular, OdsComposable.OdsTopAppBar.name, { ComponentTopAppBar(variant = AppBarsTopRegular) }) + + object AppBarsTopLarge : + Variant(R.string.component_app_bars_top_large, OdsComposable.OdsLargeTopAppBar.name, { ComponentTopAppBar(variant = AppBarsTopLarge) }, true) object ButtonsPrimary : Variant( R.string.component_buttons_highest_emphasis, 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 5d98e1320..f73c8b014 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 @@ -49,7 +49,7 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac variant?.let { with(LocalMainTopAppBarManager.current) { updateTopAppBarTitle(variant.titleRes) - setExtended(variant.extendedTopAppBar) + setLargeTopAppBar(variant.largeTopAppBar) } variant.screenContent() 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 df5fc46fc..a39e6c088 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 @@ -25,20 +25,25 @@ 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.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.CodeImplementationColumn import com.orange.ods.app.ui.utilities.composable.FunctionCallCode +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.OdsListItem import com.orange.ods.compose.component.list.OdsSwitchTrailing import kotlin.math.max @OptIn(ExperimentalMaterialApi::class) @Composable -fun ComponentTopAppBar() { +fun ComponentTopAppBar(variant: Variant) { val customizationState = rememberTopAppBarCustomizationState() + val isLargeVariant = variant == Variant.AppBarsTopLarge with(customizationState) { val customActionCount = max(0, actionCount.value - MainTopAppBarState.DefaultConfiguration.actions.size) @@ -46,6 +51,7 @@ fun ComponentTopAppBar() { .take(customActionCount) .map { TopAppBarConfiguration.Action.Custom(stringResource(id = it.textResId), it.iconResId) } val topAppBarConfiguration = TopAppBarConfiguration.Builder() + .large(isLargeVariant) .navigationIconEnabled(isNavigationIconEnabled) .actions { addAll(MainTopAppBarState.DefaultConfiguration.actions.take(actionCount.value)) @@ -53,7 +59,11 @@ fun ComponentTopAppBar() { if (isOverflowMenuEnabled) add(TopAppBarConfiguration.Action.OverflowMenu) } .build() - LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration) + + with(LocalMainTopAppBarManager.current) { + updateTopAppBar(topAppBarConfiguration) + updateTopAppBarTitle(titleLength.value.titleResId) + } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), @@ -80,14 +90,30 @@ fun ComponentTopAppBar() { enabled = isOverflowMenuSwitchEnabled ) ) - + if (isLargeVariant) { + Subtitle(textRes = R.string.component_element_title, horizontalPadding = true) + OdsChoiceChipsFlowRow( + selectedChip = titleLength, + modifier = Modifier + .padding(horizontal = dimensionResource(id = R.dimen.spacing_m)) + .padding(bottom = dimensionResource(id = R.dimen.spacing_s)), + outlinedChips = true + ) { + OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_one_line, value = TopAppBarCustomizationState.TitleLength.OneLine) + OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_two_lines, value = TopAppBarCustomizationState.TitleLength.TwoLines) + OdsChoiceChip( + textRes = R.string.component_app_bars_top_large_title_truncated, + value = TopAppBarCustomizationState.TitleLength.Truncated + ) + } + } }) { val context = LocalContext.current Column(modifier = Modifier.verticalScroll(rememberScrollState())) { CodeImplementationColumn(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin))) { FunctionCallCode( - name = OdsComposable.OdsTopAppBar.name, + name = if (isLargeVariant) OdsComposable.OdsLargeTopAppBar.name else OdsComposable.OdsTopAppBar.name, exhaustiveParameters = false, parameters = { title(context.getString(R.string.component_app_bars_top_regular)) diff --git a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt deleted file mode 100644 index 05a1031a9..000000000 --- a/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBarExtended.kt +++ /dev/null @@ -1,18 +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.components.appbars.top - -import androidx.compose.runtime.Composable - -@Composable -fun ComponentTopAppBarExtended() { - -} \ No newline at end of file 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 bc3ef5a54..cd65498e5 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 @@ -15,6 +15,7 @@ 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.app.R import com.orange.ods.app.ui.MainTopAppBarState import com.orange.ods.app.ui.TopAppBarConfiguration @@ -29,16 +30,24 @@ fun rememberTopAppBarCustomizationState( ) ) }, + titleLength: MutableState = rememberSaveable { mutableStateOf(TopAppBarCustomizationState.TitleLength.OneLine) }, ) = - remember(navigationIconEnabled, actionCount, overflowMenuEnabled) { - TopAppBarCustomizationState(navigationIconEnabled, actionCount, overflowMenuEnabled) + remember(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLength) { + TopAppBarCustomizationState(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLength) } class TopAppBarCustomizationState( val navigationIconEnabled: MutableState, val actionCount: MutableState, - val overflowMenuEnabled: MutableState + val overflowMenuEnabled: MutableState, + val titleLength: MutableState ) { + enum class TitleLength(val titleResId: Int) { + OneLine(R.string.component_app_bars_top_large_title_one_line_value), + TwoLines(R.string.component_app_bars_top_large_title_two_lines_value), + Truncated(R.string.component_app_bars_top_large_title_truncated_value) + } + private val maxActionCount = 3 val minActionCount = 0 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b3f4fcad1..4e004c109 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,13 +99,19 @@ App bars: top The app bar is a key component and organizes titling, navigation and action buttons. Regular - Extended + Large Navigation icon Overflow menu Actions count Remove action Add action - Ice Cream + One line + Two lines + Truncated + Large: One line title + Large: Two lines title allowed in top app bar + Large: Title will be truncated if it is too long to fit in the top app bar + Bottom navigation diff --git a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt similarity index 72% rename from lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt rename to lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt index 4f21d2b68..5a00365ab 100644 --- a/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsExtendedTopAppBar.kt +++ b/lib/src/main/java/com/orange/ods/compose/component/appbar/top/OdsLargeTopAppBar.kt @@ -11,16 +11,21 @@ package com.orange.ods.compose.component.appbar.top import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.orange.ods.R import com.orange.ods.compose.component.OdsComposable import com.orange.ods.compose.theme.OdsTheme @OptIn(ExperimentalMaterial3Api::class) @Composable @OdsComposable -fun OdsExtendedTopAppBar( +fun OdsLargeTopAppBar( title: String, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, @@ -30,7 +35,15 @@ fun OdsExtendedTopAppBar( ) { val contentColor = OdsTheme.colors.component.topAppBar.barContent LargeTopAppBar( - title = { Text(text = title) }, + title = { + Text( + modifier = Modifier.padding(start = 56.dp, end = dimensionResource(id = R.dimen.spacing_m)), + text = title, + style = OdsTheme.typography.h6, + overflow = TextOverflow.Ellipsis, + maxLines = 2 + ) + }, modifier = modifier, navigationIcon = { IconButton(onClick = onNavigationIconClick) { From d32dfcbe9cfe1c7b0c6e88271e6d1297a3ccf0eb Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 3 Jul 2023 17:21:41 +0200 Subject: [PATCH 074/112] Add scroll behavior to large top app bar --- .idea/codeStyles/Project.xml | 3 +- .../java/com/orange/ods/app/ui/MainScreen.kt | 68 ++++++++++++------- .../com/orange/ods/app/ui/MainTopAppBar.kt | 22 +++--- .../orange/ods/app/ui/MainTopAppBarState.kt | 6 +- .../ui/components/ComponentDetailScreen.kt | 57 +++++++--------- .../app/ui/components/ComponentsNavGraph.kt | 47 +++++++++---- .../appbars/top/ComponentTopAppBar.kt | 10 ++- .../component/appbar/top/OdsLargeTopAppBar.kt | 44 +++++++++++- 8 files changed, 175 insertions(+), 82 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index db9926f35..a92b7a9a0 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,7 +1,8 @@ - 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 390064502..5b06154da 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 @@ -19,9 +19,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.AppBarDefaults import androidx.compose.material.Scaffold import androidx.compose.material.Surface +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.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.unit.dp @@ -50,6 +55,7 @@ import com.orange.ods.utilities.extension.orElse import com.orange.ods.xml.theme.OdsXml import com.orange.ods.xml.utilities.extension.xml +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen(themeConfigurations: Set, mainViewModel: MainViewModel = viewModel()) { val isSystemInDarkTheme = isSystemInDarkTheme() @@ -90,7 +96,23 @@ fun MainScreen(themeConfigurations: Set, mainView themeConfiguration = mainState.themeState.currentThemeConfiguration, darkThemeEnabled = configuration.isDarkModeEnabled ) { + val topBarScrollBehavior: TopAppBarScrollBehavior? + val modifier: Modifier + if (mainState.topAppBarState.isLarge) { + topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) + val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection } + modifier = Modifier.nestedScroll(nestedScrollConnection) + } else { + topBarScrollBehavior = null + modifier = Modifier + } + + val showTabs by remember { + derivedStateOf { mainState.topAppBarState.tabsState.hasTabs } + } + Scaffold( + modifier = modifier, backgroundColor = OdsTheme.colors.background, topBar = { Surface(elevation = if (isSystemInDarkTheme()) 0.dp else AppBarDefaults.TopAppBarElevation) { @@ -98,14 +120,16 @@ fun MainScreen(themeConfigurations: Set, mainView SystemBarsColorSideEffect() MainTopAppBar( shouldShowUpNavigationIcon = !mainState.shouldShowBottomBar, - topAppBarState = mainState.topAppBarState, + topAppBarStateProvider = { mainState.topAppBarState }, upPress = mainState::upPress, onSearchActionClick = { mainState.navController.navigate(MainDestinations.SearchRoute) - } + }, + scrollBehavior = topBarScrollBehavior ) - // Display tabs in the top bar if needed - MainTabs(mainTabsState = mainState.topAppBarState.tabsState) + if (showTabs) { + MainTabs(mainTabsState = mainState.topAppBarState.tabsState) + } } } }, @@ -156,25 +180,23 @@ private fun SystemBarsColorSideEffect() { private fun MainTabs(mainTabsState: MainTabsState) { with(mainTabsState) { pagerState?.let { pagerState -> - if (hasTabs) { - // Do not use tabs directly because this is a SnapshotStateList - // Thus its value can be modified and can lead to crashes if it becomes empty - val tabs = tabs.toList() - if (scrollableTabs.value) { - ScrollableTabRow( - tabs = tabs, - pagerState = pagerState, - tabIconType = tabIconType.value, - tabTextEnabled = tabTextEnabled.value - ) - } else { - FixedTabRow( - tabs = tabs, - pagerState = pagerState, - tabIconType = tabIconType.value, - tabTextEnabled = tabTextEnabled.value - ) - } + // Do not use tabs directly because this is a SnapshotStateList + // Thus its value can be modified and can lead to crashes if it becomes empty + val tabs = tabs.toList() + if (scrollableTabs.value) { + ScrollableTabRow( + tabs = tabs, + pagerState = pagerState, + tabIconType = tabIconType.value, + tabTextEnabled = tabTextEnabled.value + ) + } else { + FixedTabRow( + tabs = tabs, + pagerState = pagerState, + tabIconType = tabIconType.value, + tabTextEnabled = tabTextEnabled.value + ) } } } 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 index 95dc9b65a..a4e435daa 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -19,6 +19,7 @@ import androidx.compose.material.Icon 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 @@ -51,16 +52,18 @@ import com.orange.ods.compose.theme.OdsTheme @Composable fun MainTopAppBar( shouldShowUpNavigationIcon: Boolean, - topAppBarState: MainTopAppBarState, + topAppBarStateProvider: () -> MainTopAppBarState, upPress: () -> Unit, onSearchActionClick: () -> Unit, - mainViewModel: MainViewModel = viewModel() + mainViewModel: MainViewModel = viewModel(), + scrollBehavior: TopAppBarScrollBehavior? ) { val themeManager = LocalThemeManager.current var changeThemeDialogVisible by remember { mutableStateOf(false) } + val showSearchAction = topAppBarStateProvider().titleRes.value == R.string.navigation_item_search - val title = stringResource(id = topAppBarState.titleRes.value) - val navigationIcon: (@Composable () -> Unit)? = if (shouldShowUpNavigationIcon && topAppBarState.isNavigationIconEnabled) { + val title = stringResource(id = topAppBarStateProvider().titleRes.value) + val navigationIcon: (@Composable () -> Unit)? = if (shouldShowUpNavigationIcon && topAppBarStateProvider().isNavigationIconEnabled) { { Icon( imageVector = Icons.Filled.ArrowBack, @@ -70,19 +73,20 @@ fun MainTopAppBar( } else null val actions: @Composable RowScope.() -> Unit = { - if (topAppBarState.titleRes.value == R.string.navigation_item_search) { - TopAppBarSearchAction(searchedText = topAppBarState.searchedText) + if (showSearchAction) { + TopAppBarSearchAction(searchedText = topAppBarStateProvider().searchedText) } else { - TopAppBarActions(topAppBarState, onSearchActionClick) { changeThemeDialogVisible = true } + TopAppBarActions(topAppBarStateProvider(), onSearchActionClick) { changeThemeDialogVisible = true } } } - if (topAppBarState.isLarge) { + if (topAppBarStateProvider().isLarge) { OdsLargeTopAppBar( title = title, navigationIcon = navigationIcon ?: { }, onNavigationIconClick = upPress, - actions = actions + actions = actions, + scrollBehavior = scrollBehavior ) } else { OdsTopAppBar( 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 index eb6803d26..21bf5a6a6 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -12,8 +12,12 @@ package com.orange.ods.app.ui import android.os.Parcelable import androidx.annotation.DrawableRes -import androidx.compose.runtime.* +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 kotlinx.parcelize.Parcelize 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 6dd4e71ac..e3d597023 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 @@ -16,60 +16,53 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier 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.utilities.DrawableManager +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 -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.composable.DetailScreenHeader @Composable fun ComponentDetailScreen( - componentId: Long, + component: Component, onVariantClick: (Long) -> Unit, onDemoClick: () -> Unit ) { val context = LocalContext.current - val component = remember { components.firstOrNull { component -> component.id == componentId } } - - component?.let { - LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(bottom = dimensionResource(id = R.dimen.screen_vertical_margin)) + ) { + DetailScreenHeader( + imageRes = DrawableManager.getDrawableResIdForCurrentTheme(resId = component.imageRes), + imageAlignment = component.imageAlignment, + descriptionRes = component.descriptionRes + ) Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(bottom = dimensionResource(id = R.dimen.screen_vertical_margin)) + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_m)) ) { - DetailScreenHeader( - imageRes = DrawableManager.getDrawableResIdForCurrentTheme(resId = component.imageRes), - imageAlignment = component.imageAlignment, - descriptionRes = component.descriptionRes - ) - Column( - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_m)) - ) { - if (component.variants.isEmpty()) { - ComponentDetailLinkItem( - label = context.getString(R.string.component_demo, context.getString(component.titleRes)), - composableName = component.composableName - ) { onDemoClick() } - } else { - component.variants.forEach { variant -> - ComponentDetailLinkItem(label = stringResource(id = variant.titleRes), composableName = variant.composableName) { - onVariantClick(variant.id) - } + if (component.variants.isEmpty()) { + ComponentDetailLinkItem( + label = context.getString(R.string.component_demo, context.getString(component.titleRes)), + composableName = component.composableName + ) { onDemoClick() } + } else { + component.variants.forEach { variant -> + ComponentDetailLinkItem(label = stringResource(id = variant.titleRes), composableName = variant.composableName) { + onVariantClick(variant.id) } } - } + } } } 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 f73c8b014..a49c4b535 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 @@ -10,7 +10,11 @@ package com.orange.ods.app.ui.components +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType @@ -21,37 +25,56 @@ import com.orange.ods.app.ui.MainDestinations import com.orange.ods.app.ui.MainTopAppBarState fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBackStackEntry) -> Unit) { + composable( "${MainDestinations.ComponentDetailRoute}/{${MainDestinations.ComponentIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) ) { from -> + // Lot of recompositions with this line LocalMainTopAppBarManager.current.reset() val arguments = requireNotNull(from.arguments) - val componentId = arguments.getLong(MainDestinations.ComponentIdKey) - ComponentDetailScreen( - componentId = componentId, - onVariantClick = { variantId -> navigateToElement(MainDestinations.ComponentVariantRoute, variantId, from) }, - onDemoClick = { navigateToElement(MainDestinations.ComponentDemoRoute, componentId, from) } - ) + var currentComponentId: Long by remember { mutableStateOf(-1) } + val routeComponentId = arguments.getLong(MainDestinations.ComponentIdKey) + val shouldUpdateTitle by remember { + derivedStateOf { currentComponentId != routeComponentId } + } + + val component = remember(routeComponentId) { components.firstOrNull { component -> component.id == routeComponentId } } + component?.let { + if (shouldUpdateTitle) { + LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) + currentComponentId = routeComponentId + } + ComponentDetailScreen( + component = component, + onVariantClick = { variantId -> navigateToElement(MainDestinations.ComponentVariantRoute, variantId, from) }, + onDemoClick = { navigateToElement(MainDestinations.ComponentDemoRoute, routeComponentId, from) } + ) + } } composable( "${MainDestinations.ComponentVariantRoute}/{${MainDestinations.ComponentVariantIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentVariantIdKey) { type = NavType.LongType }) ) { from -> + // Lot of recompositions with this line LocalMainTopAppBarManager.current.reset() val arguments = requireNotNull(from.arguments) - val variantId = arguments.getLong(MainDestinations.ComponentVariantIdKey) - val variant = remember { components.flatMap { it.variants }.firstOrNull { it.id == variantId } } + var currentVariantId: Long by remember { mutableStateOf(-1) } + val routeVariantId = arguments.getLong(MainDestinations.ComponentVariantIdKey) + val variant = remember(routeVariantId) { components.flatMap { it.variants }.firstOrNull { it.id == routeVariantId } } + val shouldUpdateTitle by remember { + derivedStateOf { currentVariantId != routeVariantId } + } variant?.let { - with(LocalMainTopAppBarManager.current) { - updateTopAppBarTitle(variant.titleRes) - setLargeTopAppBar(variant.largeTopAppBar) + if (shouldUpdateTitle) { + LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) + currentVariantId = routeVariantId } - + if (variant.largeTopAppBar) LocalMainTopAppBarManager.current.setLargeTopAppBar(true) variant.screenContent() } } 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 a39e6c088..6526d8be0 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 @@ -37,6 +37,10 @@ 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.text.OdsTextH1 +import com.orange.ods.compose.text.OdsTextH2 +import com.orange.ods.compose.text.OdsTextH3 +import com.orange.ods.compose.text.OdsTextH4 import kotlin.math.max @OptIn(ExperimentalMaterialApi::class) @@ -62,7 +66,7 @@ fun ComponentTopAppBar(variant: Variant) { with(LocalMainTopAppBarManager.current) { updateTopAppBar(topAppBarConfiguration) - updateTopAppBarTitle(titleLength.value.titleResId) + if (isLargeVariant) updateTopAppBarTitle(titleLength.value.titleResId) } ComponentCustomizationBottomSheetScaffold( @@ -111,6 +115,10 @@ fun ComponentTopAppBar(variant: Variant) { val context = LocalContext.current Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + OdsTextH1(text = "test pour scroll") + OdsTextH2(text = "test pour scroll") + OdsTextH3(text = "test pour scroll") + OdsTextH4(text = "test pour scroll") CodeImplementationColumn(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin))) { FunctionCallCode( name = if (isLargeVariant) OdsComposable.OdsLargeTopAppBar.name else OdsComposable.OdsTopAppBar.name, 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 5a00365ab..20a9c7c25 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 @@ -12,9 +12,18 @@ package com.orange.ods.compose.component.appbar.top import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -34,14 +43,43 @@ fun OdsLargeTopAppBar( scrollBehavior: TopAppBarScrollBehavior? = null ) { val contentColor = OdsTheme.colors.component.topAppBar.barContent + val stateChangeFraction = 0.7 + + val titleStartPadding by remember { + derivedStateOf { + if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= 0.85) 8.dp else 48.dp + } + } + + val titleAlpha by remember { + derivedStateOf { + if (scrollBehavior != null) { + when (scrollBehavior.state.collapsedFraction) { + in 0.0..stateChangeFraction -> 1 - (scrollBehavior.state.collapsedFraction * 1.5) + in (stateChangeFraction + 0.15)..1.0 -> 0 + scrollBehavior.state.collapsedFraction + else -> 0 + }.toFloat() + } else { + 1.0f + } + } + } + val titleMaxLines by remember { + derivedStateOf { + if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= stateChangeFraction) 1 else 2 + } + } + LargeTopAppBar( title = { Text( - modifier = Modifier.padding(start = 56.dp, end = dimensionResource(id = R.dimen.spacing_m)), + modifier = Modifier + .padding(start = titleStartPadding, end = dimensionResource(id = R.dimen.spacing_m)) + .alpha(titleAlpha), text = title, style = OdsTheme.typography.h6, overflow = TextOverflow.Ellipsis, - maxLines = 2 + maxLines = titleMaxLines, ) }, modifier = modifier, From 2d042320db6fd8ba4f2526d25a827e4ca53806e2 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Mon, 3 Jul 2023 18:13:17 +0200 Subject: [PATCH 075/112] Optimize recompositions --- .../main/java/com/orange/ods/app/ui/MainScreen.kt | 14 +++++++++----- .../com/orange/ods/app/ui/MainTopAppBarState.kt | 13 +++++++------ .../ods/app/ui/components/ComponentsNavGraph.kt | 4 +--- 3 files changed, 17 insertions(+), 14 deletions(-) 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 5b06154da..73425f87e 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 @@ -98,7 +98,15 @@ fun MainScreen(themeConfigurations: Set, mainView ) { val topBarScrollBehavior: TopAppBarScrollBehavior? val modifier: Modifier - if (mainState.topAppBarState.isLarge) { + + val showTabs by remember { + derivedStateOf { mainState.topAppBarState.tabsState.hasTabs } + } + val displayLargeTopAppBar by remember { + derivedStateOf { mainState.topAppBarState.isLarge } + } + + if (displayLargeTopAppBar) { topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection } modifier = Modifier.nestedScroll(nestedScrollConnection) @@ -107,10 +115,6 @@ fun MainScreen(themeConfigurations: Set, mainView modifier = Modifier } - val showTabs by remember { - derivedStateOf { mainState.topAppBarState.tabsState.hasTabs } - } - Scaffold( modifier = modifier, backgroundColor = OdsTheme.colors.background, 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 index 21bf5a6a6..3e6b56362 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -44,11 +44,11 @@ fun rememberMainTopAppBarState( actions: MutableState> = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.actions) }, navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.isNavigationIconEnabled) }, searchedText: MutableState = remember { mutableStateOf(TextFieldValue("")) }, - extended: MutableState = rememberSaveable { mutableStateOf(false) }, + large: MutableState = rememberSaveable { mutableStateOf(false) }, tabsState: MainTabsState = rememberMainTabsState(), ) = - remember(titleRes, actions, searchedText, navigationIconEnabled, extended, tabsState) { - MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, extended, tabsState) + remember(titleRes, actions, searchedText, navigationIconEnabled, large, tabsState) { + MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, large, tabsState) } @@ -63,7 +63,7 @@ class MainTopAppBarState( companion object { val DefaultConfiguration = TopAppBarConfiguration( - isExtended = false, + isLarge = false, isNavigationIconEnabled = true, actions = listOf(TopAppBarConfiguration.Action.Theme, TopAppBarConfiguration.Action.Mode) ) @@ -84,7 +84,7 @@ class MainTopAppBarState( } override fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) { - large.value = topAppBarConfiguration.isExtended + large.value = topAppBarConfiguration.isLarge navigationIconEnabled.value = topAppBarConfiguration.isNavigationIconEnabled actions.value = topAppBarConfiguration.actions } @@ -108,7 +108,7 @@ class MainTopAppBarState( } data class TopAppBarConfiguration constructor( - val isExtended: Boolean, + val isLarge: Boolean, val isNavigationIconEnabled: Boolean, val actions: List ) { @@ -158,6 +158,7 @@ data class TopAppBarConfiguration constructor( fun newBuilder() = Builder().apply { navigationIconEnabled(isNavigationIconEnabled) + large(isLarge) actions { clear() addAll(actions) 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 a49c4b535..033b19735 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 @@ -30,7 +30,6 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac "${MainDestinations.ComponentDetailRoute}/{${MainDestinations.ComponentIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) ) { from -> - // Lot of recompositions with this line LocalMainTopAppBarManager.current.reset() val arguments = requireNotNull(from.arguments) @@ -58,7 +57,6 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac "${MainDestinations.ComponentVariantRoute}/{${MainDestinations.ComponentVariantIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentVariantIdKey) { type = NavType.LongType }) ) { from -> - // Lot of recompositions with this line LocalMainTopAppBarManager.current.reset() val arguments = requireNotNull(from.arguments) @@ -74,7 +72,7 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) currentVariantId = routeVariantId } - if (variant.largeTopAppBar) LocalMainTopAppBarManager.current.setLargeTopAppBar(true) + LocalMainTopAppBarManager.current.setLargeTopAppBar(variant.largeTopAppBar) variant.screenContent() } } From 2f9ec955f4710602222040e6dcbc0309b4d0577d Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 4 Jul 2023 15:01:19 +0200 Subject: [PATCH 076/112] Fix top app bar state update not working --- .../com/orange/ods/app/ui/MainBottomNavigation.kt | 14 ++++---------- .../main/java/com/orange/ods/app/ui/MainScreen.kt | 5 +---- .../main/java/com/orange/ods/app/ui/MainState.kt | 4 ++++ .../com/orange/ods/app/ui/MainTopAppBarState.kt | 8 -------- .../com/orange/ods/app/ui/about/AboutNavGraph.kt | 3 ++- .../ods/app/ui/components/ComponentsNavGraph.kt | 15 ++++++--------- .../ods/app/ui/guidelines/GuidelinesNavGraph.kt | 7 ++++--- 7 files changed, 21 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt b/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt index 82ab3e380..ba1ba062a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainBottomNavigation.kt @@ -51,29 +51,23 @@ fun NavGraphBuilder.addBottomNavigationGraph(navigateToElement: (String, Long?, val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder() .prependAction(TopAppBarConfiguration.Action.Search) .build() - with(LocalMainTopAppBarManager.current) { - updateTopAppBar(topAppBarConfiguration) - clearTopAppBarTabs() - } + LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration) GuidelinesScreen(onGuidelineClick = { route -> navigateToElement(route, null, from) }) } composable(BottomNavigationSections.Components.route) { from -> val topAppBarConfiguration = MainTopAppBarState.DefaultConfiguration.newBuilder() .prependAction(TopAppBarConfiguration.Action.Search) .build() - with(LocalMainTopAppBarManager.current) { - updateTopAppBar(topAppBarConfiguration) - clearTopAppBarTabs() - } + LocalMainTopAppBarManager.current.updateTopAppBar(topAppBarConfiguration) ComponentsScreen(onComponentClick = { id -> navigateToElement(MainDestinations.ComponentDetailRoute, id, from) }) } composable(BottomNavigationSections.Modules.route) { - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) ModulesScreen() } composable(BottomNavigationSections.About.route) { from -> val context = LocalContext.current - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) AboutScreen(onAboutItemClick = { id -> val aboutItem = aboutItems.firstOrNull { it.id == id } if (aboutItem is UrlAboutItem) { 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 73425f87e..94ad7ab7c 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 @@ -221,10 +221,7 @@ private fun NavGraphBuilder.mainNavGraph(navigateToElement: (String, Long?, NavB composable( route = MainDestinations.SearchRoute ) { from -> - with(LocalMainTopAppBarManager.current) { - clearTopAppBarTabs() - updateTopAppBarTitle(R.string.navigation_item_search) - } + LocalMainTopAppBarManager.current.updateTopAppBarTitle(R.string.navigation_item_search) SearchScreen( searchedText, onResultItemClick = { route, id -> navigateToElement(route, id, from) } 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 f15bf6456..5d15aecd0 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 @@ -84,6 +84,10 @@ class MainState( get() = navController.currentDestination?.route fun upPress() { + with(topAppBarState) { + updateTopAppBar(MainTopAppBarState.DefaultConfiguration) + clearTopAppBarTabs() + } navController.navigateUp() } 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 index 3e6b56362..cc29dd46a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -33,9 +33,6 @@ interface MainTopAppBarManager { fun updateTopAppBarTabs(tabsConfiguration: TabsConfiguration) fun clearTopAppBarTabs() - - /** Restore default values for tabs and top app bar */ - fun reset() } @Composable @@ -100,11 +97,6 @@ class MainTopAppBarState( override fun clearTopAppBarTabs() { tabsState.clearTopAppBarTabs() } - - override fun reset() { - updateTopAppBar(DefaultConfiguration) - clearTopAppBarTabs() - } } data class TopAppBarConfiguration constructor( 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 97dae50d6..b12e4388e 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 @@ -16,13 +16,14 @@ 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 fun NavGraphBuilder.addAboutGraph() { composable( "${MainDestinations.AboutItemDetailRoute}/{${MainDestinations.AboutItemIdKey}}", arguments = listOf(navArgument(MainDestinations.AboutItemIdKey) { type = NavType.LongType }) ) { backStackEntry -> - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) val arguments = requireNotNull(backStackEntry.arguments) val aboutItemId = arguments.getLong(MainDestinations.AboutItemIdKey) AboutFileScreen(aboutItemId) 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 033b19735..dda3d9648 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 @@ -30,18 +30,17 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac "${MainDestinations.ComponentDetailRoute}/{${MainDestinations.ComponentIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentIdKey) { type = NavType.LongType }) ) { from -> - LocalMainTopAppBarManager.current.reset() - val arguments = requireNotNull(from.arguments) var currentComponentId: Long by remember { mutableStateOf(-1) } val routeComponentId = arguments.getLong(MainDestinations.ComponentIdKey) - val shouldUpdateTitle by remember { + val hasComponentIdChanged by remember { derivedStateOf { currentComponentId != routeComponentId } } val component = remember(routeComponentId) { components.firstOrNull { component -> component.id == routeComponentId } } component?.let { - if (shouldUpdateTitle) { + if (hasComponentIdChanged) { + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) LocalMainTopAppBarManager.current.updateTopAppBarTitle(component.titleRes) currentComponentId = routeComponentId } @@ -57,22 +56,20 @@ fun NavGraphBuilder.addComponentsGraph(navigateToElement: (String, Long?, NavBac "${MainDestinations.ComponentVariantRoute}/{${MainDestinations.ComponentVariantIdKey}}", arguments = listOf(navArgument(MainDestinations.ComponentVariantIdKey) { type = NavType.LongType }) ) { from -> - LocalMainTopAppBarManager.current.reset() - val arguments = requireNotNull(from.arguments) var currentVariantId: Long by remember { mutableStateOf(-1) } val routeVariantId = arguments.getLong(MainDestinations.ComponentVariantIdKey) val variant = remember(routeVariantId) { components.flatMap { it.variants }.firstOrNull { it.id == routeVariantId } } - val shouldUpdateTitle by remember { + val hasVariantIdChanged by remember { derivedStateOf { currentVariantId != routeVariantId } } variant?.let { - if (shouldUpdateTitle) { + if (hasVariantIdChanged) { LocalMainTopAppBarManager.current.updateTopAppBarTitle(variant.titleRes) + LocalMainTopAppBarManager.current.setLargeTopAppBar(variant.largeTopAppBar) currentVariantId = routeVariantId } - LocalMainTopAppBarManager.current.setLargeTopAppBar(variant.largeTopAppBar) variant.screenContent() } } 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 1d946c6f3..85c391a4e 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 @@ -14,21 +14,22 @@ 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 fun NavGraphBuilder.addGuidelinesGraph() { composable(MainDestinations.GuidelineColor) { - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) GuidelineColorScreen() } composable(MainDestinations.GuidelineTypography) { - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) GuidelineTypographyScreen() } composable(MainDestinations.GuidelineSpacing) { - LocalMainTopAppBarManager.current.reset() + LocalMainTopAppBarManager.current.updateTopAppBar(MainTopAppBarState.DefaultConfiguration) GuidelineSpacingScreen() } } From fd6166e3de7d45ff7cab211952541723bce6abbe Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Tue, 4 Jul 2023 16:35:18 +0200 Subject: [PATCH 077/112] Fix display of buttons icons --- .../java/com/orange/ods/app/ui/components/Component.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 fdc354220..6cd2d1096 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 @@ -19,6 +19,7 @@ import com.orange.ods.app.ui.components.appbars.top.ComponentTopAppBar import com.orange.ods.app.ui.components.banners.ComponentBanners import com.orange.ods.app.ui.components.bottomnavigation.ComponentBottomNavigation import com.orange.ods.app.ui.components.buttons.ComponentButtons +import com.orange.ods.app.ui.components.buttons.icons.ComponentButtonsIcons import com.orange.ods.app.ui.components.cards.ComponentCard import com.orange.ods.app.ui.components.checkboxes.ComponentCheckboxes import com.orange.ods.app.ui.components.chips.Chip @@ -295,12 +296,15 @@ sealed class Variant( "${OdsComposable.OdsButton.name} with a functional style", { ComponentButtons(variant = ButtonsFunctional) }) - object ButtonsIcon : Variant(R.string.component_buttons_icon, OdsComposable.OdsIconButton.name, { ComponentButtons(variant = ButtonsIcon) }) + object ButtonsIcon : Variant(R.string.component_buttons_icon, OdsComposable.OdsIconButton.name, { ComponentButtonsIcons(variant = ButtonsIcon) }) object ButtonsIconToggle : - Variant(R.string.component_buttons_icon_toggle, OdsComposable.OdsIconToggleButton.name, { ComponentButtons(variant = ButtonsIconToggle) }) + Variant(R.string.component_buttons_icon_toggle, OdsComposable.OdsIconToggleButton.name, { ComponentButtonsIcons(variant = ButtonsIconToggle) }) object ButtonsIconToggleGroup : - Variant(R.string.component_buttons_icon_toggle_group, OdsComposable.OdsIconToggleButtonsRow.name, { ComponentButtons(variant = ButtonsIconToggle) }) + Variant( + R.string.component_buttons_icon_toggle_group, + OdsComposable.OdsIconToggleButtonsRow.name, + { ComponentButtonsIcons(variant = ButtonsIconToggleGroup) }) object CardVerticalImageFirst : Variant(R.string.component_card_vertical_image_first, OdsComposable.OdsVerticalImageFirstCard.name, { ComponentCard(variant = CardVerticalImageFirst) }) From eb5dfa447a3af8be9182d6cecb110e2a45cf406d Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 5 Jul 2023 15:02:45 +0200 Subject: [PATCH 078/112] Add scroll behavior customization element --- .../com/orange/ods/app/ui/MainTopAppBar.kt | 2 +- .../orange/ods/app/ui/MainTopAppBarState.kt | 19 +- .../appbars/top/ComponentTopAppBar.kt | 288 ++++++++++++------ .../top/TopAppBarCustomizationState.kt | 18 +- app/src/main/res/values/strings.xml | 9 +- .../component/appbar/top/OdsLargeTopAppBar.kt | 20 +- 6 files changed, 243 insertions(+), 113 deletions(-) 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 index a4e435daa..fb7b0bd8a 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBar.kt @@ -86,7 +86,7 @@ fun MainTopAppBar( navigationIcon = navigationIcon ?: { }, onNavigationIconClick = upPress, actions = actions, - scrollBehavior = scrollBehavior + scrollBehavior = if (topAppBarStateProvider().hasScrollBehavior) scrollBehavior else null ) } else { OdsTopAppBar( 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 index cc29dd46a..a12460037 100644 --- a/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt +++ b/app/src/main/java/com/orange/ods/app/ui/MainTopAppBarState.kt @@ -20,6 +20,7 @@ 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") } @@ -42,10 +43,11 @@ fun rememberMainTopAppBarState( navigationIconEnabled: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.isNavigationIconEnabled) }, searchedText: MutableState = remember { mutableStateOf(TextFieldValue("")) }, large: MutableState = rememberSaveable { mutableStateOf(false) }, + scrollBehavior: MutableState = rememberSaveable { mutableStateOf(TopAppBarCustomizationState.ScrollBehavior.Collapsible) }, tabsState: MainTabsState = rememberMainTabsState(), ) = - remember(titleRes, actions, searchedText, navigationIconEnabled, large, tabsState) { - MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, large, tabsState) + remember(titleRes, actions, searchedText, navigationIconEnabled, large, scrollBehavior, tabsState) { + MainTopAppBarState(titleRes, actions, searchedText, navigationIconEnabled, large, scrollBehavior, tabsState) } @@ -55,12 +57,14 @@ class MainTopAppBarState( 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) ) @@ -73,6 +77,9 @@ class MainTopAppBarState( val isLarge: Boolean get() = large.value + val hasScrollBehavior: Boolean + get() = scrollBehavior.value != TopAppBarCustomizationState.ScrollBehavior.None + val isNavigationIconEnabled: Boolean get() = navigationIconEnabled.value @@ -82,6 +89,7 @@ class MainTopAppBarState( override fun updateTopAppBar(topAppBarConfiguration: TopAppBarConfiguration) { large.value = topAppBarConfiguration.isLarge + scrollBehavior.value = topAppBarConfiguration.scrollBehavior navigationIconEnabled.value = topAppBarConfiguration.isNavigationIconEnabled actions.value = topAppBarConfiguration.actions } @@ -101,6 +109,7 @@ class MainTopAppBarState( data class TopAppBarConfiguration constructor( val isLarge: Boolean, + val scrollBehavior: TopAppBarCustomizationState.ScrollBehavior, val isNavigationIconEnabled: Boolean, val actions: List ) { @@ -126,11 +135,14 @@ data class TopAppBarConfiguration constructor( 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() } @@ -145,12 +157,13 @@ data class TopAppBarConfiguration constructor( actions { add(action) } } - fun build() = TopAppBarConfiguration(isLarge, isNavigationIconEnabled, actions) + 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/components/appbars/top/ComponentTopAppBar.kt b/app/src/main/java/com/orange/ods/app/ui/components/appbars/top/ComponentTopAppBar.kt index 6526d8be0..2e2e688e4 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 @@ -10,17 +10,25 @@ package com.orange.ods.app.ui.components.appbars.top -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding +import androidx.compose.animation.core.* +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale 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.unit.dp import com.orange.ods.app.R import com.orange.ods.app.ui.LocalMainTopAppBarManager import com.orange.ods.app.ui.MainTopAppBarState @@ -29,18 +37,15 @@ 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.CodeImplementationColumn -import com.orange.ods.app.ui.utilities.composable.FunctionCallCode -import com.orange.ods.app.ui.utilities.composable.Subtitle +import com.orange.ods.app.ui.utilities.composable.* 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.text.OdsTextH1 -import com.orange.ods.compose.text.OdsTextH2 -import com.orange.ods.compose.text.OdsTextH3 -import com.orange.ods.compose.text.OdsTextH4 +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) @@ -56,6 +61,7 @@ fun ComponentTopAppBar(variant: Variant) { .map { TopAppBarConfiguration.Action.Custom(stringResource(id = it.textResId), it.iconResId) } val topAppBarConfiguration = TopAppBarConfiguration.Builder() .large(isLargeVariant) + .scrollBehavior(scrollBehavior.value) .navigationIconEnabled(isNavigationIconEnabled) .actions { addAll(MainTopAppBarState.DefaultConfiguration.actions.take(actionCount.value)) @@ -66,110 +72,200 @@ fun ComponentTopAppBar(variant: Variant) { with(LocalMainTopAppBarManager.current) { updateTopAppBar(topAppBarConfiguration) - if (isLargeVariant) updateTopAppBarTitle(titleLength.value.titleResId) + if (isLargeVariant) updateTopAppBarTitle(titleLineNum.value.titleResId) } ComponentCustomizationBottomSheetScaffold( bottomSheetScaffoldState = rememberBottomSheetScaffoldState(), - bottomSheetContent = { - OdsListItem( - text = stringResource(id = R.string.component_app_bars_top_element_navigation_icon), - trailing = OdsSwitchTrailing( - checked = navigationIconEnabled - ) - ) - ComponentCountRow( - modifier = Modifier.padding(start = dimensionResource(id = R.dimen.screen_horizontal_margin)), - title = stringResource(id = R.string.component_app_bars_top_actions_count), - count = actionCount, - minusIconContentDescription = stringResource(id = R.string.component_app_bars_top_remove_action), - plusIconContentDescription = stringResource(id = R.string.component_app_bars_top_add_action), - minCount = minActionCount, - maxCount = maxActionCountSelectable - ) - OdsListItem( - text = stringResource(id = R.string.component_app_bars_top_element_overflow_menu), - trailing = OdsSwitchTrailing( - checked = overflowMenuEnabled, - enabled = isOverflowMenuSwitchEnabled - ) - ) + bottomSheetContent = { CustomizationBottomSheetContent(customizationState = customizationState, isLargeVariant = isLargeVariant) } + ) { + val context = LocalContext.current + Column( + modifier = Modifier + .padding(vertical = dimensionResource(id = R.dimen.screen_vertical_margin)) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { if (isLargeVariant) { - Subtitle(textRes = R.string.component_element_title, horizontalPadding = true) - OdsChoiceChipsFlowRow( - selectedChip = titleLength, + OdsTextBody2(text = stringResource(id = R.string.component_app_bars_top_large_scrolling_upward)) + BlinkingChevronDown( modifier = Modifier - .padding(horizontal = dimensionResource(id = R.dimen.spacing_m)) - .padding(bottom = dimensionResource(id = R.dimen.spacing_s)), - outlinedChips = true - ) { - OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_one_line, value = TopAppBarCustomizationState.TitleLength.OneLine) - OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_two_lines, value = TopAppBarCustomizationState.TitleLength.TwoLines) - OdsChoiceChip( - textRes = R.string.component_app_bars_top_large_title_truncated, - value = TopAppBarCustomizationState.TitleLength.Truncated - ) - } + .rotate(180f) + .padding(vertical = dimensionResource(id = R.dimen.spacing_s)) + ) } - }) { - - val context = LocalContext.current - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - OdsTextH1(text = "test pour scroll") - OdsTextH2(text = "test pour scroll") - OdsTextH3(text = "test pour scroll") - OdsTextH4(text = "test pour scroll") - CodeImplementationColumn(modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin))) { - FunctionCallCode( - name = if (isLargeVariant) OdsComposable.OdsLargeTopAppBar.name else OdsComposable.OdsTopAppBar.name, - exhaustiveParameters = false, - parameters = { - title(context.getString(R.string.component_app_bars_top_regular)) + CodeImplementationColumn( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)), + contentBackground = false + ) { + CodeBackgroundColumn { + FunctionCallCode( + name = if (isLargeVariant) OdsComposable.OdsLargeTopAppBar.name else OdsComposable.OdsTopAppBar.name, + exhaustiveParameters = false, + parameters = { + title(context.getString(R.string.component_app_bars_top_regular)) - if (isNavigationIconEnabled) { - composable(name = "navigationIcon") { - FunctionCallCode( - name = "Icon", - parameters = { - simple("imageVector", "") - contentDescription(context.getString(R.string.top_app_bar_back_icon_desc)) - } - ) + if (isNavigationIconEnabled) { + composable(name = "navigationIcon") { + FunctionCallCode( + name = "Icon", + parameters = { + simple("imageVector", "") + contentDescription(context.getString(R.string.top_app_bar_back_icon_desc)) + } + ) + } } - } - composable(name = "actions") { - repeat(actionCount.value) { - FunctionCallCode( - name = OdsComposable.OdsTopAppBarActionButton.name, - parameters = { - onClick() - painter() - contentDescription("icon description") - } - ) - } - if (isOverflowMenuEnabled) { - FunctionCallCode( - name = OdsComposable.OdsTopAppBarOverflowMenuBox.name, - parameters = { string("overflowIconContentDescription", "Open overflow menu") } - ) { - for (i in 1..2) { - FunctionCallCode( - name = OdsComposable.OdsDropdownMenuItem.name, - parameters = { - text("Menu $i") - onClick() - } - ) + composable(name = "actions") { + repeat(actionCount.value) { + FunctionCallCode( + name = OdsComposable.OdsTopAppBarActionButton.name, + parameters = { + onClick() + painter() + contentDescription("icon description") + } + ) + } + if (isOverflowMenuEnabled) { + FunctionCallCode( + name = OdsComposable.OdsTopAppBarOverflowMenuBox.name, + parameters = { string("overflowIconContentDescription", "Open overflow menu") } + ) { + for (i in 1..2) { + FunctionCallCode( + name = OdsComposable.OdsDropdownMenuItem.name, + parameters = { + text("Menu $i") + onClick() + } + ) + } } } } + + simple("scrollBehavior", "") } - } + ) + } + OdsTextBody2( + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing) + ) + OdsTextCaption( + modifier = Modifier.padding(bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_1) ) + CodeBackgroundColumn { + TechnicalText(text = "val topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())") + } + OdsTextCaption( + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_2) + ) + CodeBackgroundColumn { + TechnicalText(text = "val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection }") + TechnicalText(text = "Scaffold(modifier = Modifier.nestedScroll(nestedScrollConnection), ...) { ... }") + } + } + + if (isLargeVariant) { + BlinkingChevronDown(modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.spacing_s))) + OdsTextBody2(text = stringResource(id = R.string.component_app_bars_top_large_scrolling_downward)) } } } } } + +@Composable +private fun CustomizationBottomSheetContent(customizationState: TopAppBarCustomizationState, isLargeVariant: Boolean) { + with(customizationState) { + Subtitle(textRes = R.string.component_app_bars_top_large_scroll_behavior, horizontalPadding = true) + OdsChoiceChipsFlowRow( + selectedChip = scrollBehavior, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing_m)), + outlinedChips = true + ) { + OdsChoiceChip(textRes = R.string.component_app_bars_top_large_scroll_behavior_none, value = TopAppBarCustomizationState.ScrollBehavior.None) + OdsChoiceChip( + textRes = R.string.component_app_bars_top_large_scroll_behavior_collapsible, + value = TopAppBarCustomizationState.ScrollBehavior.Collapsible + ) + } + OdsListItem( + text = stringResource(id = R.string.component_app_bars_top_element_navigation_icon), + trailing = OdsSwitchTrailing( + checked = navigationIconEnabled + ) + ) + ComponentCountRow( + modifier = Modifier.padding(start = dimensionResource(id = R.dimen.screen_horizontal_margin)), + title = stringResource(id = R.string.component_app_bars_top_actions_count), + count = actionCount, + minusIconContentDescription = stringResource(id = R.string.component_app_bars_top_remove_action), + plusIconContentDescription = stringResource(id = R.string.component_app_bars_top_add_action), + minCount = minActionCount, + maxCount = maxActionCountSelectable + ) + OdsListItem( + text = stringResource(id = R.string.component_app_bars_top_element_overflow_menu), + trailing = OdsSwitchTrailing( + checked = overflowMenuEnabled, + enabled = isOverflowMenuSwitchEnabled + ) + ) + if (isLargeVariant) { + Subtitle(textRes = R.string.component_element_title, horizontalPadding = true) + OdsChoiceChipsFlowRow( + selectedChip = titleLineNum, + modifier = Modifier + .padding(horizontal = dimensionResource(id = R.dimen.spacing_m)) + .padding(bottom = dimensionResource(id = R.dimen.spacing_s)), + outlinedChips = true + ) { + OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_one_line, value = TopAppBarCustomizationState.TitleLineNum.OneLine) + OdsChoiceChip(textRes = R.string.component_app_bars_top_large_title_two_lines, value = TopAppBarCustomizationState.TitleLineNum.TwoLines) + OdsChoiceChip( + textRes = R.string.component_app_bars_top_large_title_truncated, + value = TopAppBarCustomizationState.TitleLineNum.ThreeLinesAndMore + ) + } + } + } +} + +@Composable +private fun BlinkingChevronDown(modifier: Modifier = Modifier) { + val infiniteTransition = rememberInfiniteTransition() + + val scale by infiniteTransition.animateFloat( + initialValue = 1f, + targetValue = 1.2f, + animationSpec = infiniteRepeatable( + animation = tween(1000), + repeatMode = RepeatMode.Reverse + ) + ) + + val alpha by infiniteTransition.animateFloat( + initialValue = 0.4f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(1000), + repeatMode = RepeatMode.Reverse + ) + ) + + Icon( + modifier = modifier + .size(30.dp) + .scale(scale) + .alpha(alpha), + painter = painterResource(id = R.drawable.ic_chevron_down), + contentDescription = null, + tint = OdsTheme.colors.onSurface + ) +} \ No newline at end of file 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 cd65498e5..a0bdc7ffe 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 @@ -30,22 +30,28 @@ fun rememberTopAppBarCustomizationState( ) ) }, - titleLength: MutableState = rememberSaveable { mutableStateOf(TopAppBarCustomizationState.TitleLength.OneLine) }, + titleLineNum: MutableState = rememberSaveable { mutableStateOf(TopAppBarCustomizationState.TitleLineNum.OneLine) }, + scrollBehavior: MutableState = rememberSaveable { mutableStateOf(MainTopAppBarState.DefaultConfiguration.scrollBehavior) } ) = - remember(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLength) { - TopAppBarCustomizationState(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLength) + remember(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLineNum, scrollBehavior) { + TopAppBarCustomizationState(navigationIconEnabled, actionCount, overflowMenuEnabled, titleLineNum, scrollBehavior) } class TopAppBarCustomizationState( val navigationIconEnabled: MutableState, val actionCount: MutableState, val overflowMenuEnabled: MutableState, - val titleLength: MutableState + val titleLineNum: MutableState, + val scrollBehavior: MutableState ) { - enum class TitleLength(val titleResId: Int) { + enum class TitleLineNum(val titleResId: Int) { OneLine(R.string.component_app_bars_top_large_title_one_line_value), TwoLines(R.string.component_app_bars_top_large_title_two_lines_value), - Truncated(R.string.component_app_bars_top_large_title_truncated_value) + ThreeLinesAndMore(R.string.component_app_bars_top_large_title_truncated_value) + } + + enum class ScrollBehavior { + None, Collapsible } private val maxActionCount = 3 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e004c109..56ffa3c3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,7 +111,14 @@ Large: One line title Large: Two lines title allowed in top app bar Large: Title will be truncated if it is too long to fit in the top app bar - + Scrolling upward collapses the top app bar + Scrolling downward expands the top app bar + For collapsing version: + 1 - Pass the following scroll behavior to the top bar + 2 - Connect this behavior to the Scaffold containing the top bar through a Modifier + Scroll behavior + None + Collapsible Bottom navigation 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 20a9c7c25..94eb67e8f 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 @@ -43,11 +43,16 @@ fun OdsLargeTopAppBar( scrollBehavior: TopAppBarScrollBehavior? = null ) { val contentColor = OdsTheme.colors.component.topAppBar.barContent + val expandedTitleStartPadding = 48.dp + val collapsedTitleStartPadding = 8.dp + val expandedTitleAlpha = 1f + val expandedTitleMaxLines = 2 + val collapsedTitleMaxLines = 1 val stateChangeFraction = 0.7 val titleStartPadding by remember { derivedStateOf { - if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= 0.85) 8.dp else 48.dp + if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= 0.85) collapsedTitleStartPadding else expandedTitleStartPadding } } @@ -60,13 +65,13 @@ fun OdsLargeTopAppBar( else -> 0 }.toFloat() } else { - 1.0f + expandedTitleAlpha } } } val titleMaxLines by remember { derivedStateOf { - if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= stateChangeFraction) 1 else 2 + if (scrollBehavior != null && scrollBehavior.state.collapsedFraction >= stateChangeFraction) collapsedTitleMaxLines else expandedTitleMaxLines } } @@ -74,12 +79,15 @@ fun OdsLargeTopAppBar( title = { Text( modifier = Modifier - .padding(start = titleStartPadding, end = dimensionResource(id = R.dimen.spacing_m)) - .alpha(titleAlpha), + .padding( + start = if (scrollBehavior != null) titleStartPadding else expandedTitleStartPadding, + end = dimensionResource(id = R.dimen.spacing_m) + ) + .alpha(if (scrollBehavior != null) titleAlpha else expandedTitleAlpha), text = title, style = OdsTheme.typography.h6, overflow = TextOverflow.Ellipsis, - maxLines = titleMaxLines, + maxLines = if (scrollBehavior != null) titleMaxLines else expandedTitleMaxLines, ) }, modifier = modifier, From 388db41c27058fcce0df0984fd6ef82210c9dc78 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 5 Jul 2023 15:10:51 +0200 Subject: [PATCH 079/112] Adjust content display according top app bar type --- .../appbars/top/ComponentTopAppBar.kt | 42 ++++++++++--------- .../top/TopAppBarCustomizationState.kt | 3 ++ app/src/main/res/values/strings.xml | 6 +-- 3 files changed, 28 insertions(+), 23 deletions(-) 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 2e2e688e4..8d8aa3d15 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 @@ -86,7 +86,7 @@ fun ComponentTopAppBar(variant: Variant) { .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { - if (isLargeVariant) { + if (isLargeVariant && isCollapsible) { OdsTextBody2(text = stringResource(id = R.string.component_app_bars_top_large_scrolling_upward)) BlinkingChevronDown( modifier = Modifier @@ -150,28 +150,30 @@ fun ComponentTopAppBar(variant: Variant) { } ) } - OdsTextBody2( - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), - text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing) - ) - OdsTextCaption( - modifier = Modifier.padding(bottom = dimensionResource(id = R.dimen.spacing_xs)), - text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_1) - ) - CodeBackgroundColumn { - TechnicalText(text = "val topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())") - } - OdsTextCaption( - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), - text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_2) - ) - CodeBackgroundColumn { - TechnicalText(text = "val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection }") - TechnicalText(text = "Scaffold(modifier = Modifier.nestedScroll(nestedScrollConnection), ...) { ... }") + if (isLargeVariant && isCollapsible) { + OdsTextBody2( + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing) + ) + OdsTextCaption( + modifier = Modifier.padding(bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_1) + ) + CodeBackgroundColumn { + TechnicalText(text = "val topBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())") + } + OdsTextCaption( + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s), bottom = dimensionResource(id = R.dimen.spacing_xs)), + text = stringResource(id = R.string.component_app_bars_top_large_code_collapsing_step_2) + ) + CodeBackgroundColumn { + TechnicalText(text = "val nestedScrollConnection = remember { topBarScrollBehavior.nestedScrollConnection }") + TechnicalText(text = "Scaffold(modifier = Modifier.nestedScroll(nestedScrollConnection), ...) { ... }") + } } } - if (isLargeVariant) { + if (isLargeVariant && isCollapsible) { BlinkingChevronDown(modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.spacing_s))) OdsTextBody2(text = stringResource(id = R.string.component_app_bars_top_large_scrolling_downward)) } 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 a0bdc7ffe..f8862ec88 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 @@ -58,6 +58,9 @@ class TopAppBarCustomizationState( val minActionCount = 0 + val isCollapsible: Boolean + get() = scrollBehavior.value == ScrollBehavior.Collapsible + val isNavigationIconEnabled: Boolean get() = navigationIconEnabled.value diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56ffa3c3d..d576d3b92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,9 +113,9 @@ Large: Title will be truncated if it is too long to fit in the top app bar Scrolling upward collapses the top app bar Scrolling downward expands the top app bar - For collapsing version: - 1 - Pass the following scroll behavior to the top bar - 2 - Connect this behavior to the Scaffold containing the top bar through a Modifier + Define and connect scroll behavior: + Step 1 - Pass the following scroll behavior to the top bar + Step 2 - Connect this behavior to the Scaffold containing the top bar through a Modifier Scroll behavior None Collapsible From 145b7e604709ff86082fed1c0ffed4511746969c Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 5 Jul 2023 15:27:59 +0200 Subject: [PATCH 080/112] Update documentation --- docs/components/AppBarsTop.md | 144 ++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 50 deletions(-) diff --git a/docs/components/AppBarsTop.md b/docs/components/AppBarsTop.md index 15ec4ffaa..a6b1ca235 100644 --- a/docs/components/AppBarsTop.md +++ b/docs/components/AppBarsTop.md @@ -10,9 +10,11 @@ description: Top app bars display information and actions relating to the curren * [Specifications references](#specifications-references) * [Accessibility](#accessibility) -* [Implementation](#implementation) +* [Variants](#variants) + * [Regular top app bar](#regular-top-app-bar) + * [Large top app bar](#large-top-app-bar) * [Extras](#extras) - * [Overflow menu](#overflow-menu) + * [Overflow menu](#overflow-menu) * [Component specific tokens](#component-specific-tokens) --- @@ -49,22 +51,23 @@ For action items and items within the overflow menu, the content description needs to be set in the menu: ```xml + - - + + ``` For images within top app bars, set an `android:contentDescription` or use the `setContentDescription` method on the `ImageView`. -## Implementation +## Variants + +### Regular top app bar > **Jetpack Compose implementation** -Add `OdsTopAppBar` composable to your Scaffold topBar as follow: +Add `OdsTopAppBar` composable to your Scaffold topBar: ```kotlin OdsTopAppBar( @@ -96,36 +99,30 @@ Note: By default, the `OdsTopAppBar` is elevated but you can set `elevated` para 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) +* `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 - - - + - @@ -139,28 +136,21 @@ In the layout: In `@menu/top_app_bar.xml`: ```xml + - - + android:contentDescription="@string/content_description_search" app:showAsAction="ifRoom" /> - + ``` @@ -168,9 +158,8 @@ In `@menu/top_app_bar.xml`: In menu/navigation icons: ```xml - - + + ``` In code: @@ -207,13 +196,12 @@ content. Upon scroll, it increases elevation and lets content scroll behind it. In the layout: ```xml + - + - + @@ -225,19 +213,75 @@ _**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 + + style="@style/Widget.Orange.Toolbar.Raised" /> ``` +### Large top app bar + +> **Jetpack Compose implementation** + +Add `OdsLargeTopAppBar` composable to your Scaffold topBar: + +```kotlin +OdsLargeTopAppBar( + title = { + Text(text = "Title") + }, + navigationIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = "content description" + ) + }, + onNavigationIconClick = { + // Do something + }, + actions = { + OdsTopAppBarActionButton( + onClick = { }, + painter = painterResource(id = R.drawable.ic_share), + contentDescription = "content description" + ) // Each action should be an `OdsTopAppBarActionButton`. They are displayed in a `Row`, so icons inside will be placed horizontally. + }, + 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 +} +``` + ## Extras ### Overflow menu @@ -251,13 +295,13 @@ You can easily add an overflow menu to your top app bar by using the `OdsTopAppB OdsTopAppBarOverflowMenuBox(overflowIconContentDescription = "more actions") { OdsDropdownMenuItem( text = "account", - onClick = { + onClick = { // Do something } ) OdsDropdownMenuItem( text = "settings", - onClick = { + onClick = { // Do something } ) From 86a580fcd943a62385286a47696ab19d9ae15c0d Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 5 Jul 2023 15:29:30 +0200 Subject: [PATCH 081/112] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 5990d1208..2d3f709e2 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - \[App\] Add component image item ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[App\] Display XML version in `ButtonsContained` and `ButtonsOutlined` ([#528](https://github.com/Orange-OpenSource/ods-android/issues/528)) - \[App\] Display XML version in `ButtonsText`, `ButtonsIcon`, `ButtonsIconToggle` and `ButtonsIconToggleGroup` ([#529](https://github.com/Orange-OpenSource/ods-android/issues/529)) +- \[Lib\] Add `OdsLargeTopAppBar` component ([#118](https://github.com/Orange-OpenSource/ods-android/issues/118)) - \[Lib\] Add `OdsImageItem` component ([#473](https://github.com/Orange-OpenSource/ods-android/issues/473)) - \[Lib\] Add `@Parcelize` annotation on `OdsExposedDropdownMenuItem` to allow to save and restore it ([#545](https://github.com/Orange-OpenSource/ods-android/issues/545)) - \[Lib\] Add `themeColors` and `rippleTheme` properties to `OdsDisplaySurface` ([#329](https://github.com/Orange-OpenSource/ods-android/issues/329)) From 1a7d35cde147bb93e19708747d11677f22e8cecf Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Thu, 6 Jul 2023 16:07:45 +0200 Subject: [PATCH 082/112] Add large top app bar previews --- .../component/appbar/top/OdsLargeTopAppBar.kt | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) 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 94eb67e8f..fb4f2655a 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 @@ -12,6 +12,9 @@ package com.orange.ods.compose.component.appbar.top import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar @@ -25,10 +28,16 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import com.orange.ods.R import com.orange.ods.compose.component.OdsComposable +import com.orange.ods.compose.component.menu.OdsDropdownMenuItem +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.OdsTheme @OptIn(ExperimentalMaterial3Api::class) @@ -105,4 +114,50 @@ fun OdsLargeTopAppBar( ), scrollBehavior = scrollBehavior ) -} \ No newline at end of file +} + +@OptIn(ExperimentalMaterial3Api::class) +@UiModePreviews.Default +@Composable +private fun PreviewOdsLargeTopAppBar(@PreviewParameter(OdsLargeTopAppBarPreviewParameterProvider::class) parameter: OdsLargeTopAppBarPreviewParameter) = Preview { + OdsLargeTopAppBar( + title = parameter.title, + navigationIcon = parameter.navigationIcon, + actions = { + OdsTopAppBarActionButton( + onClick = {}, + painter = painterResource(id = android.R.drawable.ic_dialog_info), + contentDescription = "Info" + ) + OdsTopAppBarOverflowMenuBox( + overflowIconContentDescription = "more options" + ) { + OdsDropdownMenuItem(text = "settings", onClick = { }) + OdsDropdownMenuItem(text = "account", onClick = { }) + } + } + ) +} + +internal data class OdsLargeTopAppBarPreviewParameter( + val title: String, + val navigationIcon: @Composable () -> Unit +) + +private class OdsLargeTopAppBarPreviewParameterProvider : + BasicPreviewParameterProvider(*previewParameterValues.toTypedArray()) + +private val previewParameterValues: List + get() { + val navigationIcon = @Composable { + IconButton(onClick = {}) { + Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null, tint = OdsTheme.colors.component.topAppBar.barContent) + } + } + return listOf( + OdsLargeTopAppBarPreviewParameter("One line title", navigationIcon), + OdsLargeTopAppBarPreviewParameter("Two lines title is allowed in large top app bar", navigationIcon), + OdsLargeTopAppBarPreviewParameter("The title will be truncated if it is too long to fit in the large top app bar like this one", navigationIcon), + OdsLargeTopAppBarPreviewParameter("One line title", { }) + ) + } \ No newline at end of file From 9bb27763dfcb87ff9379b5cd6d0a4c9813a58e36 Mon Sep 17 00:00:00 2001 From: Pauline Auvray Date: Wed, 12 Jul 2023 08:58:30 +0200 Subject: [PATCH 083/112] Review: Add LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY option --- .idea/codeStyles/Project.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index a92b7a9a0..4ef75faf7 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,7 @@ +