Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-24 10:50:03 +01:00
commit bc84d8f5f8
579 changed files with 13604 additions and 3422 deletions

View file

@ -9,9 +9,6 @@ android {
}
dependencies {
/** Project - Common */
implementation(projects.common)
/** Project - Domain */
implementation(projects.domain.tokens.models)
implementation(projects.domain.appTheme.models)
@ -25,6 +22,12 @@ dependencies {
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.palette)
implementation(deps.androidx.windowManager) {
exclude(
deps.kotlin.coroutines.android.get().module.group,
deps.kotlin.coroutines.android.get().module.name
)
}
/** Compose */
implementation(deps.compose.constraintLayout)
@ -40,11 +43,12 @@ dependencies {
/** Other libraries */
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.compose.accompanist.permission)
implementation(deps.material)
implementation(deps.compose.shimmer)
implementation(deps.kotlin.immutable.collections)
implementation(deps.zxing.qrCore)
implementation(deps.jodatime)
api(deps.jodatime)
implementation(deps.timber)
implementation(deps.markdown)
}

View file

@ -1,11 +1,19 @@
package com.tangem.core.ui
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Stable
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.theme.AppThemeModeHolder
@Stable
interface UiDependencies {
val hapticManager: HapticManager
val appThemeModeHolder: AppThemeModeHolder
val globalSnackbarHostState: SnackbarHostState
val eventMessageHandler: EventMessageHandler
}

View file

@ -0,0 +1,33 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import com.tangem.core.ui.res.TangemTheme
/**
* A composable that draws a fade effect at the bottom of the screen. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Box(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size100 + bottomBarHeight)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}

View file

@ -0,0 +1,41 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
@Composable
fun BoxWithGradient(
modifier: Modifier = Modifier,
gradient: Brush = BottomGradient,
content: @Composable BoxScope.() -> Unit,
) {
val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
Box(modifier = modifier.fillMaxSize()) {
content()
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(TangemTheme.dimens.size164 + bottomInsetsPx.dp)
.background(gradient),
)
}
}
private val BottomGradient: Brush = Brush.verticalGradient(
colors = listOf(
TangemColorPalette.Black.copy(alpha = 0f),
TangemColorPalette.Black.copy(alpha = 0.75f),
TangemColorPalette.Black.copy(alpha = 0.95f),
TangemColorPalette.Black,
),
)

View file

@ -86,6 +86,7 @@ fun PrimaryButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
size: TangemButtonSize = TangemButtonSize.Default,
colors: ButtonColors = TangemButtonsDefaults.primaryButtonColors,
showProgress: Boolean = false,
enabled: Boolean = true,
) {
@ -94,7 +95,7 @@ fun PrimaryButton(
text = text,
icon = TangemButtonIconPosition.None,
onClick = onClick,
colors = TangemButtonsDefaults.primaryButtonColors,
colors = colors,
enabled = enabled,
showProgress = showProgress,
size = size,

View file

@ -27,8 +27,8 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.fields.SimpleDialogTextField
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -100,7 +100,7 @@ fun TextInputDialog(
confirmButton: DialogButton,
onDismissDialog: () -> Unit,
onValueChange: (TextFieldValue) -> Unit,
textFieldParams: AdditionalTextInputDialogParams,
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
title: String? = null,
dismissButton: DialogButton? = null,
isDismissable: Boolean = true,
@ -128,7 +128,7 @@ fun TextInputDialog(
confirmButton: DialogButton,
onDismissDialog: () -> Unit,
onValueChange: (String) -> Unit,
textFieldParams: AdditionalTextInputDialogParams,
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
title: String? = null,
dismissButton: DialogButton? = null,
isDismissable: Boolean = true,

View file

@ -1,12 +0,0 @@
package com.tangem.core.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import com.google.accompanist.systemuicontroller.SystemUiController
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun SystemBarsEffect(block: SystemUiController.() -> Unit) {
val systemUiController = rememberSystemUiController()
SideEffect { block(systemUiController) }
}

View file

@ -0,0 +1,61 @@
package com.tangem.core.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.res.LocalIsInDarkTheme
/**
* Provides the ability to set a scrim for 3-button navigation
*
* Automatically makes navigation bar transparent when the composable is disposed.
*
* Usage example: [com.tangem.feature.tokendetails.presentation.TokenDetailsFragment]
*/
@Composable
fun NavigationBar3ButtonsScrim() {
val systemUiController = rememberSystemUiController()
val isDarkTheme = LocalIsInDarkTheme.current
SideEffect {
systemUiController.isNavigationBarContrastEnforced = true
}
LaunchedEffect(systemUiController.isNavigationBarContrastEnforced) {
if (systemUiController.isNavigationBarContrastEnforced.not()) {
systemUiController.isNavigationBarContrastEnforced = true
}
}
DisposableEffect(isDarkTheme) {
onDispose {
systemUiController.isNavigationBarContrastEnforced = false
}
}
}
/**
* Provides the ability to set dark/light icons in cases where the darkness of the screen differs from that
* provided in [TangemTheme].
*
* Automatically returns the icon colors to their original state when the composable is disposed.
*
* Usage example: [com.tangem.feature.qrscanning.QrScanningFragment]
*/
@Composable
fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.systemBarsDarkContentEnabled = darkIcons
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
}
val isDarkTheme = LocalIsInDarkTheme.current
DisposableEffect(isDarkTheme) {
onDispose {
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
systemUiController.isNavigationBarContrastEnforced = false
}
}
}

View file

