diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index af2e26dad..259dc3446 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -90,6 +90,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationS import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages.NewPublicMessageScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ShowQRScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.redirect.LoadRedirectScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScreen @@ -138,6 +139,8 @@ fun AppNavigation( composableFromBottomArgs { PeopleListMetadataScreen(it.dTag, accountViewModel, nav) } composableFromBottomArgs { FollowPackMetadataScreen(it.dTag, accountViewModel, nav) } + composableFromBottomArgs { ShowQRScreen(it.pubkey, accountViewModel, nav) } + composableFromBottomArgs { PayViaIntentScreen(it.paymentId, accountViewModel, nav) } composableFromBottomArgs { NewUserMetadataScreen(nav, accountViewModel) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt index 2456f9d05..10fd98294 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt @@ -105,7 +105,6 @@ import com.vitorpamplona.amethyst.ui.note.LoadStatuses import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ShowQRDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -713,9 +712,6 @@ fun BottomContent( accountViewModel: AccountViewModel, nav: INav, ) { - // store the dialog open or close state - var dialogOpen by remember { mutableStateOf(false) } - Column(modifier = Modifier) { HorizontalDivider( modifier = Modifier.padding(top = 15.dp), @@ -759,7 +755,7 @@ fun BottomContent( Box(modifier = Modifier.weight(1F)) IconButton( onClick = { - dialogOpen = true + nav.nav(Route.QRDisplay(user.pubkeyHex)) nav.closeDrawer() }, ) { @@ -772,17 +768,4 @@ fun BottomContent( } } } - - if (dialogOpen) { - ShowQRDialog( - user, - accountViewModel, - onScan = { - dialogOpen = false - nav.closeDrawer() - nav.nav(it) - }, - onClose = { dialogOpen = false }, - ) - } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index becfc33bc..147b1711e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -89,6 +89,10 @@ sealed class Route { val id: String, ) : Route() + @Serializable data class QRDisplay( + val pubkey: String, + ) : Route() + @Serializable data class ContentDiscovery( val id: String, ) : Route() @@ -287,6 +291,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? { dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() + dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt index c38c54728..48b57a643 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt @@ -53,13 +53,13 @@ import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.note.DrawPlayName import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.badges.DisplayBadges -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ShowQRDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size25Modifier @@ -127,23 +127,9 @@ fun DrawAdditionalInfo( ) } - var dialogOpen by remember { mutableStateOf(false) } - - if (dialogOpen) { - ShowQRDialog( - user = user, - accountViewModel = accountViewModel, - onScan = { - dialogOpen = false - nav.nav(it) - }, - onClose = { dialogOpen = false }, - ) - } - IconButton( modifier = Size25Modifier, - onClick = { dialogOpen = true }, + onClick = { nav.nav(Route.QRDisplay(user.pubkeyHex)) }, ) { Icon( painter = painterRes(R.drawable.ic_qrcode, 1), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt index d160cb813..06158a5ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt @@ -43,6 +43,8 @@ fun NIP19QrCodeScanner( try { if (it != null) { onScan(uriToRoute(it, accountViewModel.account)) + } else { + onScan(null) } } catch (e: Throwable) { if (e is CancellationException) throw e diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt deleted file mode 100644 index dd6d24b21..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -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.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji -import com.vitorpamplona.amethyst.ui.components.DisplayNIP05 -import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState -import com.vitorpamplona.amethyst.ui.navigation.routes.Route -import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Font14SP -import com.vitorpamplona.amethyst.ui.theme.Size10dp -import com.vitorpamplona.amethyst.ui.theme.Size35dp -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier -import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata - -@Preview -@Composable -fun ShowQRDialogPreview() { - val accountViewModel = mockAccountViewModel() - accountViewModel.userProfile().info = - UserMetadata().apply { - name = "My Name" - picture = "Picture" - nip05 = null - banner = "http://banner.com/test" - website = "http://mywebsite.com/test" - about = "This is the about me" - } - - ShowQRDialog( - user = accountViewModel.userProfile(), - accountViewModel = accountViewModel, - onScan = {}, - onClose = {}, - ) -} - -@Composable -fun BackButton(onPress: () -> Unit) { - IconButton( - onClick = onPress, - ) { - ArrowBackIcon(MaterialTheme.colorScheme.onBackground) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ShowQRDialog( - user: User, - accountViewModel: AccountViewModel, - onScan: (Route) -> Unit, - onClose: () -> Unit, -) { - var presenting by remember { mutableStateOf(true) } - - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - SetDialogToEdgeToEdge() - - Scaffold( - topBar = { - TopAppBar( - title = {}, - navigationIcon = { - Row { - Spacer(modifier = StdHorzSpacer) - BackButton(onPress = onClose) - } - }, - colors = - TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) - }, - ) { pad -> - Surface( - Modifier - .fillMaxSize() - .padding( - start = 10.dp, - end = 10.dp, - top = pad.calculateTopPadding(), - bottom = pad.calculateBottomPadding(), - ).consumeWindowInsets(pad) - .imePadding(), - ) { - Column { - Column( - modifier = Modifier.padding(horizontal = 10.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - if (presenting) { - Column { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - RobohashFallbackAsyncImage( - robot = user.pubkeyHex, - model = user.profilePicture(), - contentDescription = stringRes(R.string.profile_image), - modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, - loadProfilePicture = accountViewModel.settings.showProfilePictures(), - loadRobohash = accountViewModel.settings.isNotPerformanceMode(), - ) - } - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(top = 10.dp), - ) { - CreateTextWithEmoji( - text = user.info?.bestName() ?: "", - tags = user.info?.tags, - fontWeight = FontWeight.Bold, - fontSize = 20.sp, - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(top = 4.dp), - ) { - val nip05 = user.nip05() - if (nip05 != null) { - val nip05Verified = - nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel) - - DisplayNIP05(nip05, nip05Verified, accountViewModel) - } else { - Text( - text = user.pubkeyDisplayHex(), - fontSize = Font14SP, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - } - - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), - ) { - QrCodeDrawer(user.toNostrUri()) - } - - Row(modifier = Modifier.padding(horizontal = 30.dp)) { - FilledTonalButton( - onClick = { presenting = false }, - shape = RoundedCornerShape(Size35dp), - modifier = Modifier.fillMaxWidth().height(50.dp), - ) { - Text(text = stringRes(R.string.scan_qr)) - } - } - } else { - NIP19QrCodeScanner(accountViewModel) { - if (it == null) { - presenting = true - } else { - onScan(it) - } - } - } - } - } - } - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt new file mode 100644 index 000000000..89da19efa --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +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.sp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji +import com.vitorpamplona.amethyst.ui.components.DisplayNIP05 +import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState +import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav +import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Font14SP +import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata + +@Preview +@Composable +fun ShowQRScreenPreview() { + val accountViewModel = mockAccountViewModel() + accountViewModel.userProfile().info = + UserMetadata().apply { + name = "My Name" + picture = "Picture" + nip05 = null + banner = "http://banner.com/test" + website = "http://mywebsite.com/test" + about = "This is the about me" + } + + ShowQRScreen( + pubkey = accountViewModel.userProfile().pubkeyHex, + accountViewModel = accountViewModel, + nav = EmptyNav(), + ) +} + +@Composable +fun BackButton(onPress: () -> Unit) { + IconButton( + onClick = onPress, + ) { + ArrowBackIcon(MaterialTheme.colorScheme.onBackground) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ShowQRScreen( + pubkey: HexKey, + accountViewModel: AccountViewModel, + nav: INav, +) { + LoadUser(pubkey, accountViewModel) { user -> + if (user != null) { + ShowQRScreen( + user = user, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ShowQRScreen( + user: User, + accountViewModel: AccountViewModel, + nav: INav, +) { + Scaffold( + topBar = { + TopBarWithBackButton( + "", + nav::popBack, + ) + }, + ) { pad -> + Column( + Modifier + .fillMaxSize() + .padding( + start = 20.dp, + end = 20.dp, + top = pad.calculateTopPadding(), + bottom = pad.calculateBottomPadding(), + ).consumeWindowInsets(pad) + .imePadding(), + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + ShowQRBody(user, accountViewModel, nav) + } + } +} + +@Composable +fun ShowQRBody( + user: User, + accountViewModel: AccountViewModel, + nav: INav, +) { + var presenting by remember { mutableStateOf(true) } + if (presenting) { + PresentQR(user, accountViewModel) { + presenting = false + } + } else { + NIP19QrCodeScanner(accountViewModel) { + if (it == null) { + presenting = true + } else { + nav.nav(it) + } + } + } +} + +@Composable +fun PresentQR( + user: User, + accountViewModel: AccountViewModel, + switchToScan: () -> Unit, +) { + Column { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + RobohashFallbackAsyncImage( + robot = user.pubkeyHex, + model = user.profilePicture(), + contentDescription = stringRes(R.string.profile_image), + modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, + loadProfilePicture = accountViewModel.settings.showProfilePictures(), + loadRobohash = accountViewModel.settings.isNotPerformanceMode(), + ) + } + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) { + CreateTextWithEmoji( + text = user.info?.bestName() ?: "", + tags = user.info?.tags, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 4.dp), + ) { + val nip05 = user.nip05() + if (nip05 != null) { + val nip05Verified = + nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel) + + DisplayNIP05(nip05, nip05Verified, accountViewModel) + } else { + Text( + text = user.pubkeyDisplayHex(), + fontSize = Font14SP, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), + ) { + QrCodeDrawer(user.toNostrUri()) + } + + Row(modifier = Modifier.padding(horizontal = 30.dp)) { + FilledTonalButton( + onClick = switchToScan, + shape = RoundedCornerShape(Size35dp), + modifier = Modifier.fillMaxWidth().height(50.dp), + ) { + Text(text = stringRes(R.string.scan_qr)) + } + } +}