Skip to content

Commit

Permalink
Merge pull request #331 from Orange-OpenSource/114-ods-component-toas…
Browse files Browse the repository at this point in the history
…t-snackbar

114 - ods component - snackbar
  • Loading branch information
florentmaitre authored Nov 23, 2022
2 parents 7dfce35 + abd17b3 commit 186b3c8
Show file tree
Hide file tree
Showing 27 changed files with 386 additions and 58 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/Orange-OpenSource/ods-android/compare/0.7.0...master)

### Added

- \[Demo\] Add Snackbar component ([#114](https://github.com/Orange-OpenSource/ods-android/issues/114))
- \[Lib\] Add `OdsSnackbar` and `OdsSnackbarHost` composable to manage snackbars display ([#114](https://github.com/Orange-OpenSource/ods-android/issues/114))

### Changed

- \[All\] Version numbers in changelog now display changes on GitHub when clicked ([#322](https://github.com/Orange-OpenSource/ods-android/issues/322))
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, Progress, RadioButtons, Switches, Tabs -> Alignment.Center
Buttons, Cards, Checkboxes, Chips, Dialogs, Progress, RadioButtons, Snackbars, Switches, Tabs -> Alignment.Center
}

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

object Snackbars : Component(
R.string.component_snackbars,
R.drawable.il_snackbars,
R.drawable.il_snackbars_small,
R.string.component_snackbars_description,
composableName = OdsComponent.OdsSnackbar.name
)

object Switches : Component(
R.string.component_switches,
R.drawable.il_switches,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.orange.ods.demo.ui.components.dialogs.ComponentDialog
import com.orange.ods.demo.ui.components.lists.ComponentLists
import com.orange.ods.demo.ui.components.radiobuttons.ComponentRadioButtons
import com.orange.ods.demo.ui.components.sliders.ComponentSliders
import com.orange.ods.demo.ui.components.snackbars.ComponentSnackbars
import com.orange.ods.demo.ui.components.switches.ComponentSwitches

@Composable
Expand All @@ -34,6 +35,7 @@ fun ComponentDemoScreen(componentId: Long) {
Component.Lists -> ComponentLists()
Component.RadioButtons -> ComponentRadioButtons()
Component.Sliders -> ComponentSliders()
Component.Snackbars -> ComponentSnackbars()
Component.Switches -> ComponentSwitches()
else -> {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,15 @@

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

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.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import com.orange.ods.compose.component.button.OdsButton
import com.orange.ods.compose.component.button.OdsButtonStyle
import com.orange.ods.compose.component.dialog.OdsAlertDialog
import com.orange.ods.compose.text.OdsTextBody1
import com.orange.ods.demo.R
import com.orange.ods.demo.ui.components.utilities.ComponentCustomizationBottomSheetScaffold
import com.orange.ods.demo.ui.components.utilities.ComponentLaunchContentColumn
import com.orange.ods.demo.ui.components.utilities.clickOnElement
import com.orange.ods.demo.ui.utilities.composable.SwitchListItem

Expand All @@ -45,50 +36,29 @@ fun ComponentDialog() {
SwitchListItem(labelRes = R.string.component_element_title, checked = customizationState.titleChecked)
SwitchListItem(labelRes = R.string.component_dialog_element_dismiss_button, checked = customizationState.dismissButtonChecked)
}) {
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_dialog_customize)
)
ComponentLaunchContentColumn(textRes = R.string.component_dialog_customize, buttonLabelRes = R.string.component_dialog_open) {
customizationState.openDialog.value = true
}
if (customizationState.shouldOpenDialog) {
val confirmButtonText =
stringResource(id = if (customizationState.isDismissButtonChecked) R.string.component_dialog_action_confirm else R.string.component_dialog_action_ok)
val dismissButtonText = stringResource(id = R.string.component_dialog_action_dismiss)

OdsButton(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin))
.padding(top = dimensionResource(R.dimen.spacing_m)),
text = stringResource(id = R.string.component_dialog_open),
style = OdsButtonStyle.Primary,
onClick = {
customizationState.openDialog.value = true
}
OdsAlertDialog(
titleText = if (customizationState.isTitleChecked) stringResource(id = R.string.component_element_title) else null,
text = stringResource(id = R.string.component_dialog_text),
confirmButtonText = confirmButtonText,
onConfirmButtonClick = {
clickOnElement(context = context, clickedElement = confirmButtonText)
closeDialogAction()
},
dismissButtonText = if (customizationState.isDismissButtonChecked) dismissButtonText else null,
onDismissButtonClick = {
clickOnElement(context = context, clickedElement = dismissButtonText)
closeDialogAction()
},
)

if (customizationState.shouldOpenDialog) {
val confirmButtonText =
stringResource(id = if (customizationState.isDismissButtonChecked) R.string.component_dialog_action_confirm else R.string.component_dialog_action_ok)
val dismissButtonText = stringResource(id = R.string.component_dialog_action_dismiss)

OdsAlertDialog(
titleText = if (customizationState.isTitleChecked) stringResource(id = R.string.component_element_title) else null,
text = stringResource(id = R.string.component_dialog_text),
confirmButtonText = confirmButtonText,
onConfirmButtonClick = {
clickOnElement(context = context, clickedElement = confirmButtonText)
closeDialogAction()
},
dismissButtonText = if (customizationState.isDismissButtonChecked) dismissButtonText else null,
onDismissButtonClick = {
clickOnElement(context = context, clickedElement = dismissButtonText)
closeDialogAction()
},
)
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
*
* 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.snackbars

import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.orange.ods.compose.component.snackbar.OdsSnackbar
import com.orange.ods.compose.component.snackbar.OdsSnackbarHost
import com.orange.ods.demo.R
import com.orange.ods.demo.ui.components.utilities.ComponentCustomizationBottomSheetScaffold
import com.orange.ods.demo.ui.components.utilities.ComponentLaunchContentColumn
import com.orange.ods.demo.ui.components.utilities.clickOnElement
import com.orange.ods.demo.ui.utilities.composable.SwitchListItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ComponentSnackbars() {
val context = LocalContext.current
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState()
val coroutineScope: CoroutineScope = rememberCoroutineScope()

val actionButtonChecked = rememberSaveable { mutableStateOf(false) }
val actionOnNewLineChecked = rememberSaveable { mutableStateOf(false) }
if (!actionButtonChecked.value) {
actionOnNewLineChecked.value = false
}

val snackbarMessage = stringResource(id = R.string.component_snackbar_message)
val snackbarActionLabel = stringResource(id = R.string.component_snackbar_action_label)
val snackbarActionButton = stringResource(id = R.string.component_snackbar_action_button)

ComponentCustomizationBottomSheetScaffold(
bottomSheetScaffoldState = bottomSheetScaffoldState,
snackbarHost = {
OdsSnackbarHost(hostState = it) { data ->
OdsSnackbar(snackbarData = data, actionOnNewLine = actionOnNewLineChecked.value, onActionClick = {
clickOnElement(context = context, clickedElement = snackbarActionButton)
})
}
},
bottomSheetContent = {
SwitchListItem(labelRes = R.string.component_snackbar_action_button, checked = actionButtonChecked)
SwitchListItem(labelRes = R.string.component_snackbar_action_on_new_line, checked = actionOnNewLineChecked, enabled = actionButtonChecked.value)
}) {
ComponentLaunchContentColumn(textRes = R.string.component_snackbar_customize, buttonLabelRes = R.string.component_snackbar_show) {
coroutineScope.launch {
bottomSheetScaffoldState.snackbarHostState.showSnackbar(
message = snackbarMessage,
actionLabel = if (actionButtonChecked.value) snackbarActionLabel else null,
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import androidx.compose.material.BottomSheetScaffoldState
import androidx.compose.material.BottomSheetValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
Expand All @@ -46,6 +48,7 @@ import kotlinx.coroutines.launch
@Composable
fun ComponentCustomizationBottomSheetScaffold(
bottomSheetScaffoldState: BottomSheetScaffoldState,
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(hostState = it) },
bottomSheetContent: @Composable () -> Unit,
content: @Composable BoxScope.() -> Unit
) {
Expand All @@ -62,6 +65,7 @@ fun ComponentCustomizationBottomSheetScaffold(
BottomSheetScaffold(
sheetBackgroundColor = OdsTheme.colors.surface,
scaffoldState = bottomSheetScaffoldState,
snackbarHost = snackbarHost,
sheetPeekHeight = 56.dp,
sheetContent = {
OdsListItem(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
*
* 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.utilities

import androidx.annotation.StringRes
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import com.orange.ods.compose.component.button.OdsButton
import com.orange.ods.compose.component.button.OdsButtonStyle
import com.orange.ods.compose.text.OdsTextBody1
import com.orange.ods.demo.R

@Composable
fun ComponentLaunchContentColumn(@StringRes textRes: Int, @StringRes buttonLabelRes: Int, onButtonClick: () -> Unit) {
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 = textRes)
)

OdsButton(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(R.dimen.screen_horizontal_margin))
.padding(top = dimensionResource(R.dimen.spacing_m)),
text = stringResource(id = buttonLabelRes),
style = OdsButtonStyle.Primary,
onClick = onButtonClick
)
}
}
Binary file added demo/src/main/res/drawable-hdpi/il_snackbars.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_snackbars.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-mdpi/il_snackbars.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
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
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
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.
10 changes: 10 additions & 0 deletions demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@
<string name="component_list_trailing_caption">Caption</string>
<string name="component_list_divider">Divider</string>

<!-- Component Snackbars -->
<string name="component_snackbars">Snackbars</string>
<string name="component_snackbars_description">There are two ways to show users short, non permanent messages giving system information or feedback about an operation.</string>
<string name="component_snackbar_action_button">Action button</string>
<string name="component_snackbar_action_on_new_line">Action on new line</string>
<string name="component_snackbar_customize">Customize the snackbar before displaying it.</string>
<string name="component_snackbar_show">Show snackbar</string>
<string name="component_snackbar_message">This is the message of the Snackbar.</string>
<string name="component_snackbar_action_label">Action</string>

<!-- Component Text fields -->
<string name="component_text_fields">Text fields</string>
<string name="component_text_field">Text field</string>
Expand Down
51 changes: 47 additions & 4 deletions docs/components/Snackbars.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ action that was just taken, or retrying an action that had failed.
* [Specifications references](#specifications-references)
* [Accessibility](#accessibility)
* [Implementation](#implementation)
* [Adding an action](#adding-an-action)
* [Anchoring a snackbar](#anchoring-a-snackbar)
* [Component specific tokens](#component-specific-tokens)

---
Expand All @@ -43,6 +41,51 @@ unnecessary.

### Implementation

![Snackbar light](images/snackbar_light.png)

![Snackbar dark](images/snackbar_dark.png)

With action button:

![Snackbar with action light](images/snackbar_with_action_light.png)

![Snackbar with action dark](images/snackbar_with_action_dark.png)

> **Jetpack Compose implementation**
We advise you to use a `Scaffold` to add an `OdsSnackbar` in order to make sure everything is displayed together in the right place according to Material Design.
Then use `OdsSnackbarHost` which provides the good margins to display the snackbar and `OdsSnackbar` as follow:

```kotlin
val scaffoldState = rememberScaffoldState()
val coroutineScope: CoroutineScope = rememberCoroutineScope()

Scaffold(
scaffoldState = scaffoldState,
snackbarHost = {
OdsSnackbarHost(hostState = it) { data ->
OdsSnackbar(snackbarData = data)
}
}) {
OdsButton(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.screen_horizontal_margin))
.padding(top = dimensionResource(id = R.dimen.screen_vertical_margin)),
text = "Show snackbar",
onClick = {
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = "This is the message of the Snackbar.",
actionLabel = "Action"
)
}
}
)
}
```

> **XML implementation**
Calling `make` creates the snackbar, but doesn't cause it to be visible on the
screen. To show it, use the `show` method on the returned `Snackbar` instance.
Note that only one snackbar will be shown at a time. Showing a new snackbar will
Expand All @@ -61,7 +104,7 @@ Snackbar.make(contextView, R.string.text_label, Snackbar.LENGTH_SHORT)
.show()
```

#### Adding an action
**Adding an action**

To add an action, use the `setAction` method on the object returned from `make`.
Snackbars are automatically dismissed when the action is clicked.
Expand All @@ -76,7 +119,7 @@ Snackbar.make(contextView, R.string.text_label, Snackbar.LENGTH_LONG)
.show()
```

#### Anchoring a snackbar
**Anchoring a snackbar**

By default, `Snackbar`s will be anchored to the bottom edge of their parent
view. However, you can use the `setAnchorView` method to make a `Snackbar`
Expand Down
Binary file added docs/components/images/snackbar_dark.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 docs/components/images/snackbar_light.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
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 186b3c8

Please sign in to comment.