Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/di/HttpModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import to.bitkit.utils.UrlValidator
import to.bitkit.utils.AppError
import to.bitkit.utils.Logger
import to.bitkit.utils.UrlValidator
import javax.inject.Singleton
import io.ktor.client.plugins.logging.Logger as KtorLogger

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/to/bitkit/ui/Locals.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.material3.DrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import dev.chrisbanes.haze.HazeState
import to.bitkit.models.BalanceState
import to.bitkit.repositories.CurrencyState
import to.bitkit.viewmodels.ActivityListViewModel
Expand All @@ -29,6 +30,7 @@ val LocalActivityListViewModel = staticCompositionLocalOf<ActivityListViewModel?
val LocalTransferViewModel = staticCompositionLocalOf<TransferViewModel?> { null }
val LocalSettingsViewModel = staticCompositionLocalOf<SettingsViewModel?> { null }
val LocalBackupsViewModel = staticCompositionLocalOf<BackupsViewModel?> { null }
val LocalHazeState = staticCompositionLocalOf<HazeState?> { null }

val appViewModel: AppViewModel?
@Composable get() = LocalAppViewModel.current
Expand Down Expand Up @@ -56,3 +58,6 @@ val backupsViewModel: BackupsViewModel?

val drawerState: DrawerState?
@Composable get() = LocalDrawerState.current

val hazeState: HazeState?
@Composable get() = LocalHazeState.current
142 changes: 88 additions & 54 deletions app/src/main/java/to/bitkit/ui/components/Button.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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
Expand All @@ -24,6 +25,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
Expand All @@ -33,11 +35,16 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.HazeTint
import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.rememberHazeState
import to.bitkit.R
import to.bitkit.ui.shared.modifiers.alphaFeedback
import to.bitkit.ui.shared.modifiers.clickableAlpha
import to.bitkit.ui.shared.modifiers.rememberDebouncedClick
import to.bitkit.ui.shared.util.glassBlur
import to.bitkit.ui.shared.util.primaryButtonStyle
import to.bitkit.ui.theme.AppButtonDefaults
import to.bitkit.ui.theme.AppThemeSurface
Expand Down Expand Up @@ -76,12 +83,6 @@ enum class ButtonSize {
Small -> 8.dp
Large -> 6.dp
}
val secondaryBlurRadius: Dp
get() = when (this) {
Small -> 5.dp
Large -> 8.dp
}

