Skip to content

Commit 5c47856

Browse files
authored
Merge pull request #145 from synonymdev/feat/clipboard-toast
Implement Tooltip
2 parents 0498e28 + 67bccdd commit 5c47856

File tree

4 files changed

+153
-42
lines changed

4 files changed

+153
-42
lines changed

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

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@ import androidx.compose.foundation.Image
55
import androidx.compose.foundation.background
66
import androidx.compose.foundation.layout.Box
77
import androidx.compose.foundation.layout.aspectRatio
8-
import androidx.compose.foundation.layout.fillMaxSize
9-
import androidx.compose.foundation.layout.fillMaxWidth
108
import androidx.compose.foundation.layout.padding
119
import androidx.compose.foundation.layout.size
1210
import androidx.compose.foundation.shape.CircleShape
1311
import androidx.compose.foundation.shape.RoundedCornerShape
1412
import androidx.compose.material3.CircularProgressIndicator
13+
import androidx.compose.material3.ExperimentalMaterial3Api
14+
import androidx.compose.material3.rememberTooltipState
1515
import androidx.compose.runtime.Composable
1616
import androidx.compose.runtime.LaunchedEffect
1717
import androidx.compose.runtime.getValue
1818
import androidx.compose.runtime.mutableStateOf
1919
import androidx.compose.runtime.remember
20+
import androidx.compose.runtime.rememberCoroutineScope
2021
import androidx.compose.runtime.setValue
2122
import androidx.compose.ui.Alignment
2223
import androidx.compose.ui.Modifier
@@ -25,30 +26,40 @@ import androidx.compose.ui.graphics.asImageBitmap
2526
import androidx.compose.ui.graphics.painter.BitmapPainter
2627
import androidx.compose.ui.graphics.painter.Painter
2728
import androidx.compose.ui.layout.ContentScale
29+
import androidx.compose.ui.platform.LocalClipboardManager
2830
import androidx.compose.ui.platform.LocalConfiguration
2931
import androidx.compose.ui.platform.LocalDensity
3032
import androidx.compose.ui.res.painterResource
33+
import androidx.compose.ui.text.AnnotatedString
3134
import androidx.compose.ui.tooling.preview.Preview
3235
import androidx.compose.ui.unit.Dp
3336
import androidx.compose.ui.unit.dp
37+
import androidx.core.graphics.createBitmap
3438
import com.google.zxing.BarcodeFormat
3539
import com.google.zxing.EncodeHintType
3640
import com.google.zxing.WriterException
3741
import com.google.zxing.qrcode.QRCodeWriter
3842
import kotlinx.coroutines.Dispatchers
3943
import kotlinx.coroutines.launch
4044
import to.bitkit.R
45+
import to.bitkit.ui.shared.util.clickableAlpha
4146
import to.bitkit.ui.theme.AppThemeSurface
4247
import to.bitkit.ui.theme.Colors
43-
import androidx.core.graphics.createBitmap
4448

