[Compound] Implement Snackbars based on designs (#1054)

* Make `InternalButton` internal instead of private so it can be customised.

Also, change the `ButtonColors.contentColor` for text buttons to `LocalContentColor.current` by default.

* Add temporary color for Snackbar action label

* Implement `Snackbar` component based on Compound

* Propagate changes to all other components

* Use right Preview annotation config

* Move `ButtonVisuals` to their own file

* Update screenshots

* Make previews internal

* Update screenshots

* Set a custom token for contentColor in AppBars

* Change 'Label' to 'Action' in the previews

* Add changelog

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-08-16 12:38:28 +02:00 committed by GitHub
parent acf29036a6
commit 9e1ff513e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 441 additions and 147 deletions

1
changelog.d/1054.misc Normal file
View file

@ -0,0 +1 @@
Compound: implement Snackbar component.

View file

@ -195,7 +195,7 @@ private fun AnalyticsOptInFooter(
) )
TextButton( TextButton(
text = stringResource(id = CommonStrings.action_not_now), text = stringResource(id = CommonStrings.action_not_now),
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
onClick = onTermsDeclined, onClick = onTermsDeclined,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )

View file

@ -135,7 +135,7 @@ internal fun DefaultInviteSummaryRow(
text = stringResource(CommonStrings.action_decline), text = stringResource(CommonStrings.action_decline),
onClick = onDeclineClicked, onClick = onDeclineClicked,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
@ -144,7 +144,7 @@ internal fun DefaultInviteSummaryRow(
text = stringResource(CommonStrings.action_accept), text = stringResource(CommonStrings.action_accept),
onClick = onAcceptClicked, onClick = onAcceptClicked,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
) )
} }
} }

View file

@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -78,6 +77,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.SnackbarHost
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState

View file

@ -34,9 +34,6 @@ import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -64,6 +61,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.SnackbarHost
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.matrix.ui.media.MediaRequestData
@ -99,15 +97,7 @@ fun MediaViewerView(
eventSink = state.eventSink eventSink = state.eventSink
) )
}, },
snackbarHost = { snackbarHost = { SnackbarHost(snackbarHostState) },
SnackbarHost(snackbarHostState) { data ->
Snackbar(
snackbarData = data,
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.primary
)
}
},
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier

View file

@ -24,8 +24,6 @@ import androidx.compose.material.icons.outlined.DeveloperMode
import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.InsertChart import androidx.compose.material.icons.outlined.InsertChart
import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.material.icons.outlined.VerifiedUser
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -41,6 +39,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.designsystem.theme.components.Divider import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.SnackbarHost
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
@ -65,13 +64,7 @@ fun PreferencesRootView(
modifier = modifier, modifier = modifier,
onBackPressed = onBackPressed, onBackPressed = onBackPressed,
title = stringResource(id = CommonStrings.common_settings), title = stringResource(id = CommonStrings.common_settings),
snackbarHost = { snackbarHost = { SnackbarHost(snackbarHostState) }
SnackbarHost(snackbarHostState) { data ->
Snackbar(
snackbarData = data,
)
}
}
) { ) {
UserPreferences(state.myUser) UserPreferences(state.myUser)
if (state.showCompleteVerification) { if (state.showCompleteVerification) {

View file

@ -28,8 +28,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -59,6 +57,7 @@ import io.element.android.libraries.designsystem.theme.components.FloatingAction
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.SnackbarHost
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.designsystem.R as DrawableR
@ -227,13 +226,7 @@ fun RoomListContent(
) )
} }
}, },
snackbarHost = { snackbarHost = { SnackbarHost(snackbarHostState) },
SnackbarHost(snackbarHostState) { data ->
Snackbar(
snackbarData = data,
)
}
},
) )
} }

View file

