import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; import '../database/siab_database.dart'; import '../services/ble_service.dart'; /// Screen that shows the current timbang (weight) value on a light, /// futuristic dashboard. /// When connected, title shows "TIMBANGAN [label] SIAB" from registered device. class TimbangScreen extends StatelessWidget { const TimbangScreen({super.key}); @override Widget build(BuildContext context) { final timbangTheme = Theme.of(context).copyWith( brightness: Brightness.light, colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF2563EB), brightness: Brightness.light, ), scaffoldBackgroundColor: const Color(0xFFF4F8FF), ); return Theme( data: timbangTheme, child: Scaffold( backgroundColor: const Color(0xFFF4F8FF), body: Consumer( builder: (context, ble, _) { final weightKg = ble.lastWeightKg; final displayValue = weightKg != null ? weightKg.toStringAsFixed(1) : '0.0'; final isConnected = ble.isConnected; final address = ble.deviceAddress; return _TimbangBackground( child: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 24, 20, 136), child: ConstrainedBox( constraints: BoxConstraints( minHeight: MediaQuery.sizeOf(context).height - MediaQuery.paddingOf(context).vertical - 160, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FutureBuilder( future: address != null ? SiabDatabase() .getByUuid(address) .then((d) => d?.label ?? '') : Future.value(''), builder: (context, snapshot) { final label = snapshot.data?.trim() ?? ''; final title = isConnected && label.isNotEmpty ? 'SIAB SCALE - $label' : 'SIAB SCALE'; return _HeaderSection( title: title, isConnected: isConnected, ); }, ), const SizedBox(height: 24), _WeightDisplayCard( displayValue: displayValue, isConnected: isConnected, ), const SizedBox(height: 20), _ActionPanel( ble: ble, isConnected: isConnected, ), ], ), ), ), ), ); }, ), ), ); } } class _TimbangBackground extends StatelessWidget { const _TimbangBackground({required this.child}); final Widget child; @override Widget build(BuildContext context) { return DecoratedBox( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFFFFFFF), Color(0xFFEFF8FF), Color(0xFFF7FBFF), Color(0xFFEAF1FF), ], ), ), child: Stack( children: [ const Positioned( top: -90, right: -80, child: _GlowOrb( size: 220, color: Color(0x663AB7FF), ), ), const Positioned( bottom: 80, left: -100, child: _GlowOrb( size: 240, color: Color(0x553D5AFE), ), ), const Positioned( top: 120, left: 28, right: 28, child: _LightBeam(), ), const Positioned( bottom: 188, right: 32, child: _SignalDash(), ), Positioned.fill(child: child), ], ), ); } } class _LightBeam extends StatelessWidget { const _LightBeam(); @override Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(999), gradient: LinearGradient( colors: [ Colors.transparent, const Color(0xFF38BDF8).withValues(alpha: 0.18), const Color(0xFF6366F1).withValues(alpha: 0.12), Colors.transparent, ], ), ), child: const SizedBox(height: 2), ); } } class _SignalDash extends StatelessWidget { const _SignalDash(); @override Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.58), borderRadius: BorderRadius.circular(999), border: Border.all(color: Colors.white.withValues(alpha: 0.82)), boxShadow: [ BoxShadow( color: const Color(0xFF2563EB).withValues(alpha: 0.12), blurRadius: 18, offset: const Offset(0, 8), ), ], ), child: const SizedBox(width: 72, height: 8), ); } } class _GlowOrb extends StatelessWidget { const _GlowOrb({ required this.size, required this.color, }); final double size; final Color color; @override Widget build(BuildContext context) { return Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: color, blurRadius: 80, spreadRadius: 40, ), ], ), ); } } class _HeaderSection extends StatelessWidget { const _HeaderSection({ required this.title, required this.isConnected, }); final String title; final bool isConnected; @override Widget build(BuildContext context) { return Column( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.68), borderRadius: BorderRadius.circular(999), border: Border.all(color: Colors.white.withValues(alpha: 0.88)), ), child: const Text( 'LIVE WEIGHING CONSOLE', style: TextStyle( color: Color(0xFF2563EB), fontSize: 11, fontWeight: FontWeight.w800, letterSpacing: 1.2, ), ), ), const SizedBox(height: 12), Text( title, textAlign: TextAlign.center, style: const TextStyle( color: Color(0xFF1B2B4A), fontSize: 17, fontWeight: FontWeight.w800, letterSpacing: 1.4, ), ), const SizedBox(height: 12), _StatusBadge(isConnected: isConnected), ], ); } } class _StatusBadge extends StatelessWidget { const _StatusBadge({required this.isConnected}); final bool isConnected; @override Widget build(BuildContext context) { final color = isConnected ? const Color(0xFF00A878) : const Color(0xFFE45555); return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.78), borderRadius: BorderRadius.circular(999), border: Border.all(color: color.withValues(alpha: 0.28)), boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.12), blurRadius: 18, offset: const Offset(0, 8), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isConnected ? Icons.bluetooth_connected : Icons.bluetooth_disabled, color: color, size: 18, ), const SizedBox(width: 8), Text( isConnected ? 'Scale connected' : 'Connect scale in Devices tab', style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w700, ), ), ], ), ); } } class _WeightDisplayCard extends StatelessWidget { const _WeightDisplayCard({ required this.displayValue, required this.isConnected, }); final String displayValue; final bool isConnected; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFFFFFFF), Color(0xFFE2F4FF), Color(0xFFF8FBFF), ], ), borderRadius: BorderRadius.circular(34), boxShadow: [ BoxShadow( color: const Color(0xFF2563EB).withValues(alpha: 0.16), blurRadius: 42, offset: const Offset(0, 20), ), BoxShadow( color: const Color(0xFF22D3EE).withValues(alpha: 0.12), blurRadius: 34, offset: const Offset(0, -8), ), ], ), child: LayoutBuilder( builder: (context, constraints) { final isCompact = constraints.maxWidth < 340; return Container( padding: EdgeInsets.symmetric( horizontal: isCompact ? 18 : 24, vertical: isCompact ? 28 : 36, ), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(30), border: Border.all(color: Colors.white.withValues(alpha: 0.9)), ), child: Column( children: [ Wrap( spacing: 8, runSpacing: 8, alignment: WrapAlignment.spaceBetween, children: [ const _MetricChip( icon: Icons.speed, label: 'Live load', color: Color(0xFF2563EB), ), _MetricChip( icon: isConnected ? Icons.sensors : Icons.sensors_off, label: isConnected ? 'Streaming' : 'Idle', color: isConnected ? const Color(0xFF00A878) : const Color(0xFFE45555), ), ], ), SizedBox(height: isCompact ? 22 : 30), const Text( 'CURRENT WEIGHT', style: TextStyle( color: Color(0xFF64748B), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 2.0, ), ), const SizedBox(height: 8), FittedBox( fit: BoxFit.scaleDown, child: Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( displayValue, style: TextStyle( color: const Color(0xFF0F1B33), fontSize: isCompact ? 78 : 94, height: 0.9, fontWeight: FontWeight.w900, letterSpacing: -4, fontFeatures: const [FontFeature.tabularFigures()], shadows: const [ Shadow( color: Color(0x5522D3EE), blurRadius: 28, ), ], ), ), const SizedBox(width: 10), Padding( padding: EdgeInsets.only(bottom: isCompact ? 7 : 10), child: const Text( 'kg', style: TextStyle( color: Color(0xFF2563EB), fontSize: 28, fontWeight: FontWeight.w900, letterSpacing: -0.5, ), ), ), ], ), ), SizedBox(height: isCompact ? 22 : 28), Container( height: 8, decoration: BoxDecoration( color: const Color(0xFFE0F2FE), borderRadius: BorderRadius.circular(999), ), child: FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: isConnected ? 0.78 : 0.34, child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(999), gradient: LinearGradient( colors: [ const Color(0xFF22D3EE).withValues(alpha: 0.72), const Color(0xFF2563EB).withValues( alpha: isConnected ? 0.9 : 0.48, ), const Color(0xFF6366F1).withValues( alpha: isConnected ? 0.78 : 0.36, ), ], ), boxShadow: [ BoxShadow( color: const Color(0xFF22D3EE).withValues( alpha: isConnected ? 0.28 : 0.12, ), blurRadius: 16, ), ], ), ), ), ), ], ), ); }, ), ); } } class _MetricChip extends StatelessWidget { const _MetricChip({ required this.icon, required this.label, required this.color, }); final IconData icon; final String label; final Color color; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(999), border: Border.all(color: color.withValues(alpha: 0.16)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: color, size: 16), const SizedBox(width: 6), Text( label, style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.w800, letterSpacing: 0.2, ), ), ], ), ); } } class _ActionPanel extends StatelessWidget { const _ActionPanel({ required this.ble, required this.isConnected, }); final BLEService ble; final bool isConnected; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.8), borderRadius: BorderRadius.circular(28), border: Border.all(color: Colors.white.withValues(alpha: 0.92)), boxShadow: [ BoxShadow( color: const Color(0xFF1D4ED8).withValues(alpha: 0.1), blurRadius: 30, offset: const Offset(0, 16), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (isConnected) ...[ Wrap( spacing: 12, runSpacing: 8, alignment: WrapAlignment.spaceBetween, crossAxisAlignment: WrapCrossAlignment.center, children: [ const Text( 'Ready to capture stable measurements', style: TextStyle( color: Color(0xFF334155), fontSize: 13, fontWeight: FontWeight.w800, ), ), TextButton.icon( onPressed: () async { await ble.disconnect(); }, icon: const Icon(Icons.power_settings_new, size: 18), label: const Text('Disconnect'), style: TextButton.styleFrom( foregroundColor: const Color(0xFFE45555), ), ), ], ), const SizedBox(height: 14), ElevatedButton.icon( onPressed: ble.hasStableWeight ? () async { final ok = await ble.saveStableMeasurement(); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( ok ? 'Stable weight saved to file' : 'No stable value to save yet', ), ), ); } : null, icon: const Icon(Icons.save_rounded), label: const Text('Save stable value'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2563EB), foregroundColor: Colors.white, disabledBackgroundColor: const Color(0xFFE2E8F0), disabledForegroundColor: const Color(0xFF94A3B8), elevation: 0, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(18), ), ), ), const SizedBox(height: 10), OutlinedButton.icon( onPressed: () async { try { final file = await ble.getTodayMeasurementFile(); final exists = await file.exists(); if (!context.mounted) return; if (!exists || await file.length() == 0) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'No measurements file for today yet. Save a value first.', ), ), ); return; } await Share.shareXFiles( [ XFile( file.path, mimeType: 'text/csv', name: p.basename(file.path), ), ], subject: 'SIAB Laporan hasil Timbang ', text: 'SIAB Laporan hasil Timbang harian.', ); } catch (e) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('gagal mengirim file: $e'), ), ); } }, icon: const Icon(Icons.ios_share_rounded), label: const Text('Share today CSV'), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF2563EB), side: const BorderSide(color: Color(0x663B82F6)), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(18), ), ), ), ] else const _DisconnectedHint(), ], ), ); } } class _DisconnectedHint extends StatelessWidget { const _DisconnectedHint(); @override Widget build(BuildContext context) { return const Row( children: [ Icon( Icons.info_outline_rounded, color: Color(0xFF64748B), ), SizedBox(width: 12), Expanded( child: Text( 'Open the Devices tab, scan nearby scales, then connect to start live weighing.', style: TextStyle( color: Color(0xFF475569), fontSize: 13, height: 1.35, fontWeight: FontWeight.w600, ), ), ), ], ); } }