652 lines
20 KiB
Dart
652 lines
20 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import '../../../constants/app_constants.dart';
|
||
import '../../../services/get/theme_controller.dart';
|
||
|
||
/// 时间: 2026-03-27
|
||
/// 功能: 主题个性化设置页面
|
||
/// 介绍: 允许用户自定义应用主题、颜色、字体大小等
|
||
class AppDiyPage extends StatefulWidget {
|
||
const AppDiyPage({super.key});
|
||
|
||
@override
|
||
State<AppDiyPage> createState() => _AppDiyPageState();
|
||
}
|
||
|
||
class _AppDiyPageState extends State<AppDiyPage> {
|
||
// GetX ThemeController
|
||
late ThemeController _themeController;
|
||
|
||
// 本地状态(不通过ThemeController管理的设置)
|
||
bool _showGuideOnStartup = true;
|
||
int _cardSizeIndex = 1; // 0: 小, 1: 中, 2: 大
|
||
bool _enableSystemNavigation = false;
|
||
|
||
// 滚动控制
|
||
final ScrollController _scrollController = ScrollController();
|
||
bool _isScrolling = true;
|
||
late Timer _scrollTimer;
|
||
|
||
// 主题颜色选项
|
||
final List<Color> _themeColors = [
|
||
AppConstants.primaryColor, // 默认紫色
|
||
Colors.blue, // 蓝色
|
||
Colors.green, // 绿色
|
||
Colors.orange, // 橙色
|
||
Colors.red, // 红色
|
||
Colors.teal, // 青色
|
||
];
|
||
|
||
// 强调色选项
|
||
final List<Color> _accentColors = [
|
||
AppConstants.primaryColor, // 默认紫色
|
||
Colors.yellow, // 黄色
|
||
Colors.pink, // 粉色
|
||
Colors.cyan, // 青色
|
||
Colors.purple, // 深紫色
|
||
];
|
||
|
||
// 字体大小选项
|
||
final List<String> _fontSizes = ['小', '中', '大'];
|
||
|
||
// 卡片大小选项
|
||
final List<String> _cardSizes = ['小', '中', '大'];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 获取 ThemeController
|
||
_themeController = Get.find<ThemeController>();
|
||
_loadLocalSettings();
|
||
_startScrolling();
|
||
// 延迟显示开发中提示对话框
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_showDevNoticeDialog();
|
||
});
|
||
}
|
||
|
||
// 显示开发中提示对话框
|
||
void _showDevNoticeDialog() {
|
||
showDialog(
|
||
context: context,
|
||
barrierDismissible: false,
|
||
builder: (BuildContext context) {
|
||
return AlertDialog(
|
||
title: Row(
|
||
children: [
|
||
Icon(Icons.construction, color: AppConstants.primaryColor),
|
||
const SizedBox(width: 8),
|
||
const Text('开发中'),
|
||
],
|
||
),
|
||
content: const Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'个性化设置开发中',
|
||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||
),
|
||
SizedBox(height: 12),
|
||
Text('• 当前设置仅当前页面生效'),
|
||
Text('• 后续版本将陆续支持'),
|
||
Text('• 可提前预览主题风格设置'),
|
||
SizedBox(height: 12),
|
||
Text(
|
||
'感谢您的耐心等待!',
|
||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: AppConstants.primaryColor,
|
||
foregroundColor: Colors.white,
|
||
),
|
||
child: const Text('我知道了'),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_scrollController.dispose();
|
||
_scrollTimer.cancel();
|
||
super.dispose();
|
||
}
|
||
|
||
void _startScrolling() {
|
||
_isScrolling = true;
|
||
_scrollTimer = Timer.periodic(const Duration(milliseconds: 30), (timer) {
|
||
if (_scrollController.hasClients) {
|
||
_scrollController.jumpTo(_scrollController.offset + 1);
|
||
// 当滚动到末尾时,重新开始滚动
|
||
if (_scrollController.offset >=
|
||
_scrollController.position.maxScrollExtent) {
|
||
_scrollController.jumpTo(0);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
void _stopScrolling() {
|
||
_isScrolling = false;
|
||
_scrollTimer.cancel();
|
||
}
|
||
|
||
void _toggleScroll() {
|
||
if (_isScrolling) {
|
||
_stopScrolling();
|
||
} else {
|
||
_startScrolling();
|
||
}
|
||
}
|
||
|
||
/// 加载本地设置(不通过ThemeController管理的设置)
|
||
void _loadLocalSettings() async {
|
||
// ThemeController 会自动加载主题相关设置
|
||
// 这里只加载本地独有的设置
|
||
setState(() {
|
||
_showGuideOnStartup =
|
||
_themeController.prefs?.getBool('showGuideOnStartup') ?? true;
|
||
_cardSizeIndex = _themeController.prefs?.getInt('cardSizeIndex') ?? 1;
|
||
_enableSystemNavigation =
|
||
_themeController.prefs?.getBool('enableSystemNavigation') ?? false;
|
||
});
|
||
}
|
||
|
||
/// 保存本地设置
|
||
void _saveLocalSettings() async {
|
||
await _themeController.prefs?.setBool(
|
||
'showGuideOnStartup',
|
||
_showGuideOnStartup,
|
||
);
|
||
await _themeController.prefs?.setInt('cardSizeIndex', _cardSizeIndex);
|
||
await _themeController.prefs?.setBool(
|
||
'enableSystemNavigation',
|
||
_enableSystemNavigation,
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
// 使用 Obx 监听 ThemeController 的状态变化
|
||
return Obx(() {
|
||
final isDark = _themeController.isDarkModeRx.value;
|
||
final themeColorIdx = _themeController.themeColorIndexRx.value;
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('个性化'),
|
||
backgroundColor: isDark ? Colors.grey[900] : Colors.white,
|
||
foregroundColor: isDark ? Colors.white : AppConstants.primaryColor,
|
||
elevation: 0,
|
||
),
|
||
backgroundColor: isDark ? Colors.grey[900] : Colors.grey[50],
|
||
body: ListView(
|
||
padding: const EdgeInsets.all(16),
|
||
children: [
|
||
// 主题模式
|
||
_buildSection('显示设置', isDark),
|
||
_buildSwitchItem(
|
||
'深色模式',
|
||
_themeController.isDarkModeRx.value,
|
||
(value) => _themeController.toggleDarkMode(value),
|
||
icon: Icons.dark_mode,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 主题颜色
|
||
_buildSection('主题颜色', isDark),
|
||
_buildColorSelector(
|
||
'色彩',
|
||
_themeColors,
|
||
_themeController.themeColorIndexRx.value,
|
||
(index) => _themeController.setThemeColorIndex(index),
|
||
isDark: isDark,
|
||
),
|
||
|
||
_buildColorSelector(
|
||
'强调色',
|
||
_accentColors,
|
||
_themeController.accentColorIndexRx.value,
|
||
(index) => _themeController.setAccentColorIndex(index),
|
||
isDark: isDark,
|
||
),
|
||
|
||
// 字体大小
|
||
_buildSection('字体设置', isDark),
|
||
_buildOptionSelector(
|
||
'字体大小',
|
||
_fontSizes,
|
||
_themeController.fontSizeIndexRx.value,
|
||
(index) {
|
||
if (index != null) {
|
||
_themeController.setFontSizeIndex(index);
|
||
}
|
||
},
|
||
icon: Icons.font_download,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 卡片大小
|
||
_buildSection('界面设置', isDark),
|
||
_buildOptionSelector(
|
||
'卡片阴影',
|
||
_cardSizes,
|
||
_cardSizeIndex,
|
||
(index) {
|
||
if (index != null) {
|
||
setState(() {
|
||
_cardSizeIndex = index;
|
||
});
|
||
_saveLocalSettings();
|
||
}
|
||
},
|
||
icon: Icons.crop_square,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 启动显示
|
||
_buildSwitchItem(
|
||
'启动时显示引导页',
|
||
_showGuideOnStartup,
|
||
(value) {
|
||
setState(() {
|
||
_showGuideOnStartup = value;
|
||
});
|
||
_saveLocalSettings();
|
||
},
|
||
icon: Icons.info_outline,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 动画效果
|
||
_buildSwitchItem(
|
||
'启用动画效果',
|
||
_themeController.enableAnimationRx.value,
|
||
(value) => _themeController.toggleAnimation(value),
|
||
icon: Icons.animation,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 模糊效果
|
||
_buildSwitchItem(
|
||
'启用模糊效果',
|
||
_themeController.enableBlurEffectRx.value,
|
||
(value) => _themeController.toggleBlurEffect(value),
|
||
icon: Icons.blur_on,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 系统导航
|
||
_buildSwitchItem(
|
||
'软件悬浮球',
|
||
_enableSystemNavigation,
|
||
(value) {
|
||
setState(() {
|
||
_enableSystemNavigation = value;
|
||
});
|
||
_saveLocalSettings();
|
||
},
|
||
icon: Icons.navigation,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
_buildSwitchItem(
|
||
'转场动画',
|
||
_themeController.enableAnimationRx.value,
|
||
(value) => _themeController.toggleAnimation(value),
|
||
icon: Icons.track_changes,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
|
||
// 设计风格
|
||
_buildSection('设计风格', isDark),
|
||
_buildDesignStyleCard(isDark, themeColorIdx),
|
||
|
||
const SizedBox(height: 40),
|
||
],
|
||
),
|
||
);
|
||
});
|
||
}
|
||
|
||
Widget _buildSection(String title, bool isDark) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: isDark ? Colors.grey[300] : Colors.grey[700],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSwitchItem(
|
||
String title,
|
||
bool value,
|
||
ValueChanged<bool> onChanged, {
|
||
required IconData icon,
|
||
required bool isDark,
|
||
required int themeColorIdx,
|
||
}) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? Colors.grey[800] : Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withAlpha(5),
|
||
blurRadius: 2,
|
||
offset: const Offset(0, 1),
|
||
),
|
||
],
|
||
),
|
||
child: ListTile(
|
||
leading: Icon(icon, color: _themeColors[themeColorIdx]),
|
||
title: Text(
|
||
title,
|
||
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
|
||
),
|
||
trailing: Switch(
|
||
value: value,
|
||
onChanged: onChanged,
|
||
activeThumbColor: _themeColors[themeColorIdx],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildOptionSelector(
|
||
String title,
|
||
List<String> options,
|
||
int selectedIndex,
|
||
ValueChanged<int?> onChanged, {
|
||
required IconData icon,
|
||
required bool isDark,
|
||
required int themeColorIdx,
|
||
}) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? Colors.grey[800] : Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withAlpha(5),
|
||
blurRadius: 2,
|
||
offset: const Offset(0, 1),
|
||
),
|
||
],
|
||
),
|
||
child: ListTile(
|
||
leading: Icon(icon, color: _themeColors[themeColorIdx]),
|
||
title: Text(
|
||
title,
|
||
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
|
||
),
|
||
trailing: DropdownButton<int>(
|
||
value: selectedIndex,
|
||
onChanged: (value) {
|
||
if (value != null) {
|
||
onChanged(value);
|
||
}
|
||
},
|
||
items: options.asMap().entries.map((entry) {
|
||
return DropdownMenuItem<int>(
|
||
value: entry.key,
|
||
child: Text(
|
||
entry.value,
|
||
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
|
||
),
|
||
);
|
||
}).toList(),
|
||
dropdownColor: isDark ? Colors.grey[800] : Colors.white,
|
||
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildColorSelector(
|
||
String title,
|
||
List<Color> colors,
|
||
int selectedIndex,
|
||
ValueChanged<int> onChanged, {
|
||
required bool isDark,
|
||
}) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? Colors.grey[800] : Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withAlpha(5),
|
||
blurRadius: 2,
|
||
offset: const Offset(0, 1),
|
||
),
|
||
],
|
||
),
|
||
child: ListTile(
|
||
title: Text(
|
||
title,
|
||
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
|
||
),
|
||
trailing: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: colors.asMap().entries.map((entry) {
|
||
return GestureDetector(
|
||
onTap: () => onChanged(entry.key),
|
||
child: Container(
|
||
width: 32,
|
||
height: 32,
|
||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||
decoration: BoxDecoration(
|
||
color: entry.value,
|
||
shape: BoxShape.circle,
|
||
border: entry.key == selectedIndex
|
||
? Border.all(
|
||
color: isDark ? Colors.white : Colors.black,
|
||
width: 2,
|
||
)
|
||
: null,
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildDesignStyleCard(bool isDark, int themeColorIdx) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? Colors.grey[800] : Colors.white,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withAlpha(5),
|
||
blurRadius: 2,
|
||
offset: const Offset(0, 1),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.teal.withAlpha(10),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Icon(
|
||
Icons.design_services,
|
||
color: isDark ? Colors.teal[300] : Colors.teal[700],
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Text(
|
||
'设计样式&主题风格',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: isDark ? Colors.white : Colors.black87,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: _toggleScroll,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child: SingleChildScrollView(
|
||
controller: _scrollController,
|
||
scrollDirection: Axis.horizontal,
|
||
child: Row(
|
||
children: [
|
||
_buildStyleChip(
|
||
'Material 2',
|
||
Icons.style,
|
||
isMd: true,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip(
|
||
'Material 3',
|
||
Icons.auto_awesome,
|
||
isMd: true,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip(
|
||
'透明毛玻璃',
|
||
Icons.blur_on,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip(
|
||
'沉浸式渐变色',
|
||
Icons.color_lens,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip(
|
||
'动态光感效果',
|
||
Icons.lightbulb_outline,
|
||
isDark: isDark,
|
||
themeColorIdx: themeColorIdx,
|
||
),
|
||
const SizedBox(width: 8),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
_themeColors[themeColorIdx].withAlpha(10),
|
||
_themeColors[themeColorIdx].withAlpha(5),
|
||
],
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
Icons.lightbulb_outline,
|
||
size: 16,
|
||
color: _themeColors[themeColorIdx],
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'采用现代Material Design设计语言,\n参考透明毛玻璃、沉浸式渐变色和动态光感等效果',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: isDark ? Colors.grey[300] : Colors.grey[700],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStyleChip(
|
||
String label,
|
||
IconData icon, {
|
||
bool isMd = false,
|
||
required bool isDark,
|
||
required int themeColorIdx,
|
||
}) {
|
||
final color = isMd
|
||
? _themeColors[themeColorIdx]
|
||
: isDark
|
||
? Colors.blue[400]!
|
||
: Colors.blue[600]!;
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: color.withAlpha(10),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: color.withAlpha(50), width: 1),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(icon, size: 14, color: color),
|
||
const SizedBox(width: 4),
|
||
Text(label, style: TextStyle(fontSize: 12, color: color)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|