@ -1,23 +1,57 @@
package com.tangem.core.ui.components.appbar
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.appbar.models.AdditionalButton
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* App bar with title and two additional buttons
*
* @param startButton button information attached to the left edge
* @param endButton button information attached to the right edge
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=50%3A403&t=tRQ4KlzkjV7TCLZl-4">
* Figma Component</a>
*/
@Composable
fun AppBarWithAdditionalButtons(
text: TextReference,
modifier: Modifier = Modifier,
startButton: AdditionalButton? = null,
endButton: AdditionalButton? = null,
textColor: Color = TangemTheme.colors.text.primary1,
iconColor: Color = TangemTheme.colors.icon.primary1,
) {
AppBarWithAdditionalButtons(
text = text.resolveReference(),
startButton = startButton,
endButton = endButton,
modifier = modifier,
textColor = textColor,
iconColor = iconColor,
)
}
/**
* App bar with title and two additional buttons
@ -31,43 +65,63 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun AppBarWithAdditionalButtons(
text: String,
modifier: Modifier = Modifier,
startButton: AdditionalButton? = null,
endButton: AdditionalButton? = null,
textColor: Color = TangemTheme.colors.text.primary1,
iconColor: Color = TangemTheme.colors.icon.primary1,
) {
Box(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.heightIn(TangemTheme.dimens.size56)
.padding(all = TangemTheme.dimens.spacing16),
) {
if (startButton != null) {
IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) {
Icon(
painter = painterResource(id = startButton.iconRes),
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size24),
tint = TangemTheme.colors.icon.primary1,
)
}
Icon(
painter = painterResource(id = startButton.iconRes),
contentDescription = null,
tint = iconColor,
modifier = Modifier
.size(TangemTheme.dimens.size24)
.align(Alignment.CenterStart)
.clickable(
onClick = startButton.onIconClicked,
role = Role.Button,
interactionSource = remember { MutableInteractionSource() },
indication = androidx.compose.material.ripple.rememberRipple(
bounded = false,
radius = TangemTheme.dimens.size24 / 2,
),
),
)
}
Text(
text = text,
modifier = Modifier.align(Alignment.Center),
color = TangemTheme.colors.text.primary1,
color = textColor,
maxLines = 1,
style = TangemTheme.typography.subtitle1,
)
if (endButton != null) {
IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) {
Icon(
painter = painterResource(id = endButton.iconRes),
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size24),
tint = TangemTheme.colors.icon.primary1,
)
}
Icon(
painter = painterResource(id = endButton.iconRes),
contentDescription = null,
tint = iconColor,
modifier = Modifier
.size(TangemTheme.dimens.size24)
.align(Alignment.CenterEnd)
.clickable(
onClick = endButton.onIconClicked,
role = Role.Button,
interactionSource = remember { MutableInteractionSource() },
indication = androidx.compose.material.ripple.rememberRipple(
bounded = false,
radius = TangemTheme.dimens.size24 / 2,
),
),
)
}
}
}
@ -87,6 +141,7 @@ private fun Preview_AppBarWithAdditionalButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@ -102,6 +157,7 @@ private fun Preview_AppBarWithOnlyStartButtons() {
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
),
modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@ -117,6 +173,7 @@ private fun Preview_AppBarWithOnlyEndButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}

View file

@ -0,0 +1,75 @@
package com.tangem.core.ui.components.appbar.models
import androidx.compose.foundation.layout.size
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBarMedium(
title: TextReference,
scrollBehavior: TopAppBarScrollBehavior,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
navigationIconResId: Int = R.drawable.ic_back_24,
colors: TopAppBarColors = TangemTopAppBarColors,
) {
MediumTopAppBar(
modifier = modifier,
scrollBehavior = scrollBehavior,
colors = colors,
title = {
val collapsedStyle = TangemTheme.typography.subtitle1
val expandedStyle = TangemTheme.typography.h1
val style by remember(scrollBehavior.state.collapsedFraction) {
derivedStateOf {
if (scrollBehavior.state.collapsedFraction >= COLLAPSED_APP_BAR_THRESHOLD) {
collapsedStyle
} else {
expandedStyle
}
}
}
Text(
text = title.resolveReference(),
style = style,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = onBackClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = navigationIconResId),
contentDescription = null,
)
}
},
)
}
@OptIn(ExperimentalMaterial3Api::class)
internal val TangemTopAppBarColors: TopAppBarColors
@Composable
@ReadOnlyComposable
get() = TopAppBarColors(
containerColor = TangemTheme.colors.background.secondary,
scrolledContainerColor = TangemTheme.colors.background.secondary,
navigationIconContentColor = TangemTheme.colors.icon.primary1,
titleContentColor = TangemTheme.colors.text.primary1,
actionIconContentColor = TangemTheme.colors.icon.primary1,
)

View file

@ -0,0 +1,85 @@
package com.tangem.core.ui.components.atoms.radiobutton
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* [Radio button](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=101-119&t=FH84ljLBk1vmGAei-4)
*
* @param isSelected Whether the radio button is selected
* @param onClick Called when the user clicks the button
* @param modifier Modifier to be applied to the button
* @param isEnabled Whether the button click is enabled
*/
@Composable
fun TangemRadioButton(
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
) {
Box(
modifier = modifier.clickable(
enabled = isEnabled,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false, radius = TangemTheme.dimens.size16),
onClick = onClick,
),
) {
val color = TangemTheme.colors.stroke.secondary
val radius = with(LocalDensity.current) { TangemTheme.dimens.size9.toPx() }
val width = with(LocalDensity.current) { TangemTheme.dimens.size2.toPx() }
Canvas(
modifier = Modifier
.size(TangemTheme.dimens.size24)
.padding(TangemTheme.dimens.spacing2),
) {
drawCircle(
color = color,
radius = radius,
style = Stroke(width),
)
}
AnimatedVisibility(
visible = isSelected,
label = "Radio button animation",
modifier = modifier
.size(TangemTheme.dimens.size24),
) {
Icon(
painter = painterResource(id = R.drawable.ic_check_circle_24),
contentDescription = null,
tint = TangemTheme.colors.control.checked,
)
}
}
}
// region Preview
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TangemRadioButton_Preview() {
var isSelected by remember { mutableStateOf(false) }
TangemThemePreview {
TangemRadioButton(isSelected, onClick = { isSelected = !isSelected })
}
}
// endregion

View file

@ -0,0 +1,37 @@
package com.tangem.core.ui.components.block
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
@Composable
fun BlockCard(
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: CardColors = TangemBlockCardColors,
onClick: () -> Unit = {},
content: @Composable ColumnScope.() -> Unit = {},
) {
Card(
modifier = modifier,
onClick = onClick,
shape = TangemTheme.shapes.roundedCornersXMedium,
colors = colors,
enabled = enabled,
content = content,
)
}
val TangemBlockCardColors: CardColors
@Composable
@ReadOnlyComposable
get() = CardColors(
containerColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledContainerColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
)

View file

