diff --git a/app/src/main/java/to/bitkit/ui/components/QrCodeImage.kt b/app/src/main/java/to/bitkit/ui/components/QrCodeImage.kt index 4bb856051..373e7302c 100644 --- a/app/src/main/java/to/bitkit/ui/components/QrCodeImage.kt +++ b/app/src/main/java/to/bitkit/ui/components/QrCodeImage.kt @@ -5,18 +5,19 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -25,12 +26,15 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.core.graphics.createBitmap import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.WriterException @@ -38,17 +42,24 @@ import com.google.zxing.qrcode.QRCodeWriter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import to.bitkit.R +import to.bitkit.ui.shared.util.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors -import androidx.core.graphics.createBitmap +@OptIn(ExperimentalMaterial3Api::class) @Composable fun QrCodeImage( content: String, modifier: Modifier = Modifier, logoPainter: Painter? = null, + tipMessage: String = "", size: Dp = LocalConfiguration.current.screenWidthDp.dp, ) { + val clipboard = LocalClipboardManager.current + + val tooltipState = rememberTooltipState() + val coroutineScope = rememberCoroutineScope() + Box( contentAlignment = Alignment.TopCenter, modifier = modifier @@ -59,11 +70,24 @@ fun QrCodeImage( val bitmap = rememberQrBitmap(content, size) if (bitmap != null) { - Image( - painter = remember(bitmap) { BitmapPainter(bitmap.asImageBitmap()) }, - contentDescription = null, - contentScale = ContentScale.Inside, - ) + Tooltip( + text = tipMessage, + tooltipState = tooltipState + ) { + Image( + painter = remember(bitmap) { BitmapPainter(bitmap.asImageBitmap()) }, + contentDescription = null, + contentScale = ContentScale.Inside, + modifier = if (tipMessage.isNotBlank()) { + Modifier.clickableAlpha { + coroutineScope.launch { + clipboard.setText(AnnotatedString(content)) + tooltipState.show() + } + } + } else Modifier + ) + } logoPainter?.let { Box( contentAlignment = Alignment.Center, diff --git a/app/src/main/java/to/bitkit/ui/components/Text.kt b/app/src/main/java/to/bitkit/ui/components/Text.kt index 9b4c2c826..5789cf225 100644 --- a/app/src/main/java/to/bitkit/ui/components/Text.kt +++ b/app/src/main/java/to/bitkit/ui/components/Text.kt @@ -349,6 +349,7 @@ fun CaptionB( text: String, modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.primary, + textAlign: TextAlign = TextAlign.Start, ) { Text( text = text, @@ -359,7 +360,7 @@ fun CaptionB( letterSpacing = 0.4.sp, fontFamily = InterFontFamily, color = color, - textAlign = TextAlign.Start, + textAlign = textAlign, ), modifier = modifier, ) diff --git a/app/src/main/java/to/bitkit/ui/components/Tooltip.kt b/app/src/main/java/to/bitkit/ui/components/Tooltip.kt new file mode 100644 index 000000000..c0da1d79d --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/components/Tooltip.kt @@ -0,0 +1,54 @@ +package to.bitkit.ui.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.PlainTooltip +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.TooltipState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import to.bitkit.ui.theme.Colors + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Tooltip( + text: String, + tooltipState: TooltipState, + modifier: Modifier = Modifier, + content: @Composable (() -> Unit) +) { + + TooltipBox( + modifier = modifier, + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip( + caretSize = DpSize( + width = 16.dp, + height = 12.dp + ), + containerColor = Colors.Black92, + contentColor = Colors.White, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + ) { + CaptionB( + text, + color = Colors.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 37.dp, vertical = 24.dp) + ) + } + }, + state = tooltipState, + content = content + ) +} diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt index f55a4943b..e01d7875b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt @@ -17,8 +17,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Switch +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -27,6 +29,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate @@ -59,6 +62,7 @@ import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.components.Headline import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.QrCodeImage +import to.bitkit.ui.components.Tooltip import to.bitkit.ui.scaffold.SheetTopBar import to.bitkit.ui.screens.wallets.send.AddTagScreen import to.bitkit.ui.shared.PagerWithIndicator @@ -100,7 +104,7 @@ fun ReceiveQrSheet( val cjitInvoice = remember { mutableStateOf(null) } val showCreateCjit = remember { mutableStateOf(false) } val cjitEntryDetails = remember { mutableStateOf(null) } - val lightningState : LightningState by wallet.lightningState.collectAsStateWithLifecycle() + val lightningState: LightningState by wallet.lightningState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { try { @@ -130,7 +134,7 @@ fun ReceiveQrSheet( LaunchedEffect(Unit) { wallet.walletEffect.collect { effect -> - when(effect) { + when (effect) { WalletViewModelEffects.NavigateGeoBlockScreen -> { navController.navigate(ReceiveRoutes.LOCATION_BLOCK) } @@ -150,6 +154,7 @@ fun ReceiveQrSheet( showCreateCjit.value = false cjitInvoice.value = null } + active && cjitInvoice.value == null -> { showCreateCjit.value = true navController.navigate(ReceiveRoutes.AMOUNT) @@ -419,6 +424,7 @@ private fun ReceiveLightningFunds( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ReceiveQrSlide( uri: String, @@ -429,6 +435,9 @@ private fun ReceiveQrSlide( val context = LocalContext.current val clipboard = LocalClipboardManager.current + val qrButtonTooltipState = rememberTooltipState() + val coroutineScope = rememberCoroutineScope() + Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier @@ -436,7 +445,8 @@ private fun ReceiveQrSlide( QrCodeImage( content = uri, logoPainter = qrLogoPainter, - modifier = Modifier.weight(1f, fill = false) + tipMessage = stringResource(R.string.wallet__receive_copied), + modifier = Modifier.weight(1f, fill = false), ) Spacer(modifier = Modifier.height(16.dp)) @@ -459,21 +469,29 @@ private fun ReceiveQrSlide( ) } ) - PrimaryButton( - text = stringResource(R.string.common__copy), - size = ButtonSize.Small, - onClick = { clipboard.setText(AnnotatedString(uri)) }, - fullWidth = false, - color = Colors.White10, - icon = { - Icon( - painter = painterResource(R.drawable.ic_copy), - contentDescription = null, - tint = Colors.Brand, - modifier = Modifier.size(18.dp) - ) - } - ) + Tooltip( + text = stringResource(R.string.wallet__receive_copied), + tooltipState = qrButtonTooltipState + ) { + PrimaryButton( + text = stringResource(R.string.common__copy), + size = ButtonSize.Small, + onClick = { + clipboard.setText(AnnotatedString(uri)) + coroutineScope.launch { qrButtonTooltipState.show() } + }, + fullWidth = false, + color = Colors.White10, + icon = { + Icon( + painter = painterResource(R.drawable.ic_copy), + contentDescription = null, + tint = Colors.Brand, + modifier = Modifier.size(18.dp) + ) + } + ) + } PrimaryButton( text = stringResource(R.string.common__share), size = ButtonSize.Small, @@ -532,6 +550,7 @@ private fun CopyValuesSlide( enum class CopyAddressType { ONCHAIN, LIGHTNING } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun CopyAddressCard( title: String, @@ -540,6 +559,10 @@ private fun CopyAddressCard( ) { val clipboard = LocalClipboardManager.current val context = LocalContext.current + + val tooltipState = rememberTooltipState() + val coroutineScope = rememberCoroutineScope() + Column( modifier = Modifier .fillMaxWidth() @@ -559,21 +582,30 @@ private fun CopyAddressCard( Row( horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - PrimaryButton( - text = stringResource(R.string.common__copy), - size = ButtonSize.Small, - onClick = { clipboard.setText(AnnotatedString(address)) }, - fullWidth = false, - color = Colors.White10, - icon = { - Icon( - painter = painterResource(R.drawable.ic_copy), - contentDescription = null, - tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple, - modifier = Modifier.size(18.dp) - ) - } - ) + + Tooltip( + text = stringResource(R.string.wallet__receive_copied), + tooltipState = tooltipState + ) { + PrimaryButton( + text = stringResource(R.string.common__copy), + size = ButtonSize.Small, + onClick = { + clipboard.setText(AnnotatedString(address)) + coroutineScope.launch { tooltipState.show() } + }, + fullWidth = false, + color = Colors.White10, + icon = { + Icon( + painter = painterResource(R.drawable.ic_copy), + contentDescription = null, + tint = if (type == CopyAddressType.ONCHAIN) Colors.Brand else Colors.Purple, + modifier = Modifier.size(18.dp) + ) + } + ) + } PrimaryButton( text = stringResource(R.string.common__share), size = ButtonSize.Small,