import 'dart:async'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../widgets/animated_gradient_background.dart'; import '../widgets/gradient_button.dart'; class WifiConfigPage extends StatefulWidget { const WifiConfigPage({super.key}); @override State createState() => _WifiConfigPageState(); } class _WifiConfigPageState extends State with TickerProviderStateMixin { int _currentStep = 1; String _selectedWifiSsid = ''; final TextEditingController _passwordController = TextEditingController(); bool _obscurePassword = true; // Progress State double _progress = 0.0; String _progressText = '正在连接WiFi...'; // Device Info (Mock or from Route Args) // We'll try to get it from arguments, default to a fallback Map _deviceInfo = {}; // Mock WiFi List final List> _wifiList = [ {'ssid': 'Home_5G', 'level': 4}, {'ssid': 'Office_WiFi', 'level': 3}, {'ssid': 'Guest_Network', 'level': 2}, ]; @override void didChangeDependencies() { super.didChangeDependencies(); // Retrieve device info from arguments final args = ModalRoute.of(context)?.settings.arguments; if (args is Map) { _deviceInfo = args; } } void _handleNext() { if (_currentStep == 1 && _selectedWifiSsid.isEmpty) return; if (_currentStep == 2 && _passwordController.text.isEmpty) return; if (_currentStep == 4) { // Navigate to Device Control // Use pushNamedAndRemoveUntil to remove Bluetooth and WiFi pages from stack // but keep Home page so back button goes to Home context.go('/device-control'); return; } setState(() { _currentStep++; }); if (_currentStep == 3) { _startConnecting(); } } void _handleBack() { if (_currentStep > 1) { setState(() { _currentStep--; }); } else { context.go('/home'); } } void _startConnecting() { const steps = [ {'progress': 0.3, 'text': '正在连接WiFi...'}, {'progress': 0.6, 'text': '正在验证密码...'}, {'progress': 0.9, 'text': '正在同步设备...'}, {'progress': 1.0, 'text': '完成!'}, ]; int stepIndex = 0; Timer.periodic(const Duration(milliseconds: 800), (timer) { if (stepIndex < steps.length) { if (mounted) { setState(() { _progress = steps[stepIndex]['progress'] as double; _progressText = steps[stepIndex]['text'] as String; }); } stepIndex++; } else { timer.cancel(); if (mounted) { setState(() { _currentStep = 4; }); } } }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, resizeToAvoidBottomInset: true, body: Stack( children: [ // Background _buildGradientBackground(), Positioned.fill( child: SafeArea( child: Column( children: [ // Header _buildHeader(), // Content Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( children: [ // Steps Indicator _buildStepIndicator(), const SizedBox(height: 32), // Dynamic Step Content _buildCurrentStepContent(), ], ), ), ), // Footer _buildFooter(), ], ), ), ), ], ), ); } // Common Gradient Background Widget _buildGradientBackground() { return const AnimatedGradientBackground(); } Widget _buildHeader() { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( children: [ // Back button - HTML: bg rgba(255,255,255,0.6), border-radius: 12px, color #4B5563 GestureDetector( onTap: _handleBack, child: Container( width: 40, height: 40, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.white.withOpacity(0.6), ), child: const Icon( Icons.arrow_back_ios_new, size: 18, color: Color(0xFF4B5563), // Gray per HTML, not purple ), ), ), Expanded( child: Text( 'WiFi配网', textAlign: TextAlign.center, style: GoogleFonts.outfit( fontSize: 17, fontWeight: FontWeight.w600, color: const Color(0xFF1F2937), ), ), ), const SizedBox(width: 48), // Balance back button ], ), ); } Widget _buildStepIndicator() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(4, (index) { int step = index + 1; bool isActive = step == _currentStep; bool isCompleted = step < _currentStep; return AnimatedContainer( duration: const Duration(milliseconds: 300), width: isActive ? 24 : 8, height: 8, margin: const EdgeInsets.symmetric(horizontal: 4), decoration: BoxDecoration( color: isCompleted ? const Color(0xFF22C55E) // Green for completed : isActive ? const Color(0xFF8B5CF6) // Purple for active : const Color(0xFF8B5CF6).withOpacity(0.3), // Faded purple borderRadius: BorderRadius.circular(4), ), ); }), ); } Widget _buildCurrentStepContent() { switch (_currentStep) { case 1: return _buildStep1(); case 2: return _buildStep2(); case 3: return _buildStep3(); case 4: return _buildStep4(); default: return const SizedBox.shrink(); } } // Step 1: Select Network Widget _buildStep1() { return Column( children: [ // Icon Container( margin: const EdgeInsets.only(bottom: 24), child: const Icon(Icons.wifi, size: 80, color: Color(0xFF8B5CF6)), ), Text( '选择WiFi网络', style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.bold, color: const Color(0xFF1F2937), ), ), const SizedBox(height: 8), const Text( '设备需要连接WiFi以使用AI功能', style: TextStyle( fontSize: 14, color: Color(0xFF6B7280), ), ), const SizedBox(height: 24), // List Column( children: _wifiList.map((wifi) => _buildWifiItem(wifi)).toList(), ), ], ); } Widget _buildWifiItem(Map wifi) { bool isSelected = _selectedWifiSsid == wifi['ssid']; return GestureDetector( onTap: () { setState(() => _selectedWifiSsid = wifi['ssid']); }, child: Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? const Color(0xFF8B5CF6) : Colors.white.withOpacity(0.5), width: isSelected ? 2 : 1, ), boxShadow: isSelected ? [ BoxShadow( color: const Color(0xFF8B5CF6).withOpacity(0.2), blurRadius: 0, spreadRadius: 2, ), ] : null, ), child: Row( children: [ Expanded( child: Text( wifi['ssid'], style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF1F2937), ), ), ), // HTML uses per-level SVG icons: wifi-1.svg to wifi-4.svg Opacity( opacity: 0.8, child: SvgPicture.asset( 'assets/www/icons/wifi-${wifi['level']}.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode( Color(0xFF6B7280), BlendMode.srcIn, ), ), ), ], ), ), ); } // Step 2: Enter Password Widget _buildStep2() { return Column( children: [ Container( margin: const EdgeInsets.only(bottom: 24), child: const Icon( Icons.lock_outline, size: 80, color: Color(0xFF8B5CF6), ), ), Text( _selectedWifiSsid, style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.bold, color: const Color(0xFF1F2937), ), ), const SizedBox(height: 8), Text( '请输入WiFi密码', style: TextStyle( fontSize: 14, color: const Color(0xFF6B7280), ), ), const SizedBox(height: 24), TextField( controller: _passwordController, obscureText: _obscurePassword, onChanged: (_) => setState(() {}), decoration: InputDecoration( hintText: '输入密码', filled: true, fillColor: Colors.white.withOpacity(0.8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.all(20), suffixIcon: Padding( padding: const EdgeInsets.only(right: 8), child: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF9CA3AF), size: 22, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), ), style: const TextStyle(fontSize: 16), ), ], ); } // Step 3: Connecting Widget _buildStep3() { return Column( children: [ // Animation placeholder (using Icon for now, can be upgraded to Wave animation) SizedBox( height: 120, child: Center( child: TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), duration: const Duration(seconds: 1), builder: (context, value, child) { return Icon( Icons.wifi_tethering, size: 80 + (value * 10), color: const Color(0xFF8B5CF6).withOpacity(1 - value * 0.5), ); }, onEnd: () {}, // Repeat logic usually handled by AnimationController ), ), ), Text( '正在配网...', style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.bold, color: const Color(0xFF1F2937), ), ), const SizedBox(height: 32), // Progress Bar ClipRRect( borderRadius: BorderRadius.circular(3), child: SizedBox( height: 6, child: LinearProgressIndicator( value: _progress, backgroundColor: const Color(0xFF8B5CF6).withOpacity(0.2), valueColor: const AlwaysStoppedAnimation(Color(0xFF8B5CF6)), ), ), ), const SizedBox(height: 16), Text( _progressText, style: TextStyle( fontSize: 14, color: const Color(0xFF6B7280), ), ), ], ); } // Get device icon path based on device type String _getDeviceIconPath() { final type = _deviceInfo['type'] as String? ?? 'plush'; switch (type) { case 'plush_core': case 'plush': return 'assets/www/icons/pixel-capybara.svg'; case 'badge_ai': return 'assets/www/icons/pixel-badge-ai.svg'; case 'badge_basic': case 'badge': return 'assets/www/icons/pixel-badge-basic.svg'; default: return 'assets/www/icons/pixel-capybara.svg'; } } // Step 4: Result (Success) - centered vertically Widget _buildStep4() { return Column( children: [ const SizedBox(height: 80), // Success Icon Stack - HTML: no white background Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ // Device icon container - 120x120 per HTML SizedBox( width: 120, height: 120, child: SvgPicture.asset( _getDeviceIconPath(), width: 120, height: 120, placeholderBuilder: (_) => const Icon( Icons.smart_toy, size: 80, color: Color(0xFF8B5CF6), ), ), ), // Check badge Positioned( bottom: -5, right: -5, child: Container( width: 32, height: 32, decoration: BoxDecoration( color: const Color(0xFF22C55E), shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 3), boxShadow: [ BoxShadow( color: const Color(0xFF22C55E).withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: const Icon(Icons.check, color: Colors.white, size: 18), ), ), ], ), const SizedBox(height: 24), Text( '配网成功!', style: GoogleFonts.outfit( fontSize: 24, fontWeight: FontWeight.bold, color: const Color(0xFF1F2937), ), ), const SizedBox(height: 8), Text( '设备已成功连接到网络', style: TextStyle( fontSize: 14, color: const Color(0xFF6B7280), ), ), ], ); } Widget _buildFooter() { bool showNext = false; String nextText = '下一步'; if (_currentStep == 1 && _selectedWifiSsid.isNotEmpty) showNext = true; if (_currentStep == 2 && _passwordController.text.isNotEmpty) { showNext = true; nextText = '连接'; } if (_currentStep == 4) { showNext = true; nextText = '进入设备'; } if (!showNext && _currentStep != 3) { // Show cancel only? return Padding( padding: const EdgeInsets.all(32), child: TextButton( onPressed: () => context.go('/bluetooth'), child: Text( '取消', style: TextStyle( color: const Color(0xFF6B7280), ), ), ), ); } if (_currentStep == 3) return const SizedBox(height: 100); // Hide buttons during connection return Container( padding: EdgeInsets.fromLTRB( 32, 20, 32, MediaQuery.of(context).padding.bottom + 40, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Cancel button - frosted glass style if (_currentStep < 4) GestureDetector( onTap: _handleBack, child: Container( padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 14, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(25), border: Border.all(color: const Color(0xFFE5E7EB)), ), child: const Text( '取消', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF6B7280), ), ), ), ), if (_currentStep < 4) const SizedBox(width: 16), // Constrained button (not full-width) GradientButton( text: nextText, onPressed: _handleNext, height: 56, width: _currentStep == 4 ? 200 : 160, ), ], ), ); } }