@ -0,0 +1,53 @@
package com.tangem.core.ui.components.block
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@Composable
fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
onClick = model.onClick,
) {
Row(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12, Alignment.Start),
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = model.iconRes),
tint = when (model.accentType) {
BlockUM.AccentType.NONE -> TangemTheme.colors.icon.secondary
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
},
contentDescription = null,
)
Text(
text = model.text.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = when (model.accentType) {
BlockUM.AccentType.NONE -> TangemTheme.colors.text.primary1
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.core.ui.components.block.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.extensions.TextReference
data class BlockUM(
val text: TextReference,
@DrawableRes val iconRes: Int,
val onClick: () -> Unit,
val accentType: AccentType = AccentType.NONE,
) {
enum class AccentType {
NONE, ACCENT, WARNING,
}
}

View file

@ -1,13 +1,14 @@
package com.tangem.core.ui.components.bottomsheets
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.WindowInsetsZero
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
* Tangem bottom sheet with custom draggable header and config
*
* @param config data model containing logic and ui models
* @param
* @param content custom bottom sheet content
*/
@OptIn(ExperimentalMaterial3Api::class)
@ -22,20 +24,33 @@ import kotlinx.coroutines.launch
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
config: TangemBottomSheetConfig,
containerColor: Color = TangemTheme.colors.background.primary,
addBottomInsets: Boolean = true,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
var isVisible by remember { mutableStateOf(value = config.isShow) }
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (isVisible && config.content is T) {
ModalBottomSheet(
// FIXME temporary solution to fix height of the bottom sheet
modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight),
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = containerColor,
shape = TangemTheme.shapes.bottomSheetLarge,
windowInsets = WindowInsetsZero,
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
) {
content(config.content)
if (addBottomInsets) {
Column(
// FIXME temporary solution to fix height of the bottom sheet
modifier = Modifier.navigationBarsPadding(),
) {
content(config.content)
}
} else {
content(config.content)
}
}
}

View file

@ -8,10 +8,7 @@ import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@ -21,8 +18,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@ -42,7 +40,7 @@ import kotlinx.collections.immutable.persistentListOf
*/
@Composable
inline fun <reified T> SegmentedButtons(
config: PersistentList<T>,
config: ImmutableList<T>,
crossinline onClick: (T) -> Unit,
modifier: Modifier = Modifier,
color: Color = TangemTheme.colors.background.tertiary,
@ -51,7 +49,7 @@ inline fun <reified T> SegmentedButtons(
showIndication: Boolean = true,
initialSelectedItem: T? = null,
isEnabled: Boolean = true,
crossinline buttonContent: @Composable (T) -> Unit,
crossinline buttonContent: @Composable BoxScope.(T) -> Unit,
) {
if (config.isEmpty() || config.size == 1) return
@ -97,7 +95,7 @@ inline fun <reified T> SegmentedButtons(
onClick(config[index])
},
) {
buttonContent.invoke(config[index])
buttonContent.invoke(this, config[index])
}
}
}

View file

@ -5,7 +5,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
@ -14,18 +13,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
@ -63,7 +59,7 @@ fun InputRowBestRate(
modifier = Modifier
.padding(TangemTheme.dimens.spacing12),
) {
InnerIcon(imageUrl = imageUrl)
InputRowAsyncImage(imageUrl = imageUrl, modifier = Modifier.size(TangemTheme.dimens.spacing40))
Column(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12),
@ -131,29 +127,6 @@ private fun InnerTitle(title: TextReference, titleExtra: TextReference, showTag:
}
}
@Composable
private fun InnerIcon(imageUrl: String) {
SubcomposeAsyncImage(
modifier = Modifier.size(TangemTheme.dimens.size40),
model = ImageRequest.Builder(context = LocalContext.current)
.data(imageUrl)
.crossfade(enable = true)
.allowHardware(enable = false)
.build(),
loading = { LoadingIcon() },
error = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.tertiary,
shape = CircleShape,
),
)
},
contentDescription = null,
)
}
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -0,0 +1,54 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun InputRowImageBase(
subtitle: TextReference,
caption: TextReference,
imageUrl: String,
modifier: Modifier = Modifier,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
extraContent: @Composable RowScope.() -> Unit = {},
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
InputRowAsyncImage(
imageUrl = imageUrl,
modifier = Modifier
.size(TangemTheme.dimens.spacing36)
.padding(vertical = TangemTheme.dimens.size1),
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = TangemTheme.dimens.spacing12),
) {
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = subtitleColor,
)
Text(
text = caption.resolveAnnotatedReference(),
style = TangemTheme.typography.caption2,
color = captionColor,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
)
}
extraContent()
}
}

View file

@ -0,0 +1,46 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
/**
* Input row component with selector
* [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2100-842&t=hoBXmDX8NeLrp4p6-4)
*
* @param subtitle subtitle text
* @param caption caption text
* @param imageUrl icon to load
* @param modifier modifier
* @param subtitleColor subtitle text color
* @param captionColor caption text color
*/
@Composable
fun InputRowImageChevron(
subtitle: TextReference,
caption: TextReference,
imageUrl: String,
modifier: Modifier = Modifier,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
) {
InputRowImageBase(
subtitle = subtitle,
caption = caption,
imageUrl = imageUrl,
modifier = modifier,
subtitleColor = subtitleColor,
captionColor = captionColor,
) {
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}

View file

@ -0,0 +1,131 @@
package com.tangem.core.ui.components.inputrow
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ripple.rememberRipple
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.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.atoms.radiobutton.TangemRadioButton
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.annotatedReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
import java.math.BigDecimal
/**
* Input row component with selector
* [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2772-905&t=FH84ljLBk1vmGAei-4)
*
* @param subtitle subtitle text
* @param caption caption text
* @param imageUrl icon to load
* @param onSelect callback when selected
* @param modifier modifier
* @param subtitleColor subtitle text color
* @param captionColor caption text color
* @param isSelected true if selected
*/
@Composable
fun InputRowImageSelector(
subtitle: TextReference,
caption: TextReference,
imageUrl: String,
onSelect: () -> Unit,
modifier: Modifier = Modifier,
subtitleColor: Color = TangemTheme.colors.text.primary1,
captionColor: Color = TangemTheme.colors.text.tertiary,
isSelected: Boolean = false,
) {
InputRowImageBase(
subtitle = subtitle,
caption = caption,
imageUrl = imageUrl,
subtitleColor = subtitleColor,
captionColor = captionColor,
modifier = modifier
.clickable(
onClick = onSelect,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
)
.padding(TangemTheme.dimens.spacing12),
) {
TangemRadioButton(isSelected = isSelected, isEnabled = false, onClick = onSelect)
}
}
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun InputRowImageSelectorPreview(
@PreviewParameter(InputRowImageSelectorPreviewDataProvider::class) data: InputRowImageSelectorPreviewData,
) {
TangemThemePreview {
InputRowImageSelector(
modifier = Modifier.background(TangemTheme.colors.background.action),
subtitle = data.subtitle,
caption = combinedReference(
resourceReference(R.string.staking_details_apr),
annotatedReference(
buildAnnotatedString {
append(" ")
withStyle(style = SpanStyle(color = TangemTheme.colors.text.accent)) {
append(
BigDecimalFormatter.formatPercent(BigDecimal.ZERO, true),
)
}
},
),
),
imageUrl = "",
isSelected = false,
onSelect = {},
)
}
}
private data class InputRowImageSelectorPreviewData(
val subtitle: TextReference,
val caption: TextReference,
val showDivider: Boolean,
val actionIconRes: Int?,
val isSelected: Boolean,
)
private class InputRowImageSelectorPreviewDataProvider :
PreviewParameterProvider<InputRowImageSelectorPreviewData> {
override val values: Sequence<InputRowImageSelectorPreviewData>
get() = sequenceOf(
InputRowImageSelectorPreviewData(
subtitle = TextReference.Str("subtitle"),
caption = TextReference.Str("caption"),
actionIconRes = null,
showDivider = false,
isSelected = false,
),
InputRowImageSelectorPreviewData(
subtitle = TextReference.Str("subtitle"),
caption = TextReference.Str("caption"),
actionIconRes = R.drawable.ic_chevron_right_24,
showDivider = true,
isSelected = true,
),
)
}
//endregion

View file

@ -0,0 +1,41 @@
package com.tangem.core.ui.components.inputrow.inner
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
import com.tangem.core.ui.res.TangemTheme
/**
* Loads image by url for icon in the input row
*
* @param imageUrl url of the image
* @param modifier modifier
*/
@Composable
internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier) {
SubcomposeAsyncImage(
modifier = modifier,
model = ImageRequest.Builder(context = LocalContext.current)
.data(imageUrl)
.crossfade(enable = true)
.allowHardware(enable = false)
.build(),
loading = { LoadingIcon() },
error = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.tertiary,
shape = CircleShape,
),
)
},
contentDescription = null,
)
}

