/// ============================================================ /// 闲言APP — 其他设置页面 /// 创建时间: 2026-05-23 /// 更新时间: 2026-05-26 /// 作用: 承载从通用设置和更多设置中拆出的低频/高级设置项 /// 上次更新: 完善导出/导入设置交互,支持文件选择和确认对话框 /// ============================================================ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart' show Divider; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:file_picker/file_picker.dart'; import '../../../../core/router/app_routes.dart'; import '../../../../core/router/app_nav_extension.dart'; import '../../../../core/services/data/data_export_service.dart'; import '../../../../core/services/data/settings_export_service.dart'; import '../../../../core/services/device/haptic_service.dart'; import '../../../../core/services/accessibility/accessibility_service.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../core/theme/app_spacing.dart'; import '../../../../core/theme/app_typography.dart'; import '../../../../core/theme/app_radius.dart'; import '../../../../core/utils/logger.dart'; import '../../../../shared/widgets/adaptive/adaptive_back_button.dart'; import '../providers/general_settings_provider.dart'; import '../services/settings_change_logger.dart'; import 'shared/settings_tiles.dart'; class OtherSettingsPage extends ConsumerStatefulWidget { const OtherSettingsPage({super.key}); @override ConsumerState createState() => _OtherSettingsPageState(); } class _OtherSettingsPageState extends ConsumerState { @override Widget build(BuildContext context) { final ext = AppTheme.ext(context); final settings = ref.watch(generalSettingsProvider); return CupertinoPageScaffold( backgroundColor: ext.bgPrimary, navigationBar: CupertinoNavigationBar( leading: const AdaptiveBackButton(), middle: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(CupertinoIcons.gear_solid, size: 18, color: ext.accent), const SizedBox(width: 6), Text( '其他设置', style: AppTypography.title3.copyWith(color: ext.textPrimary), ), ], ), backgroundColor: ext.bgElevated.withValues(alpha: 0.85), border: null, ), child: SafeArea( child: ListView( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.sm, ), children: [ _buildAdvancedSection(ext, settings), const SizedBox(height: AppSpacing.md), _buildAccessibilitySection(ext, settings), const SizedBox(height: AppSpacing.md), _buildBatteryStorageSection(ext, settings), const SizedBox(height: AppSpacing.xl), ], ), ), ); } // ── 分组1: 高级设置 ── Widget _buildAdvancedSection( AppThemeExtension ext, GeneralSettingsState settings, ) { return SettingsSectionCard( header: '高级设置', icon: CupertinoIcons.wrench_fill, children: [ SettingsNavigationTile( icon: CupertinoIcons.ellipsis_circle, iconColor: const Color(0xFF8E8E93), title: '更多设置', subtitle: '内容搜索、存储管理等', onTap: () { HapticService.light(); context.appPush(AppRoutes.moreSettings); }, ), SettingsSwitchTile( icon: CupertinoIcons.arrow_clockwise, iconColor: const Color(0xFF8E8E93), title: '自动检查更新', subtitle: '启动时自动检查新版本', value: settings.autoCheckUpdate, onChanged: (v) { HapticService.toggleSwitch(); ref.read(generalSettingsProvider.notifier).setAutoCheckUpdate(v); }, ), SettingsSwitchTile( icon: CupertinoIcons.arrow_up_arrow_down_circle_fill, iconColor: const Color(0xFF8E8E93), title: '同步设置', subtitle: '跨设备同步设置数据', value: settings.syncEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref.read(generalSettingsProvider.notifier).setSyncEnabled(v); }, ), SettingsNavigationTile( icon: CupertinoIcons.list_bullet, iconColor: const Color(0xFFFF9500), title: '日志管理', subtitle: '查看和管理应用日志', trailing: '${Log.entryCount} 条', onTap: () { HapticService.light(); context.appPush(AppRoutes.logViewer); }, ), SettingsNavigationTile( icon: CupertinoIcons.doc_text_search, iconColor: const Color(0xFF5856D6), title: '设置变更日志', subtitle: '查看设置修改记录', trailing: '${SettingsChangeLogger.entries.length} 条', onTap: () { HapticService.light(); _showSettingsChangeLog(ext); }, ), SettingsActionTile( icon: CupertinoIcons.arrow_up_arrow_down_circle_fill, iconColor: const Color(0xFF5AC8FA), title: '导出/导入设置', subtitle: '备份或恢复设置数据', onTap: () => _showExportImportSheet(ext), ), SettingsActionTile( icon: CupertinoIcons.square_arrow_up_fill, iconColor: const Color(0xFF30D158), title: '数据导出', subtitle: '导出应用数据为文件', onTap: () => _showDataExportDialog(ext), ), SettingsActionTile( icon: CupertinoIcons.trash_fill, iconColor: const Color(0xFFFF9500), title: '清理缓存', subtitle: '清理临时缓存数据 (${settings.cacheSizeText})', onTap: () => _showClearCacheDialog(ext), ), SettingsActionTile( icon: CupertinoIcons.exclamationmark_triangle_fill, iconColor: const Color(0xFFFF3B30), title: '重置设置', subtitle: '恢复所有设置为默认值', isDestructive: true, onTap: () => _showResetDialog(ext), ), ], ); } // ── 分组2: 辅助功能 ── Widget _buildAccessibilitySection( AppThemeExtension ext, GeneralSettingsState settings, ) { return SettingsSectionCard( header: '辅助功能', icon: CupertinoIcons.hand_raised_fill, children: [ SettingsSwitchTile( icon: CupertinoIcons.circle_lefthalf_fill, iconColor: const Color(0xFF8E8E93), title: '高对比度', subtitle: '增强界面元素对比度', value: settings.highContrastEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref .read(generalSettingsProvider.notifier) .setHighContrastEnabled(v); }, ), SettingsSelectionTile( icon: CupertinoIcons.eye_fill, iconColor: const Color(0xFF30D158), title: '色弱适配', subtitle: '调整色彩以适配色弱用户', value: settings.colorWeakType.label, onTap: () => _showColorWeakPicker(settings), ), SettingsSwitchTile( icon: CupertinoIcons.textformat_abc, iconColor: const Color(0xFF5856D6), title: '文字加粗', subtitle: '增强文字显示粗细', value: settings.boldTextEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref.read(generalSettingsProvider.notifier).setBoldTextEnabled(v); }, ), SettingsSwitchTile( icon: CupertinoIcons.eye_fill, iconColor: const Color(0xFFAF52DE), title: '语义调试', subtitle: '显示语义标注覆盖层,辅助开发无障碍适配', value: settings.semanticsDebugEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref .read(generalSettingsProvider.notifier) .setSemanticsDebugEnabled(v); AccessibilityService.instance.setSemanticsDebug(v); }, ), ], ); } // ── 分组3: 电池与存储 ── Widget _buildBatteryStorageSection( AppThemeExtension ext, GeneralSettingsState settings, ) { return SettingsSectionCard( header: '电池与存储', icon: CupertinoIcons.battery_25, children: [ SettingsSwitchTile( icon: CupertinoIcons.battery_25, iconColor: const Color(0xFF34C759), title: '电池优化', subtitle: '低电量自动开启省流模式', value: settings.batteryOptimizationEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref .read(generalSettingsProvider.notifier) .setBatteryOptimizationEnabled(v); }, ), SettingsSwitchTile( icon: CupertinoIcons.arrow_clockwise, iconColor: ext.accent, title: '后台刷新', subtitle: '后台获取新内容', value: settings.backgroundRefreshEnabled, onChanged: (v) { HapticService.toggleSwitch(); ref .read(generalSettingsProvider.notifier) .setBackgroundRefreshEnabled(v); }, ), SettingsNavigationTile( icon: CupertinoIcons.folder_fill, iconColor: const Color(0xFFFF9500), title: '存储管理', subtitle: '管理缓存和数据存储', onTap: () { HapticService.light(); context.appPush(AppRoutes.dataManagement); }, ), ], ); } // ============================================================ // 选择器弹窗 // ============================================================ void _showColorWeakPicker(GeneralSettingsState settings) { final ext = AppTheme.ext(context); HapticService.selection(); showCupertinoModalPopup( context: context, builder: (ctx) => Container( height: 260, decoration: BoxDecoration( color: ext.bgElevated, borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), ), child: Column( children: [ Padding( padding: const EdgeInsets.all(AppSpacing.md), child: Text( '色弱适配', style: AppTypography.subhead.copyWith( color: ext.textPrimary, fontWeight: FontWeight.w600, ), ), ), const Divider(height: 1), Expanded( child: CupertinoPicker( itemExtent: 40, scrollController: FixedExtentScrollController( initialItem: ColorWeakType.values .indexWhere((e) => e.id == settings.colorWeakTypeId) .clamp(0, ColorWeakType.values.length - 1), ), onSelectedItemChanged: (i) { ref .read(generalSettingsProvider.notifier) .setColorWeakType(ColorWeakType.values[i].id); }, children: ColorWeakType.values .map( (e) => Center( child: Text( e.label, style: AppTypography.body.copyWith( color: ext.textPrimary, ), ), ), ) .toList(), ), ), ], ), ), ); } // ============================================================ // 操作对话框 // ============================================================ void _showClearCacheDialog(AppThemeExtension ext) { HapticService.impact(); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(CupertinoIcons.trash_fill, size: 18, color: Color(0xFFFF9500)), SizedBox(width: 8), Text('清理缓存'), ], ), content: const Text('确定要清理所有临时缓存数据吗?'), actions: [ CupertinoDialogAction( child: const Text('取消'), onPressed: () => Navigator.pop(ctx), ), CupertinoDialogAction( isDestructiveAction: true, child: const Text('清理'), onPressed: () { ref.read(generalSettingsProvider.notifier).clearCache(); Navigator.pop(ctx); }, ), ], ), ); } void _showResetDialog(AppThemeExtension ext) { HapticService.heavy(); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon( CupertinoIcons.exclamationmark_triangle_fill, size: 18, color: Color(0xFFFF3B30), ), SizedBox(width: 8), Text('重置设置'), ], ), content: const Text('确定要将所有设置恢复为默认值吗?此操作不可撤销。'), actions: [ CupertinoDialogAction( child: const Text('取消'), onPressed: () => Navigator.pop(ctx), ), CupertinoDialogAction( isDestructiveAction: true, child: const Text('重置'), onPressed: () { ref.read(generalSettingsProvider.notifier).resetAll(); Navigator.pop(ctx); }, ), ], ), ); } void _showExportImportSheet(AppThemeExtension ext) { HapticService.light(); showCupertinoModalPopup( context: context, builder: (ctx) => Container( padding: const EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( color: ext.bgElevated, borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), ), child: SafeArea( top: false, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( '📤 导出/导入设置', style: AppTypography.headline.copyWith( color: ext.textPrimary, fontWeight: FontWeight.w600, ), ), const SizedBox(height: AppSpacing.sm), Text( '导出当前设置到JSON文件,或从文件恢复设置', style: AppTypography.caption1.copyWith( color: ext.textSecondary, ), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.lg), SizedBox( width: double.infinity, child: CupertinoButton( color: ext.accent, borderRadius: AppRadius.mdBorder, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( CupertinoIcons.square_arrow_up, size: 18, color: CupertinoColors.white, ), const SizedBox(width: AppSpacing.sm), Text( '导出设置', style: AppTypography.body.copyWith( color: CupertinoColors.white, ), ), ], ), onPressed: () async { Navigator.pop(ctx); try { await SettingsExportService.shareSettings(); } catch (e) { if (mounted) { _showMessage('导出失败: $e'); } } }, ), ), const SizedBox(height: AppSpacing.md), SizedBox( width: double.infinity, child: CupertinoButton( color: ext.bgSecondary, borderRadius: AppRadius.mdBorder, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( CupertinoIcons.square_arrow_down, size: 18, color: ext.textPrimary, ), const SizedBox(width: AppSpacing.sm), Text( '从文件导入', style: AppTypography.body.copyWith( color: ext.textPrimary, ), ), ], ), onPressed: () { Navigator.pop(ctx); _importFromFile(ext); }, ), ), const SizedBox(height: AppSpacing.sm), SizedBox( width: double.infinity, child: CupertinoButton( color: ext.bgSecondary, borderRadius: AppRadius.mdBorder, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( CupertinoIcons.doc_on_clipboard, size: 18, color: ext.textPrimary, ), const SizedBox(width: AppSpacing.sm), Text( '从剪贴板导入', style: AppTypography.body.copyWith( color: ext.textPrimary, ), ), ], ), onPressed: () { Navigator.pop(ctx); _showImportFromClipboardDialog(ext); }, ), ), const SizedBox(height: AppSpacing.md), CupertinoButton( child: Text( '取消', style: AppTypography.body.copyWith(color: ext.textHint), ), onPressed: () => Navigator.pop(ctx), ), ], ), ), ), ); } void _importFromFile(AppThemeExtension ext) async { HapticService.light(); try { FilePickerResult? result; try { result = await FilePicker.pickFiles( type: FileType.custom, allowedExtensions: ['json'], ); } catch (_) { result = await FilePicker.pickFiles(); } if (result == null || result.files.isEmpty) return; final filePath = result.files.single.path; if (filePath == null || filePath.isEmpty) { if (mounted) _showMessage('文件路径无效'); return; } final file = File(filePath); if (!await file.exists()) { if (mounted) _showMessage('文件不存在'); return; } final jsonStr = await file.readAsString(); if (jsonStr.isEmpty) { if (mounted) _showMessage('文件内容为空'); return; } if (!mounted) return; _showImportConfirmDialog(ext, jsonStr); } catch (e) { Log.e('文件选择失败', e); if (mounted) _showMessage('文件选择失败: $e'); } } void _showImportFromClipboardDialog(AppThemeExtension ext) { final controller = TextEditingController(); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(CupertinoIcons.doc_on_clipboard, size: 18), SizedBox(width: 8), Text('从剪贴板导入'), ], ), content: Padding( padding: const EdgeInsets.only(top: AppSpacing.sm), child: CupertinoTextField( controller: controller, maxLines: 6, placeholder: '粘贴导出的 JSON 数据', style: AppTypography.caption1.copyWith( color: ext.textPrimary, fontFamily: 'monospace', ), ), ), actions: [ CupertinoDialogAction( child: const Text('取消'), onPressed: () { controller.dispose(); Navigator.pop(ctx); }, ), CupertinoDialogAction( isDefaultAction: true, child: const Text('确认导入'), onPressed: () { final json = controller.text; controller.dispose(); Navigator.pop(ctx); if (json.isNotEmpty) { _showImportConfirmDialog(ext, json); } }, ), ], ), ); } void _showImportConfirmDialog(AppThemeExtension ext, String jsonStr) { HapticService.impact(); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon( CupertinoIcons.exclamationmark_triangle_fill, size: 18, color: Color(0xFFFF9500), ), SizedBox(width: 8), Text('确认导入'), ], ), content: const Text('导入设置将覆盖当前设置,此操作不可撤销。确定要继续吗?'), actions: [ CupertinoDialogAction( child: const Text('取消'), onPressed: () => Navigator.pop(ctx), ), CupertinoDialogAction( isDestructiveAction: true, child: const Text('导入'), onPressed: () async { Navigator.pop(ctx); final success = await SettingsExportService.importFromJson( jsonStr, ); if (mounted) { _showMessage(success ? '✅ 导入成功,设置已更新' : '❌ 导入失败,请检查文件格式'); if (success) { ref.read(generalSettingsProvider.notifier).reload(); } } }, ), ], ), ); } void _showDataExportDialog(AppThemeExtension ext) { HapticService.light(); try { DataExportService.shareData(); } catch (e) { if (mounted) { _showMessage('数据导出失败: $e'); } } } void _showSettingsChangeLog(AppThemeExtension ext) { final entries = SettingsChangeLogger.entries; showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( title: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(CupertinoIcons.doc_text_search, size: 18, color: ext.accent), const SizedBox(width: 8), const Text('设置变更日志'), ], ), content: SizedBox( width: double.maxFinite, height: 320, child: entries.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( CupertinoIcons.doc_text, size: 36, color: ext.textHint, ), const SizedBox(height: AppSpacing.sm), Text( '暂无变更记录', style: AppTypography.subhead.copyWith( color: ext.textHint, ), ), ], ), ) : ListView.separated( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: entries.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (_, index) { final entry = entries[index]; final time = '${entry.changedAt.month}/${entry.changedAt.day} ' '${entry.changedAt.hour.toString().padLeft(2, '0')}:' '${entry.changedAt.minute.toString().padLeft(2, '0')}'; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( entry.key, style: AppTypography.caption1.copyWith( color: ext.accent, fontWeight: FontWeight.w600, ), ), const Spacer(), Text( time, style: AppTypography.caption2.copyWith( color: ext.textHint, ), ), ], ), const SizedBox(height: 2), Text( '${entry.oldValue} → ${entry.newValue}', style: AppTypography.caption1.copyWith( color: ext.textSecondary, ), ), ], ), ); }, ), ), actions: [ if (entries.isNotEmpty) CupertinoDialogAction( isDestructiveAction: true, child: const Text('清空日志'), onPressed: () async { await SettingsChangeLogger.clear(); if (!context.mounted) return; Navigator.pop(ctx); setState(() {}); }, ), CupertinoDialogAction( isDefaultAction: true, child: const Text('关闭'), onPressed: () => Navigator.pop(ctx), ), ], ), ); } void _showMessage(String message) { showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( content: Text(message), actions: [ CupertinoDialogAction( isDefaultAction: true, child: const Text('好的'), onPressed: () => Navigator.pop(ctx), ), ], ), ); } }