feat(ui): add QR code display for invite code in signing session
Android TransferScreen: - Add QR code display above invite code text - Import QRCodeWriter and related components - Add generateInviteQRCode helper function - Update hint text to mention scanning Electron CoSignSession: - Import QRCodeSVG from qrcode.react - Add QR code above invite code text with proper styling - Center QR code and update hint text 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ed55be2b86
commit
ad79679ee2
|
|
@ -23,10 +23,16 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import com.durian.tssparty.domain.model.NetworkType
|
import com.durian.tssparty.domain.model.NetworkType
|
||||||
import com.durian.tssparty.domain.model.SessionStatus
|
import com.durian.tssparty.domain.model.SessionStatus
|
||||||
import com.durian.tssparty.domain.model.ShareRecord
|
import com.durian.tssparty.domain.model.ShareRecord
|
||||||
import com.durian.tssparty.util.TransactionUtils
|
import com.durian.tssparty.util.TransactionUtils
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -650,20 +656,48 @@ private fun SigningScreen(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
// Invite code (if waiting)
|
// Invite code with QR code (if waiting)
|
||||||
if (sessionStatus == SessionStatus.WAITING && inviteCode != null) {
|
if (sessionStatus == SessionStatus.WAITING && inviteCode != null) {
|
||||||
Card(
|
Card(
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "邀请码",
|
text = "邀请码",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// QR Code for invite code
|
||||||
|
val qrBitmap = remember(inviteCode) {
|
||||||
|
generateInviteQRCode(inviteCode, 200)
|
||||||
|
}
|
||||||
|
qrBitmap?.let { bitmap ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(200.dp)
|
||||||
|
.background(Color.White),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = bitmap.asImageBitmap(),
|
||||||
|
contentDescription = "邀请码二维码",
|
||||||
|
modifier = Modifier.size(200.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Invite code text with copy button
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
|
@ -679,11 +713,15 @@ private fun SigningScreen(
|
||||||
Text("复制")
|
Text("复制")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "将此邀请码分享给其他签名参与者",
|
text = "扫描二维码或分享邀请码给其他签名参与者",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1089,3 +1127,24 @@ private fun InfoRow(label: String, value: String) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code bitmap for invite code
|
||||||
|
*/
|
||||||
|
private fun generateInviteQRCode(content: String, size: Int): Bitmap? {
|
||||||
|
return try {
|
||||||
|
val writer = QRCodeWriter()
|
||||||
|
val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size)
|
||||||
|
val width = bitMatrix.width
|
||||||
|
val height = bitMatrix.height
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||||
|
for (x in 0 until width) {
|
||||||
|
for (y in 0 until height) {
|
||||||
|
bitmap.setPixel(x, y, if (bitMatrix[x, y]) android.graphics.Color.BLACK else android.graphics.Color.WHITE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitmap
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import styles from './Session.module.css';
|
import styles from './Session.module.css';
|
||||||
import {
|
import {
|
||||||
finalizeTransaction,
|
finalizeTransaction,
|
||||||
|
|
@ -346,6 +347,28 @@ export default function CoSignSession() {
|
||||||
{session.inviteCode && session.status === 'waiting' && (
|
{session.inviteCode && session.status === 'waiting' && (
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<h3 className={styles.sectionTitle}>邀请码</h3>
|
<h3 className={styles.sectionTitle}>邀请码</h3>
|
||||||
|
|
||||||
|
{/* QR Code */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 'var(--spacing-md)',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
padding: 'var(--spacing-md)',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 'var(--radius-md)',
|
||||||
|
border: '1px solid var(--border-color)',
|
||||||
|
}}>
|
||||||
|
<QRCodeSVG
|
||||||
|
value={session.inviteCode}
|
||||||
|
size={180}
|
||||||
|
level="M"
|
||||||
|
includeMargin={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.publicKeyWrapper}>
|
<div className={styles.publicKeyWrapper}>
|
||||||
<code className={styles.publicKey}>{session.inviteCode}</code>
|
<code className={styles.publicKey}>{session.inviteCode}</code>
|
||||||
<button
|
<button
|
||||||
|
|
@ -361,8 +384,8 @@ export default function CoSignSession() {
|
||||||
复制
|
复制
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: 'var(--spacing-xs)' }}>
|
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: 'var(--spacing-xs)', textAlign: 'center' }}>
|
||||||
将此邀请码分享给其他参与方,他们可以使用此码加入签名
|
扫描二维码或分享邀请码给其他参与方加入签名
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue