diff --git a/app/src/main/java/to/bitkit/models/ActivityBannerType.kt b/app/src/main/java/to/bitkit/models/ActivityBannerType.kt new file mode 100644 index 000000000..d13d6df9c --- /dev/null +++ b/app/src/main/java/to/bitkit/models/ActivityBannerType.kt @@ -0,0 +1,24 @@ +package to.bitkit.models + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.Color +import to.bitkit.R +import to.bitkit.ui.theme.Colors + +enum class ActivityBannerType( + @DrawableRes val icon: Int, + @StringRes val title: Int, + val color: Color, +) { + SPENDING( + color = Colors.Purple, + icon = R.drawable.ic_transfer, + title = R.string.activity_banner__transfer_in_progress + ), + SAVINGS( + color = Colors.Brand, + icon = R.drawable.ic_transfer, + title = R.string.activity_banner__transfer_in_progress + ) +} diff --git a/app/src/main/java/to/bitkit/models/Suggestion.kt b/app/src/main/java/to/bitkit/models/Suggestion.kt index 54a9ce369..6c70d0b72 100644 --- a/app/src/main/java/to/bitkit/models/Suggestion.kt +++ b/app/src/main/java/to/bitkit/models/Suggestion.kt @@ -67,40 +67,6 @@ enum class Suggestion( color = Colors.Green24, icon = R.drawable.fast_forward ), - - /**Replaces LIGHTNING when a LN channel is being force closed*/ - TRANSFER_PENDING( - title = R.string.cards__lightningSettingUp__title, - description = R.string.cards__transferPending__description, - color = Colors.Purple24, - icon = R.drawable.transfer, - dismissible = false - ), - - /**When the LN channel could not be cooped closed immediately*/ - TRANSFER_CLOSING_CHANNEL( - title = R.string.cards__transferClosingChannel__title, - description = R.string.cards__transferClosingChannel__description, - color = Colors.Red24, - icon = R.drawable.transfer, - dismissible = false - ), - - /**Replaces LIGHTNING when the transfer to spending balance is in progress*/ - LIGHTNING_SETTING_UP( - title = R.string.cards__lightningSettingUp__title, - description = R.string.cards__lightningSettingUp__description, - color = Colors.Purple24, - icon = R.drawable.transfer, - dismissible = false - ), - LIGHTNING_READY( - title = R.string.cards__lightningReady__title, - description = R.string.cards__lightningReady__description, - color = Colors.Purple24, - icon = R.drawable.transfer, - dismissible = false, - ), NOTIFICATIONS( title = R.string.cards__notifications__title, description = R.string.cards__notifications__description, diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 65bbd3c1f..e95620a0a 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -36,7 +36,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute -import dev.chrisbanes.haze.rememberHazeState import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -351,7 +350,6 @@ fun ContentView( val hasSeenShopIntro by settingsViewModel.hasSeenShopIntro.collectAsStateWithLifecycle() val currentSheet by appViewModel.currentSheet.collectAsStateWithLifecycle() - val hazeState = rememberHazeState() Box( modifier = modifier.fillMaxSize() diff --git a/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt new file mode 100644 index 000000000..6ef4898b0 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt @@ -0,0 +1,191 @@ +package to.bitkit.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.ShapeDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import to.bitkit.R +import to.bitkit.models.ActivityBannerType +import to.bitkit.ui.shared.util.clickableAlpha +import to.bitkit.ui.shared.util.outerGlow +import to.bitkit.ui.theme.Colors + +private const val GLOW_ANIMATION_MILLIS = 1200 + +@Composable +fun ActivityBanner( + gradientColor: Color, + title: String, + @DrawableRes icon: Int, + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, +) { + val infiniteTransition = rememberInfiniteTransition(label = "glow") + + val innerShadowOpacity by infiniteTransition.animateFloat( + initialValue = 0.32f, + targetValue = 0.64f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = GLOW_ANIMATION_MILLIS), + repeatMode = RepeatMode.Reverse + ), + label = "inner_shadow_opacity" + ) + + val dropShadowOpacity by infiniteTransition.animateFloat( + initialValue = 1.0f, + targetValue = 0.0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = GLOW_ANIMATION_MILLIS), + repeatMode = RepeatMode.Reverse + ), + label = "drop_shadow_opacity" + ) + + val radialGradientOpacity by infiniteTransition.animateFloat( + initialValue = 0.6f, + targetValue = 0.0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = GLOW_ANIMATION_MILLIS), + repeatMode = RepeatMode.Reverse + ), + label = "radial_gradient_opacity" + ) + + val borderOpacity by infiniteTransition.animateFloat( + initialValue = 0.32f, + targetValue = 1.0f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = GLOW_ANIMATION_MILLIS), + repeatMode = RepeatMode.Reverse + ), + label = "border_opacity" + ) + + val density = LocalDensity.current.density + + Box( + modifier = modifier + .requiredHeight(72.dp) + .outerGlow( + glowColor = gradientColor, + glowOpacity = dropShadowOpacity, + glowRadius = 12.dp, + cornerRadius = 16.dp + ) + .clickableAlpha(onClick = onClick) + ) { + // Main card content with clipped backgrounds + Box( + modifier = Modifier + .fillMaxWidth() + .requiredHeight(72.dp) + .clip(ShapeDefaults.Large) + // Layer 1: Base color (black) + .background(Color.Black) + // Layer 2: Inner shadow approximation (radial gradient from edges) + .background( + brush = Brush.radialGradient( + colors = listOf( + Color.Transparent, + gradientColor.copy(alpha = innerShadowOpacity) + ), + radius = 400f + ) + ) + // Layer 3: Linear gradient (top to bottom) + .background( + brush = Brush.verticalGradient( + colors = listOf( + gradientColor.copy(alpha = 0.32f), + gradientColor.copy(alpha = 0f) + ) + ) + ) + // Layer 4: Radial gradient (top-left corner) + .background( + brush = Brush.radialGradient( + colors = listOf( + gradientColor.copy(alpha = radialGradientOpacity), + gradientColor.copy(alpha = 0f) + ), + center = Offset(0f, 0f), + radius = 160f * density + ) + ) + // Border with animated opacity + .border( + width = 1.dp, + color = gradientColor.copy(alpha = borderOpacity), + shape = ShapeDefaults.Large + ) + ) + + Row( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 12.dp) + .align(Alignment.Center), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + tint = gradientColor + ) + + Headline20( + text = AnnotatedString(title), + color = Colors.White, + ) + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun Preview() { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxSize(), + ) { + items(items = ActivityBannerType.entries) { item -> + ActivityBanner( + gradientColor = item.color, + title = stringResource(R.string.activity_banner__transfer_in_progress), + icon = item.icon, + onClick = {}, + modifier = Modifier.fillMaxWidth() + ) + } + } +} diff --git a/app/src/main/java/to/bitkit/ui/components/TabBar.kt b/app/src/main/java/to/bitkit/ui/components/TabBar.kt index 95bec2bf6..0a1de06ba 100644 --- a/app/src/main/java/to/bitkit/ui/components/TabBar.kt +++ b/app/src/main/java/to/bitkit/ui/components/TabBar.kt @@ -46,7 +46,6 @@ import to.bitkit.ui.shared.util.primaryButtonStyle import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors - private val iconToTextGap = 4.dp private val iconSize = 20.dp diff --git a/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt b/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt index fe7125e37..01584ab21 100644 --- a/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt +++ b/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt @@ -44,7 +44,6 @@ fun RowScope.WalletBalanceView( sats: Long, icon: Painter, modifier: Modifier = Modifier, - showTransferIcon: Boolean = false, ) { val isPreview = LocalInspectionMode.current if (isPreview) { @@ -63,7 +62,6 @@ fun RowScope.WalletBalanceView( primaryDisplay = PrimaryDisplay.BITCOIN, displayUnit = BitcoinDisplayUnit.MODERN, hideBalance = false, - showTransferIcon = showTransferIcon, ) } @@ -82,7 +80,6 @@ fun RowScope.WalletBalanceView( primaryDisplay = primaryDisplay, displayUnit = displayUnit, hideBalance = hideBalance, - showTransferIcon = showTransferIcon, ) } @@ -95,7 +92,6 @@ private fun RowScope.Content( primaryDisplay: PrimaryDisplay, displayUnit: BitcoinDisplayUnit, hideBalance: Boolean, - showTransferIcon: Boolean, ) { Column( modifier = Modifier @@ -134,14 +130,6 @@ private fun RowScope.Content( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else btcComponents.value) - if (showTransferIcon) { - Icon( - painter = painterResource(R.drawable.ic_transfer), - contentDescription = null, - tint = Colors.White64, - modifier = Modifier.size(16.dp) - ) - } } } } else { @@ -156,14 +144,6 @@ private fun RowScope.Content( ) { BodyMSB(text = converted.symbol) BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else converted.formatted) - if (showTransferIcon) { - Icon( - painter = painterResource(R.drawable.ic_transfer), - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = Colors.White64 - ) - } } } } @@ -217,7 +197,6 @@ private fun PreviewTransferToSpending() { title = stringResource(R.string.wallet__spending__title), sats = 250_000, icon = painterResource(R.drawable.ic_ln_circle), - showTransferIcon = true, ) } } @@ -237,7 +216,6 @@ private fun PreviewTransferToSavings() { title = stringResource(R.string.wallet__savings__title), sats = 1_250_000, icon = painterResource(R.drawable.ic_btc_circle), - showTransferIcon = true, ) VerticalDivider() WalletBalanceView( @@ -263,14 +241,12 @@ private fun PreviewTransfers() { title = stringResource(R.string.wallet__savings__title), sats = 1_150_000, icon = painterResource(R.drawable.ic_btc_circle), - showTransferIcon = true, ) VerticalDivider() WalletBalanceView( title = stringResource(R.string.wallet__spending__title), sats = 150_000, icon = painterResource(R.drawable.ic_ln_circle), - showTransferIcon = true, ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt index 2301c9fcd..876883ef2 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt @@ -69,11 +69,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import to.bitkit.R import to.bitkit.env.Env +import to.bitkit.models.ActivityBannerType import to.bitkit.models.BalanceState import to.bitkit.models.Suggestion import to.bitkit.models.WidgetType import to.bitkit.ui.LocalBalances import to.bitkit.ui.Routes +import to.bitkit.ui.components.ActivityBanner import to.bitkit.ui.components.AppStatus import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView @@ -235,10 +237,6 @@ fun HomeScreen( } } - Suggestion.TRANSFER_PENDING -> Unit - Suggestion.TRANSFER_CLOSING_CHANNEL -> Unit - Suggestion.LIGHTNING_SETTING_UP -> rootNavController.navigate(Routes.SettingUp) - Suggestion.LIGHTNING_READY -> Unit Suggestion.NOTIFICATIONS -> { if (bgPaymentsIntroSeen) { rootNavController.navigate(Routes.BackgroundPaymentsSettings) @@ -360,7 +358,6 @@ private fun Content( title = stringResource(R.string.wallet__savings__title), sats = balances.totalOnchainSats.toLong(), icon = painterResource(id = R.drawable.ic_btc_circle), - showTransferIcon = balances.balanceInTransferToSavings > 0u, modifier = Modifier .clickableAlpha { walletNavController.navigate(HomeRoutes.Savings) } .padding(vertical = 4.dp) @@ -372,7 +369,6 @@ private fun Content( title = stringResource(R.string.wallet__spending__title), sats = balances.totalLightningSats.toLong(), icon = painterResource(id = R.drawable.ic_ln_circle), - showTransferIcon = balances.balanceInTransferToSpending > 0u, modifier = Modifier .clickableAlpha { walletNavController.navigate(HomeRoutes.Spending) } .padding(vertical = 4.dp) @@ -479,8 +475,31 @@ private fun Content( ) } Spacer(modifier = Modifier.height(32.dp)) - Text13Up(stringResource(R.string.wallet__activity), color = Colors.White64) - Spacer(modifier = Modifier.height(16.dp)) + + AnimatedVisibility(homeUiState.banners.isNotEmpty()) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 18.dp) + ) { + homeUiState.banners.forEach { banner -> + ActivityBanner( + gradientColor = banner.color, + title = stringResource(banner.title), + icon = banner.icon, + onClick = { + when (banner) { + ActivityBannerType.SPENDING -> rootNavController.navigate(Routes.SettingUp) + ActivityBannerType.SAVINGS -> Unit + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + } + ActivityListSimple( items = latestActivities, onAllActivityClick = { rootNavController.navigateToAllActivity() }, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt index 23298a21f..7b086e64e 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeUiState.kt @@ -2,6 +2,7 @@ package to.bitkit.ui.screens.wallets import androidx.compose.runtime.Stable import to.bitkit.data.dto.price.PriceDTO +import to.bitkit.models.ActivityBannerType import to.bitkit.models.Suggestion import to.bitkit.models.WidgetType import to.bitkit.models.WidgetWithPosition @@ -17,6 +18,7 @@ import to.bitkit.ui.screens.widgets.blocks.WeatherModel @Stable data class HomeUiState( val suggestions: List = listOf(), + val banners: List = listOf(), val showWidgets: Boolean = false, val showWidgetTitles: Boolean = false, val widgetsWithPosition: List = emptyList(), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt index eae90fe09..5d63703b5 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt @@ -8,10 +8,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import to.bitkit.data.SettingsStore +import to.bitkit.models.ActivityBannerType import to.bitkit.models.Suggestion import to.bitkit.models.TransferType import to.bitkit.models.WidgetType @@ -88,6 +90,7 @@ class HomeViewModel @Inject constructor( _uiState.update { newState } } } + viewModelScope.launch { createBannersFlow() } } private fun setupArticleRotation() { @@ -212,6 +215,22 @@ class HomeViewModel @Inject constructor( _uiState.update { it.copy(isEditingWidgets = false) } } + private suspend fun createBannersFlow() { + transferRepo.activeTransfers + .distinctUntilChanged() + .collect { transfers -> + val banners = listOfNotNull( + ActivityBannerType.SPENDING.takeIf { + transfers.any { it.type == TransferType.TO_SPENDING || it.type == TransferType.MANUAL_SETUP } + }, + ActivityBannerType.SAVINGS.takeIf { + transfers.any { it.type == TransferType.COOP_CLOSE || it.type == TransferType.FORCE_CLOSE } + }, + ) + _uiState.update { it.copy(banners = banners) } + } + } + private fun createSuggestionsFlow() = combine( walletRepo.balanceState, settingsStore.data, @@ -221,13 +240,6 @@ class HomeViewModel @Inject constructor( balanceState.totalLightningSats > 0uL -> { // With Lightning listOfNotNull( Suggestion.BACK_UP.takeIf { !settings.backupVerified }, - Suggestion.LIGHTNING_READY.takeIf { - Suggestion.LIGHTNING_SETTING_UP in _uiState.value.suggestions && - transfers.all { it.type != TransferType.TO_SPENDING } - }, - Suggestion.LIGHTNING_SETTING_UP.takeIf { transfers.any { it.type == TransferType.TO_SPENDING } }, - Suggestion.TRANSFER_CLOSING_CHANNEL.takeIf { transfers.any { it.type == TransferType.COOP_CLOSE } }, - Suggestion.TRANSFER_PENDING.takeIf { transfers.any { it.type == TransferType.FORCE_CLOSE } }, Suggestion.SECURE.takeIf { !settings.isPinEnabled }, Suggestion.BUY, Suggestion.SUPPORT, @@ -244,9 +256,7 @@ class HomeViewModel @Inject constructor( Suggestion.BACK_UP.takeIf { !settings.backupVerified }, Suggestion.LIGHTNING.takeIf { transfers.all { it.type != TransferType.TO_SPENDING } - } ?: Suggestion.LIGHTNING_SETTING_UP, - Suggestion.TRANSFER_CLOSING_CHANNEL.takeIf { transfers.any { it.type == TransferType.COOP_CLOSE } }, - Suggestion.TRANSFER_PENDING.takeIf { transfers.any { it.type == TransferType.FORCE_CLOSE } }, + }, Suggestion.SECURE.takeIf { !settings.isPinEnabled }, Suggestion.BUY, Suggestion.SUPPORT, @@ -261,7 +271,7 @@ class HomeViewModel @Inject constructor( Suggestion.BUY, Suggestion.LIGHTNING.takeIf { transfers.all { it.type != TransferType.TO_SPENDING } - } ?: Suggestion.LIGHTNING_SETTING_UP, + }, Suggestion.BACK_UP.takeIf { !settings.backupVerified }, Suggestion.SECURE.takeIf { !settings.isPinEnabled }, Suggestion.SUPPORT, diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index f4780e839..596170d32 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -24,9 +24,16 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import to.bitkit.ui.theme.Colors @@ -110,6 +117,100 @@ fun Modifier.screen( .then(if (noBackground) Modifier else Modifier.background(MaterialTheme.colorScheme.background)) .then(if (insets == null) Modifier else Modifier.windowInsetsPadding(insets)) +/** + * Draws an animated outer glow effect that extends beyond the component's bounds. + * Uses Canvas with setShadowLayer to create a blur effect. + * + * @param glowColor The color of the glow effect + * @param glowOpacity The animated opacity value (0.0 to 1.0) + * @param glowRadius The blur radius in dp (how far the glow extends) + * @param cornerRadius The corner radius of the glow shape in dp + */ +fun Modifier.outerGlow( + glowColor: Color, + glowOpacity: Float, + glowRadius: Dp = 12.dp, + cornerRadius: Dp = 16.dp, +): Modifier = this.then( + OuterGlowElement( + glowColor = glowColor, + glowOpacity = glowOpacity, + glowRadius = glowRadius, + cornerRadius = cornerRadius + ) +) + +private data class OuterGlowElement( + val glowColor: Color, + val glowOpacity: Float, + val glowRadius: Dp, + val cornerRadius: Dp, +) : ModifierNodeElement() { + override fun create(): OuterGlowNode = OuterGlowNode( + glowColor = glowColor, + glowOpacity = glowOpacity, + glowRadius = glowRadius, + cornerRadius = cornerRadius + ) + + override fun update(node: OuterGlowNode) { + node.glowColor = glowColor + node.glowOpacity = glowOpacity + node.glowRadius = glowRadius + node.cornerRadius = cornerRadius + } + + override fun InspectorInfo.inspectableProperties() { + name = "outerGlow" + properties["glowColor"] = glowColor + properties["glowOpacity"] = glowOpacity + properties["glowRadius"] = glowRadius + properties["cornerRadius"] = cornerRadius + } +} + +private class OuterGlowNode( + var glowColor: Color, + var glowOpacity: Float, + var glowRadius: Dp, + var cornerRadius: Dp, +) : DrawModifierNode, Modifier.Node() { + override fun androidx.compose.ui.graphics.drawscope.ContentDrawScope.draw() { + val glowRadiusPx = glowRadius.toPx() + val cornerRadiusPx = cornerRadius.toPx() + + drawIntoCanvas { canvas -> + val paint = Paint().apply { + color = glowColor.copy(alpha = 0f) // Transparent fill + isAntiAlias = true + } + + // Draw blurred shadow behind the component + val frameworkPaint = paint.asFrameworkPaint() + frameworkPaint.color = glowColor.copy(alpha = 0f).toArgb() + frameworkPaint.setShadowLayer( + glowRadiusPx, + 0f, + 0f, + glowColor.copy(alpha = glowOpacity).toArgb() + ) + + canvas.drawRoundRect( + left = 0f, + top = 0f, + right = size.width, + bottom = size.height, + radiusX = cornerRadiusPx, + radiusY = cornerRadiusPx, + paint = paint + ) + } + + // Draw the actual content + drawContent() + } +} + fun Modifier.primaryButtonStyle( isEnabled: Boolean, shape: Shape, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0be2b7736..1e6bb944a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ Get paid When Bitkit is closed Suggestions + TRANSFER IN PROGRESS Advanced Continue Cancel