feat: add paste-from-clipboard button to QR scanner

This commit is contained in:
Forte11Cuba
2026-04-17 05:07:53 -06:00
parent 8015b4817b
commit f2951d2bef
24 changed files with 139 additions and 27 deletions
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Cashu empfangen",
"pasteTheCashuToken": "Füge den Cashu Token ein:",
"pasteFromClipboard": "Aus Zwischenablage einfügen",
"emptyClipboard": "Zwischenablage leer",
"validToken": "Token gültig",
"invalidToken": "Ungültiger oder fehlerhafter Token",
"amount": "Betrag:",
+1
View File
@@ -76,6 +76,7 @@
"receiveCashu": "Receive Cashu",
"pasteTheCashuToken": "Paste the Cashu token:",
"pasteFromClipboard": "Paste from clipboard",
"emptyClipboard": "Clipboard is empty",
"validToken": "Valid token",
"invalidToken": "Invalid or malformed token",
"amount": "Amount:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Recibir Cashu",
"pasteTheCashuToken": "Pega el token Cashu:",
"pasteFromClipboard": "Pegar del portapapeles",
"emptyClipboard": "Portapapeles vacío",
"validToken": "Token válido",
"invalidToken": "Token inválido o malformado",
"amount": "Monto:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Recevoir Cashu",
"pasteTheCashuToken": "Collez le token Cashu :",
"pasteFromClipboard": "Coller depuis le presse-papiers",
"emptyClipboard": "Presse-papiers vide",
"validToken": "Token valide",
"invalidToken": "Token invalide ou malformé",
"amount": "Montant :",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Ricevi Cashu",
"pasteTheCashuToken": "Incolla il token Cashu:",
"pasteFromClipboard": "Incolla dagli appunti",
"emptyClipboard": "Appunti vuoti",
"validToken": "Token valido",
"invalidToken": "Token non valido o malformato",
"amount": "Importo:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Cashuを受け取る",
"pasteTheCashuToken": "Cashuトークンを貼り付け:",
"pasteFromClipboard": "クリップボードから貼り付け",
"emptyClipboard": "クリップボードは空です",
"validToken": "有効なトークン",
"invalidToken": "無効または不正なトークン",
"amount": "金額:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Cashu 받기",
"pasteTheCashuToken": "Cashu 토큰을 붙여넣으세요:",
"pasteFromClipboard": "클립보드에서 붙여넣기",
"emptyClipboard": "클립보드가 비어 있음",
"validToken": "유효한 토큰",
"invalidToken": "유효하지 않거나 손상된 토큰",
"amount": "금액:",
+6
View File
@@ -505,6 +505,12 @@ abstract class L10n {
/// **'Pegar del portapapeles'**
String get pasteFromClipboard;
/// No description provided for @emptyClipboard.
///
/// In es, this message translates to:
/// **'Portapapeles vacío'**
String get emptyClipboard;
/// No description provided for @validToken.
///
/// In es, this message translates to:
+3
View File
@@ -215,6 +215,9 @@ class L10nDe extends L10n {
@override
String get pasteFromClipboard => 'Aus Zwischenablage einfügen';
@override
String get emptyClipboard => 'Zwischenablage leer';
@override
String get validToken => 'Token gültig';
+3
View File
@@ -212,6 +212,9 @@ class L10nEn extends L10n {
@override
String get pasteFromClipboard => 'Paste from clipboard';
@override
String get emptyClipboard => 'Clipboard is empty';
@override
String get validToken => 'Valid token';
+3
View File
@@ -212,6 +212,9 @@ class L10nEs extends L10n {
@override
String get pasteFromClipboard => 'Pegar del portapapeles';
@override
String get emptyClipboard => 'Portapapeles vacío';
@override
String get validToken => 'Token válido';
+3
View File
@@ -216,6 +216,9 @@ class L10nFr extends L10n {
@override
String get pasteFromClipboard => 'Coller depuis le presse-papiers';
@override
String get emptyClipboard => 'Presse-papiers vide';
@override
String get validToken => 'Token valide';
+3
View File
@@ -213,6 +213,9 @@ class L10nIt extends L10n {
@override
String get pasteFromClipboard => 'Incolla dagli appunti';
@override
String get emptyClipboard => 'Appunti vuoti';
@override
String get validToken => 'Token valido';
+3
View File
@@ -208,6 +208,9 @@ class L10nJa extends L10n {
@override
String get pasteFromClipboard => 'クリップボードから貼り付け';
@override
String get emptyClipboard => 'クリップボードは空です';
@override
String get validToken => '有効なトークン';
+3
View File
@@ -210,6 +210,9 @@ class L10nKo extends L10n {
@override
String get pasteFromClipboard => '클립보드에서 붙여넣기';
@override
String get emptyClipboard => '클립보드가 비어 있음';
@override
String get validToken => '유효한 토큰';
+3
View File
@@ -213,6 +213,9 @@ class L10nPt extends L10n {
@override
String get pasteFromClipboard => 'Colar da área de transferência';
@override
String get emptyClipboard => 'Área de transferência vazia';
@override
String get validToken => 'Token válido';
+3
View File
@@ -212,6 +212,9 @@ class L10nRu extends L10n {
@override
String get pasteFromClipboard => 'Вставить из буфера обмена';
@override
String get emptyClipboard => 'Буфер обмена пуст';
@override
String get validToken => 'Токен действителен';
+3
View File
@@ -212,6 +212,9 @@ class L10nSw extends L10n {
@override
String get pasteFromClipboard => 'Bandika kutoka ubao';
@override
String get emptyClipboard => 'Ubao wa kunakili ni tupu';
@override
String get validToken => 'Tokeni halali';
+3
View File
@@ -207,6 +207,9 @@ class L10nZh extends L10n {
@override
String get pasteFromClipboard => '从剪贴板粘贴';
@override
String get emptyClipboard => '剪贴板为空';
@override
String get validToken => '有效代币';
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Receber Cashu",
"pasteTheCashuToken": "Cole o token Cashu:",
"pasteFromClipboard": "Colar da área de transferência",
"emptyClipboard": "Área de transferência vazia",
"validToken": "Token válido",
"invalidToken": "Token inválido ou malformado",
"amount": "Valor:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Получить Cashu",
"pasteTheCashuToken": "Вставьте Cashu токен:",
"pasteFromClipboard": "Вставить из буфера обмена",
"emptyClipboard": "Буфер обмена пуст",
"validToken": "Токен действителен",
"invalidToken": "Недействительный или повреждённый токен",
"amount": "Сумма:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "Pokea Cashu",
"pasteTheCashuToken": "Bandika tokeni ya Cashu:",
"pasteFromClipboard": "Bandika kutoka ubao",
"emptyClipboard": "Ubao wa kunakili ni tupu",
"validToken": "Tokeni halali",
"invalidToken": "Tokeni batili au imeharibika",
"amount": "Kiasi:",
+1
View File
@@ -86,6 +86,7 @@
"receiveCashu": "接收 Cashu",
"pasteTheCashuToken": "粘贴 Cashu 代币:",
"pasteFromClipboard": "从剪贴板粘贴",
"emptyClipboard": "剪贴板为空",
"validToken": "有效代币",
"invalidToken": "无效或格式错误的代币",
"amount": "金额:",
+89 -27
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:elcaju/l10n/app_localizations.dart';
@@ -59,38 +60,81 @@ class _ScanScreenState extends State<ScanScreen> {
children: [
// Scanner
QrScannerWidget(
onDetect: _onCodeDetected,
showFlashControl: true,
showCameraSwitch: false,
),
// Instrucciones en la parte inferior
Positioned(
bottom: 100,
left: 24,
right: 24,
child: Text(
_getInstructionForMode(l10n),
style: TextStyle(
fontFamily: 'Inter',
fontSize: 16,
color: Colors.white.withValues(alpha: 0.8),
),
textAlign: TextAlign.center,
onDetect: _onCodeDetected,
showFlashControl: true,
showCameraSwitch: false,
),
),
// Indicador de procesamiento
if (_isProcessing)
Container(
color: Colors.black54,
child: const Center(
child: CircularProgressIndicator(
color: AppColors.primaryAction,
// Instrucciones en la parte inferior
Positioned(
bottom: 170,
left: 24,
right: 24,
child: Text(
_getInstructionForMode(l10n),
style: TextStyle(
fontFamily: 'Inter',
fontSize: 16,
color: Colors.white.withValues(alpha: 0.8),
),
textAlign: TextAlign.center,
),
),
// Botón pegar del portapapeles (encima del flash)
Positioned(
bottom: 100,
left: 24,
right: 24,
child: Center(
child: Material(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(24),
child: InkWell(
onTap: _isProcessing ? null : _pasteFromClipboard,
borderRadius: BorderRadius.circular(24),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
LucideIcons.clipboard,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
Text(
l10n.pasteFromClipboard,
style: const TextStyle(
fontFamily: 'Inter',
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
),
),
),
),
),
],
// Indicador de procesamiento
if (_isProcessing)
Container(
color: Colors.black54,
child: const Center(
child: CircularProgressIndicator(
color: AppColors.primaryAction,
),
),
),
],
),
),
);
@@ -118,6 +162,24 @@ class _ScanScreenState extends State<ScanScreen> {
}
}
Future<void> _pasteFromClipboard() async {
final l10n = L10n.of(context)!;
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
final text = clipboardData?.text?.trim();
if (!mounted) return;
if (text == null || text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.emptyClipboard),
backgroundColor: AppColors.warning,
duration: const Duration(seconds: 2),
),
);
return;
}
_onCodeDetected(text);
}
void _onCodeDetected(String rawData) async {
if (_isProcessing) return;