@ -83,7 +83,7 @@ internal fun RequestVerificationHeader(
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
Button( Button(
text = stringResource(CommonStrings.action_continue), text = stringResource(CommonStrings.action_continue),
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = onVerifyClicked, onClick = onVerifyClicked,
) )

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.components.button
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.TextButton
/**
* A sealed class that represents the different visual styles that a button can have.
*/
sealed interface ButtonVisuals {
val action: () -> Unit
/**
* Creates a [Button] composable based on the visual state.
*/
@Composable
fun Composable()
data class Text(val text: String, override val action: () -> Unit) : ButtonVisuals {
@Composable
override fun Composable() {
TextButton(text = text, onClick = action)
}
}
data class Icon(val iconSource: IconSource, override val action: () -> Unit) : ButtonVisuals {
@Composable
override fun Composable() {
IconButton(onClick = action) {
Icon(iconSource.getPainter(), iconSource.contentDescription)
}
}
}
}

View file

@ -30,6 +30,7 @@ object PreviewGroup {
const val Preferences = "Preferences" const val Preferences = "Preferences"
const val Progress = "Progress Indicators" const val Progress = "Progress Indicators"
const val Search = "Search views" const val Search = "Search views"
const val Snackbars = "Snackbars"
const val Sliders = "Sliders" const val Sliders = "Sliders"
const val Text = "Text" const val Text = "Text"
const val TextFields = "TextFields" const val TextFields = "TextFields"

View file

@ -78,7 +78,7 @@ private fun ContentToPreview() {
WithRulers(xRulersOffset = 20.dp, yRulersOffset = 15.dp) { WithRulers(xRulersOffset = 20.dp, yRulersOffset = 15.dp) {
OutlinedButton( OutlinedButton(
text = "A Button with rulers on it!", text = "A Button with rulers on it!",
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
onClick = {}, onClick = {},
) )
} }

View file

@ -72,19 +72,19 @@ internal fun SimpleAlertDialogContent(
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f // Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
TextButton( TextButton(
text = thirdButtonText, text = thirdButtonText,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
onClick = onThirdButtonClicked, onClick = onThirdButtonClicked,
) )
} }
TextButton( TextButton(
text = cancelText, text = cancelText,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
onClick = onCancelClicked, onClick = onCancelClicked,
) )
if (submitText != null) { if (submitText != null) {
Button( Button(
text = submitText, text = submitText,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
onClick = onSubmitClicked, onClick = onSubmitClicked,
) )
} }

View file

@ -32,6 +32,7 @@ import androidx.compose.foundation.progressSemantics
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -41,6 +42,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
@ -60,10 +62,19 @@ fun Button(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
buttonSize: ButtonSize = ButtonSize.Large, size: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false, showProgress: Boolean = false,
leadingIcon: IconSource? = null, leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Filled, modifier, enabled, buttonSize, showProgress, leadingIcon) ) = ButtonInternal(
text = text,
onClick = onClick,
style = ButtonStyle.Filled,
modifier = modifier,
enabled = enabled,
size = size,
showProgress = showProgress,
leadingIcon = leadingIcon
)
@Composable @Composable
fun OutlinedButton( fun OutlinedButton(
@ -71,10 +82,19 @@ fun OutlinedButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
buttonSize: ButtonSize = ButtonSize.Large, size: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false, showProgress: Boolean = false,
leadingIcon: IconSource? = null, leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Outlined, modifier, enabled, buttonSize, showProgress, leadingIcon) ) = ButtonInternal(
text = text,
onClick = onClick,
style = ButtonStyle.Outlined,
modifier = modifier,
enabled = enabled,
size = size,
showProgress = showProgress,
leadingIcon = leadingIcon
)
@Composable @Composable
fun TextButton( fun TextButton(
@ -82,17 +102,27 @@ fun TextButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
buttonSize: ButtonSize = ButtonSize.Large, size: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false, showProgress: Boolean = false,
leadingIcon: IconSource? = null, leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Text, modifier, enabled, buttonSize, showProgress, leadingIcon) ) = ButtonInternal(
text = text,
onClick = onClick,
style = ButtonStyle.Text,
modifier = modifier,
enabled = enabled,
size = size,
showProgress = showProgress,
leadingIcon = leadingIcon
)
@Composable @Composable
private fun ButtonInternal( internal fun ButtonInternal(
text: String, text: String,
onClick: () -> Unit, onClick: () -> Unit,
style: ButtonStyle, style: ButtonStyle,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: ButtonColors = style.getColors(),
enabled: Boolean = true, enabled: Boolean = true,
size: ButtonSize = ButtonSize.Large, size: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false, showProgress: Boolean = false,
@ -123,21 +153,6 @@ private fun ButtonInternal(
ButtonStyle.Text -> RectangleShape ButtonStyle.Text -> RectangleShape
} }
val colors = when (style) {
ButtonStyle.Filled -> ButtonDefaults.buttonColors(
containerColor = ElementTheme.materialColors.primary,
contentColor = ElementTheme.materialColors.onPrimary,
disabledContainerColor = ElementTheme.colors.bgActionPrimaryDisabled,
disabledContentColor = ElementTheme.colors.textOnSolidPrimary
)
ButtonStyle.Outlined, ButtonStyle.Text -> ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ElementTheme.materialColors.primary,
disabledContainerColor = Color.Transparent,
disabledContentColor = ElementTheme.colors.textDisabled,
)
}
val border = when (style) { val border = when (style) {
ButtonStyle.Filled, ButtonStyle.Text -> null ButtonStyle.Filled, ButtonStyle.Text -> null
ButtonStyle.Outlined -> BorderStroke( ButtonStyle.Outlined -> BorderStroke(
@ -202,8 +217,10 @@ private fun ButtonInternal(
} }
sealed interface IconSource { sealed interface IconSource {
data class Resource(val id: Int) : IconSource val contentDescription: String?
data class Vector(val vector: ImageVector) : IconSource
data class Resource(val id: Int, override val contentDescription: String? = null) : IconSource
data class Vector(val vector: ImageVector, override val contentDescription: String? = null) : IconSource
@Composable @Composable
fun getPainter(): Painter = when (this) { fun getPainter(): Painter = when (this) {
@ -216,16 +233,38 @@ enum class ButtonSize {
Medium, Large Medium, Large
} }
private enum class ButtonStyle { internal enum class ButtonStyle {
Filled, Outlined, Text Filled, Outlined, Text;
@Composable
fun getColors(): ButtonColors = when (this) {
Filled -> ButtonDefaults.buttonColors(
containerColor = ElementTheme.materialColors.primary,
contentColor = ElementTheme.materialColors.onPrimary,
disabledContainerColor = ElementTheme.colors.bgActionPrimaryDisabled,
disabledContentColor = ElementTheme.colors.textOnSolidPrimary
)
Outlined -> ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ElementTheme.materialColors.primary,
disabledContainerColor = Color.Transparent,
disabledContentColor = ElementTheme.colors.textDisabled,
)
Text -> ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = if (LocalContentColor.current.isSpecified) LocalContentColor.current else ElementTheme.materialColors.primary,
disabledContainerColor = Color.Transparent,
disabledContentColor = ElementTheme.colors.textDisabled,
)
}
} }
@Preview(group = PreviewGroup.Buttons) @Preview(group = PreviewGroup.Buttons)
@Composable @Composable
internal fun FilledButtonMediumPreview() { internal fun FilledButtonMediumPreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Filled, style = ButtonStyle.Filled,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
) )
} }
@ -233,8 +272,8 @@ internal fun FilledButtonMediumPreview() {
@Composable @Composable
internal fun FilledButtonLargePreview() { internal fun FilledButtonLargePreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Filled, style = ButtonStyle.Filled,
buttonSize = ButtonSize.Large, size = ButtonSize.Large,
) )
} }
@ -242,8 +281,8 @@ internal fun FilledButtonLargePreview() {
@Composable @Composable
internal fun OutlinedButtonMediumPreview() { internal fun OutlinedButtonMediumPreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Outlined, style = ButtonStyle.Outlined,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
) )
} }
@ -251,8 +290,8 @@ internal fun OutlinedButtonMediumPreview() {
@Composable @Composable
internal fun OutlinedButtonLargePreview() { internal fun OutlinedButtonLargePreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Outlined, style = ButtonStyle.Outlined,
buttonSize = ButtonSize.Large, size = ButtonSize.Large,
) )
} }
@ -260,8 +299,8 @@ internal fun OutlinedButtonLargePreview() {
@Composable @Composable
internal fun TextButtonMediumPreview() { internal fun TextButtonMediumPreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Text, style = ButtonStyle.Text,
buttonSize = ButtonSize.Medium, size = ButtonSize.Medium,
) )
} }
@ -269,15 +308,15 @@ internal fun TextButtonMediumPreview() {
@Composable @Composable
internal fun TextButtonLargePreview() { internal fun TextButtonLargePreview() {
ButtonCombinationPreview( ButtonCombinationPreview(
buttonStyle = ButtonStyle.Text, style = ButtonStyle.Text,
buttonSize = ButtonSize.Large, size = ButtonSize.Large,
) )
} }
@Composable @Composable
private fun ButtonCombinationPreview( private fun ButtonCombinationPreview(
buttonStyle: ButtonStyle, style: ButtonStyle,
buttonSize: ButtonSize, size: ButtonSize,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
ElementThemedPreview { ElementThemedPreview {
@ -290,24 +329,24 @@ private fun ButtonCombinationPreview(
// Normal // Normal
ButtonRowPreview( ButtonRowPreview(
modifier = Modifier.then(modifier), modifier = Modifier.then(modifier),
buttonStyle = buttonStyle, style = style,
buttonSize = buttonSize, size = size,
) )
// With icon // With icon
ButtonRowPreview( ButtonRowPreview(
modifier = Modifier.then(modifier), modifier = Modifier.then(modifier),
leadingIcon = IconSource.Vector(Icons.Outlined.Share), leadingIcon = IconSource.Vector(Icons.Outlined.Share),
buttonStyle = buttonStyle, style = style,
buttonSize = buttonSize, size = size,
) )
// With progress // With progress
ButtonRowPreview( ButtonRowPreview(
modifier = Modifier.then(modifier), modifier = Modifier.then(modifier),
showProgress = true, showProgress = true,
buttonStyle = buttonStyle, style = style,
buttonSize = buttonSize, size = size,
) )
} }
} }
@ -315,8 +354,8 @@ private fun ButtonCombinationPreview(
@Composable @Composable
private fun ButtonRowPreview( private fun ButtonRowPreview(
buttonStyle: ButtonStyle, style: ButtonStyle,
buttonSize: ButtonSize, size: ButtonSize,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
leadingIcon: IconSource? = null, leadingIcon: IconSource? = null,
showProgress: Boolean = false, showProgress: Boolean = false,
@ -326,8 +365,8 @@ private fun ButtonRowPreview(
text = "A button", text = "A button",
showProgress = showProgress, showProgress = showProgress,
onClick = {}, onClick = {},
style = buttonStyle, style = style,
size = buttonSize, size = size,
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
modifier = Modifier.then(modifier), modifier = Modifier.then(modifier),
) )
@ -336,8 +375,8 @@ private fun ButtonRowPreview(
showProgress = showProgress, showProgress = showProgress,
enabled = false, enabled = false,
onClick = {}, onClick = {},
style = buttonStyle, style = style,
size = buttonSize, size = size,
leadingIcon = leadingIcon, leadingIcon = leadingIcon,
modifier = Modifier.then(modifier), modifier = Modifier.then(modifier),
) )

View file

@ -18,15 +18,21 @@ package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.theme.ElementTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -43,7 +49,11 @@ fun MediumTopAppBar(
title = title, title = title,
modifier = modifier, modifier = modifier,
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = {
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textActionPrimary) {
actions()
}
},
windowInsets = windowInsets, windowInsets = windowInsets,
colors = colors, colors = colors,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
@ -58,5 +68,14 @@ internal fun MediumTopAppBarPreview() =
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ContentToPreview() { private fun ContentToPreview() {
MediumTopAppBar(title = { Text(text = "Title") }) MediumTopAppBar(
title = { Text(text = "Title") },
navigationIcon = { BackButton(onClick = {}) },
actions = {
TextButton(text = "Action", onClick = {})
IconButton(onClick = {}) {
Icon(imageVector = Icons.Default.Share, contentDescription = null)
}
}
)
} }

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.SnackbarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.button.ButtonVisuals
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.theme.SnackBarLabelColorDark
import io.element.android.libraries.theme.SnackBarLabelColorLight
@Composable
fun Snackbar(
message: String,
modifier: Modifier = Modifier,
action: ButtonVisuals? = null,
dismissAction: ButtonVisuals? = null,
actionOnNewLine: Boolean = false,
shape: Shape = RoundedCornerShape(8.dp),
containerColor: Color = SnackbarDefaults.color,
contentColor: Color = ElementTheme.materialColors.inverseOnSurface,
actionContentColor: Color = actionContentColor(),
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
) {
Snackbar(
modifier = modifier,
action = action?.let { @Composable { it.Composable() } },
dismissAction = dismissAction?.let { @Composable { it.Composable() } },
actionOnNewLine = actionOnNewLine,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
actionContentColor = actionContentColor,
dismissActionContentColor = dismissActionContentColor,
content = { Text(text = message) },
)
}
@Composable
fun Snackbar(
modifier: Modifier = Modifier,
action: @Composable (() -> Unit)? = null,
dismissAction: @Composable (() -> Unit)? = null,
actionOnNewLine: Boolean = false,
shape: Shape = RoundedCornerShape(8.dp),
containerColor: Color = SnackbarDefaults.color,
contentColor: Color = ElementTheme.materialColors.inverseOnSurface,
actionContentColor: Color = actionContentColor(),
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
content: @Composable () -> Unit
) {
androidx.compose.material3.Snackbar(
modifier = modifier,
action = action,
dismissAction = dismissAction,
actionOnNewLine = actionOnNewLine,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
actionContentColor = actionContentColor,
dismissActionContentColor = dismissActionContentColor,
content = content,
)
}
// TODO this color is temporary, an `inverse` version should be added to the semantic colors instead
@Composable
private fun actionContentColor(): Color {
return if (ElementTheme.isLightTheme) {
SnackBarLabelColorLight
} else {
SnackBarLabelColorDark
}
}
@Preview(name = "Snackbar", group = PreviewGroup.Snackbars)
@Composable
internal fun SnackbarPreview() {
ElementThemedPreview {
Snackbar(message = "Snackbar supporting text")
}
}
@Preview(name = "Snackbar with action", group = PreviewGroup.Snackbars)
@Composable
internal fun SnackbarWithActionPreview() {
ElementThemedPreview {
Snackbar(message = "Snackbar supporting text", action = ButtonVisuals.Text("Action", {}))
}
}
@Preview(name = "Snackbar with action and close button", group = PreviewGroup.Snackbars)
@Composable
internal fun SnackbarWithActionAndCloseButtonPreview() {
ElementThemedPreview {
Snackbar(
message = "Snackbar supporting text",
action = ButtonVisuals.Text("Action", {}),
dismissAction = ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), {})
)
}
}
@Preview(name = "Snackbar with action on new line", group = PreviewGroup.Snackbars)
@Composable
internal fun SnackbarWithActionOnNewLinePreview() {
ElementThemedPreview {
Snackbar(message = "Snackbar supporting text", action = ButtonVisuals.Text("Action", {}), actionOnNewLine = true)
}
}
@Preview(name = "Snackbar with action and close button on new line", group = PreviewGroup.Snackbars)
@Composable
internal fun SnackbarWithActionOnNewLineAndCloseButtonPreview() {
ElementThemedPreview {
Snackbar(
message = "Snackbar supporting text",
action = ButtonVisuals.Text("Action", {}),
dismissAction = ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), {}),
actionOnNewLine = true
)
}
}

