Skip to content

Commit

Permalink
Merge pull request #414 from Orange-OpenSource/111-ods-component-menu
Browse files Browse the repository at this point in the history
111 - ods component - menu
  • Loading branch information
florentmaitre authored Feb 1, 2023
2 parents f55bae9 + 1c05912 commit 8bfebcd
Show file tree
Hide file tree
Showing 36 changed files with 650 additions and 49 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- \[Demo\] Add new entries in about screen ([#403](https://github.com/Orange-OpenSource/ods-android/issues/403))
- \[Lib\] Add `OdsFloatingActionButton` and `OdsExtendedFloatingActionButton` components ([#109](https://github.com/Orange-OpenSource/ods-android/issues/109))
- \[Lib\] Add `OdsListItem` composable signature with an `OdsListItemTrailing` as trailing parameter in order to manage accessibility ([#387](https://github.com/Orange-OpenSource/ods-android/issues/387))
- \[Lib\] Add `OdsDropdownMenu` and `OdsExposedDropdownMenu` composables and related documentation ([#111](https://github.com/Orange-OpenSource/ods-android/issues/111))
- \[ThemeConfigurationContract\] Add `textFieldStyle` boolean in the theme contract to allow to choose between outlined or filled text fields in a custom theme ([#415](https://github.com/Orange-OpenSource/ods-android/issues/415))

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ sealed class Component(
AppBarsTop, BottomNavigation -> Alignment.TopCenter
Lists -> Alignment.BottomCenter
Sliders, TextFields -> Alignment.CenterEnd
Buttons, Cards, Checkboxes, Chips, Dialogs, FloatingActionButtons, Progress, RadioButtons, Snackbars, Switches, Tabs -> Alignment.Center
Buttons, Cards, Checkboxes, Chips, Dialogs, FloatingActionButtons, Menus, Progress, RadioButtons, Snackbars, Switches, Tabs -> Alignment.Center
}

object AppBarsTop : Component(
Expand Down Expand Up @@ -120,6 +120,14 @@ sealed class Component(
composableName = OdsComponent.OdsListItem.name
)

object Menus : Component(
R.string.component_menus,
R.drawable.il_menus,
null,
R.string.component_menus_description,
listOf(Variant.DropdownMenu, Variant.ExposedDropdownMenu)
)

object Progress : Component(
R.string.component_progress,
R.drawable.il_progress,
Expand Down Expand Up @@ -203,6 +211,9 @@ sealed class Variant(
object Chip : Variant(R.string.component_chip, OdsComponent.OdsChip.name)
object ChipFilter : Variant(R.string.component_chip_type_filter, OdsComponent.OdsFilterChip.name)

object DropdownMenu : Variant(R.string.component_menu_dropdown, OdsComponent.OdsDropdownMenu.name)
object ExposedDropdownMenu : Variant(R.string.component_menu_exposed_dropdown, OdsComponent.OdsExposedDropdownMenu.name)

object ProgressBar : Variant(R.string.component_progress_bar, OdsComponent.OdsLinearProgressIndicator.name)
object ProgressActivityIndicator : Variant(R.string.component_progress_activity_indicator, OdsComponent.OdsCircularProgressIndicator.name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.orange.ods.demo.ui.components.buttons.ComponentButtons
import com.orange.ods.demo.ui.components.cards.ComponentCard
import com.orange.ods.demo.ui.components.chips.Chip
import com.orange.ods.demo.ui.components.chips.ChipFilter
import com.orange.ods.demo.ui.components.menus.ComponentMenu
import com.orange.ods.demo.ui.components.progress.ComponentProgress
import com.orange.ods.demo.ui.components.tabs.ComponentTabs
import com.orange.ods.demo.ui.components.textfields.ComponentTextField
Expand All @@ -34,6 +35,7 @@ fun ComponentVariantScreen(variantId: Long) {
Component.Buttons -> ComponentButtons(variant = variant)
Component.Cards -> ComponentCard(variant = variant)
Component.Chips -> if (variant == Variant.ChipFilter) ChipFilter() else Chip()
Component.Menus -> ComponentMenu(variant = variant)
Component.Progress -> ComponentProgress(variant = variant)
Component.TextFields -> ComponentTextField(variant = variant)
Component.Tabs -> ComponentTabs(variant)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private val ListItemCustomizationState.trailing: OdsListItemTrailing?
modifier = Modifier.clickable {
clickOnElement(context, iconText)
},
iconRes = R.drawable.ic_info,
painter = painterResource(id = R.drawable.ic_info),
contentDescription = stringResource(id = R.string.component_list_information)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
*
* 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.demo.ui.components.menus

import androidx.compose.runtime.Composable
import com.orange.ods.demo.ui.components.Variant

@Composable
fun ComponentMenu(variant: Variant) {
when (variant) {
Variant.DropdownMenu -> MenuDropdown()
Variant.ExposedDropdownMenu -> MenuExposedDropdown()
else -> {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
*
* 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.demo.ui.components.menus

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.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
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.orange.ods.compose.component.divider.OdsDivider
import com.orange.ods.compose.component.list.OdsIconTrailing
import com.orange.ods.compose.component.list.OdsListItem
import com.orange.ods.compose.component.list.OdsSwitchTrailing
import com.orange.ods.compose.component.menu.OdsDropdownMenu
import com.orange.ods.compose.component.menu.OdsDropdownMenuItem
import com.orange.ods.compose.text.OdsTextBody1
import com.orange.ods.demo.R
import com.orange.ods.demo.domain.recipes.LocalRecipes
import com.orange.ods.demo.ui.components.utilities.ComponentCustomizationBottomSheetScaffold
import com.orange.ods.demo.ui.components.utilities.clickOnElement

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MenuDropdown() {
val customizationState = rememberMenuDropdownCustomizationState()

var menuExpanded by remember { mutableStateOf(false) }
val context = LocalContext.current
val recipes = LocalRecipes.current
val recipe = rememberSaveable { recipes.filter { it.ingredients.isNotEmpty() }.random() }

with(customizationState) {
ComponentCustomizationBottomSheetScaffold(
bottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
bottomSheetContent = {
OdsListItem(
text = stringResource(id = R.string.component_menu_icons),
trailing = OdsSwitchTrailing(checked = icons)
)
OdsListItem(
text = stringResource(id = R.string.component_menu_divider),
trailing = OdsSwitchTrailing(checked = dividerExample)
)
}) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(top = dimensionResource(id = R.dimen.screen_vertical_margin), bottom = dimensionResource(id = R.dimen.spacing_s))
) {
OdsTextBody1(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)),
text = stringResource(id = R.string.component_menu_dropdown_description)
)

Box(modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s))) {
OdsListItem(
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing_s)),
text = recipe.title,
secondaryText = recipe.subtitle,
trailing = OdsIconTrailing(
modifier = Modifier.clickable { menuExpanded = true },
painter = rememberVectorPainter(image = Icons.Filled.MoreVert),
contentDescription = stringResource(id = R.string.component_menu_show_ingredients),
)
)
OdsDropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, offset = DpOffset(x = (-100).dp, y = (-10).dp)) {
recipes.take(MenuDropdownCustomizationState.MenuItemCount).forEachIndexed { index, recipe ->
OdsDropdownMenuItem(
text = recipe.title,
icon = if (hasIcons && recipe.iconResId != null) painterResource(id = recipe.iconResId) else null,
onClick = { clickOnElement(context, recipe.title) }
)
if (hasDividerExample && index == 2) {
OdsDivider()
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
*
* Copyright 2021 Orange
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* /
*/

package com.orange.ods.demo.ui.components.menus

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 rememberMenuDropdownCustomizationState(
icons: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) },
dividerExample: MutableState<Boolean> = rememberSaveable { mutableStateOf(false) },
enabled: MutableState<Boolean> = rememberSaveable { mutableStateOf(true) }
) =
remember(icons, dividerExample, enabled) {
MenuDropdownCustomizationState(icons, dividerExample, enabled)
}

class MenuDropdownCustomizationState(
val icons: MutableState<Boolean>,
val dividerExample: MutableState<Boolean>,
val enabled: MutableState<Boolean>
) {
companion object {
const val MenuItemCount = 5
}

val hasIcons
get() = icons.value

val hasDividerExample
get() = dividerExample.value

val isEnabled
get() = enabled.value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
*
* 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.demo.ui.components.menus

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
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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.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.demo.R
import com.orange.ods.demo.domain.recipes.LocalRecipes
import com.orange.ods.demo.ui.components.utilities.ComponentCustomizationBottomSheetScaffold
import com.orange.ods.demo.ui.components.utilities.clickOnElement

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MenuExposedDropdown() {
val customizationState = rememberMenuDropdownCustomizationState()

val context = LocalContext.current
val recipes = LocalRecipes.current.take(4)

val dropdownItems = recipes.map { recipe ->
OdsExposedDropdownMenuItem(label = recipe.title, icon = recipe.iconResId?.let { painterResource(id = it) })
}
val textOnlyDropdownItems = recipes.map { recipe ->
OdsExposedDropdownMenuItem(label = recipe.title)
}

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<OdsExposedDropdownMenuItem> = rememberSaveable(stateSaver = itemSaver) { mutableStateOf(dropdownItems.first()) }
if (hasIcons) {
items = dropdownItems
selectedItem.value = dropdownItems.first { selectedItem.value.label == it.label }
} else {
items = textOnlyDropdownItems
selectedItem.value = textOnlyDropdownItems.first { selectedItem.value.label == it.label }
}

ComponentCustomizationBottomSheetScaffold(
bottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
bottomSheetContent = {
OdsListItem(
text = stringResource(id = R.string.component_state_enabled),
trailing = OdsSwitchTrailing(checked = enabled)
)
OdsListItem(
text = stringResource(id = R.string.component_menu_icons),
trailing = OdsSwitchTrailing(checked = icons)
)
}) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(top = dimensionResource(id = R.dimen.screen_vertical_margin), bottom = dimensionResource(id = R.dimen.spacing_s))
.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin)),
) {
OdsExposedDropdownMenu(
modifier = Modifier
.fillMaxWidth()
.padding(top = dimensionResource(id = R.dimen.spacing_s)),
label = stringResource(id = R.string.data_recipe),
items = items,
selectedItem = selectedItem,
onItemSelectionChange = { item ->
clickOnElement(context, item.label)
},
enabled = isEnabled
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.orange.ods.demo.R

@Composable
fun TextFieldPassword(customizationState: TextFieldCustomizationState) {

Column {
OdsPasswordTextField(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ object DrawableManager {
R.drawable.il_dialogs_generic to R.drawable.il_dialogs,
R.drawable.il_fab_generic to R.drawable.il_fab,
R.drawable.il_lists_generic to R.drawable.il_lists,
R.drawable.il_menus_generic to R.drawable.il_menus,
R.drawable.il_progress_generic to R.drawable.il_progress,
R.drawable.il_radio_buttons_generic to R.drawable.il_radio_buttons,
R.drawable.il_sliders_generic to R.drawable.il_sliders,
Expand Down
Binary file added demo/src/main/res/drawable-hdpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/main/res/drawable-ldpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/main/res/drawable-mdpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/main/res/drawable-xhdpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/main/res/drawable-xxhdpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/main/res/drawable-xxxhdpi/il_menus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8bfebcd

Please sign in to comment.