49+
@OptIn(ExperimentalMaterial3Api::class)
4550
@Composable
4651
fun QrCodeImage(
4752
content: String,
4853
modifier: Modifier = Modifier,
4954
logoPainter: Painter? = null,
55+
tipMessage: String = "",
5056
size: Dp = LocalConfiguration.current.screenWidthDp.dp,
5157
) {
58+
val clipboard = LocalClipboardManager.current
59+
60+
val tooltipState = rememberTooltipState()
61+
val coroutineScope = rememberCoroutineScope()
62+
5263
Box(
5364
contentAlignment = Alignment.TopCenter,
5465
modifier = modifier
@@ -59,11 +70,24 @@ fun QrCodeImage(
5970
val bitmap = rememberQrBitmap(content, size)
6071

6172
if (bitmap != null) {
62-
Image(
63-
painter = remember(bitmap) { BitmapPainter(bitmap.asImageBitmap()) },
64-
contentDescription = null,
65-
contentScale = ContentScale.Inside,
66-
)
73+
Tooltip(
74+
text = tipMessage,
75+
tooltipState = tooltipState
76+
) {
77+
Image(
78+
painter = remember(bitmap) { BitmapPainter(bitmap.asImageBitmap()) },
79+
contentDescription = null,
80+
contentScale = ContentScale.Inside,
81+
modifier = if (tipMessage.isNotBlank()) {
82+
Modifier.clickableAlpha {
83+
coroutineScope.launch {
84+
clipboard.setText(AnnotatedString(content))
85+
tooltipState.show()
86+
}
87+
}
88+
} else Modifier
89+
)
90+
}
6791
logoPainter?.let {
6892
Box(
6993
contentAlignment = Alignment.Center,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ fun CaptionB(
349349
text: String,
350350
modifier: Modifier = Modifier,
351351
color: Color = MaterialTheme.colorScheme.primary,
352+
textAlign: TextAlign = TextAlign.Start,
352353
) {
353354
Text(
354355
text = text,
@@ -359,7 +360,7 @@ fun CaptionB(
359360
letterSpacing = 0.4.sp,
360361
fontFamily = InterFontFamily,
361362
color = color,
362-
textAlign = TextAlign.Start,
363+
textAlign = textAlign,
363364
),
364365
modifier = modifier,
365366
)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package to.bitkit.ui.components
2+
3+
import androidx.compose.foundation.layout.fillMaxWidth
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.material3.ExperimentalMaterial3Api
6+
import androidx.compose.material3.PlainTooltip
7+
import androidx.compose.material3.TooltipBox
8+
import androidx.compose.material3.TooltipDefaults
9+
import androidx.compose.material3.TooltipState
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.text.style.TextAlign
13+
import androidx.compose.ui.unit.DpSize
14+
import androidx.compose.ui.unit.dp
15+
import to.bitkit.ui.theme.Colors
16+
17+
@OptIn(ExperimentalMaterial3Api::class)
18+
@Composable
19+
fun Tooltip(
20+
text: String,
21+
tooltipState: TooltipState,
22+
modifier: Modifier = Modifier,
23+
content: @Composable (() -> Unit)
24+
) {
25+
26+
TooltipBox(
27+
modifier = modifier,
28+
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
29+
tooltip = {
30+
PlainTooltip(
31+
caretSize = DpSize(
32+
width = 16.dp,
33+
height = 12.dp
34+
),
35+
containerColor = Colors.Black92,
36+
contentColor = Colors.White,
37+
modifier = Modifier
38+
.fillMaxWidth()
39+
.padding(horizontal = 32.dp)
40+
) {
41+
CaptionB(
42+
text,
43+
color = Colors.White,
44+
textAlign = TextAlign.Center,
45+
modifier = Modifier
46+
.fillMaxWidth()
47+
.padding(horizontal = 37.dp, vertical = 24.dp)
48+
)
49+
}
50+
},
51+
state = tooltipState,
52+
content = content
53+
)
54+
}

app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import androidx.compose.foundation.layout.width
1717
import androidx.compose.foundation.pager.rememberPagerState
1818
import androidx.compose.material3.Card
1919
import androidx.compose.material3.CardDefaults
20+
import androidx.compose.material3.ExperimentalMaterial3Api
2021
import androidx.compose.material3.Icon
2122
import androidx.compose.material3.Switch
23+
import androidx.compose.material3.rememberTooltipState
2224
import androidx.compose.runtime.Composable
2325
import androidx.compose.runtime.DisposableEffect
2426
import androidx.compose.runtime.LaunchedEffect
@@ -27,6 +29,7 @@ import androidx.compose.runtime.getValue
2729
import androidx.compose.runtime.mutableIntStateOf
2830
import androidx.compose.runtime.mutableStateOf
2931
import androidx.compose.runtime.remember
32+
import androidx.compose.runtime.rememberCoroutineScope
3033
import androidx.compose.ui.Alignment
3134
import androidx.compose.ui.Modifier
3235
import androidx.compose.ui.draw.rotate
@@ -59,6 +62,7 @@ import to.bitkit.ui.components.Caption13Up
5962
import to.bitkit.ui.components.Headline
6063
import to.bitkit.ui.components.PrimaryButton
6164
import to.bitkit.ui.components.QrCodeImage
65+
import to.bitkit.ui.components.Tooltip
6266
import to.bitkit.ui.scaffold.SheetTopBar
6367
import to.bitkit.ui.screens.wallets.send.AddTagScreen
6468
import to.bitkit.ui.shared.PagerWithIndicator
@@ -100,7 +104,7 @@ fun ReceiveQrSheet(
100104
val cjitInvoice = remember { mutableStateOf<String?>(null) }
101105
val showCreateCjit = remember { mutableStateOf(false) }
102106
val cjitEntryDetails = remember { mutableStateOf<CjitEntryDetails?>(null) }
103-
val lightningState : LightningState by wallet.lightningState.collectAsStateWithLifecycle()
107+
val lightningState: LightningState by wallet.lightningState.collectAsStateWithLifecycle()
104108

105109
LaunchedEffect(Unit) {
106110
try {
@@ -130,7 +134,7 @@ fun ReceiveQrSheet(
130134

131135
LaunchedEffect(Unit) {
132136
wallet.walletEffect.collect { effect ->
133-
when(effect) {
137+
when (effect) {
134138
WalletViewModelEffects.NavigateGeoBlockScreen -> {
135139
navController.navigate(ReceiveRoutes.LOCATION_BLOCK)
136140
}
@@ -150,6 +154,7 @@ fun ReceiveQrSheet(
150154
showCreateCjit.value = false
151155
cjitInvoice.value = null
152156
}
157+
153158
active && cjitInvoice.value == null -> {
154159
showCreateCjit.value = true
155160
navController.navigate(ReceiveRoutes.AMOUNT)
@@ -419,6 +424,7 @@ private fun ReceiveLightningFunds(
419424
}
420425
}
421426

427+
@OptIn(ExperimentalMaterial3Api::class)
422428
@Composable
423429
private fun ReceiveQrSlide(
424430
uri: String,
@@ -429,14 +435,18 @@ private fun ReceiveQrSlide(
429435
val context = LocalContext.current
430436
val clipboard = LocalClipboardManager.current
431437

438+
val qrButtonTooltipState = rememberTooltipState()
439+
val coroutineScope = rememberCoroutineScope()
440+
432441
Column(
433442
horizontalAlignment = Alignment.CenterHorizontally,
434443
modifier = modifier
435444
) {
436445
QrCodeImage(
437446
content = uri,
438447
logoPainter = qrLogoPainter,
439-
modifier = Modifier.weight(1f, fill = false)
448+
tipMessage = stringResource(R.string.wallet__receive_copied),
449+
modifier = Modifier.weight(1f, fill = false),
440450
)
441451

442452
Spacer(modifier = Modifier.height(16.dp))
@@ -459,21 +469,29 @@ private fun ReceiveQrSlide(
459469
)
460470
}
461471
)
462-
PrimaryButton(
463-
text = stringResource(R.string.common__copy),
464-
size = ButtonSize.Small,
465-
onClick = { clipboard.setText(AnnotatedString(uri)) },
466-
fullWidth = false,
467-
color = Colors.White10,
468-
icon = {
469-
Icon(
470-
painter = painterResource(R.drawable.ic_copy),
471-
contentDescription = null,
472-
tint = Colors.Brand,
473-
modifier = Modifier.size(18.dp)
474-
)
475-
}
476-
)
472+
Tooltip(
473+
text = stringResource(R.string.wallet__receive_copied),
474+
tooltipState = qrButtonTooltipState
475+
) {
476+
PrimaryButton(
477+
text = stringResource(R.string.common__copy),
478+
size = ButtonSize.Small,
479+
onClick = {
480+
clipboard.setText(AnnotatedString(uri))
481+
coroutineScope.launch { qrButtonTooltipState.show() }
482+
},
483+
fullWidth = false,
484+
color = Colors.White10,
485+
icon = {
486+
Icon(
487+
painter = painterResource(R.drawable.ic_copy),
488+
contentDescription = null,
489+
tint = Colors.Brand,
490+
modifier = Modifier.size(18.dp)
491+
)
492+
}
493+
)
494+
}
477495
PrimaryButton(
478496
text = stringResource(R.string.common__share),
479497
size = ButtonSize.Small,
@@ -532,6 +550,7 @@ private fun CopyValuesSlide(
532550

533551
enum class CopyAddressType { ONCHAIN, LIGHTNING }
534552

553+
@OptIn(ExperimentalMaterial3Api::class)
535554
@Composable
536555
private fun CopyAddressCard(
537556
title: String,
@@ -540,6 +559,10 @@ private fun CopyAddressCard(
540559
) {
541560
val clipboard = LocalClipboardManager.current
542561
val context = LocalContext.current
562+
563+
val tooltipState = rememberTooltipState()
564+
val coroutineScope = rememberCoroutineScope()
565+
543566
Column(
544567
modifier = Modifier
545568
.fillMaxWidth()
@@ -559,21 +582,30 @@ private fun CopyAddressCard(
559582
Row(
560583
horizontalArrangement = Arrangement.spacedBy(16.dp)
561584
) {
562-
PrimaryButton(
563-
text = stringResource(R.string.common__copy),
564-
size = ButtonSize.Small,
565-
onClick = { clipboard.setText(AnnotatedString(address)) },
566-
fullWidth = false,
567-
color = Colors.White10,
568-
icon = {
569-
Icon(
570-
painter = painterResource(R.drawable.ic_copy),
571-
contentDescription = null,
572-
tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple,
573-
modifier = Modifier.size(18.dp)
574-
)
575-
}
576-
)
585+
586+
Tooltip(
587+
text = stringResource(R.string.wallet__receive_copied),
588+
tooltipState = tooltipState
589+
) {
590+
PrimaryButton(
591+
text = stringResource(R.string.common__copy),
592+
size = ButtonSize.Small,
593+
onClick = {
594+
clipboard.setText(AnnotatedString(address))
595+
coroutineScope.launch { tooltipState.show() }
596+
},
597+
fullWidth = false,
598+
color = Colors.White10,
599+
icon = {
600+
Icon(
601+
painter = painterResource(R.drawable.ic_copy),
602+
contentDescription = null,
603+
tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple,
604+
modifier = Modifier.size(18.dp)
605+
)
606+
}
607+
)
608+
}
577609
PrimaryButton(
578610
text = stringResource(R.string.common__share),
579611
size = ButtonSize.Small,

0 commit comments

Comments
 (0)