Files
wushu/lib/views/profile/theme/app-diy.dart
Developer cba04235c8 release
2026-04-03 03:26:06 +08:00

816 lines
26 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constants/app_constants.dart';
import '../../../models/colors/theme_colors.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;
bool _showDevNotice = true; // 是否显示开发中提示对话框
// 滚动控制
final ScrollController _scrollController = ScrollController();
bool _isScrolling = true;
late Timer _scrollTimer;
// 主题颜色选项
final List<Color> _themeColors = ThemeColors.themeColors;
// 强调色选项
final List<Color> _accentColors = ThemeColors.accentColors;
// 字体大小选项
final List<String> _fontSizes = ['', '', ''];
// 卡片大小选项
final List<String> _cardSizes = ['', '', ''];
@override
void initState() {
super.initState();
// 获取 ThemeController
_themeController = Get.find<ThemeController>();
_loadLocalSettings();
_startScrolling();
// 延迟显示开发中提示对话框
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_showDevNotice) {
_showDevNoticeDialog();
}
});
}
// 显示开发中提示对话框
void _showDevNoticeDialog() {
bool doNotShowAgain = false;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
final primaryColor = _themeController.currentThemeColor;
final isDark = _themeController.isDarkMode;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: Row(
children: [
Icon(Icons.construction, color: primaryColor),
const SizedBox(width: 8),
Text(
'开发中',
style: TextStyle(
color: isDark ? Colors.white : Colors.black,
),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'个性化设置开发中',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: isDark ? Colors.white : Colors.black,
),
),
const SizedBox(height: 12),
Text(
'• ✅ 深色模式(已支持)',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.black87,
),
),
Text(
'• ✅ 主题色彩(已支持)',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.black87,
),
),
Text(
'• ⏳ 其他设置仅当前页面生效',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.black87,
),
),
Text(
'• ⏳ 后续版本将陆续支持',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.black87,
),
),
const SizedBox(height: 12),
Text(
'感谢您的耐心等待!',
style: TextStyle(
color: isDark ? Colors.grey[400] : Colors.grey[600],
fontSize: 12,
),
),
],
),
actions: [
Row(
children: [
Checkbox(
value: doNotShowAgain,
onChanged: (value) {
setState(() {
doNotShowAgain = value ?? false;
});
},
activeColor: primaryColor,
),
Text(
'不再提醒',
style: TextStyle(
color: isDark ? Colors.grey[300] : Colors.black87,
fontSize: 14,
),
),
const Spacer(),
ElevatedButton(
onPressed: () async {
if (doNotShowAgain) {
await _themeController.prefs?.setBool(
'showDevNotice',
false,
);
setState(() {
_showDevNotice = false;
});
}
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
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;
_showDevNotice = _themeController.prefs?.getBool('showDevNotice') ?? true;
});
}
/// 保存本地设置
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: Text(
'个性化',
style: TextStyle(color: _themeController.currentThemeColor),
),
backgroundColor: isDark ? Colors.grey[900] : Colors.white,
elevation: 0,
actions: [
IconButton(
icon: Icon(
Icons.info_outline,
color: _themeController.currentThemeColor,
),
onPressed: () => _showDevNoticeDialog(),
tooltip: '显示开发中提示',
),
],
),
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: _themeController.currentThemeColor),
title: Text(
title,
style: TextStyle(color: isDark ? Colors.white : Colors.black87),
),
trailing: Switch(
value: value,
onChanged: onChanged,
activeThumbColor: _themeController.currentThemeColor,
),
),
);
}
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: _themeController.currentThemeColor),
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),
),
),
);
}
// 颜色名称映射
final Map<Color, String> _colorNames = {
AppConstants.primaryColor: '紫韵',
Colors.blue: '天蓝',
Colors.green: '翠绿',
Colors.orange: '橙光',
Colors.red: '朱红',
Colors.teal: '青碧',
const Color(0xFF8B4513): '书褐',
Colors.yellow: '明黄',
Colors.pink: '桃粉',
Colors.cyan: '湖青',
Colors.purple: '罗兰',
const Color(0xFFF5F5F0): '宣纸',
};
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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
child: Text(
title,
style: TextStyle(
color: isDark ? Colors.white : Colors.black87,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(
height: 80,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 12),
itemCount: colors.length,
itemBuilder: (context, index) {
final color = colors[index];
final colorName = _colorNames[color] ?? '色彩${index + 1}';
final isSelected = index == selectedIndex;
return GestureDetector(
onTap: () => onChanged(index),
child: Container(
width: 60,
margin: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 8,
),
child: Column(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(
color: isDark ? Colors.white : Colors.black,
width: 3,
)
: Border.all(
color: isDark
? Colors.grey[600]!
: Colors.grey[300]!,
width: 1,
),
boxShadow: isSelected
? [
BoxShadow(
color: color.withAlpha(128),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
child: isSelected
? Icon(
Icons.check,
color: color.computeLuminance() > 0.5
? Colors.black
: Colors.white,
size: 24,
)
: null,
),
const SizedBox(height: 4),
Text(
colorName,
style: TextStyle(
fontSize: 11,
color: isSelected
? (isDark ? Colors.white : Colors.black)
: (isDark
? Colors.grey[400]
: Colors.grey[600]),
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
),
],
),
);
}
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,
),
const SizedBox(width: 8),
_buildStyleChip(
'Material 3',
Icons.auto_awesome,
isMd: true,
isDark: isDark,
),
const SizedBox(width: 8),
_buildStyleChip(
'透明毛玻璃',
Icons.blur_on,
isDark: isDark,
),
const SizedBox(width: 8),
_buildStyleChip(
'沉浸式渐变色',
Icons.color_lens,
isDark: isDark,
),
const SizedBox(width: 8),
_buildStyleChip(
'动态光感效果',
Icons.lightbulb_outline,
isDark: isDark,
),
const SizedBox(width: 8),
],
),
),
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
_themeController.currentThemeColor.withAlpha(10),
_themeController.currentThemeColor.withAlpha(5),
],
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
Icons.lightbulb_outline,
size: 16,
color: _themeController.currentThemeColor,
),
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,
}) {
final color = isMd
? _themeController.currentThemeColor
: 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)),
],
),
);
}
}