import 'dart:async'; import 'package:flutter/material.dart'; import 'bridge/location_bridge.dart'; import 'preferences.dart'; import 'settings_screen.dart'; import 'status_screen.dart'; class MainScreen extends StatefulWidget { const MainScreen({super.key}); @override State createState() => _MainScreenState(); } class _MainScreenState extends State with TickerProviderStateMixin { StreamSubscription>? _locationSubscription; bool _isTracking = false; String _lastLat = '--'; String _lastLon = '--'; String _lastSpeed = '--'; String _lastTime = '--'; late AnimationController _pulseController; late Animation _pulseAnimation; @override void initState() { super.initState(); _pulseController = AnimationController( duration: const Duration(milliseconds: 1400), vsync: this, )..repeat(reverse: true); _pulseAnimation = Tween(begin: 0.6, end: 1.0).animate( CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), ); _initLocationStream(); } @override void dispose() { _locationSubscription?.cancel(); _pulseController.dispose(); super.dispose(); } Future _initLocationStream() async { final status = await LocationBridge.getStatus(); if (mounted) { setState(() { _isTracking = status?['isTracking'] == true; final lat = status?['lastLatitude']; final lon = status?['lastLongitude']; if (lat != null && lat != 0.0) { _lastLat = lat.toStringAsFixed(5); } if (lon != null && lon != 0.0) { _lastLon = lon.toStringAsFixed(5); } final speed = status?['lastSpeed']; if (speed != null) { _lastSpeed = (speed * 3.6).toStringAsFixed(1); } final timestamp = status?['lastTimestamp']; if (timestamp != null) { _lastTime = _formatDateTime( DateTime.fromMillisecondsSinceEpoch(timestamp), ); } }); } _locationSubscription = LocationBridge.locationUpdates.listen((location) { if (mounted) { setState(() { _lastLat = location['latitude']?.toStringAsFixed(5) ?? '--'; _lastLon = location['longitude']?.toStringAsFixed(5) ?? '--'; _lastSpeed = location['speed'] != null ? (location['speed'] * 3.6).toStringAsFixed(1) : '--'; _lastTime = location['timestamp'] != null ? _formatDateTime( DateTime.fromMillisecondsSinceEpoch(location['timestamp']), ) : '--'; }); } }); } String _formatDateTime(DateTime dt) { return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ' '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}'; } Future _toggleTracking() async { final previousState = _isTracking; final newState = !_isTracking; setState(() => _isTracking = newState); bool success; if (newState) { success = await LocationBridge.startTracking( config: { 'serverUrl': Preferences.serverUrl, 'deviceId': Preferences.deviceId, 'password': Preferences.password, 'accuracy': Preferences.accuracy, 'distanceFilter': Preferences.distanceFilter, 'interval': Preferences.interval, 'heartbeat': Preferences.heartbeat, 'offlineBuffer': Preferences.offlineBuffer, 'stopDetection': Preferences.stopDetection, }, ); } else { success = await LocationBridge.stopTracking(); } if (!success && mounted) { setState(() => _isTracking = previousState); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( previousState ? 'Failed to stop tracking' : 'Failed to start tracking', ), backgroundColor: const Color(0xFF1a1a1a), ), ); } } // ─── BUILD ──────────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0d0d0d), body: SafeArea( child: Column( children: [ _buildHeader(), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 16), _buildStatusCard(), const SizedBox(height: 16), _buildTrackingToggle(), const SizedBox(height: 12), _buildSendButton(), const SizedBox(height: 12), _buildNavButtons(), const SizedBox(height: 20), ], ), ), ), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), decoration: const BoxDecoration( color: Color(0xFF161616), border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)), ), child: Row( children: [ Container( width: 10, height: 10, decoration: BoxDecoration( shape: BoxShape.circle, color: _isTracking ? const Color(0xFF00e676) : const Color(0xFF616161), boxShadow: _isTracking ? [ BoxShadow( color: const Color(0xFF00e676).withValues(alpha: 0.6), blurRadius: 8, spreadRadius: 2, ), ] : null, ), ), const SizedBox(width: 12), const Text( 'TracPulse', style: TextStyle( fontFamily: 'monospace', fontSize: 18, fontWeight: FontWeight.w700, letterSpacing: 3, color: Color(0xFFe0e0e0), ), ), const Spacer(), Text( _isTracking ? 'ACTIVE' : 'IDLE', style: TextStyle( fontFamily: 'monospace', fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 2, color: _isTracking ? const Color(0xFF00e676) : const Color(0xFF616161), ), ), ], ), ); } Widget _buildStatusCard() { return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color(0xFF161616), borderRadius: BorderRadius.circular(4), border: Border.all(color: const Color(0xFF2a2a2a), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.gps_fixed, size: 14, color: Color(0xFF00bcd4)), const SizedBox(width: 8), Text( 'COORDINATES', style: TextStyle( fontFamily: 'monospace', fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 2, color: const Color(0xFF00bcd4).withValues(alpha: 0.8), ), ), ], ), const SizedBox(height: 16), _buildCoordRow('LAT', _lastLat, const Color(0xFF69f0ae)), const SizedBox(height: 10), _buildCoordRow('LON', _lastLon, const Color(0xFF69f0ae)), const SizedBox(height: 18), Container(height: 1, color: const Color(0xFF2a2a2a)), const SizedBox(height: 18), _buildCoordRow('SPD', _lastSpeed, const Color(0xFFff9800)), const SizedBox(height: 10), _buildCoordRow('TIM', _lastTime, const Color(0xFF9e9e9e)), ], ), ); } Widget _buildCoordRow(String label, String value, Color valueColor) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 36, child: Text( label, style: const TextStyle( fontFamily: 'monospace', fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1, color: Color(0xFF616161), ), ), ), Expanded( child: Text( value, style: TextStyle( fontFamily: 'monospace', fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 1, color: valueColor, ), ), ), ], ); } Widget _buildTrackingToggle() { return AnimatedBuilder( animation: _pulseAnimation, builder: (context, child) { return GestureDetector( onTap: _toggleTracking, child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), decoration: BoxDecoration( color: _isTracking ? Color.lerp( const Color(0xFF1b5e20), const Color(0xFF2e7d32), _pulseAnimation.value, ) : const Color(0xFF1a1a1a), borderRadius: BorderRadius.circular(4), border: Border.all( color: _isTracking ? const Color(0xFF00e676) : const Color(0xFF2a2a2a), width: 1.5, ), boxShadow: _isTracking ? [ BoxShadow( color: const Color( 0xFF00e676, ).withValues(alpha: 0.15 * _pulseAnimation.value), blurRadius: 12, spreadRadius: 0, ), ] : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( _isTracking ? Icons.radio_button_checked : Icons.radio_button_off, color: _isTracking ? const Color(0xFF00e676) : const Color(0xFF616161), size: 20, ), const SizedBox(width: 12), Text( _isTracking ? 'TRACKING ACTIVE' : 'START TRACKING', style: TextStyle( fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.w700, letterSpacing: 2, color: _isTracking ? const Color(0xFF00e676) : const Color(0xFF9e9e9e), ), ), ], ), ), ); }, ); } Widget _buildSendButton() { if (!_isTracking) return const SizedBox.shrink(); return SizedBox( height: 44, child: OutlinedButton.icon( onPressed: () async { final success = await LocationBridge.reportLocation(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( success ? 'Location sent to server' : 'Failed — check logs', ), backgroundColor: const Color(0xFF1a1a1a), ), ); } }, style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF00bcd4), side: const BorderSide(color: Color(0xFF00bcd4), width: 1), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), icon: const Icon(Icons.arrow_upward, size: 16), label: const Text( 'SEND LOCATION', style: TextStyle( fontFamily: 'monospace', letterSpacing: 1, fontSize: 13, ), ), ), ); } Widget _buildNavButtons() { return Row( children: [ Expanded( child: _buildNavButton( icon: Icons.tune, label: 'SETTINGS', onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ), ), ), const SizedBox(width: 12), Expanded( child: _buildNavButton( icon: Icons.terminal, label: 'LOGS', onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => const StatusScreen()), ), ), ), ], ); } Widget _buildNavButton({ required IconData icon, required String label, required VoidCallback onTap, }) { return Material( color: const Color(0xFF161616), borderRadius: BorderRadius.circular(4), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(4), child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all(color: const Color(0xFF2a2a2a), width: 1), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 16, color: const Color(0xFF757575)), const SizedBox(width: 8), Text( label, style: const TextStyle( fontFamily: 'monospace', fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 2, color: Color(0xFF9e9e9e), ), ), ], ), ), ), ); } }