View file

@ -18,15 +18,21 @@ package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.theme.ElementTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -43,7 +49,11 @@ fun TopAppBar(
title = title, title = title,
modifier = modifier, modifier = modifier,
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = {
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textActionPrimary) {
actions()
}
},
windowInsets = windowInsets, windowInsets = windowInsets,
colors = colors, colors = colors,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
@ -58,5 +68,14 @@ internal fun TopAppBarPreview() =
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ContentToPreview() { private fun ContentToPreview() {
TopAppBar(title = { Text(text = "Title") }) TopAppBar(
title = { Text(text = "Title") },
navigationIcon = { BackButton(onClick = {}) },
actions = {
TextButton(text = "Action", onClick = {})
IconButton(onClick = {}) {
Icon(imageVector = Icons.Default.Share, contentDescription = null)
}
}
)
} }

View file

@ -17,6 +17,8 @@
package io.element.android.libraries.designsystem.utils package io.element.android.libraries.designsystem.utils
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -25,7 +27,11 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.button.ButtonVisuals
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.Snackbar
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -65,6 +71,19 @@ fun SnackbarDispatcher.collectSnackbarMessageAsState(): State<SnackbarMessage?>
return snackbarMessage.collectAsState(initial = null) return snackbarMessage.collectAsState(initial = null)
} }
@Composable
fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
androidx.compose.material3.SnackbarHost(hostState, modifier) { data ->
Snackbar(
message = data.visuals.message,
action = data.visuals.actionLabel?.let { ButtonVisuals.Text(it, data::performAction) },
dismissAction = if (data.visuals.withDismissAction) {
ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), data::dismiss)
} else null,
)
}
}
@Composable @Composable
fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostState { fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostState {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }

View file

@ -17,6 +17,8 @@
package io.element.android.libraries.theme package io.element.android.libraries.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import io.element.android.libraries.theme.compound.generated.internal.DarkDesignTokens
import io.element.android.libraries.theme.compound.generated.internal.LightDesignTokens
// ================================================================================================= // =================================================================================================
// IMPORTANT! // IMPORTANT!
@ -26,3 +28,6 @@ import androidx.compose.ui.graphics.Color
// ================================================================================================= // =================================================================================================
val LinkColor = Color(0xFF0086E6) val LinkColor = Color(0xFF0086E6)
val SnackBarLabelColorLight = LightDesignTokens.colorGray700
val SnackBarLabelColorDark = DarkDesignTokens.colorGray700

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:364f107ffaa4844d0141361642ce3a494a187588f27be50b5fd27d44be21fa64 oid sha256:016ca2c634b467ba6d98ed221506d5bbb250b58a5ea631a0076a572f745cfa84
size 8887 size 8710

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8d0a5de3c4e09d76b6453ccc6ace5c690d540d945a62e630474932734209058a oid sha256:c129a4d13320fb3fec6039e37c802de33d7dd607faa3890f9f1b34f663eecc46
size 11716 size 11552

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9218d1d514342c400051bebb10ef458c99103fda618bba45e61a524c5a58eb63 oid sha256:abe3498282504f7b0bfa3a2713ea556b7d4d361d5173ea56bcdadb37766d0b93
size 11903 size 11742

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f19878925f3b5b377a91885540fb15d29a5b78d8be2282d64e8809af0bbf5ff4 oid sha256:d52d7c6d6271577c80ad1ca170aeb9686d8bb572459212d6ef1fe97c490b382d
size 12195 size 12035

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6d98aabf3bd99793367632e18d5cf678e75fb5ad872a1cb300a7e939ad0c2683 oid sha256:3e6442ca746462345d0573abfb72028a613b08d53f6a5e3f0ef3c5f09beab423
size 19725 size 19491

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c936d2d804bc9e98fcc49430f11ddaa572b05fc8d3a0df93ad6521ee8e78f708 oid sha256:906b39149036cccadd5232c0373a6a94e039b374988ace3d732a5b63b7d81829
size 21806 size 21590

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ac01bc1992e3fa27950c7071cd3e8a06b94a608238d55972816fa2a1a3175e7c oid sha256:545fcfe023789a167a5ee2fe11e7f65426890d794fa08ed32ea4e9fe0fe5f59a
size 9448 size 9387

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e39c5ba30983b034886a6adc330d1510b3a48c40511697edcaf6530716c7ba2e oid sha256:b7ebe27e257108881e4869213ea754926fceb8a047ccb81120fc1a1df19279f4
size 12500 size 12444

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e4ff75e74c19280308878e3001a8791aaa735439cc667946fefd21070611630e oid sha256:ecb0ec0f6b9bffc5cd0ab19f85beb962610d5624652b8e6f21d8aedda69a0fb4
size 12698 size 12645

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:065d2a09680e35540b870862ec0ba5c54182e016017938ef64e510b6132333c9 oid sha256:989b630c0925f0a822b2b7460648e874357e1ecf0ab75caea56c78189d936349
size 13389 size 13329

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:94be795b626868e8afbcc26c3f0161ddcb946a122c02eeed7e823825c9aeba19 oid sha256:caa94ed925e37306cc9453a5ade0266a897dc010cdde86b92a9e1d4710039edc
size 22263 size 22184

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0bb88c64bc68b10b1fb709135f445de5a5e4d78623448d0fef97504e025d5f6d oid sha256:6d9458392de2e0f291f752a8cd9558c9eb891d4140e07c5c61a42a16a3c77838
size 24551 size 24507

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d40dd7970069fb798b738a1775b5843522cc28a581d904f44974d7ceefdbf3c9 oid sha256:cafa0bf978a59ff5903429bcb605afd2cb347bb64c3d58e749b0dd7ddfca6199
size 148087 size 148827

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:47f20f3dc4e4bdbb6641f26b6e03d0dba0f2c7e0fe22fce927870d14f1472066 oid sha256:a19c6af3352ad430c6502ccc9dac1ae8b4b8d690c810ab424c61208fa31e52b0
size 148796 size 149411

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1016b14f6d975828470aa8d6786177d6dde909cc727f39b2c66742a101735e8f oid sha256:852b7b5515cad1bb129273ba95fc528f9cc67bf4dde0995276a96521c4399cbe
size 66240 size 66851

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d40dd7970069fb798b738a1775b5843522cc28a581d904f44974d7ceefdbf3c9 oid sha256:cafa0bf978a59ff5903429bcb605afd2cb347bb64c3d58e749b0dd7ddfca6199
size 148087 size 148827

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:77983bf8dfa0683d472683cdb7ad545beb6b05fdfb8b82ecf90db49d387db72e oid sha256:e4e9e0b3c5be9a07af8cd9a4f4ae725505148805327455350d9504a5974032c2
size 395299 size 395377

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9a28b7439185193fca0f389d36f287fa38d29d18bfc046a125f936026ffd1918 oid sha256:9225a19843eb5c9e1a78a7bd307958c2cad544c58ca2637224455326dfcfffb6
size 6318 size 6315

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:459ff294007ea6238f87954e04e8e13614b7c5fa7ade01f44670014013ebfead oid sha256:628072ca45ae0d215ba9bbe965b68442153d81ad98f330ab1178a0f8fcff3e24
size 15382 size 15339

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b2824676afbff473eff8c8cfbcf8b3e58b2851e37324c6a8d9ba47d626b0f8bc oid sha256:93ca4c1155010d01315b00bb79d9807cf0e6f80a609be668cb5600c16e82ef4d
size 14234 size 14190

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8850f5c0e52e9441f92010bebe8828cdf5e3715250aadb68f31d996ec1fb7520 oid sha256:b7a8ca9eb62e3988fd6adc3d3992bb6a8577b91b44d05a6c45a4af48b3bdbb4f
size 38042 size 38282

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8338dfb26935d63918749a3dd0c3d06d752172aba69bc67969b4928667fa8da1 oid sha256:29c635e2bfb59cea20c8ee2c4c008a2edc74127313c437d63f7e4ef4f0851921
size 39183 size 39425

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c90d81b34d6876b7d4406adce0a775dc67b48da91eac6b4a814eaae9a7b028c2 oid sha256:8242b5826755b17c8a1bd919bc87358b701fc8431ba107c7624c69d2d6a61085
size 54262 size 54268

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0e1999174751655dd87b042f1ea22f281cbf9b5deecc494f05307f125d6e9dac oid sha256:05588cb33208fb210e7953509edc739cf9401f713132a7b44a9883615a4423c0
size 56358 size 56365

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c7f0987b11f1ecc5e8359b668ac8cf29f0d5b6ef6f5c74d3660e68696be699ae oid sha256:c591956dbbc4265381b6bff537efe21bdb70f3ab14862b63976ff2038b193dc6
size 7219 size 12176

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5f470f47f972d0c948a308622e49855bb88365675d8b251cd1cce9972f5569c9 oid sha256:b47ed55d8919fd15da1e3877df6fa32a58bd51ebd676a7ff303e09ddb1d2d7fd
size 6878 size 11604

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:313860648fc6bf47ec98182ff018c54df61703fd8c1e302811a1e3a79a35b72a
size 15933

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:92ffb912aeeea5d161bd8c94ce7ac865eee13de50a7bc15217bfd0955e5a9b32
size 18516

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:55e08f85f71dc07addee6079425158831ed50209f4a43fad9a8d0252d55d0f4a
size 19486

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de21230c271bc78f767f130e07403d778ac65d347cbb4ba564d883c679a283ad
size 20084

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:62b5669a3f1a1138f5b982d27a1d4b2f0a2f79e862a4e50eb238c1d09de3d390
size 18833