View file

@ -36,6 +36,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemColorPalette.White
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Travala notification with image background
@ -192,7 +193,7 @@ private fun formatSubtitle(subtitle: String): AnnotatedString {
@Preview
@Composable
private fun TravalaNotificationWithBackgroundPreview() {
TangemTheme {
TangemThemePreview {
TravalaNotificationWithBackground(
config = NotificationConfig(
title = resourceReference(

View file

@ -0,0 +1,147 @@
package com.tangem.core.ui.components.rows
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.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.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.R
@Suppress("LongParameterList")
@Composable
fun RoundableCornersRow(
startText: String,
startTextColor: Color,
startTextStyle: TextStyle,
endText: String,
endTextColor: Color,
endTextStyle: TextStyle,
cornersToRound: CornersToRound,
iconResId: Int? = null,
) {
Surface(
shape = cornersToRound.getShape(),
color = TangemTheme.colors.background.primary,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(TangemTheme.dimens.size48)
.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing12,
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = startText,
color = startTextColor,
maxLines = 1,
style = startTextStyle,
)
if (iconResId != null) {
Icon(
modifier = Modifier
.padding(TangemTheme.dimens.spacing4)
.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_alert_24),
contentDescription = null,
tint = TangemTheme.colors.text.tertiary,
)
}
Spacer(modifier = Modifier.weight(1f))
Text(
text = endText,
color = endTextColor,
maxLines = 1,
style = endTextStyle,
)
}
}
}
enum class CornersToRound {
ALL_4,
TOP_2,
BOTTOM_2,
ZERO,
;
@Suppress("TopLevelComposableFunctions")
@Composable
fun getShape(): RoundedCornerShape {
val radius = TangemTheme.dimens.radius12
return when (this) {
ALL_4 -> RoundedCornerShape(radius)
TOP_2 -> RoundedCornerShape(topStart = radius, topEnd = radius)
BOTTOM_2 -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
ZERO -> RoundedCornerShape(0.dp)
}
}
}
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_RoundableCornersRow(
@PreviewParameter(RoundableCornersRowDataProvider::class) previewData: RoundableCornersRowPreviewData,
) {
TangemThemePreview {
Box(modifier = Modifier.background(color = TangemTheme.colors.icon.attention)) {
RoundableCornersRow(
startText = previewData.startText,
startTextColor = TangemTheme.colors.text.tertiary,
startTextStyle = TangemTheme.typography.subtitle2,
endText = previewData.endText,
endTextColor = TangemTheme.colors.text.primary1,
endTextStyle = TangemTheme.typography.subtitle2,
cornersToRound = previewData.cornersToRound,
iconResId = previewData.iconResId,
)
}
}
}
private data class RoundableCornersRowPreviewData(
val startText: String,
val endText: String,
val cornersToRound: CornersToRound,
val iconResId: Int? = null,
)
private class RoundableCornersRowDataProvider :
PreviewParameterProvider<RoundableCornersRowPreviewData> {
override val values: Sequence<RoundableCornersRowPreviewData>
get() = sequenceOf(
getPreviewData(cornersToRound = CornersToRound.ZERO),
getPreviewData(cornersToRound = CornersToRound.TOP_2),
getPreviewData(cornersToRound = CornersToRound.BOTTOM_2),
getPreviewData(cornersToRound = CornersToRound.ZERO, iconResId = R.drawable.ic_alert_24),
getPreviewData(cornersToRound = CornersToRound.TOP_2, iconResId = R.drawable.ic_alert_24),
getPreviewData(cornersToRound = CornersToRound.BOTTOM_2, iconResId = R.drawable.ic_alert_24),
)
private fun getPreviewData(cornersToRound: CornersToRound, iconResId: Int? = null) = RoundableCornersRowPreviewData(
startText = "startText",
endText = "endText",
cornersToRound = cornersToRound,
iconResId = iconResId,
)
}

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.utils.Strings
@Composable
fun SelectorRowItem(
@ -127,7 +128,7 @@ private fun RowScope.SelectorValueContent(
)
if (postDot != null) {
Text(
text = "",
text = Strings.DOT,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,

View file

@ -0,0 +1,143 @@
package com.tangem.core.ui.components.showcase
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.showcase.model.ShowcaseButtonModel
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* @param headerIconRes big header icon
* @param headerText header text
* @param showcaseItems list of bullet points
* @param primaryButton primary button
* @param secondaryButton secondary button
* @param modifier compose modifier
*
* @see <a href = "https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=12620-90568&t=WeCOVTgeYfkr24Ff-4"
* >Figma component</a>
*/
@Composable
fun Showcase(
@DrawableRes headerIconRes: Int,
headerText: TextReference,
showcaseItems: ImmutableList<ShowcaseItemModel>,
primaryButton: ShowcaseButtonModel,
secondaryButton: ShowcaseButtonModel,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.fillMaxSize(),
) {
ShowcaseContent(
headerIconRes = headerIconRes,
headerText = headerText,
showcaseItems = showcaseItems,
modifier = Modifier
.weight(1f)
.align(Alignment.CenterHorizontally),
)
ShowcaseButtons(
primaryButtonText = primaryButton.buttonText,
onPrimaryClick = primaryButton.onClick,
secondaryButtonText = secondaryButton.buttonText,
onSecondaryClick = secondaryButton.onClick,
)
}
}
@Composable
private fun ShowcaseButtons(
primaryButtonText: TextReference,
secondaryButtonText: TextReference,
onPrimaryClick: () -> Unit,
onSecondaryClick: () -> Unit,
hint: TextReference? = null,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
PrimaryButton(
text = primaryButtonText.resolveReference(),
onClick = onPrimaryClick,
modifier = Modifier
.fillMaxWidth()
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
),
)
SecondaryButton(
text = secondaryButtonText.resolveReference(),
onClick = onSecondaryClick,
modifier = Modifier
.fillMaxWidth()
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing16,
),
)
hint?.let {
Text(
text = it.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
modifier = Modifier
.fillMaxWidth()
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing12,
),
)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Showcase_Preview() {
TangemThemePreview {
Showcase(
headerIconRes = R.drawable.ic_notifications_unread_24,
headerText = resourceReference(R.string.user_push_notification_agreement_header),
showcaseItems = persistentListOf(
ShowcaseItemModel(
R.drawable.ic_rocket_launch_24,
resourceReference(R.string.user_push_notification_agreement_argument_one),
),
ShowcaseItemModel(
R.drawable.ic_storefront_24,
resourceReference(R.string.user_push_notification_agreement_argument_two),
),
),
primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}),
secondaryButton = ShowcaseButtonModel(resourceReference(R.string.common_later), {}),
modifier = Modifier.background(TangemTheme.colors.background.primary),
)
}
}
// endregion

View file

@ -0,0 +1,71 @@
package com.tangem.core.ui.components.showcase
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
@Composable
fun ShowcaseContent(
@DrawableRes headerIconRes: Int,
headerText: TextReference,
showcaseItems: ImmutableList<ShowcaseItemModel>,
modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.Center,
modifier = modifier,
) {
Icon(
painter = painterResource(id = headerIconRes),
contentDescription = null,
tint = TangemTheme.colors.icon.primary1,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.size(TangemTheme.dimens.size56),
)
Text(
text = headerText.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(
start = TangemTheme.dimens.spacing34,
end = TangemTheme.dimens.spacing34,
top = TangemTheme.dimens.spacing28,
),
)
Column(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing34,
end = TangemTheme.dimens.spacing34,
top = TangemTheme.dimens.spacing28,
),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
horizontalAlignment = Alignment.CenterHorizontally,
) {
repeat(showcaseItems.size) { index ->
ShowcaseItem(
iconRes = showcaseItems[index].iconRes,
text = showcaseItems[index].text,
)
}
}
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.core.ui.components.showcase
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun ShowcaseItem(@DrawableRes iconRes: Int, text: TextReference) {
Row {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = TangemTheme.colors.icon.primary1,
)
Text(
text = text.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing20),
)
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.components.showcase.model
import com.tangem.core.ui.extensions.TextReference
data class ShowcaseButtonModel(
val buttonText: TextReference,
val onClick: () -> Unit,
)

View file

@ -0,0 +1,9 @@
package com.tangem.core.ui.components.showcase.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.extensions.TextReference
data class ShowcaseItemModel(
@DrawableRes val iconRes: Int,
val text: TextReference,
)

View file

@ -19,6 +19,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Snackbar to inform the user about copying text to the clipboard
@ -100,7 +101,7 @@ private fun MessageText(text: TextReference, modifier: Modifier = Modifier) {
private fun Preview_CopiedTextSnackbar(
@PreviewParameter(CopiedTextSnackbarDataProvider::class) message: TextReference,
) {
TangemTheme(isDark = false) {
TangemThemePreview {
CopiedTextSnackbar(message = message)
}
}

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.tangem.common.Strings
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
@ -35,6 +34,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.utils.Strings
import java.util.UUID
/**

View file

@ -0,0 +1,13 @@
package com.tangem.core.ui.decompose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@Stable
fun interface ComposableContentComponent {
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
fun Content(modifier: Modifier)
}

View file

@ -0,0 +1,14 @@
package com.tangem.core.ui.decompose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@Stable
interface ComposableDialogComponent {
val doOnDismiss: () -> Unit
@Composable
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
fun Dialog()
}

View file

@ -11,6 +11,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import org.intellij.markdown.MarkdownElementTypes
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
/**
* Utility class for creating text as [String] or [StringRes].
@ -46,6 +48,13 @@ sealed interface TextReference {
*/
data class Str(val value: String) : TextReference
/**
* Annotated text
*
* @property value annotated string
*/
data class Annotated(val value: AnnotatedString) : TextReference
/**
* Combined reference. It concatenates all [refs].
*
@ -81,6 +90,16 @@ fun stringReference(value: String): TextReference {
return TextReference.Str(value)
}
/**
* Creates a [TextReference] using an annotated string value.
*
* @param value The annotated string value.
* @return A [TextReference] representing the provided annotated string value.
*/
fun annotatedReference(value: AnnotatedString): TextReference {
return TextReference.Annotated(value)
}
/**
* Creates a [TextReference] using a plural string resource ID with count and optional format arguments.
*
@ -131,6 +150,7 @@ fun TextReference.resolveReference(): String {
}
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Annotated -> value.text
is TextReference.Combined -> {
buildString {
refs.forEach {
@ -153,6 +173,7 @@ fun TextReference.resolveReference(resources: Resources): String {
}
is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Annotated -> value.text
is TextReference.Combined -> {
buildString {
refs.forEach {
@ -178,9 +199,10 @@ fun TextReference.resolveAnnotatedReference(): AnnotatedString {
pluralStringResource(id, count, *formatArgs.toTypedArray()),
)
is TextReference.Str -> formatAnnotated(value)
is TextReference.Annotated -> value
is TextReference.Combined -> buildAnnotatedString {
refs.forEach {
append(formatAnnotated(it.resolveReference()))
append(it.resolveAnnotatedReference())
}
}
}
@ -193,10 +215,21 @@ operator fun TextReference.plus(ref: TextReference): TextReference {
is TextReference.PluralRes,
is TextReference.Res,
is TextReference.Str,
is TextReference.Annotated,
-> TextReference.Combined(refs = wrappedList(this, ref))
}
}
@Suppress("NOTHING_TO_INLINE")
@OptIn(ExperimentalContracts::class)
inline fun TextReference?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this == TextReference.EMPTY
}
@Composable
private fun formatAnnotated(rawString: String): AnnotatedString {
val markdownDescriptor = rememberMarkdownParser()

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.message
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.core.ui.event.StateEvent
@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
/**
* Message handler that is used to show or remove an [EventMessage] in the UI.
*/
@Stable
class EventMessageHandler(
private val events: MutableStateFlow<StateEvent<EventMessage>> = MutableStateFlow(consumedEvent()),
) : UiMessageHandler, StateFlow<StateEvent<EventMessage>> by events {

View file

@ -83,7 +83,6 @@ class TangemColors internal constructor(
warning: Color,
attention: Color,
accent: Color = TangemColorPalette.Azure,
constant: Color = TangemColorPalette.White,
) {
var primary1 by mutableStateOf(primary1)
private set

View file

@ -47,6 +47,7 @@ data class TangemDimens internal constructor(
val size5: Dp = 5.dp,
val size7: Dp = 7.dp,
val size8: Dp = 8.dp,
val size9: Dp = 9.dp,
val size10: Dp = 10.dp,
val size11: Dp = 11.dp,
val size12: Dp = 12.dp,
@ -81,8 +82,10 @@ data class TangemDimens internal constructor(
val size90: Dp = 90.dp,
val size93: Dp = 93.dp,
val size96: Dp = 96.dp,
val size100: Dp = 100.dp,
val size102: Dp = 102.dp,
val size108: Dp = 108.dp,
val size110: Dp = 110.dp,
val size116: Dp = 116.dp,
val size120: Dp = 120.dp,
val size142: Dp = 142.dp,
@ -99,9 +102,11 @@ data class TangemDimens internal constructor(
val spacing2: Dp = 2.dp,
val spacing3: Dp = 3.dp,
val spacing4: Dp = 4.dp,
val spacing5: Dp = 5.dp,
val spacing6: Dp = 6.dp,
val spacing8: Dp = 8.dp,
val spacing10: Dp = 10.dp,
val spacing11: Dp = 11.dp,
val spacing12: Dp = 12.dp,
val spacing14: Dp = 14.dp,
val spacing15: Dp = 15.dp,

View file

@ -3,19 +3,22 @@ package com.tangem.core.ui.res
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.MockHapticManager
// TODO: use isSystemInDarkTheme() for automatic color detection
internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false
import com.tangem.core.ui.windowsize.WindowSize
@Composable
fun TangemTheme(
isDark: Boolean = false,
windowSize: WindowSize,
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
hapticManager: HapticManager = MockHapticManager,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
content: @Composable () -> Unit,
) {
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
@ -23,6 +26,16 @@ fun TangemTheme(
.also { it.update(themeColors) }
val shapes = remember { TangemShapes(dimens) }
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = !isDark,
isNavigationBarContrastEnforced = false,
)
}
MaterialTheme(
colors = materialThemeColors(colors = themeColors, isDark = isDark),
) {
@ -33,6 +46,8 @@ fun TangemTheme(
LocalTangemShapes provides shapes,
LocalIsInDarkTheme provides isDark,
LocalHapticManager provides hapticManager,
LocalSnackbarHostState provides snackbarHostState,
LocalWindowSize provides windowSize,
) {
ProvideTextStyle(
value = TangemTheme.typography.body1,
@ -204,4 +219,12 @@ val LocalIsInDarkTheme = staticCompositionLocalOf { false }
val LocalHapticManager = staticCompositionLocalOf<HapticManager> {
error("No HapticManager provided")
}
val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
error("No SnackbarHostState provided")
}
val LocalWindowSize = staticCompositionLocalOf<WindowSize> {
error("No WindowSize provided")
}

View file

@ -1,7 +1,9 @@
package com.tangem.core.ui.res
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.runtime.Composable
import com.tangem.core.ui.windowsize.rememberWindowSizePreview
@Composable
fun TangemThemePreview(
@ -12,10 +14,13 @@ fun TangemThemePreview(
) {
val isDarkTheme = isDark ?: isSystemInDarkTheme()
TangemTheme(
isDark = isDarkTheme,
typography = typography,
dimens = dimens,
content = content,
)
BoxWithConstraints {
TangemTheme(
isDark = isDarkTheme,
typography = typography,
dimens = dimens,
windowSize = rememberWindowSizePreview(maxWidth, maxHeight),
content = content,
)
}
}

View file

@ -12,6 +12,6 @@ abstract class ComposeActivity : ComponentActivity(), ComposeScreen {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(createComposeView(context = this))
setContentView(createComposeView(context = this, activity = this))
}
}

View file

@ -52,7 +52,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), Compose
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return createComposeView(inflater.context)
return createComposeView(inflater.context, requireActivity())
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

View file

@ -17,7 +17,7 @@ abstract class ComposeFragment : Fragment(), ComposeScreen {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val isTransitionsInflated = TransitionInflater.from(requireContext()).inflateTransitions()
return createComposeView(inflater.context).also {
return createComposeView(inflater.context, requireActivity()).also {
it.isTransitionGroup = isTransitionsInflated
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.screen
import android.app.Activity
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
@ -10,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.windowsize.rememberWindowSize
import com.tangem.domain.apptheme.model.AppThemeMode
/**
@ -50,14 +52,17 @@ internal interface ComposeScreen {
* @param context The context.
* @return A [ComposeView] instance with the defined screen content.
*/
internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
internal fun ComposeScreen.createComposeView(context: Context, activity: Activity): ComposeView {
return ComposeView(context).apply {
setContent {
val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
val windowSize = rememberWindowSize(activity = activity)
TangemTheme(
isDark = shouldUseDarkTheme(appThemeMode),
windowSize = windowSize,
hapticManager = uiDependencies.hapticManager,
snackbarHostState = uiDependencies.globalSnackbarHostState,
) {
ScreenContent(modifier = screenModifier)
}

View file

@ -0,0 +1,52 @@
package com.tangem.core.ui.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
/**
* Returns push permission requester.
* Handles granting permission from app settings.
*/
@Suppress("LongParameterList")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun requestPushPermission(
isFirstTimeAsking: Boolean,
pushPermission: String?,
isClicked: MutableState<Boolean>,
onAllow: () -> Unit,
onDeny: () -> Unit,
onOpenSettings: () -> Unit,
): () -> Unit {
val permissionState = pushPermission?.let { permission ->
val tempPermissionState = rememberPermissionState(permission = permission)
rememberPermissionState(permission = permission) {
when {
it -> onAllow()
!tempPermissionState.status.shouldShowRationale && !isFirstTimeAsking -> onOpenSettings()
else -> onDeny()
}
}
}
// Check if user granted permission and close bottom sheet
LaunchedEffect(key1 = permissionState?.status, isClicked) {
if (!isClicked.value) return@LaunchedEffect
if (permissionState?.status?.isGranted == true) {
onAllow()
} else {
onDeny()
}
}
return if (permissionState == null) {
onOpenSettings
} else {
permissionState::launchPermissionRequest
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.core.ui.utils
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
object WindowInsetsZero : WindowInsets {
override fun getBottom(density: Density): Int = 0
override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int = 0
override fun getRight(density: Density, layoutDirection: LayoutDirection): Int = 0
override fun getTop(density: Density): Int = 0
}

View file

@ -0,0 +1,99 @@
package com.tangem.core.ui.windowsize
import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.window.layout.WindowMetricsCalculator
/**
* The preferred way to determine the window size is because it provides the actual size of the window as opposed to
* [LocalConfiguration.current.screenHeightDp]
*
* @property width actual window width
* @property height actual window height
* @property widthSizeType window size type based on width
* @property heightSizeType window size type based on height
* @property smallestSize smallest size of width and height
*/
data class WindowSize(
val width: Dp,
val height: Dp,
) {
val widthSizeType: WindowSizeType = getWidthWindowSizeType(width)
val heightSizeType: WindowSizeType = getHeightWindowSizeType(height)
val smallestSize: Dp = minOf(width, height)
fun widthAtLeast(type: WindowSizeType): Boolean = this.widthSizeType.ordinal >= type.ordinal
fun widthAtLeast(value: Dp): Boolean = this.width >= value
fun heightAtLeast(type: WindowSizeType): Boolean = this.heightSizeType.ordinal >= type.ordinal
fun heightAtLeast(value: Dp): Boolean = this.height >= value
fun widthAtMost(type: WindowSizeType): Boolean = this.widthSizeType.ordinal <= type.ordinal
fun widthAtMost(value: Dp): Boolean = this.width <= value
fun heightAtMost(type: WindowSizeType): Boolean = this.heightSizeType.ordinal <= type.ordinal
fun heightAtMost(value: Dp): Boolean = this.height <= value
private fun getWidthWindowSizeType(windowDp: Dp): WindowSizeType = when {
windowDp < 300.dp -> WindowSizeType.ExtraSmall
windowDp < 380.dp -> WindowSizeType.Small
windowDp < 540.dp -> WindowSizeType.Normal
windowDp < 700.dp -> WindowSizeType.Large
else -> WindowSizeType.ExtraLarge
}
private fun getHeightWindowSizeType(heightDp: Dp): WindowSizeType = when {
heightDp < 480.dp -> WindowSizeType.ExtraSmall
heightDp < 640.dp -> WindowSizeType.Small
heightDp < 860.dp -> WindowSizeType.Normal
heightDp < 1100.dp -> WindowSizeType.Large
else -> WindowSizeType.ExtraLarge
}
}
enum class WindowSizeType {
ExtraSmall, Small, Normal, Large, ExtraLarge
}
@Composable
fun rememberWindowSize(activity: Activity): WindowSize {
val windowBoundsSize = rememberWindowBoundsSize(activity)
val windowDpSize = with(LocalDensity.current) {
windowBoundsSize.toDpSize()
}
return WindowSize(
width = windowDpSize.width,
height = windowDpSize.height,
)
}
@Composable
internal fun rememberWindowSizePreview(width: Dp, height: Dp): WindowSize {
return remember(width, height) {
WindowSize(
width = width,
height = height,
)
}
}
@Composable
private fun rememberWindowBoundsSize(activity: Activity): Size {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
}
return windowMetrics.bounds.toComposeRect().size
}

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#1E1E1E" android:pathData="M12,2C6.5,2 2,6.5 2,12C2,17.5 6.5,22 12,22C17.5,22 22,17.5 22,12C22,6.5 17.5,2 12,2ZM10,17L5,12L6.41,10.59L10,14.17L17.59,6.58L19,8L10,17Z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<group>
<clip-path android:pathData="M0,0h24v24h-24z"/>
<path android:fillColor="#1E1E1E" android:pathData="M11.998,21.692C11.501,21.692 11.075,21.515 10.722,21.161C10.369,20.807 10.192,20.382 10.192,19.885H13.808C13.808,20.383 13.631,20.809 13.276,21.163C12.922,21.516 12.496,21.692 11.998,21.692ZM4.5,18.885V17.385H6.308V9.923C6.308,8.578 6.723,7.389 7.553,6.356C8.383,5.322 9.449,4.661 10.75,4.373V3.75C10.75,3.403 10.871,3.108 11.114,2.865C11.357,2.622 11.652,2.5 11.999,2.5C12.346,2.5 12.641,2.622 12.884,2.865C13.128,3.108 13.25,3.403 13.25,3.75V3.95C13.086,4.246 12.956,4.547 12.859,4.854C12.763,5.16 12.704,5.478 12.683,5.808C12.569,5.787 12.458,5.769 12.35,5.754C12.242,5.738 12.125,5.731 12,5.731C10.842,5.731 9.854,6.14 9.035,6.959C8.217,7.777 7.808,8.765 7.808,9.923V17.385H16.192V11.133C16.422,11.215 16.664,11.279 16.919,11.325C17.174,11.371 17.432,11.391 17.692,11.385V17.385H19.5V18.885H4.5ZM17.482,9.202C16.718,9.202 16.069,8.935 15.534,8.4C14.998,7.866 14.731,7.217 14.731,6.453C14.731,5.689 14.998,5.04 15.533,4.505C16.067,3.97 16.716,3.702 17.479,3.702C18.243,3.702 18.893,3.969 19.428,4.504C19.963,5.038 20.231,5.687 20.231,6.451C20.231,7.214 19.963,7.864 19.429,8.399C18.895,8.934 18.245,9.202 17.482,9.202Z"/>
</group>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<group>
<clip-path android:pathData="M0,0h24v24h-24z"/>
<path android:fillColor="#1E1E1E" android:pathData="M5.65,10.025L7.6,10.85C7.833,10.383 8.075,9.933 8.325,9.5C8.575,9.067 8.85,8.633 9.15,8.2L7.75,7.925L5.65,10.025ZM9.2,12.1L12.05,14.925C12.75,14.658 13.5,14.25 14.3,13.7C15.1,13.15 15.85,12.525 16.55,11.825C17.717,10.658 18.629,9.363 19.288,7.938C19.946,6.512 20.233,5.2 20.15,4C18.95,3.917 17.633,4.204 16.2,4.863C14.767,5.521 13.467,6.433 12.3,7.6C11.6,8.3 10.975,9.05 10.425,9.85C9.875,10.65 9.467,11.4 9.2,12.1ZM13.65,10.475C13.267,10.092 13.075,9.621 13.075,9.063C13.075,8.504 13.267,8.033 13.65,7.65C14.033,7.267 14.508,7.075 15.075,7.075C15.642,7.075 16.117,7.267 16.5,7.65C16.883,8.033 17.075,8.504 17.075,9.063C17.075,9.621 16.883,10.092 16.5,10.475C16.117,10.858 15.642,11.05 15.075,11.05C14.508,11.05 14.033,10.858 13.65,10.475ZM14.125,18.5L16.225,16.4L15.95,15C15.517,15.3 15.083,15.571 14.65,15.813C14.217,16.054 13.767,16.292 13.3,16.525L14.125,18.5ZM21.95,2.175C22.267,4.192 22.071,6.154 21.362,8.063C20.654,9.971 19.433,11.792 17.7,13.525L18.2,16C18.267,16.333 18.25,16.658 18.15,16.975C18.05,17.292 17.883,17.567 17.65,17.8L13.45,22L11.35,17.075L7.075,12.8L2.15,10.7L6.325,6.5C6.558,6.267 6.837,6.1 7.162,6C7.487,5.9 7.817,5.883 8.15,5.95L10.625,6.45C12.358,4.717 14.175,3.492 16.075,2.775C17.975,2.058 19.933,1.858 21.95,2.175ZM3.925,15.975C4.508,15.392 5.221,15.096 6.062,15.087C6.904,15.079 7.617,15.367 8.2,15.95C8.783,16.533 9.071,17.246 9.062,18.087C9.054,18.929 8.758,19.642 8.175,20.225C7.758,20.642 7.062,21 6.087,21.3C5.112,21.6 3.767,21.867 2.05,22.1C2.283,20.383 2.55,19.038 2.85,18.063C3.15,17.087 3.508,16.392 3.925,15.975ZM5.35,17.375C5.183,17.542 5.017,17.846 4.85,18.288C4.683,18.729 4.567,19.175 4.5,19.625C4.95,19.558 5.396,19.446 5.837,19.288C6.279,19.129 6.583,18.967 6.75,18.8C6.95,18.6 7.058,18.358 7.075,18.075C7.092,17.792 7,17.55 6.8,17.35C6.6,17.15 6.358,17.054 6.075,17.063C5.792,17.071 5.55,17.175 5.35,17.375Z"/>
</group>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<group>
<clip-path android:pathData="M0,0h24v24h-24z"/>
<path android:fillColor="#1E1E1E" android:pathData="M21.025,11.05V19C21.025,19.55 20.829,20.021 20.438,20.413C20.046,20.804 19.575,21 19.025,21H5.025C4.475,21 4.004,20.804 3.612,20.413C3.221,20.021 3.025,19.55 3.025,19V11.05C2.642,10.7 2.346,10.25 2.137,9.7C1.929,9.15 1.925,8.55 2.125,7.9L3.175,4.5C3.308,4.067 3.546,3.708 3.887,3.425C4.229,3.142 4.625,3 5.075,3H18.975C19.425,3 19.817,3.138 20.15,3.412C20.483,3.688 20.725,4.05 20.875,4.5L21.925,7.9C22.125,8.55 22.121,9.142 21.913,9.675C21.704,10.208 21.408,10.667 21.025,11.05ZM14.225,10C14.675,10 15.017,9.846 15.25,9.538C15.483,9.229 15.575,8.883 15.525,8.5L14.975,5H13.025V8.7C13.025,9.05 13.142,9.354 13.375,9.613C13.608,9.871 13.892,10 14.225,10ZM9.725,10C10.108,10 10.421,9.871 10.663,9.613C10.904,9.354 11.025,9.05 11.025,8.7V5H9.075L8.525,8.5C8.458,8.9 8.546,9.25 8.787,9.55C9.029,9.85 9.342,10 9.725,10ZM5.275,10C5.575,10 5.837,9.892 6.062,9.675C6.287,9.458 6.425,9.183 6.475,8.85L7.025,5H5.075L4.075,8.35C3.975,8.683 4.029,9.042 4.237,9.425C4.446,9.808 4.792,10 5.275,10ZM18.775,10C19.258,10 19.608,9.808 19.825,9.425C20.042,9.042 20.092,8.683 19.975,8.35L18.925,5H17.025L17.575,8.85C17.625,9.183 17.763,9.458 17.987,9.675C18.212,9.892 18.475,10 18.775,10ZM5.025,19H19.025V11.95C18.942,11.983 18.888,12 18.862,12H18.775C18.325,12 17.929,11.925 17.587,11.775C17.246,11.625 16.908,11.383 16.575,11.05C16.275,11.35 15.933,11.583 15.55,11.75C15.167,11.917 14.758,12 14.325,12C13.875,12 13.454,11.917 13.063,11.75C12.671,11.583 12.325,11.35 12.025,11.05C11.742,11.35 11.413,11.583 11.038,11.75C10.663,11.917 10.258,12 9.825,12C9.342,12 8.904,11.917 8.512,11.75C8.121,11.583 7.775,11.35 7.475,11.05C7.125,11.4 6.779,11.646 6.437,11.788C6.096,11.929 5.708,12 5.275,12H5.162C5.121,12 5.075,11.983 5.025,11.95V19Z"/>
</group>
</vector>