import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart'; class ScanQrScreen extends StatefulWidget { const ScanQrScreen({super.key}); @override State createState() => _ScanQrScreenState(); } class _ScanQrScreenState extends State { static const Color primaryColor = Color(0xFFA5CC36); final TextEditingController nominalController = TextEditingController(); bool loading = false; bool isScanned = false; String? qrData; String rupiah(int value) { return NumberFormat.currency( locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0, ).format(value); } void addNumber(String number) { setState(() { nominalController.text += number; }); } void backspace() { setState(() { if (nominalController.text.isNotEmpty) { nominalController.text = nominalController.text.substring( 0, nominalController.text.length - 1, ); } }); } void clearNominal() { setState(() { nominalController.clear(); }); } void showError(String pesan) { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), child: Padding( padding: const EdgeInsets.all(25), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 90, height: 90, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: const Icon(Icons.close, size: 60, color: Colors.white), ), const SizedBox(height: 20), const Text( "Pembayaran Gagal", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Text(pesan, textAlign: TextAlign.center), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text("OK"), ), ), ], ), ), ), ); } void showSuccess(int nominal, int saldoBaru) { showDialog( context: context, barrierDismissible: false, builder: (_) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), child: Padding( padding: const EdgeInsets.all(25), child: Column( mainAxisSize: MainAxisSize.min, children: [ TweenAnimationBuilder( duration: const Duration(milliseconds: 700), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.scale( scale: value, child: Container( width: 100, height: 100, decoration: const BoxDecoration( color: Color(0xFFA5CC36), shape: BoxShape.circle, ), child: const Icon( Icons.check, size: 65, color: Colors.black, ), ), ); }, ), const SizedBox(height: 20), const Text( "Pembayaran Berhasil", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Text("Nominal pembayaran", textAlign: TextAlign.center), const SizedBox(height: 5), Text( rupiah(nominal), style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20, ), ), const SizedBox(height: 10), Text( "Sisa saldo : ${rupiah(saldoBaru)}", textAlign: TextAlign.center, ), const SizedBox(height: 25), SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.black, ), onPressed: () { Navigator.pop(context); Navigator.pop(context); }, child: const Text("SELESAI"), ), ), ], ), ), ), ); } Future prosesPembayaran() async { final nominal = int.tryParse(nominalController.text) ?? 0; if (qrData == null) { showError("QR belum dipindai"); return; } if (nominal < 1000) { showError("Minimal pembayaran Rp1.000"); return; } setState(() => loading = true); try { final uid = FirebaseAuth.instance.currentUser!.uid; final firestoreUser = await FirebaseFirestore.instance .collection("users") .doc(uid) .get(); final rfid = firestoreUser.data()?["rfid_uid"]?.toString(); if (rfid == null || rfid.isEmpty) { throw Exception("RFID user tidak ditemukan"); } final userRef = FirebaseDatabase.instance.ref("users/$rfid"); final snapshot = await userRef.get(); final data = Map.from(snapshot.value as Map); final saldo = (data["saldo"] ?? 0) as int; if (saldo < nominal) { showError("Saldo tidak mencukupi"); return; } final saldoBaru = saldo - nominal; await userRef.update({"saldo": saldoBaru}); await userRef.child("history").push().set({ "type": "Pembayaran QRIS", "nilai": nominal, "waktu": DateTime.now().toIso8601String(), "saldo_sebelum": saldo, "saldo_sesudah": saldoBaru, "qr_data": qrData, }); if (mounted) { showSuccess(nominal, saldoBaru); } } catch (e) { showError(e.toString()); } finally { if (mounted) { setState(() => loading = false); } } } Widget buildKeypad() { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ for (var row in [ ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"], ["C", "0", "⌫"], ]) Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: row.map((e) { return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: SizedBox( height: 60, child: ElevatedButton( onPressed: () { if (e == "C") { clearNominal(); } else if (e == "⌫") { backspace(); } else { addNumber(e); } }, child: Text(e), ), ), ), ); }).toList(), ), ), ], ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0B1600), appBar: AppBar( title: const Text("Scan QRIS"), backgroundColor: primaryColor, ), body: qrData == null ? Stack( children: [ MobileScanner( onDetect: (capture) { if (isScanned) return; final code = capture.barcodes.first.rawValue; if (code != null) { setState(() { isScanned = true; qrData = code; }); } }, ), Center( child: Container( width: 260, height: 260, decoration: BoxDecoration( border: Border.all(color: primaryColor, width: 4), borderRadius: BorderRadius.circular(20), ), ), ), ], ) : Padding( padding: const EdgeInsets.all(20), child: Column( children: [ const Text( "QRIS Berhasil Dipindai", style: TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20), Container( width: double.infinity, padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(12), ), child: Text( nominalController.text.isEmpty ? "Rp 0" : "Rp ${nominalController.text}", style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 20), Expanded(child: buildKeypad()), SizedBox( width: double.infinity, height: 55, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.black, ), onPressed: loading ? null : prosesPembayaran, child: loading ? const CircularProgressIndicator() : const Text("Bayar"), ), ), ], ), ), ); } }