Skip to content

Commit a9bbc22

Browse files
authored
Merge pull request #171 from synonymdev/feat/balance-security-settings
feat: Balance Security & Privacy Settings
2 parents b0e6eb2 + b4ae0f4 commit a9bbc22

File tree

15 files changed

+565
-69
lines changed

15 files changed

+565
-69
lines changed

app/src/main/java/to/bitkit/data/SettingsStore.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,10 @@ data class SettingsData(
7777
val showWidgets: Boolean = false,
7878
val showWidgetTitles: Boolean = false,
7979
val lastUsedTags: List<String> = emptyList(),
80-
val widgets: List<WidgetWithPosition> = emptyList()
80+
val widgets: List<WidgetWithPosition> = emptyList(),
81+
val enableSwipeToHideBalance: Boolean = true,
82+
val hideBalance: Boolean = false,
83+
val hideBalanceOnOpen: Boolean = false,
84+
val enableAutoReadClipboard: Boolean = false,
85+
val enableSendAmountWarning: Boolean = false,
8186
)

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ fun ContentView(
165165
walletViewModel.observeLdkWallet()
166166
}
167167

168+
LaunchedEffect(Unit) { walletViewModel.handleHideBalanceOnOpen() }
169+
168170
LaunchedEffect(appViewModel) {
169171
appViewModel.mainScreenEffect.collect {
170172
when (it) {

app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt

Lines changed: 126 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
package to.bitkit.ui.components
22

3+
import androidx.compose.animation.AnimatedContent
34
import androidx.compose.foundation.layout.Arrangement
45
import androidx.compose.foundation.layout.Column
56
import androidx.compose.foundation.layout.Row
67
import androidx.compose.foundation.layout.Spacer
78
import androidx.compose.foundation.layout.fillMaxWidth
89
import androidx.compose.foundation.layout.height
910
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.size
12+
import androidx.compose.material3.Icon
1013
import androidx.compose.runtime.Composable
14+
import androidx.compose.runtime.getValue
1115
import androidx.compose.ui.Alignment
1216
import androidx.compose.ui.Modifier
1317
import androidx.compose.ui.platform.LocalInspectionMode
18+
import androidx.compose.ui.res.painterResource
1419
import androidx.compose.ui.tooling.preview.Preview
1520
import androidx.compose.ui.unit.dp
21+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
22+
import to.bitkit.R
1623
import to.bitkit.models.BITCOIN_SYMBOL
1724
import to.bitkit.models.ConvertedAmount
1825
import to.bitkit.models.PrimaryDisplay
1926
import to.bitkit.ui.LocalCurrencies
2027
import to.bitkit.ui.currencyViewModel
28+
import to.bitkit.ui.settingsViewModel
29+
import to.bitkit.ui.shared.animations.BalanceAnimations
30+
import to.bitkit.ui.shared.modifiers.swipeToHide
31+
import to.bitkit.ui.shared.UiConstants
2132
import to.bitkit.ui.shared.util.clickableAlpha
2233
import to.bitkit.ui.theme.AppThemeSurface
2334
import to.bitkit.ui.theme.Colors
@@ -28,8 +39,11 @@ fun BalanceHeaderView(
2839
modifier: Modifier = Modifier,
2940
prefix: String? = null,
3041
showBitcoinSymbol: Boolean = true,
42+
forceShowBalance: Boolean = false,
43+
showEyeIcon: Boolean = false,
3144
) {
3245
val isPreview = LocalInspectionMode.current
46+
3347
if (isPreview) {
3448
BalanceHeader(
3549
modifier = modifier,
@@ -39,14 +53,23 @@ fun BalanceHeaderView(
3953
largeRowText = "$sats",
4054
largeRowSymbol = BITCOIN_SYMBOL,
4155
showSymbol = showBitcoinSymbol,
56+
hideBalance = false,
57+
isSwipeToHideEnabled = false,
58+
showEyeIcon = showEyeIcon,
4259
onClick = {},
60+
onToggleHideBalance = {},
4361
)
4462
return
4563
}
4664

65+
val settings = settingsViewModel ?: return
4766
val currency = currencyViewModel ?: return
48-
val (rates, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
49-
val converted: ConvertedAmount? = if (rates.isNotEmpty()) currency.convert(sats = sats) else null
67+
val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
68+
val converted: ConvertedAmount? = currency.convert(sats = sats)
69+
70+
val isSwipeToHideEnabled by settings.enableSwipeToHideBalance.collectAsStateWithLifecycle()
71+
val hideBalance by settings.hideBalance.collectAsStateWithLifecycle()
72+
val shouldHideBalance = hideBalance && !forceShowBalance
5073

5174
converted?.let { converted ->
5275
val btcComponents = converted.bitcoinDisplay(displayUnit)
@@ -60,7 +83,11 @@ fun BalanceHeaderView(
6083
largeRowText = btcComponents.value,
6184
largeRowSymbol = btcComponents.symbol,
6285
showSymbol = showBitcoinSymbol,
63-
onClick = { currency.togglePrimaryDisplay() }
86+
hideBalance = shouldHideBalance,
87+
isSwipeToHideEnabled = isSwipeToHideEnabled,
88+
showEyeIcon = showEyeIcon,
89+
onClick = { currency.togglePrimaryDisplay() },
90+
onToggleHideBalance = { settings.setHideBalance(!hideBalance) },
6491
)
6592
} else {
6693
BalanceHeader(
@@ -71,7 +98,11 @@ fun BalanceHeaderView(
7198
largeRowText = converted.formatted,
7299
largeRowSymbol = converted.symbol,
73100
showSymbol = true,
74-
onClick = { currency.togglePrimaryDisplay() }
101+
hideBalance = shouldHideBalance,
102+
isSwipeToHideEnabled = isSwipeToHideEnabled,
103+
showEyeIcon = showEyeIcon,
104+
onClick = { currency.togglePrimaryDisplay() },
105+
onToggleHideBalance = { settings.setHideBalance(!hideBalance) },
75106
)
76107
}
77108
}
@@ -86,35 +117,77 @@ fun BalanceHeader(
86117
largeRowText: String,
87118
largeRowSymbol: String,
88119
showSymbol: Boolean,
89-
onClick: () -> Unit
120+
hideBalance: Boolean = false,
121+
isSwipeToHideEnabled: Boolean = false,
122+
showEyeIcon: Boolean = false,
123+
onClick: () -> Unit,
124+
onToggleHideBalance: () -> Unit = {},
90125
) {
91126
Column(
92127
verticalArrangement = Arrangement.Center,
93128
horizontalAlignment = Alignment.Start,
94-
modifier = modifier.clickableAlpha { onClick() }
129+
modifier = modifier
130+
.swipeToHide(
131+
enabled = isSwipeToHideEnabled,
132+
onSwipe = onToggleHideBalance,
133+
)
134+
.clickableAlpha { onClick() }
95135
) {
96136
SmallRow(
97137
symbol = smallRowSymbol,
98-
text = smallRowText
138+
text = smallRowText,
139+
hideBalance = hideBalance
99140
)
100141

101142
Spacer(modifier = Modifier.height(12.dp))
102143

103-
LargeRow(
104-
prefix = largeRowPrefix,
105-
text = largeRowText,
106-
symbol = largeRowSymbol,
107-
showSymbol = showSymbol
108-
)
144+
Row(
145+
verticalAlignment = Alignment.CenterVertically,
146+
) {
147+
LargeRow(
148+
prefix = largeRowPrefix,
149+
text = largeRowText,
150+
symbol = largeRowSymbol,
151+
showSymbol = showSymbol,
152+
hideBalance = hideBalance
153+
)
154+
155+
if (showEyeIcon) {
156+
Spacer(modifier = Modifier.weight(1f))
157+
AnimatedContent(
158+
targetState = hideBalance,
159+
transitionSpec = { BalanceAnimations.eyeIconTransition },
160+
label = "eyeIconAnimation",
161+
modifier = Modifier.size(24.dp)
162+
) { isHidden ->
163+
if (isHidden) {
164+
Icon(
165+
painter = painterResource(R.drawable.ic_eye),
166+
contentDescription = null,
167+
tint = Colors.White64,
168+
modifier = Modifier
169+
.size(24.dp)
170+
.clickableAlpha { onToggleHideBalance() }
171+
)
172+
}
173+
}
174+
}
175+
}
109176
}
110177
}
111178

112179
@Composable
113-
fun LargeRow(prefix: String?, text: String, symbol: String, showSymbol: Boolean) {
180+
fun LargeRow(
181+
prefix: String?,
182+
text: String,
183+
symbol: String,
184+
showSymbol: Boolean,
185+
hideBalance: Boolean = false
186+
) {
114187
Row(
115188
verticalAlignment = Alignment.CenterVertically,
116189
) {
117-
if (prefix != null) {
190+
if (!hideBalance && prefix != null) {
118191
Display(
119192
text = prefix,
120193
color = Colors.White64,
@@ -128,12 +201,18 @@ fun LargeRow(prefix: String?, text: String, symbol: String, showSymbol: Boolean)
128201
modifier = Modifier.padding(end = 8.dp)
129202
)
130203
}
131-
Display(text = text)
204+
AnimatedContent(
205+
targetState = hideBalance,
206+
transitionSpec = { BalanceAnimations.mainBalanceTransition },
207+
label = "largeRowTextAnimation"
208+
) { isHidden ->
209+
Display(text = if (isHidden) UiConstants.HIDE_BALANCE_LONG else text)
210+
}
132211
}
133212
}
134213

135214
@Composable
136-
private fun SmallRow(symbol: String?, text: String) {
215+
private fun SmallRow(symbol: String?, text: String, hideBalance: Boolean = false) {
137216
Row(
138217
verticalAlignment = Alignment.Bottom,
139218
horizontalArrangement = Arrangement.spacedBy(4.dp),
@@ -144,10 +223,16 @@ private fun SmallRow(symbol: String?, text: String) {
144223
color = Colors.White64,
145224
)
146225
}
147-
Caption13Up(
148-
text = text,
149-
color = Colors.White64,
150-
)
226+
AnimatedContent(
227+
targetState = hideBalance,
228+
transitionSpec = { BalanceAnimations.secondaryBalanceTransition },
229+
label = "smallRowTextAnimation"
230+
) { isHidden ->
231+
Caption13Up(
232+
text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else text,
233+
color = Colors.White64,
234+
)
235+
}
151236
}
152237
}
153238

@@ -167,3 +252,23 @@ private fun Preview() {
167252
)
168253
}
169254
}
255+
256+
@Preview(showBackground = true)
257+
@Composable
258+
private fun PreviewHidden() {
259+
AppThemeSurface {
260+
BalanceHeader(
261+
smallRowSymbol = "$",
262+
smallRowText = "27.36",
263+
largeRowPrefix = "+",
264+
largeRowText = "136 825",
265+
largeRowSymbol = "",
266+
showSymbol = true,
267+
hideBalance = true,
268+
isSwipeToHideEnabled = true,
269+
modifier = Modifier.fillMaxWidth(),
270+
onClick = {},
271+
onToggleHideBalance = {}
272+
)
273+
}
274+
}

app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package to.bitkit.ui.components
22

3+
import androidx.compose.animation.AnimatedContent
34
import androidx.compose.foundation.layout.Arrangement
45
import androidx.compose.foundation.layout.Column
56
import androidx.compose.foundation.layout.Row
@@ -10,15 +11,20 @@ import androidx.compose.foundation.layout.padding
1011
import androidx.compose.foundation.layout.size
1112
import androidx.compose.material3.Icon
1213
import androidx.compose.runtime.Composable
14+
import androidx.compose.runtime.getValue
1315
import androidx.compose.ui.Alignment
1416
import androidx.compose.ui.Modifier
1517
import androidx.compose.ui.graphics.Color
1618
import androidx.compose.ui.graphics.painter.Painter
1719
import androidx.compose.ui.unit.dp
20+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1821
import to.bitkit.models.ConvertedAmount
1922
import to.bitkit.models.PrimaryDisplay
2023
import to.bitkit.ui.LocalCurrencies
2124
import to.bitkit.ui.currencyViewModel
25+
import to.bitkit.ui.settingsViewModel
26+
import to.bitkit.ui.shared.animations.BalanceAnimations
27+
import to.bitkit.ui.shared.UiConstants
2228
import to.bitkit.ui.theme.Colors
2329

2430
@Composable
@@ -28,10 +34,13 @@ fun RowScope.WalletBalanceView(
2834
icon: Painter,
2935
modifier: Modifier,
3036
) {
37+
val settings = settingsViewModel ?: return
3138
val currency = currencyViewModel ?: return
3239
val (_, _, _, _, displayUnit, primaryDisplay) = LocalCurrencies.current
3340
val converted: ConvertedAmount? = currency.convert(sats = sats)
3441

42+
val hideBalance by settings.hideBalance.collectAsStateWithLifecycle()
43+
3544
Column(
3645
modifier = Modifier
3746
.weight(1f)
@@ -44,37 +53,42 @@ fun RowScope.WalletBalanceView(
4453
Spacer(modifier = Modifier.height(8.dp))
4554

4655
converted?.let { converted ->
47-
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
48-
val btcComponents = converted.bitcoinDisplay(displayUnit)
49-
Row(
50-
verticalAlignment = Alignment.CenterVertically,
51-
horizontalArrangement = Arrangement.spacedBy(4.dp),
52-
) {
53-
Icon(
54-
painter = icon,
55-
contentDescription = title,
56-
tint = Color.Unspecified,
57-
modifier = Modifier
58-
.padding(end = 4.dp)
59-
.size(24.dp)
60-
)
61-
BodyMSB(text = btcComponents.value)
62-
}
63-
} else {
64-
Row(
65-
verticalAlignment = Alignment.CenterVertically,
66-
horizontalArrangement = Arrangement.spacedBy(4.dp),
67-
) {
68-
Icon(
69-
painter = icon,
70-
contentDescription = title,
71-
tint = Color.Unspecified,
72-
modifier = Modifier
73-
.padding(end = 4.dp)
74-
.size(24.dp)
75-
)
76-
BodyMSB(text = converted.symbol)
77-
BodyMSB(text = converted.formatted)
56+
Row(
57+
verticalAlignment = Alignment.CenterVertically,
58+
horizontalArrangement = Arrangement.spacedBy(4.dp),
59+
) {
60+
Icon(
61+
painter = icon,
62+
contentDescription = title,
63+
tint = Color.Unspecified,
64+
modifier = Modifier
65+
.padding(end = 4.dp)
66+
.size(24.dp)
67+
)
68+
69+
if (primaryDisplay == PrimaryDisplay.BITCOIN) {
70+
val btcComponents = converted.bitcoinDisplay(displayUnit)
71+
AnimatedContent(
72+
targetState = hideBalance,
73+
transitionSpec = { BalanceAnimations.walletBalanceTransition },
74+
label = "bitcoinBalanceAnimation"
75+
) { isHidden ->
76+
BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else btcComponents.value)
77+
}
78+
} else {
79+
AnimatedContent(
80+
targetState = hideBalance,
81+
transitionSpec = { BalanceAnimations.walletBalanceTransition },
82+
label = "fiatBalanceAnimation"
83+
) { isHidden ->
84+
Row(
85+
verticalAlignment = Alignment.CenterVertically,
86+
horizontalArrangement = Arrangement.spacedBy(4.dp),
87+
) {
88+
BodyMSB(text = converted.symbol)
89+
BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else converted.formatted)
90+
}
91+
}
7892
}
7993
}
8094
}

0 commit comments

Comments
 (0)