mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 22:46:49 +01:00
- Improves rendering of the QR Code elements
- Makes the QR bigger - Moves QR Code to rounder corners - Moves QR Screen to use the arrow icon instead of the close button
This commit is contained in:
@@ -108,11 +108,11 @@ fun FollowingIcon(modifier: Modifier) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArrowBackIcon() {
|
fun ArrowBackIcon(tint: Color = MaterialTheme.colorScheme.grayText) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = stringRes(R.string.back),
|
contentDescription = stringRes(R.string.back),
|
||||||
tint = MaterialTheme.colorScheme.grayText,
|
tint = tint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
@@ -46,13 +47,16 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
|||||||
import com.google.zxing.qrcode.encoder.ByteMatrix
|
import com.google.zxing.qrcode.encoder.ByteMatrix
|
||||||
import com.google.zxing.qrcode.encoder.Encoder
|
import com.google.zxing.qrcode.encoder.Encoder
|
||||||
import com.google.zxing.qrcode.encoder.QRCode
|
import com.google.zxing.qrcode.encoder.QRCode
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
|
|
||||||
const val QR_MARGIN_PX = 100f
|
const val QR_MARGIN_PX = 100f
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun QrCodeDrawerPreview() {
|
fun QrCodeDrawerPreview() {
|
||||||
|
Box(Modifier.background(Color.Black).padding(10.dp)) {
|
||||||
QrCodeDrawer("Test QR data")
|
QrCodeDrawer("Test QR data")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -62,19 +66,21 @@ fun QrCodeDrawer(
|
|||||||
) {
|
) {
|
||||||
val qrCode = remember(contents) { createQrCode(contents = contents) }
|
val qrCode = remember(contents) { createQrCode(contents = contents) }
|
||||||
|
|
||||||
val foregroundColor = MaterialTheme.colorScheme.onSurface
|
val foregroundColor = Color.Black
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
.clip(shape = QuoteBorder)
|
||||||
.defaultMinSize(48.dp, 48.dp)
|
.defaultMinSize(48.dp, 48.dp)
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.background(MaterialTheme.colorScheme.background),
|
.background(Color.White),
|
||||||
) {
|
) {
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
// Calculate the height and width of each column/row
|
// Calculate the height and width of each column/row
|
||||||
val rowHeight = (size.width - QR_MARGIN_PX * 2f) / qrCode.matrix.height
|
val rowHeight = (size.width - QR_MARGIN_PX * 2f) / qrCode.matrix.height
|
||||||
val columnWidth = (size.width - QR_MARGIN_PX * 2f) / qrCode.matrix.width
|
val columnWidth = (size.width - QR_MARGIN_PX * 2f) / qrCode.matrix.width
|
||||||
|
val radius = CornerRadius(20f)
|
||||||
|
|
||||||
// Draw all of the finder patterns required by the QR spec. Calculate the ratio
|
// Draw all of the finder patterns required by the QR spec. Calculate the ratio
|
||||||
// of the number of rows/columns to the width and height
|
// of the number of rows/columns to the width and height
|
||||||
@@ -86,6 +92,7 @@ fun QrCodeDrawer(
|
|||||||
height = rowHeight * FINDER_PATTERN_ROW_COUNT,
|
height = rowHeight * FINDER_PATTERN_ROW_COUNT,
|
||||||
),
|
),
|
||||||
color = foregroundColor,
|
color = foregroundColor,
|
||||||
|
cornerRadius = radius,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Draw data bits (encoded data part)
|
// Draw data bits (encoded data part)
|
||||||
@@ -162,6 +169,7 @@ fun DrawScope.drawAllQrCodeDataBits(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
).forEach { section ->
|
).forEach { section ->
|
||||||
|
val newSize = Size(size.width + 0.5f, size.height + 0.5f)
|
||||||
for (y in section.first.second until section.second.second) {
|
for (y in section.first.second until section.second.second) {
|
||||||
for (x in section.first.first until section.second.first) {
|
for (x in section.first.first until section.second.first) {
|
||||||
if (bytes[x, y] == 1.toByte()) {
|
if (bytes[x, y] == 1.toByte()) {
|
||||||
@@ -177,7 +185,7 @@ fun DrawScope.drawAllQrCodeDataBits(
|
|||||||
x = QR_MARGIN_PX + x * size.width,
|
x = QR_MARGIN_PX + x * size.width,
|
||||||
y = QR_MARGIN_PX + y * size.height,
|
y = QR_MARGIN_PX + y * size.height,
|
||||||
),
|
),
|
||||||
size = size,
|
size = newSize,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -191,7 +199,7 @@ fun DrawScope.drawAllQrCodeDataBits(
|
|||||||
const val FINDER_PATTERN_ROW_COUNT = 7
|
const val FINDER_PATTERN_ROW_COUNT = 7
|
||||||
private const val INTERIOR_EXTERIOR_SHAPE_RATIO = 3f / FINDER_PATTERN_ROW_COUNT
|
private const val INTERIOR_EXTERIOR_SHAPE_RATIO = 3f / FINDER_PATTERN_ROW_COUNT
|
||||||
private const val INTERIOR_EXTERIOR_OFFSET_RATIO = 2f / FINDER_PATTERN_ROW_COUNT
|
private const val INTERIOR_EXTERIOR_OFFSET_RATIO = 2f / FINDER_PATTERN_ROW_COUNT
|
||||||
private const val INTERIOR_EXTERIOR_SHAPE_CORNER_RADIUS = 0.12f
|
private const val INTERIOR_EXTERIOR_SHAPE_CORNER_RADIUS = 0.50f
|
||||||
private const val INTERIOR_BACKGROUND_EXTERIOR_SHAPE_RATIO = 5f / FINDER_PATTERN_ROW_COUNT
|
private const val INTERIOR_BACKGROUND_EXTERIOR_SHAPE_RATIO = 5f / FINDER_PATTERN_ROW_COUNT
|
||||||
private const val INTERIOR_BACKGROUND_EXTERIOR_OFFSET_RATIO = 1f / FINDER_PATTERN_ROW_COUNT
|
private const val INTERIOR_BACKGROUND_EXTERIOR_OFFSET_RATIO = 1f / FINDER_PATTERN_ROW_COUNT
|
||||||
private const val INTERIOR_BACKGROUND_EXTERIOR_SHAPE_CORNER_RADIUS = 0.5f
|
private const val INTERIOR_BACKGROUND_EXTERIOR_SHAPE_CORNER_RADIUS = 0.5f
|
||||||
@@ -206,6 +214,7 @@ private const val INTERIOR_BACKGROUND_EXTERIOR_SHAPE_CORNER_RADIUS = 0.5f
|
|||||||
internal fun DrawScope.drawQrCodeFinders(
|
internal fun DrawScope.drawQrCodeFinders(
|
||||||
sideLength: Float,
|
sideLength: Float,
|
||||||
finderPatternSize: Size,
|
finderPatternSize: Size,
|
||||||
|
cornerRadius: CornerRadius,
|
||||||
color: Color,
|
color: Color,
|
||||||
) {
|
) {
|
||||||
setOf(
|
setOf(
|
||||||
@@ -219,7 +228,7 @@ internal fun DrawScope.drawQrCodeFinders(
|
|||||||
drawQrCodeFinder(
|
drawQrCodeFinder(
|
||||||
topLeft = offset,
|
topLeft = offset,
|
||||||
finderPatternSize = finderPatternSize,
|
finderPatternSize = finderPatternSize,
|
||||||
cornerRadius = CornerRadius.Zero,
|
cornerRadius = cornerRadius,
|
||||||
color = color,
|
color = color,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -54,14 +54,16 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayNIP05
|
import com.vitorpamplona.amethyst.ui.components.DisplayNIP05
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
|
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
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.Size35dp
|
||||||
import com.vitorpamplona.quartz.events.UserMetadata
|
import com.vitorpamplona.quartz.events.UserMetadata
|
||||||
|
|
||||||
@@ -87,6 +89,15 @@ fun ShowQRDialogPreview() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BackButton(onPress: () -> Unit) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onPress,
|
||||||
|
) {
|
||||||
|
ArrowBackIcon(MaterialTheme.colorScheme.onBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShowQRDialog(
|
fun ShowQRDialog(
|
||||||
user: User,
|
user: User,
|
||||||
@@ -107,7 +118,7 @@ fun ShowQRDialog(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
CloseButton(onPress = onClose)
|
BackButton(onPress = onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -126,23 +137,23 @@ fun ShowQRDialog(
|
|||||||
contentDescription = stringRes(R.string.profile_image),
|
contentDescription = stringRes(R.string.profile_image),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.width(100.dp)
|
.width(120.dp)
|
||||||
.height(100.dp)
|
.height(120.dp)
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape),
|
.border(3.dp, MaterialTheme.colorScheme.onBackground, CircleShape),
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 5.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 10.dp),
|
||||||
) {
|
) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = user.info?.bestName() ?: "",
|
text = user.info?.bestName() ?: "",
|
||||||
tags = user.info?.tags,
|
tags = user.info?.tags,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 18.sp,
|
fontSize = 20.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +171,7 @@ fun ShowQRDialog(
|
|||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = user.pubkeyDisplayHex(),
|
text = user.pubkeyDisplayHex(),
|
||||||
fontSize = 14.sp,
|
fontSize = Font14SP,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
@@ -170,20 +181,16 @@ fun ShowQRDialog(
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = Size35dp),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp),
|
||||||
) {
|
) {
|
||||||
QrCodeDrawer(user.toNostrUri())
|
QrCodeDrawer(user.toNostrUri())
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(modifier = Modifier.padding(horizontal = 30.dp)) {
|
Row(modifier = Modifier.padding(horizontal = 30.dp)) {
|
||||||
Button(
|
FilledTonalButton(
|
||||||
onClick = { presenting = false },
|
onClick = { presenting = false },
|
||||||
shape = RoundedCornerShape(Size35dp),
|
shape = RoundedCornerShape(Size35dp),
|
||||||
modifier = Modifier.fillMaxWidth().height(50.dp),
|
modifier = Modifier.fillMaxWidth().height(50.dp),
|
||||||
colors =
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
Text(text = stringRes(R.string.scan_qr))
|
Text(text = stringRes(R.string.scan_qr))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ class Nip19Bech32Test {
|
|||||||
Assert.assertEquals(null, actual)
|
Assert.assertEquals(null, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test()
|
||||||
|
fun uri_to_route_complete_nprofile_2() {
|
||||||
|
val actual = Nip19Bech32.uriToRoute("nostr:nprofile1qqsyvrp9u6p0mfur9dfdru3d853tx9mdjuhkphxuxgfwmryja7zsvhqpzamhxue69uhhv6t5daezumn0wd68yvfwvdhk6tcpz9mhxue69uhkummnw3ezuamfdejj7qgwwaehxw309ahx7uewd3hkctcscpyug")
|
||||||
|
|
||||||
|
Assert.assertNotNull(actual)
|
||||||
|
Assert.assertTrue(actual?.entity is Nip19Bech32.NProfile)
|
||||||
|
Assert.assertEquals("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", (actual?.entity as? Nip19Bech32.NProfile)?.hex)
|
||||||
|
Assert.assertEquals("wss://vitor.nostr1.com/", (actual?.entity as? Nip19Bech32.NProfile)?.relay?.first())
|
||||||
|
}
|
||||||
|
|
||||||
@Test()
|
@Test()
|
||||||
fun uri_to_route_complete_nprofile() {
|
fun uri_to_route_complete_nprofile() {
|
||||||
val actual = Nip19Bech32.uriToRoute("nostr:nprofile1qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qgwwaehxw309ahx7uewd3hkctcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uqzq9thu3vem5gvsc6f3l3uyz7c92h6lq56t9wws0zulzkrgc6nrvym5jfztf")
|
val actual = Nip19Bech32.uriToRoute("nostr:nprofile1qy2hwumn8ghj7un9d3shjtnyv9kh2uewd9hj7qgwwaehxw309ahx7uewd3hkctcpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uqzq9thu3vem5gvsc6f3l3uyz7c92h6lq56t9wws0zulzkrgc6nrvym5jfztf")
|
||||||
|
|||||||
Reference in New Issue
Block a user