fun secondaryBorder(enabled: Boolean): BorderStroke = when (this) {
Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent)
Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent)
Expand Down Expand Up @@ -170,53 +171,75 @@ fun SecondaryButton(
size: ButtonSize = ButtonSize.Large,
enabled: Boolean = true,
fullWidth: Boolean = true,
hazeState: HazeState? = null,
) {
val contentPadding = PaddingValues(horizontal = size.secondaryHorizontalPadding.takeIf { text != null } ?: 0.dp)
val border = size.secondaryBorder(enabled)
val contentColor = when (size) {
ButtonSize.Large -> Colors.White80
ButtonSize.Small -> Colors.White64
}
OutlinedButton(
onClick = rememberDebouncedClick(onClick = onClick),
enabled = enabled && !isLoading,
colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor),
contentPadding = contentPadding,
border = border,
// hazeEffect must be on a Box wrapper (not OutlinedButton — Material's Surface draws over it)
// and AFTER size modifiers (Haze needs to know dimensions)
val buttonShape = MaterialTheme.shapes.extraLarge
Box(
modifier = modifier
.then(if (fullWidth) Modifier.fillMaxWidth() else Modifier)
.requiredHeight(size.height)
.glassBlur(blurRadius = size.secondaryBlurRadius)
) {
if (isLoading) {
GradientCircularProgressIndicator(
strokeWidth = 2.dp,
modifier = Modifier.size(size.height / 2)
.clip(buttonShape)
.then(
if (hazeState != null) {
Modifier.hazeEffect(
state = hazeState,
style = HazeStyle(
blurRadius = 12.dp,
backgroundColor = Color.Black,
tint = HazeTint(Color.Black.copy(alpha = 0.2f)),
),
)
} else {
Modifier
}
)
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.secondaryGap),
) {
if (icon != null) {
Box(
modifier = if (enabled) {
Modifier
} else {
Modifier.graphicsLayer {
colorFilter = ColorFilter.tint(Colors.White32)
) {
OutlinedButton(
onClick = rememberDebouncedClick(onClick = onClick),
enabled = enabled && !isLoading,
colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor),
contentPadding = contentPadding,
border = border,
modifier = if (fullWidth) Modifier.fillMaxSize() else Modifier,
) {
if (isLoading) {
GradientCircularProgressIndicator(
strokeWidth = 2.dp,
modifier = Modifier.size(size.height / 2)
)
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.secondaryGap),
) {
if (icon != null) {
Box(
modifier = if (enabled) {
Modifier
} else {
Modifier.graphicsLayer {
colorFilter = ColorFilter.tint(Colors.White32)
}
}
) {
icon()
}
) {
icon()
}
}
text?.let {
Text(
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
text?.let {
Text(
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
Expand Down Expand Up @@ -278,7 +301,7 @@ fun TertiaryButton(
}
}

@Preview(showBackground = true)
@Preview
@Composable
private fun PrimaryButtonPreview() {
AppThemeSurface {
Expand Down Expand Up @@ -405,33 +428,39 @@ private fun PrimaryButtonPreview() {
}
}

@Preview(showBackground = true)
@Preview
@Composable
private fun SecondaryButtonPreview() {
val hazeState = rememberHazeState()
AppThemeSurface {
Box {
Image(
painter = painterResource(R.drawable.lightning),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize()
)
Box(
modifier = Modifier
.matchParentSize()
.hazeSource(hazeState)
) {
Image(
painter = painterResource(R.drawable.lightning),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize()
)
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(16.dp)
) {
SecondaryButton(
text = "Secondary",
onClick = {},
)
SecondaryButton(text = "Secondary", hazeState = hazeState, onClick = {})
SecondaryButton(
text = "Secondary With padding",
modifier = Modifier.padding(horizontal = 32.dp),
hazeState = hazeState,
onClick = {},
modifier = Modifier.padding(horizontal = 32.dp)
)
SecondaryButton(
text = "Secondary With Icon",
onClick = {},
hazeState = hazeState,
icon = {
Icon(
imageVector = Icons.Filled.Favorite,
Expand All @@ -443,12 +472,14 @@ private fun SecondaryButtonPreview() {
SecondaryButton(
text = "Secondary Loading",
isLoading = true,
hazeState = hazeState,
onClick = {},
)
SecondaryButton(
text = "Secondary Disabled",
onClick = {},
enabled = false,
hazeState = hazeState,
icon = {
Icon(
imageVector = Icons.Filled.Favorite,
Expand All @@ -461,12 +492,14 @@ private fun SecondaryButtonPreview() {
text = "Secondary Small",
size = ButtonSize.Small,
fullWidth = false,
hazeState = hazeState,
onClick = {},
)
SecondaryButton(
text = "Secondary Small Loading",
size = ButtonSize.Small,
isLoading = true,
hazeState = hazeState,
onClick = {},
)
SecondaryButton(
Expand All @@ -485,6 +518,7 @@ private fun SecondaryButtonPreview() {
onClick = {},
fullWidth = false,
size = ButtonSize.Large,
hazeState = hazeState,
icon = {
Icon(
imageVector = Icons.Filled.Favorite,
Expand Down Expand Up @@ -513,7 +547,7 @@ private fun SecondaryButtonPreview() {
}
}

@Preview(showBackground = true)
@Preview
@Composable
private fun TertiaryButtonPreview() {
AppThemeSurface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand All @@ -26,6 +31,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.synonym.bitkitcore.Activity
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.rememberHazeState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import to.bitkit.R
Expand Down Expand Up @@ -68,22 +75,32 @@ fun SavingsWalletScreen(
mutableStateOf(hasFunds && !isGeoBlocked)
}

val hazeState = rememberHazeState()
Box(
modifier = Modifier
.fillMaxSize()
.background(Colors.Black)
.blockPointerInputPassthrough()
) {
Image(
painter = painterResource(id = R.drawable.piggybank),
contentDescription = null,
contentScale = ContentScale.Fit,
// Background layer: hazeSource must be a sibling of hazeEffect, not a parent.
// Haze can't blur an ancestor — source and effect must be at the same level.
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 32.dp)
.offset(x = (120).dp)
.size(268.dp)
)
.matchParentSize()
.background(Colors.Black)
.hazeSource(hazeState)
) {
Image(
painter = painterResource(id = R.drawable.piggybank),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 0.dp)
.offset(x = (160).dp)
.size(360.dp)
.windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical))
)
}
ScreenColumn(noBackground = true) {
AppTopBar(
titleText = stringResource(R.string.wallet__savings__title),
Expand All @@ -108,7 +125,7 @@ fun SavingsWalletScreen(
IncomingTransfer(
amount = balances.balanceInTransferToSavings,
remainingDuration = forceCloseRemainingDuration,
modifier = Modifier.padding(vertical = 8.dp),
modifier = Modifier.padding(vertical = 8.dp)
)
}

Expand All @@ -123,9 +140,10 @@ fun SavingsWalletScreen(
Icon(
painter = painterResource(R.drawable.ic_transfer),
contentDescription = null,
modifier = Modifier.size(16.dp),
modifier = Modifier.size(16.dp)
)
},
hazeState = hazeState,
modifier = Modifier.testTag("TransferToSpending")
)
}
Expand Down
Loading
Loading