1027 lines
31 KiB
Dart
1027 lines
31 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 通用设置页面
|
|
/// 创建时间: 2026-04-28
|
|
/// 更新时间: 2026-06-05
|
|
/// 作用: 声音/震动/通知/显示/性能/隐私/高级等设置
|
|
/// 上次更新: 添加无障碍分组支持(语义调试开关+系统级状态指示)
|
|
/// ============================================================
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:xianyan/core/router/app_nav_extension.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
|
|
import '../../../../../../core/constants/app_constants.dart';
|
|
import '../../../../../../core/router/app_routes.dart';
|
|
import '../../../../../../core/services/device/app_lock_service.dart';
|
|
import '../../../../../../core/services/device/haptic_service.dart';
|
|
import '../../../../../../core/services/feature/feature_flag_service.dart';
|
|
import '../../../../../../core/services/search/search_history_service.dart';
|
|
import '../../../../../../core/storage/kv_storage.dart';
|
|
import '../../../../../../core/theme/app_colors.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 '../../../../../../l10n/translations.dart';
|
|
import '../../../../../../shared/widgets/adaptive/adaptive_back_button.dart';
|
|
import '../../../../../../shared/widgets/containers/glass_container.dart';
|
|
import '../../providers/general_settings_provider.dart';
|
|
import '../../providers/plugin_provider.dart';
|
|
import '../../../../../../core/providers/split_view_provider.dart';
|
|
import 'package:xianyan/features/home/providers/offline_provider.dart';
|
|
import 'general_settings_sections.dart';
|
|
import 'general_settings_pickers.dart';
|
|
import 'general_settings_actions.dart';
|
|
import 'log_level_settings_page.dart';
|
|
import 'setting_models.dart';
|
|
|
|
class GeneralSettingsPage extends ConsumerStatefulWidget {
|
|
const GeneralSettingsPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<GeneralSettingsPage> createState() =>
|
|
_GeneralSettingsPageState();
|
|
}
|
|
|
|
class _GeneralSettingsPageState extends ConsumerState<GeneralSettingsPage>
|
|
with GeneralSettingsPickers, GeneralSettingsActions {
|
|
String _searchQuery = '';
|
|
final _searchController = TextEditingController();
|
|
final _searchFocusNode = FocusNode();
|
|
String _versionText = '';
|
|
List<String> _searchHistory = [];
|
|
|
|
static const _maxHistory = 10;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_searchFocusNode.canRequestFocus = false;
|
|
_loadVersion();
|
|
_loadSearchHistory();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
ref.read(generalSettingsProvider.notifier).calculateCacheSize();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
_searchFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _loadSearchHistory() {
|
|
final settings = ref.read(generalSettingsProvider);
|
|
if (!settings.searchHistoryEnabled) {
|
|
setState(() => _searchHistory = []);
|
|
return;
|
|
}
|
|
final allHistory = SearchHistoryService.instance.getSearchHistory();
|
|
setState(() {
|
|
_searchHistory = allHistory.take(_maxHistory).toList();
|
|
});
|
|
}
|
|
|
|
void _addToHistory(String query) {
|
|
if (query.trim().isEmpty) return;
|
|
final settings = ref.read(generalSettingsProvider);
|
|
if (!settings.searchHistoryEnabled) return;
|
|
SearchHistoryService.instance.addSearchRecord(query.trim());
|
|
setState(() {
|
|
_searchHistory.remove(query.trim());
|
|
_searchHistory.insert(0, query.trim());
|
|
if (_searchHistory.length > _maxHistory) {
|
|
_searchHistory = _searchHistory.sublist(0, _maxHistory);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _removeHistory(String query) {
|
|
SearchHistoryService.instance.removeSearchRecord(query);
|
|
setState(() {
|
|
_searchHistory.remove(query);
|
|
});
|
|
}
|
|
|
|
void _clearHistory() {
|
|
SearchHistoryService.instance.clearSearchHistory();
|
|
setState(() {
|
|
_searchHistory.clear();
|
|
});
|
|
}
|
|
|
|
// ── 加载版本号 ──
|
|
|
|
Future<void> _loadVersion() async {
|
|
try {
|
|
final info = await PackageInfo.fromPlatform();
|
|
if (mounted) {
|
|
setState(() {
|
|
_versionText = 'v${info.version} (${info.buildNumber})';
|
|
});
|
|
}
|
|
} catch (_) {
|
|
_versionText = 'v${AppVersion.version}';
|
|
}
|
|
}
|
|
|
|
// ── 过滤后的设置分组 ──
|
|
|
|
List<SettingSection> get _filteredSections {
|
|
final settings = ref.watch(generalSettingsProvider);
|
|
final notificationEnabled = ref.watch(notificationEnabledProvider);
|
|
final offlineState = ref.watch(offlineProvider);
|
|
final pluginState = ref.watch(pluginProvider);
|
|
final t = ref.watch(translationsProvider);
|
|
final splitState = ref.watch(splitViewProvider);
|
|
|
|
final allSections = buildGeneralSettingSections(
|
|
settings: settings,
|
|
notificationEnabled: notificationEnabled,
|
|
preloadEnabled: offlineState.config.preloadOnWifi,
|
|
appLockMethodLabel: ref.watch(appLockProvider).method.label,
|
|
pluginState: pluginState,
|
|
t: t,
|
|
navBarPositionIndex: splitState.navBarPosition.index,
|
|
splitRatio: splitState.splitRatio,
|
|
splitViewEnabled: splitState.splitViewEnabled,
|
|
);
|
|
return filterSettingSections(
|
|
allSections,
|
|
_searchQuery,
|
|
pinyinMatcher: _matchesPinyin,
|
|
);
|
|
}
|
|
|
|
static const _pinyinMap = <String, String>{
|
|
'shengyin': '声音',
|
|
'zhendong': '震动',
|
|
'yinjian': '音效',
|
|
'tongzhi': '通知',
|
|
'xianshi': '显示',
|
|
'ziti': '字体',
|
|
'yuyan': '语言',
|
|
'xingneng': '性能',
|
|
'yinsi': '隐私',
|
|
'quanxian': '权限',
|
|
'gaoji': '高级',
|
|
'qita': '其他',
|
|
'suoping': '锁屏',
|
|
'yingyong': '应用',
|
|
'houtai': '后台',
|
|
'huancun': '缓存',
|
|
'jianpan': '键盘',
|
|
'jianqieban': '剪贴板',
|
|
'yejian': '夜间',
|
|
'shendu': '深度',
|
|
'zhineng': '智能',
|
|
'moshi': '模式',
|
|
'jiaohu': '交互',
|
|
'tixing': '提醒',
|
|
'meiri': '每日',
|
|
'shuaka': '刷新',
|
|
'chakan': '查看',
|
|
'shezhi': '设置',
|
|
'guanyu': '关于',
|
|
'banben': '版本',
|
|
'gengxin': '更新',
|
|
'daochu': '导出',
|
|
'daoru': '导入',
|
|
'chongzhi': '重置',
|
|
'qingli': '清理',
|
|
'fuzhu': '辅助',
|
|
'secai': '色彩',
|
|
'jiacu': '加粗',
|
|
'dianchi': '电池',
|
|
'cunchu': '存储',
|
|
'guanli': '管理',
|
|
'changliang': '常亮',
|
|
'chidu': '尺寸',
|
|
'midu': '密度',
|
|
'donghua': '动画',
|
|
'texiao': '特效',
|
|
'beijing': '背景',
|
|
'qiehuan': '切换',
|
|
'yidao': '摇到',
|
|
'yaoyiyao': '摇一摇',
|
|
'changjing': '场景',
|
|
'yuedu': '阅读',
|
|
'liulan': '浏览',
|
|
'sousuo': '搜索',
|
|
'shoucang': '收藏',
|
|
'lishi': '历史',
|
|
'jiance': '检测',
|
|
'jiankang': '健康',
|
|
'bizhi': '壁纸',
|
|
'yuan': '源',
|
|
'xiaozujian': '小组件',
|
|
'zhuomian': '桌面',
|
|
'bianji': '编辑',
|
|
'muban': '模板',
|
|
'tupian': '图片',
|
|
'fenlei': '分类',
|
|
'biaoqian': '标签',
|
|
'shuju': '数据',
|
|
'tongbu': '同步',
|
|
'beifen': '备份',
|
|
'huifu': '恢复',
|
|
'anzhuang': '安装',
|
|
'xiezai': '卸载',
|
|
'zidingyi': '自定义',
|
|
'zhuti': '主题',
|
|
'peise': '配色',
|
|
'yese': '夜色',
|
|
'riqi': '日期',
|
|
'shijian': '时间',
|
|
'daojishi': '倒计时',
|
|
'fanqiezhong': '番茄钟',
|
|
'jieqi': '节气',
|
|
'qiandao': '签到',
|
|
'yunshi': '运势',
|
|
'juju': '句子',
|
|
};
|
|
|
|
bool _matchesPinyin(String query, String text) {
|
|
final q = query.toLowerCase();
|
|
if (text.toLowerCase().contains(q)) return true;
|
|
for (final entry in _pinyinMap.entries) {
|
|
if (entry.key.contains(q) && text.contains(entry.value)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ── 页面构建 ──
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ext = AppTheme.ext(context);
|
|
final t = ref.watch(translationsProvider);
|
|
|
|
return CupertinoPageScaffold(
|
|
backgroundColor: ext.bgPrimary,
|
|
navigationBar: CupertinoNavigationBar(
|
|
leading: const AdaptiveBackButton(),
|
|
middle: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(CupertinoIcons.settings, size: 18, color: ext.accent),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
t.generalSettings,
|
|
style: AppTypography.title3.copyWith(color: ext.textPrimary),
|
|
),
|
|
],
|
|
),
|
|
backgroundColor: ext.bgElevated.withValues(alpha: 0.85),
|
|
border: null,
|
|
),
|
|
child: SafeArea(
|
|
bottom: false,
|
|
child: Column(
|
|
children: [
|
|
_buildSearchBar(ext),
|
|
Expanded(
|
|
child: ListView(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
children: [
|
|
..._filteredSections.expand(
|
|
(section) => [
|
|
_buildSectionHeader(ext, section),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
...section.items.map(
|
|
(item) => Padding(
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.xs),
|
|
child: _buildSettingCard(ext, item),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
],
|
|
),
|
|
_buildSuggestions(ext),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildFooter(ext),
|
|
const SizedBox(height: AppSpacing.xl),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── 搜索栏 ──
|
|
|
|
Widget _buildSearchBar(AppThemeExtension ext) {
|
|
final t = ref.watch(translationsProvider);
|
|
return Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
_searchFocusNode.canRequestFocus = true;
|
|
_searchFocusNode.requestFocus();
|
|
},
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: ext.bgSecondary,
|
|
borderRadius: AppRadius.lgBorder,
|
|
),
|
|
child: CupertinoTextField(
|
|
controller: _searchController,
|
|
focusNode: _searchFocusNode,
|
|
placeholder: t.searchSettings,
|
|
prefix: Padding(
|
|
padding: const EdgeInsets.only(left: AppSpacing.md),
|
|
child: Icon(
|
|
CupertinoIcons.search,
|
|
size: 16,
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
suffix: _searchQuery.isNotEmpty
|
|
? GestureDetector(
|
|
onTap: () {
|
|
_searchController.clear();
|
|
setState(() => _searchQuery = '');
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: AppSpacing.sm),
|
|
child: Icon(
|
|
CupertinoIcons.clear_circled_solid,
|
|
size: 18,
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
)
|
|
: null,
|
|
placeholderStyle: AppTypography.subhead.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
style: AppTypography.body.copyWith(color: ext.textPrimary),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.sm,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
decoration: null,
|
|
onChanged: (v) => setState(() => _searchQuery = v),
|
|
onSubmitted: (v) {
|
|
if (v.trim().isNotEmpty) _addToHistory(v.trim());
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (_searchQuery.isEmpty && _searchHistory.isNotEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'搜索历史',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: _clearHistory,
|
|
child: Text(
|
|
'清除',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.accent,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
Wrap(
|
|
spacing: 6,
|
|
runSpacing: 6,
|
|
children: _searchHistory.map((keyword) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
_searchController.text = keyword;
|
|
setState(() => _searchQuery = keyword);
|
|
},
|
|
onLongPress: () => _removeHistory(keyword),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgSecondary,
|
|
borderRadius: AppRadius.pillBorder,
|
|
),
|
|
child: Text(
|
|
keyword,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ── 分组标题 ──
|
|
|
|
Widget _buildSectionHeader(AppThemeExtension ext, SettingSection section) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: AppSpacing.xs),
|
|
child: Row(
|
|
children: [
|
|
Icon(section.icon, size: 14, color: section.color),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
section.title,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── 设置项卡片 ──
|
|
|
|
Widget _buildSettingCard(AppThemeExtension ext, SettingItem item) {
|
|
return GlassContainer(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm + 2,
|
|
),
|
|
child: _buildItemContent(ext, item),
|
|
);
|
|
}
|
|
|
|
Widget _buildPlaceholderContent(
|
|
AppThemeExtension ext,
|
|
SettingItem item,
|
|
FeatureFlag? flag,
|
|
) {
|
|
final iconWidget = Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: CupertinoColors.systemGrey.withValues(alpha: 0.08),
|
|
borderRadius: AppRadius.xsBorder,
|
|
),
|
|
child: Center(
|
|
child: Icon(
|
|
CupertinoIcons.lock_fill,
|
|
size: 15,
|
|
color: CupertinoColors.systemGrey.resolveFrom(context),
|
|
),
|
|
),
|
|
);
|
|
|
|
final titleColumn = Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
item.title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: CupertinoColors.secondaryLabel.resolveFrom(context),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Text(
|
|
item.subtitle,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: CupertinoColors.tertiaryLabel.resolveFrom(context),
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
Widget trailing;
|
|
switch (item.type) {
|
|
case SettingType.toggle:
|
|
trailing = const CupertinoSwitch(
|
|
value: false,
|
|
activeTrackColor: CupertinoColors.systemGrey,
|
|
onChanged: null,
|
|
);
|
|
case SettingType.selection:
|
|
case SettingType.navigation:
|
|
trailing = Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'开发中',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: CupertinoColors.tertiaryLabel.resolveFrom(context),
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: CupertinoColors.tertiaryLabel.resolveFrom(context),
|
|
),
|
|
],
|
|
);
|
|
case SettingType.action:
|
|
trailing = Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: CupertinoColors.tertiaryLabel.resolveFrom(context),
|
|
);
|
|
}
|
|
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
HapticService.selection();
|
|
final message = flag?.unsupportedMessage ?? '该功能当前设备不支持';
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (_) => CupertinoAlertDialog(
|
|
title: Text(item.title),
|
|
content: Text(message),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
child: const Text('知道了'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
child: Opacity(
|
|
opacity: 0.6,
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(children: [iconWidget, titleColumn, trailing]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── 设置项内容(根据类型渲染不同控件) ──
|
|
|
|
Widget _buildItemContent(AppThemeExtension ext, SettingItem item) {
|
|
final flag = FeatureFlagService.fromSettingId(item.id);
|
|
if (item.isPlaceholder || FeatureFlagService.isPlaceholder(flag)) {
|
|
return _buildPlaceholderContent(ext, item, flag);
|
|
}
|
|
|
|
final iconWidget = Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: item.iconColor.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.xsBorder,
|
|
),
|
|
child: Center(child: Icon(item.icon, size: 17, color: item.iconColor)),
|
|
);
|
|
|
|
final titleColumn = Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
item.title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: item.isDestructive ? ext.accent : ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Text(
|
|
item.subtitle,
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
switch (item.type) {
|
|
case SettingType.toggle:
|
|
if (item.isLocked) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () {
|
|
HapticService.selection();
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (_) => CupertinoAlertDialog(
|
|
content: const Text('该功能暂不可用'),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
child: const Text('知道了'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
child: Opacity(
|
|
opacity: 0.5,
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(
|
|
children: [
|
|
iconWidget,
|
|
titleColumn,
|
|
const CupertinoSwitch(
|
|
value: false,
|
|
activeTrackColor: CupertinoColors.systemGrey,
|
|
onChanged: null,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return Row(
|
|
children: [
|
|
iconWidget,
|
|
titleColumn,
|
|
CupertinoSwitch(
|
|
value: item.value ?? false,
|
|
activeTrackColor: ext.accent,
|
|
onChanged: (v) => _onToggle(item.id, v),
|
|
),
|
|
],
|
|
);
|
|
case SettingType.selection:
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () => _onSelection(item.id),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(
|
|
children: [
|
|
iconWidget,
|
|
titleColumn,
|
|
Text(
|
|
item.displayValue ?? '',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
case SettingType.navigation:
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () => _onNavigate(item.id),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(
|
|
children: [
|
|
iconWidget,
|
|
titleColumn,
|
|
Text(
|
|
item.displayValue ?? '',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
case SettingType.action:
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () => _onAction(item.id),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(
|
|
children: [
|
|
iconWidget,
|
|
titleColumn,
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── 推荐设置区域 ──
|
|
|
|
Widget _buildSuggestions(AppThemeExtension ext) {
|
|
final t = ref.watch(translationsProvider);
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: AppSpacing.xs),
|
|
child: Row(
|
|
children: [
|
|
const Icon(
|
|
CupertinoIcons.lightbulb_fill,
|
|
size: 14,
|
|
color: AppColors.iosYellow,
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
t.youMayBeLookingFor,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
children: [
|
|
_suggestionRow(
|
|
ext,
|
|
CupertinoIcons.paintbrush_fill,
|
|
AppColors.iosPurple,
|
|
t.themeCustomization,
|
|
'/settings/theme',
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
_suggestionRow(
|
|
ext,
|
|
CupertinoIcons.textbox,
|
|
AppColors.iosLightPurple,
|
|
t.fontManagement,
|
|
'/settings/fonts',
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
_suggestionRow(
|
|
ext,
|
|
CupertinoIcons.folder_fill,
|
|
AppColors.iosOrange,
|
|
t.profile.dataManagement,
|
|
'/settings/data',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ── 推荐项行 ──
|
|
|
|
Widget _suggestionRow(
|
|
AppThemeExtension ext,
|
|
IconData icon,
|
|
Color color,
|
|
String title,
|
|
String route,
|
|
) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () => context.appPush(route),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(minHeight: 44),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.xsBorder,
|
|
),
|
|
child: Center(child: Icon(icon, size: 15, color: color)),
|
|
),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: AppTypography.subhead.copyWith(color: ext.textPrimary),
|
|
),
|
|
),
|
|
Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── 页脚(版本号 + 版权) ──
|
|
|
|
Widget _buildFooter(AppThemeExtension ext) {
|
|
final t = ref.watch(translationsProvider);
|
|
return Column(
|
|
children: [
|
|
Text(
|
|
'闲言 $_versionText',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
t.copyright,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ── 开关切换事件 ──
|
|
|
|
void _onToggle(String id, bool value) {
|
|
HapticService.toggleSwitch();
|
|
final notifier = ref.read(generalSettingsProvider.notifier);
|
|
final lockedIds = {'predictive_back', 'long_press_preview'};
|
|
if (lockedIds.contains(id)) {
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (_) => CupertinoAlertDialog(
|
|
content: const Text('该功能暂不可用'),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
child: const Text('知道了'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
switch (id) {
|
|
case 'sound':
|
|
notifier.setSoundEnabled(value);
|
|
case 'immersive_status':
|
|
notifier.setImmersiveStatusBar(value);
|
|
case 'reduce_animations':
|
|
notifier.setReduceAnimations(value);
|
|
case 'preload':
|
|
final config = ref.read(offlineProvider).config;
|
|
ref
|
|
.read(offlineProvider.notifier)
|
|
.updateConfig(config.copyWith(preloadOnWifi: value));
|
|
case 'data_saver':
|
|
notifier.setDataSaverMode(value);
|
|
case 'clipboard_read':
|
|
notifier.setClipboardReadEnabled(value);
|
|
case 'app_lock':
|
|
context.appPush(AppRoutes.appLockSettings);
|
|
case 'sfx':
|
|
notifier.setSfxEnabled(value);
|
|
case 'shake_to_switch':
|
|
notifier.setShakeToSwitch(value);
|
|
case 'daily_notification':
|
|
notifier.setDailyNotification(value);
|
|
case 'shader_background':
|
|
notifier.setShaderBackground(value);
|
|
case 'nearby_discovery':
|
|
notifier.setNearbyDiscovery(value);
|
|
case 'split_view_enabled':
|
|
ref.read(splitViewProvider.notifier).setSplitViewEnabled(value);
|
|
}
|
|
}
|
|
|
|
// ── 选择器事件 ──
|
|
|
|
void _onSelection(String id) {
|
|
HapticService.selection();
|
|
_searchFocusNode.unfocus();
|
|
switch (id) {
|
|
case 'vibration':
|
|
showVibrationPicker();
|
|
case 'sound_effect':
|
|
showSoundEffectPicker();
|
|
case 'screen_timeout':
|
|
showScreenTimeoutPicker();
|
|
case 'language':
|
|
context.appPush(AppRoutes.languageSettings);
|
|
case 'startup_page':
|
|
showStartupPagePicker();
|
|
case 'page_transition_mode':
|
|
showPageTransitionModePicker();
|
|
case 'content_density':
|
|
showContentDensityPicker();
|
|
case 'cache_strategy':
|
|
showCacheStrategyPicker();
|
|
case 'image_quality':
|
|
showImageQualityPicker();
|
|
case 'sfx_style':
|
|
showSfxStylePicker();
|
|
case 'screen_always_on':
|
|
showScreenAlwaysOnPicker();
|
|
case 'notify_time':
|
|
showNotifyTimePicker();
|
|
case 'nav_bar_position':
|
|
showNavBarPositionPicker();
|
|
case 'split_view_ratio':
|
|
showSplitRatioPicker();
|
|
}
|
|
}
|
|
|
|
// ── 导航事件 ──
|
|
|
|
void _onNavigate(String id) {
|
|
HapticService.selection();
|
|
_searchFocusNode.unfocus();
|
|
switch (id) {
|
|
case 'plugin':
|
|
context.appPush(AppRoutes.plugin);
|
|
case 'notification':
|
|
context.appPush(AppRoutes.notificationSettings);
|
|
case 'smart_mode':
|
|
context.appPush(AppRoutes.smartModeSettings);
|
|
case 'other_settings':
|
|
context.appPush(AppRoutes.otherSettings);
|
|
case 'permission':
|
|
context.appPush(AppRoutes.permissionManagement);
|
|
case 'font_scale':
|
|
context.appPush(AppRoutes.fontManagement);
|
|
case 'app_lock':
|
|
context.appPush(AppRoutes.appLockSettings);
|
|
case 'log_level':
|
|
Navigator.of(context).push<void>(
|
|
CupertinoPageRoute<void>(
|
|
builder: (_) => const LogLevelSettingsPage(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── 操作事件 ──
|
|
|
|
void _onAction(String id) {
|
|
HapticService.impact();
|
|
_searchFocusNode.unfocus();
|
|
switch (id) {
|
|
case 'reopen_onboarding':
|
|
// 需要先重置引导页状态,否则路由重定向会阻止跳转
|
|
_reopenOnboarding();
|
|
}
|
|
}
|
|
|
|
/// 重新打开引导页:先重置存储状态,再导航
|
|
Future<void> _reopenOnboarding() async {
|
|
await KvStorage.setBool(StorageKeys.onboardingCompleted, false);
|
|
await KvStorage.setShowOnboarding(true);
|
|
if (mounted) {
|
|
context.appGo(AppRoutes.onboarding);
|
|
}
|
|
}
|
|
}
|