diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index a9504e668a..5e07c484b8 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.extensions import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager @@ -29,12 +30,10 @@ import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.R import timber.log.Timber -private class Navigation - fun FragmentActivity.openFragment( screen: AppScreen, addToBackstack: Boolean, - fgShareTransition: FragmentShareTransition? = null + fgShareTransition: FragmentShareTransition? = null, ) { val transaction = this.supportFragmentManager.beginTransaction() val fragment = fragmentFactory(screen) @@ -48,9 +47,14 @@ fun FragmentActivity.openFragment( } } } - transaction.replace(R.id.fragment_container, fragment, screen.name) - if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) - transaction.commitAllowingStateLoss() + if (screen.isDialogFragment) { + (fragment as DialogFragment).show(transaction, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + } else { + transaction.replace(R.id.fragment_container, fragment, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + transaction.commitAllowingStateLoss() + } } fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) { diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 224e12755b..05ec744393 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -9,7 +9,9 @@ data class NavigationState( val activity: WeakReference? = null, ) : StateType -enum class AppScreen { +enum class AppScreen( + val isDialogFragment: Boolean = false, +) { Home, Shop, Disclaimer, @@ -19,5 +21,5 @@ enum class AppScreen { Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory, AddTokens, AddCustomToken, WalletConnectSessions, - QrScan + QrScan, } \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 3c509f8c3f..6881f67082 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -30,6 +30,8 @@ android { } dependencies { + /** AndroidX libraries */ + implementation(AndroidX.fragmentKtx) /** Compose */ implementation(Compose.foundation) @@ -38,4 +40,5 @@ dependencies { /** Other libraries */ implementation(Library.accompanistSystemUiController) + implementation(Library.materialComponent) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index cc09da8dbc..11dae7b514 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,183 +1,582 @@ package com.tangem.core.ui.components -import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Button +import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ButtonElevation +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.State +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R -import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.buttonColor -import com.tangem.core.ui.res.textColor +import com.tangem.core.ui.res.TangemTypography -@Preview(widthDp = 328, heightDp = 48, showBackground = true) +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A103&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem, - enabled = false, - onClicked = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem, - enabled = false, - onClicked = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) - } +fun TextButton( + modifier: Modifier = Modifier, + text: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.textButtonColors, + size = TangemButtonSize.Text, + ) } /** - * Primary button with an icon at the beginning of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClicked the lambda to be invoked when this button is pressed - * - * @see Figma component - */ + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A62&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun PrimaryStartIconButton( +fun TextButtonIconLeft( modifier: Modifier = Modifier, text: String, - @DrawableRes iconResId: Int, + icon: Painter, enabled: Boolean = true, - onClicked: () -> Unit, + onClick: () -> Unit, ) { - PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) - Text(text = text) - } + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.textButtonColors, + size = TangemButtonSize.Text, + ) +} + +@Composable +fun PrimaryButton( + modifier: Modifier = Modifier, + text: String, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) } /** - * Primary button with an icon at the end of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClicked the lambda to be invoked when this button is pressed - * - * @see Figma component - */ + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun PrimaryEndIconButton( +fun PrimaryButtonIconRight( modifier: Modifier = Modifier, text: String, - @DrawableRes iconResId: Int, + icon: Painter, + showProgress: Boolean = false, enabled: Boolean = true, - onClicked: () -> Unit, + onClick: () -> Unit, ) { - PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { - Text(text = text) - Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - } + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Right(icon), + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun PrimaryButtonIconLeft( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) } @Composable -private fun PrimaryButtonRow( - modifier: Modifier, +fun SecondaryButton( + modifier: Modifier = Modifier, + text: String, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=99%3A50&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun SecondaryButtonIconRight( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Right(icon), + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A262&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun SecondaryButtonIconLeft( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +// region Defaults +@Composable +private fun TangemButton( + modifier: Modifier = Modifier, + text: String, + icon: TangemButtonIcon, + onClick: () -> Unit, + colors: ButtonColors, + showProgress: Boolean, enabled: Boolean, - onClicked: () -> Unit, - content: @Composable (RowScope.() -> Unit), + size: TangemButtonSize = TangemButtonSize.Default, + elevation: ButtonElevation = TangemButtonsDefaults.elevation, ) { Button( - onClick = onClicked, modifier = modifier - .fillMaxWidth() - .height(dimensionResource(R.dimen.size48)), + .width(IntrinsicSize.Min) + .height(IntrinsicSize.Min) + .heightIn(size.toHeightDp()), + onClick = { + if (!showProgress) { + onClick() + } + }, enabled = enabled, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), - colors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), - contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), - disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), - ), - content = content, - ) -} \ No newline at end of file + elevation = elevation, + shape = size.toShape(), + colors = colors, + ) { + ButtonContent( + text = text, + buttonIcon = icon, + colors = colors, + showProgress = showProgress, + enabled = enabled, + ) + } +} + +@Composable +private fun ButtonContent( + text: String, + buttonIcon: TangemButtonIcon, + colors: ButtonColors, + enabled: Boolean, + showProgress: Boolean, +) { + val icon = @Composable { painter: Painter -> + Icon( + modifier = Modifier.size(TangemTheme.dimens.size20), + painter = painter, + tint = colors.contentColor(enabled = enabled).value, + contentDescription = null, + ) + } + + if (showProgress) Box( + modifier = Modifier.wrapContentSize(), + ) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(TangemTheme.dimens.size24), + color = colors.contentColor(enabled = enabled).value, + strokeWidth = TangemTheme.dimens.size4, + ) + } else Row { + if (buttonIcon is TangemButtonIcon.Left) { + icon(buttonIcon.painter) + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + } + Text( + text = text, + style = TangemTypography.button, + color = colors.contentColor(enabled = enabled).value, + ) + if (buttonIcon is TangemButtonIcon.Right) { + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + icon(buttonIcon.painter) + } + } +} + +sealed interface TangemButtonIcon { + val painter: Painter? + + @Immutable + data class Left(override val painter: Painter) : TangemButtonIcon + + @Immutable + data class Right(override val painter: Painter) : TangemButtonIcon + + object None : TangemButtonIcon { + override val painter: Painter? = null + } +} + +enum class TangemButtonSize { + Default, + Text, +} + +@Composable +private fun TangemButtonSize.toHeightDp(): Dp = when (this) { + TangemButtonSize.Default -> TangemTheme.dimens.size48 + TangemButtonSize.Text -> TangemTheme.dimens.size40 +} + +@Composable +private fun TangemButtonSize.toShape(): Shape = when (this) { + TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall +} + +private object TangemButtonsDefaults { + val elevation: ButtonElevation + @Composable get() = ButtonDefaults + .elevation( + defaultElevation = TangemTheme.dimens.elevation0, + pressedElevation = TangemTheme.dimens.elevation0, + ) + + val primaryButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.button.primary, + contentColor = TangemTheme.colors.text.primary2, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) + + val secondaryButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.button.secondary, + contentColor = TangemTheme.colors.text.primary1, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) + + val textButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = Color.Transparent, + contentColor = TangemTheme.colors.text.secondary, + disabledBackgroundColor = Color.Transparent, + disabledContentColor = TangemTheme.colors.text.disabled, + ) +} + +@Immutable +private class TangemButtonColors( + private val backgroundColor: Color, + private val contentColor: Color, + private val disabledBackgroundColor: Color, + private val disabledContentColor: Color, +) : ButtonColors { + @Composable + override fun backgroundColor(enabled: Boolean): State { + return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor) + } + + @Composable + override fun contentColor(enabled: Boolean): State { + return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor) + } +} +// endregion Defaults + +// region Preview +@Composable +private fun PrimaryButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun PrimaryButtonPreview_Light() { + TangemTheme { + PrimaryButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun PrimaryButtonPreview_Dark() { + TangemTheme(isDark = true) { + PrimaryButtonSample() + } +} + +@Composable +private fun SecondaryButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SecondaryButtonPreview_Light() { + TangemTheme { + SecondaryButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SecondaryButtonPreview_Dark() { + TangemTheme(isDark = true) { + SecondaryButtonSample() + } +} + +@Composable +private fun TextButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + TextButton( + text = "Enabled", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButtonIconLeft( + text = "Enabled", + icon = painterResource(id = R.drawable.ic_plus), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButton( + text = "Enabled", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButtonIconLeft( + text = "Enabled", + icon = painterResource(id = R.drawable.ic_plus), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun TextButtonPreview_Light() { + TangemTheme { + TextButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun TextButtonPreview_Dark() { + TangemTheme(isDark = true) { + TextButtonSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt new file mode 100644 index 0000000000..0ff2ce98a8 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt @@ -0,0 +1,185 @@ +package com.tangem.core.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +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.ButtonColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.buttonColor +import com.tangem.core.ui.res.textColor + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +/** + * Primary button with an icon at the beginning of the layout + * + * @param modifier button modifier + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Deprecated("Use PrimaryButtonIconRight instead") +@Composable +fun PrimaryStartIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Text(text = text) + } +} + +/** + * Primary button with an icon at the end of the layout + * + * @param modifier button modifier + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Deprecated("Use PrimaryButtonIconLeft instead") +@Composable +fun PrimaryEndIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { + Text(text = text) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + } +} + +@Composable +private fun PrimaryButtonRow( + modifier: Modifier, + enabled: Boolean, + onClicked: () -> Unit, + content: @Composable (RowScope.() -> Unit), +) { + Button( + onClick = onClicked, + modifier = modifier + .fillMaxWidth() + .height(dimensionResource(R.dimen.size48)), + enabled = enabled, + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), + contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), + disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), + ), + content = content, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt new file mode 100644 index 0000000000..34ac768e47 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt @@ -0,0 +1,14 @@ +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) } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt new file mode 100644 index 0000000000..f14d87c904 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -0,0 +1,414 @@ +package com.tangem.core.ui.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.TextFieldColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemTypography + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=213%3A218&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun OutlineTextField( + modifier: Modifier = Modifier, + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + label: String? = null, + placeholder: String? = null, + caption: String? = null, + enabled: Boolean = true, + isError: Boolean = false, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + TangemTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + singleLine = true, + label = label, + placeholder = placeholder, + caption = caption, + enabled = enabled, + isError = isError, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + interactionSource = interactionSource, + ) +} + +// region Defaults +@Composable +private fun TangemTextField( + modifier: Modifier = Modifier, + value: TextFieldValue, + singleLine: Boolean, + onValueChange: (TextFieldValue) -> Unit, + label: String? = null, + placeholder: String? = null, + caption: String? = null, + enabled: Boolean = true, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, + size: TangemTextFieldSize = TangemTextFieldSize.Default, + onClear: () -> Unit = { onValueChange(TextFieldValue()) }, +) { + Column(modifier = modifier) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .heightIn(size.toHeightDp()), + value = value, + textStyle = TangemTypography.body1, + onValueChange = onValueChange, + singleLine = singleLine, + enabled = enabled, + isError = isError, + visualTransformation = visualTransformation, + keyboardActions = keyboardActions, + keyboardOptions = keyboardOptions, + interactionSource = interactionSource, + shape = size.toShape(), + colors = colors, + label = { + if (!label.isNullOrEmpty()) { + Text( + text = label, + style = TangemTypography.caption, + color = colors.labelColor( + enabled = enabled, + error = isError, + interactionSource = interactionSource, + ).value, + ) + } + }, + placeholder = { + if (!placeholder.isNullOrEmpty()) { + Text( + text = placeholder, + style = TangemTypography.body1, + color = colors.placeholderColor(enabled = enabled).value, + ) + } + }, + trailingIcon = { + val iconRes by rememberUpdatedState( + newValue = if (isError) R.drawable.ic_alert else R.drawable.ic_close, + ) + IconButton( + modifier = Modifier.size(32.dp), + onClick = onClear, + enabled = !isError && enabled, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = iconRes), + tint = colors.trailingIconColor(enabled = enabled, isError = isError).value, + contentDescription = "Clear input", + ) + } + }, + ) + + AnimatedVisibility( + visible = !caption.isNullOrEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + if (caption.isNullOrEmpty()) return@AnimatedVisibility + Column { + Spacer(modifier = Modifier.height(4.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = caption, + style = TangemTypography.body1, + color = colors.captionColor(enabled = enabled, isError = isError).value, + ) + } + } + } +} + +private enum class TangemTextFieldSize { + Default +} + +@Composable +private fun TangemTextFieldSize.toHeightDp(): Dp = when (this) { + TangemTextFieldSize.Default -> TangemTheme.dimens.size56 +} + +@Composable +private fun TangemTextFieldSize.toShape(): Shape = when (this) { + TangemTextFieldSize.Default -> TangemTheme.shapes.roundedCornersSmall2 +} + +private object TangemTextFieldsDefault { + val defaultTextFieldColors: TangemTextFieldColors + @Composable @Stable get() = TangemTextFieldColors( + textColor = TangemTheme.colors.text.primary1, + disabledTextColor = TangemTheme.colors.text.disabled, + backgroundColor = Color.Transparent, + cursorColor = TangemTheme.colors.icon.primary1, + errorCursorColor = TangemTheme.colors.icon.warning, + focusedIndicatorColor = TangemTheme.colors.icon.primary1, + unfocusedIndicatorColor = TangemTheme.colors.stroke.primary, + disabledIndicatorColor = TangemTheme.colors.stroke.primary, + errorIndicatorColor = TangemTheme.colors.icon.warning, + leadingIconColor = TangemTheme.colors.icon.informative, + disabledLeadingIconColor = Color.Transparent, + errorLeadingIconColor = TangemTheme.colors.icon.warning, + trailingIconColor = TangemTheme.colors.icon.informative, + disabledTrailingIconColor = Color.Transparent, + errorTrailingIconColor = TangemTheme.colors.icon.warning, + focusedLabelColor = TangemTheme.colors.text.primary1, + unfocusedLabelColor = TangemTheme.colors.text.secondary, + disabledLabelColor = TangemTheme.colors.text.disabled, + errorLabelColor = TangemTheme.colors.icon.warning, + placeholderColor = TangemTheme.colors.text.secondary, + disabledPlaceholderColor = TangemTheme.colors.text.disabled, + captionColor = TangemTheme.colors.text.tertiary, + disabledCaptionColor = TangemTheme.colors.text.disabled, + errorCaptionColor = TangemTheme.colors.icon.warning, + ) +} + +@Immutable +private data class TangemTextFieldColors( + private val textColor: Color, + private val disabledTextColor: Color, + private val cursorColor: Color, + private val errorCursorColor: Color, + private val focusedIndicatorColor: Color, + private val unfocusedIndicatorColor: Color, + private val errorIndicatorColor: Color, + private val disabledIndicatorColor: Color, + private val leadingIconColor: Color, + private val disabledLeadingIconColor: Color, + private val errorLeadingIconColor: Color, + private val trailingIconColor: Color, + private val disabledTrailingIconColor: Color, + private val errorTrailingIconColor: Color, + private val backgroundColor: Color, + private val focusedLabelColor: Color, + private val unfocusedLabelColor: Color, + private val disabledLabelColor: Color, + private val errorLabelColor: Color, + private val placeholderColor: Color, + private val disabledPlaceholderColor: Color, + private val captionColor: Color, + private val disabledCaptionColor: Color, + private val errorCaptionColor: Color, +) : TextFieldColors { + + @Composable + override fun leadingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledLeadingIconColor + isError -> errorLeadingIconColor + else -> leadingIconColor + }, + ) + } + + @Composable + override fun trailingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledTrailingIconColor + isError -> errorTrailingIconColor + else -> trailingIconColor + }, + ) + } + + @Composable + override fun indicatorColor( + enabled: Boolean, + isError: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledIndicatorColor + isError -> errorIndicatorColor + focused -> focusedIndicatorColor + else -> unfocusedIndicatorColor + } + return if (enabled) { + animateColorAsState(targetValue, tween(durationMillis = 120)) + } else { + rememberUpdatedState(targetValue) + } + } + + @Composable + override fun backgroundColor(enabled: Boolean): State { + return rememberUpdatedState(backgroundColor) + } + + @Composable + override fun placeholderColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor) + } + + @Composable + override fun labelColor( + enabled: Boolean, + error: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledLabelColor + error -> errorLabelColor + focused -> focusedLabelColor + else -> unfocusedLabelColor + } + return rememberUpdatedState(targetValue) + } + + @Composable + override fun textColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) textColor else disabledTextColor) + } + + @Composable + override fun cursorColor(isError: Boolean): State { + return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) + } + + @Composable + fun captionColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + newValue = when { + !enabled -> disabledCaptionColor + isError -> errorCaptionColor + else -> captionColor + }, + ) + } +} +// endregion Defaults + +// region Preview +@Composable +private fun OutlineTextFieldSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding(horizontal = 16.dp), + ) { + val text = TextFieldValue(text = "Input") + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + label = "Default", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + enabled = false, + label = "Disabled", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + isError = true, + label = "Error", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = TextFieldValue(), + onValueChange = { /* no-op */ }, + label = "Default without value", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OutlineTextFieldPreview_Light() { + TangemTheme { + OutlineTextFieldSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OutlineTextFieldPreview_Dark() { + TangemTheme(isDark = true) { + OutlineTextFieldSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt index 800b674c51..de03ea2174 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.res.iconColor * @see * Figma Component */ +@Deprecated(message = "Use Hand component instead") @Composable fun BottomSheetIndicator() { Box( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt new file mode 100644 index 0000000000..e3ade6def9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt @@ -0,0 +1,72 @@ +package com.tangem.core.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +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.res.TangemTheme + +/** + * [Show in Figma](https://www.figma.com/file/17JyRbuUEZ42DluaFEuGQk/Atoms?node-id=135%3A25&t=Jo68S6ilyewVU8MV-4) + * */ +@Composable +fun Hand( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .padding(vertical = TangemTheme.dimens.spacing8) + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .width(TangemTheme.dimens.size32) + .height(TangemTheme.dimens.size4) + .background( + color = TangemTheme.colors.icon.inactive, + shape = TangemTheme.shapes.roundedCornersSmall, + ), + ) + } +} + +// region Preview +@Composable +private fun HandSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding(TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Hand() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun HandPreview_Light() { + TangemTheme { + HandSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun HandPreview_Dark() { + TangemTheme(isDark = true) { + HandSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt new file mode 100644 index 0000000000..2a7d4c0864 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt @@ -0,0 +1,87 @@ +package com.tangem.core.ui.fragments + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.FloatRange +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme + +abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { + open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED + + @FloatRange(from = 0.0, to = 1.0) + open val expandedHeightFraction: Float? = null + + override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return createComposeView(inflater.context) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + (dialog as BottomSheetDialog).behavior.apply { + state = initialBottomSheetState + skipCollapsed = true + } + + return dialog + } + + private fun createComposeView(context: Context): View { + return ComposeView(context).apply { + setContent { + TangemTheme { + val backgroundColor = TangemTheme.colors.background.plain + SystemBarsEffect { + setSystemBarsColor( + color = backgroundColor, + ) + } + + ScreenContent( + modifier = Modifier + .fillMaxWidth() + .let { + if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it + } + .background( + color = backgroundColor, + shape = TangemTheme.shapes.bottomSheet, + ), + state = provideState().value, + ) + } + } + } + } + + @Composable + protected abstract fun provideState(): State + + @Composable + protected abstract fun ScreenContent( + modifier: Modifier, + state: ScreenState, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt new file mode 100644 index 0000000000..863f22f185 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt @@ -0,0 +1,57 @@ +package com.tangem.core.ui.fragments + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme + +abstract class ComposeFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return createComposeView(inflater.context) + } + + private fun createComposeView(context: Context): View { + return ComposeView(context).apply { + setContent { + TangemTheme { + val backgroundColor = TangemTheme.colors.background.primary + SystemBarsEffect { + setSystemBarsColor( + color = backgroundColor, + ) + } + + ScreenContent( + modifier = Modifier + .fillMaxSize() + .background(color = backgroundColor), + state = provideState().value, + ) + } + } + } + } + + @Composable + protected abstract fun provideState(): State + + @Composable + protected abstract fun ScreenContent( + modifier: Modifier, + state: ScreenState, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_alert.xml b/core/ui/src/main/res/drawable/ic_alert.xml new file mode 100644 index 0000000000..1d855289ee --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_alert.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_close.xml b/core/ui/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000..ffc64fb7a8 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_plus.xml b/core/ui/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000000..b85852c706 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/values/styles.xml b/core/ui/src/main/res/values/styles.xml new file mode 100644 index 0000000000..001815837e --- /dev/null +++ b/core/ui/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + +