import 'package:flutter/material.dart'; import '../../core/app_state.dart'; import '../../core/app_theme.dart'; import '../../core/sync/sync_coordinator.dart'; import '../../core/sync/sync_models.dart'; import '../../widgets/app_widgets.dart'; /// 跨平台同步设置底部面板 class SyncSettingsSheet extends StatefulWidget { const SyncSettingsSheet({super.key, required this.state}); final AppState state; static Future show(BuildContext context, AppState state) { return showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (context) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SafeArea( child: SyncSettingsSheet(state: state), ), ), ); } @override State createState() => _SyncSettingsSheetState(); } class _SyncSettingsSheetState extends State { final _coordinator = SyncCoordinator.instance; late final TextEditingController _serverController; final _userController = TextEditingController(); final _passwordController = TextEditingController(); bool _isRegisterMode = false; bool _testingConnection = false; String? _testMessage; bool? _testOk; @override void initState() { super.initState(); _serverController = TextEditingController(text: _coordinator.serverUrl); } @override void dispose() { _serverController.dispose(); _userController.dispose(); _passwordController.dispose(); super.dispose(); } Future _handleTestConnection() async { setState(() { _testingConnection = true; _testMessage = null; _testOk = null; }); final ok = await _coordinator.testConnection(_serverController.text); if (!mounted) return; setState(() { _testingConnection = false; _testOk = ok; _testMessage = ok ? '服务器连接正常' : '无法连接到服务器,请检查地址或网络'; }); } Future _handleAuth() async { final server = _serverController.text.trim(); final user = _userController.text.trim(); final pwd = _passwordController.text; if (server.isEmpty || user.isEmpty || pwd.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请填写完整的服务器地址、账号和密码')), ); return; } final success = _isRegisterMode ? await _coordinator.register( serverUrl: server, username: user, password: pwd, ) : await _coordinator.login( serverUrl: server, username: user, password: pwd, ); if (!mounted) return; if (success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_isRegisterMode ? '注册并登录成功!' : '登录成功!'), backgroundColor: AppColors.green, ), ); // 登录成功后自动执行一次同步 await _coordinator.syncNow(widget.state); } } Future _handleSyncNow() async { final ok = await _coordinator.syncNow(widget.state); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(ok ? '同步完成,进度已合并。' : '同步失败:${_coordinator.errorMessage}'), backgroundColor: ok ? AppColors.green : Colors.redAccent, ), ); } String _formatTime(DateTime? time) { if (time == null) return '从未同步'; final local = time.toLocal(); final now = DateTime.now(); final diff = now.difference(local); if (diff.inSeconds < 60) return '刚刚'; if (diff.inMinutes < 60) return '${diff.inMinutes} 分钟前'; if (diff.inHours < 24) return '${diff.inHours} 小时前'; return '${local.year}-${local.month.toString().padLeft(2, '0')}-${local.day.toString().padLeft(2, '0')} ${local.hour.toString().padLeft(2, '0')}:${local.minute.toString().padLeft(2, '0')}'; } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _coordinator, builder: (context, _) { final isLoggedIn = _coordinator.isLoggedIn; return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), child: SpacedColumn( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '云同步与多端备份', style: Theme.of(context).textTheme.titleLarge, ), if (isLoggedIn) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), decoration: BoxDecoration( color: _coordinator.state == SyncState.syncing ? AppColors.warm : _coordinator.state == SyncState.error ? Colors.red.shade50 : AppColors.softGreen, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _coordinator.state == SyncState.syncing ? Icons.sync : _coordinator.state == SyncState.error ? Icons.error_outline : Icons.cloud_done_outlined, size: 14, color: _coordinator.state == SyncState.syncing ? AppColors.warmInk : _coordinator.state == SyncState.error ? Colors.redAccent : AppColors.green, ), const SizedBox(width: 4), Text( _coordinator.state == SyncState.syncing ? '同步中' : _coordinator.state == SyncState.error ? '同步异常' : '已连接', style: TextStyle( fontSize: 12, color: _coordinator.state == SyncState.syncing ? AppColors.warmInk : _coordinator.state == SyncState.error ? Colors.redAccent : AppColors.green, fontWeight: FontWeight.w600, ), ), ], ), ), ], ), Text( '本地优先架构:无网络时不影响学习,联网后自动双向合并学习进度与复习掌握度。', style: TextStyle(fontSize: 13, color: AppColors.muted), ), const SizedBox(height: 8), if (isLoggedIn) ...[ // Logged In State SectionCard( child: Column( children: [ Row( children: [ CircleAvatar( radius: 18, backgroundColor: AppColors.softGreen, child: Icon(Icons.person, color: AppColors.green), ), const SizedBox(width: 12), Expanded( child: Column( children: [ Text( _coordinator.username ?? '同步用户', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 15, ), ), Text( _coordinator.serverUrl, style: TextStyle( fontSize: 12, color: AppColors.muted, ), ), ], ), ), TextButton( onPressed: () => _coordinator.logout(), child: const Text('退出登录'), ), ], ), const Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('上次同步:'), Text( _formatTime(_coordinator.lastSyncTime), style: const TextStyle(fontWeight: FontWeight.w500), ), ], ), ], ), ), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('自动后台同步'), subtitle: const Text('在关卡完成与复习提交后自动静默同步'), value: _coordinator.autoSyncEnabled, activeThumbColor: AppColors.green, onChanged: (val) => _coordinator.setAutoSyncEnabled(val), ), if (_coordinator.errorMessage != null) SectionCard( tint: Colors.red.shade50, child: Row( children: [ const Icon(Icons.error_outline, color: Colors.redAccent), const SizedBox(width: 10), Expanded( child: Text( '同步失败:${_coordinator.errorMessage}', style: const TextStyle( color: Colors.redAccent, fontSize: 13, ), ), ), ], ), ), const SizedBox(height: 8), PrimaryButton( label: _coordinator.state == SyncState.syncing ? '正在同步...' : '立即同步 (双向合并)', icon: Icons.sync, onPressed: _coordinator.state == SyncState.syncing ? null : _handleSyncNow, ), ] else ...[ // Not Logged In State (Login / Register Form) Row( children: [ Expanded( child: ChoiceChip( label: const Center(child: Text('登录已有账号')), selected: !_isRegisterMode, onSelected: (val) => setState(() => _isRegisterMode = false), ), ), const SizedBox(width: 12), Expanded( child: ChoiceChip( label: const Center(child: Text('注册新账号')), selected: _isRegisterMode, onSelected: (val) => setState(() => _isRegisterMode = true), ), ), ], ), const SizedBox(height: 8), TextField( controller: _serverController, keyboardType: TextInputType.url, decoration: InputDecoration( labelText: '自建同步服务器地址', hintText: 'https://syncenglish.slcydia.fun', border: const OutlineInputBorder(), suffixIcon: IconButton( tooltip: '测试连通性', icon: _testingConnection ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.network_check), onPressed: _testingConnection ? null : _handleTestConnection, ), ), ), if (_testMessage != null) Text( _testMessage!, style: TextStyle( fontSize: 12, color: _testOk == true ? AppColors.green : Colors.redAccent, ), ), TextField( controller: _userController, decoration: const InputDecoration( labelText: '用户名', border: OutlineInputBorder(), ), ), TextField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), ), ), if (_coordinator.errorMessage != null) SectionCard( tint: Colors.red.shade50, child: Text( _coordinator.errorMessage!, style: const TextStyle( color: Colors.redAccent, fontSize: 13, ), ), ), PrimaryButton( label: _coordinator.state == SyncState.syncing ? '处理中...' : (_isRegisterMode ? '注册账号并开启同步' : '登录并同步进度'), onPressed: _coordinator.state == SyncState.syncing ? null : _handleAuth, ), ], ], ), ); }, ); } }