import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:lottie/lottie.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../../controllers/tenant/create_tenant.dart'; import '../../controllers/tenant_controller /tenant_list.dart'; import '../home_view.dart'; class QrScannerPage extends StatefulWidget { const QrScannerPage({super.key}); @override State createState() => _QrScannerPageState(); } class _QrScannerPageState extends State with WidgetsBindingObserver, SingleTickerProviderStateMixin { final MobileScannerController scannerController = MobileScannerController(); final Create_tenant tenantController = Get.put(Create_tenant()); final TenantController tenantControllers = Get.put(TenantController()); String? qrData; bool isProcessing = false; Timer? refreshTimer; late AnimationController _animationController; late Animation _scanAnimation; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // Initialize scanning animation _animationController = AnimationController( vsync: this, duration: const Duration(seconds: 2), )..repeat(reverse: true); _scanAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); _startScanner(); _startAutoRefresh(); } void _startScanner() { scannerController.start(); tenantController.responseMessage.value = ''; qrData = null; setState(() {}); } void _startAutoRefresh() { refreshTimer?.cancel(); refreshTimer = Timer.periodic(const Duration(seconds: 10), (_) async { if (!isProcessing) { await scannerController.stop(); await Future.delayed(const Duration(milliseconds: 300)); _startScanner(); } }); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (!mounted) return; if (state == AppLifecycleState.resumed) { _startScanner(); } else if (state == AppLifecycleState.paused) { scannerController.stop(); } } Future onDetect(BarcodeCapture capture) async { if (isProcessing) return; isProcessing = true; final barcode = capture.barcodes.first; final rawValue = barcode.rawValue; if (rawValue != null) { setState(() => qrData = rawValue); try { final decoded = jsonDecode(rawValue); final tenantId = decoded['tenantid']; final locationId = decoded['locationid']; if (tenantId != null && locationId != null) { await tenantController.createTenantCustomerFromQR( tenantId: tenantId, locationId: locationId, ); } else { tenantController.responseMessage.value = "Invalid QR format!"; } } catch (e) { tenantController.responseMessage.value = "Invalid QR code content!"; } } await Future.delayed(const Duration(seconds: 2)); await scannerController.stop(); final msg = tenantController.responseMessage.value; if (msg.isNotEmpty) { final bottomNavController = Get.find(); bottomNavController.currentIndex.value = 0; // Go to dashboard Navigator.of(context).pop(); await tenantControllers.loadTenants(); } isProcessing = false; } @override void dispose() { WidgetsBinding.instance.removeObserver(this); scannerController.dispose(); refreshTimer?.cancel(); _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.white, // or transparent statusBarIconBrightness: Brightness.dark, // Android statusBarBrightness: Brightness.light, // iOS ), ); return SafeArea( child: Scaffold( backgroundColor: Colors.black, extendBodyBehindAppBar: true, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: const Text( 'Scan QR Code', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20, ), ), centerTitle: true, ), body: Stack( children: [ // Camera Scanner MobileScanner( controller: scannerController, onDetect: onDetect, ), // Dark overlay with hole for scanner Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), ), ), // Scanning frame with animated border Center( child: Stack( alignment: Alignment.center, children: [ // Main scanning frame Container( width: 280, height: 280, decoration: BoxDecoration( border: Border.all( color: Colors.white.withOpacity(0.5), width: 2, ), borderRadius: BorderRadius.circular(24), ), ), // Corner decorations ...List.generate(4, (index) { return Positioned( top: index < 2 ? 0 : null, bottom: index >= 2 ? 0 : null, left: index % 2 == 0 ? 0 : null, right: index % 2 == 1 ? 0 : null, child: Container( width: 50, height: 50, decoration: BoxDecoration( border: Border( top: index < 2 ? BorderSide(color: Colors.blue.shade400, width: 4) : BorderSide.none, bottom: index >= 2 ? BorderSide(color: Colors.blue.shade400, width: 4) : BorderSide.none, left: index % 2 == 0 ? BorderSide(color: Colors.blue.shade400, width: 4) : BorderSide.none, right: index % 2 == 1 ? BorderSide(color: Colors.blue.shade400, width: 4) : BorderSide.none, ), borderRadius: BorderRadius.only( topLeft: index == 0 ? const Radius.circular(24) : Radius.zero, topRight: index == 1 ? const Radius.circular(24) : Radius.zero, bottomLeft: index == 2 ? const Radius.circular(24) : Radius.zero, bottomRight: index == 3 ? const Radius.circular(24) : Radius.zero, ), ), ), ); }), // Animated scanning line AnimatedBuilder( animation: _scanAnimation, builder: (context, child) { return Positioned( top: 20 + (_scanAnimation.value * 240), child: Container( width: 250, height: 3, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.transparent, Colors.blue.shade400, Colors.transparent, ], ), boxShadow: [ BoxShadow( color: Colors.blue.shade400.withOpacity(0.5), blurRadius: 10, spreadRadius: 2, ), ], ), ), ); }, ), ], ), ), // Response dialog Align( alignment: Alignment.bottomCenter, child: Obx(() { final msg = tenantController.responseMessage.value; if (msg.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { bool isSuccess = msg.contains('created successfully'); bool isConflict = msg.contains('already assigned'); bool isError = msg.toLowerCase().contains('error'); String title; Color titleColor; String lottieAsset; if (isSuccess) { title = "Success!"; titleColor = Colors.green; lottieAsset = 'assets/lotties/Successful.json'; } else if (isConflict) { title = "Already Registered"; titleColor = Colors.orange; lottieAsset = 'assets/lotties/Failed.json'; } else { title = "Failed!"; titleColor = Colors.red; lottieAsset = 'assets/lotties/Failed.json'; } showDialog( context: Get.context!, barrierDismissible: false, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), elevation: 0, backgroundColor: Colors.transparent, child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: titleColor.withOpacity(0.2), blurRadius: 20, spreadRadius: 5, ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Lottie animation with background Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: titleColor.withOpacity(0.1), shape: BoxShape.circle, ), child: Lottie.asset( lottieAsset, height: 100, width: 100, repeat: false, ), ), SizedBox(height: 20), // Title with icon Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( isSuccess ? Icons.check_circle_rounded : isConflict ? Icons.info_rounded : Icons.error_rounded, color: titleColor, size: 28, ), const SizedBox(width: 8), Text( title, style: TextStyle( color: titleColor, fontSize: 24, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 16), // Message in a card Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(12), ), child: Text( msg, textAlign: TextAlign.center, style: TextStyle( fontSize: 15, color: Colors.grey.shade800, height: 1.4, ), ), ), const SizedBox(height: 24), // OK Button with gradient Container( width: double.infinity, height: 50, decoration: BoxDecoration( gradient: LinearGradient( colors: [ titleColor, titleColor.withOpacity(0.8), ], ), borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( color: titleColor.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), onPressed: () { Navigator.pop(context); tenantController.responseMessage.value = ""; }, child: const Text( "OK", style: TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ), ), ); }); } return const SizedBox.shrink(); }), ), ], ), ), ); } }