586 lines
18 KiB
Dart
586 lines
18 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../../../constants/app_constants.dart';
|
||
|
||
/// 时间: 2026-03-27
|
||
/// 功能: 主题个性化设置页面
|
||
/// 介绍: 允许用户自定义应用主题、颜色、字体大小等
|
||
class AppDiyPage extends StatefulWidget {
|
||
const AppDiyPage({super.key});
|
||
|
||
@override
|
||
State<AppDiyPage> createState() => _AppDiyPageState();
|
||
}
|
||
|
||
class _AppDiyPageState extends State<AppDiyPage> {
|
||
// 主题设置
|
||
bool _isDarkMode = false;
|
||
int _themeColorIndex = 0;
|
||
int _fontSizeIndex = 1; // 0: 小, 1: 中, 2: 大
|
||
bool _showGuideOnStartup = true;
|
||
int _cardSizeIndex = 1; // 0: 小, 1: 中, 2: 大
|
||
bool _enableAnimation = true;
|
||
bool _enableBlurEffect = true;
|
||
bool _enableSystemNavigation = false;
|
||
int _accentColorIndex = 0;
|
||
|
||
// 滚动控制
|
||
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();
|
||
_loadSettings();
|
||
_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();
|
||
}
|
||
}
|
||
|
||
void _loadSettings() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
setState(() {
|
||
_isDarkMode = prefs.getBool('darkMode') ?? false;
|
||
_themeColorIndex = prefs.getInt('themeColorIndex') ?? 0;
|
||
_fontSizeIndex = prefs.getInt('fontSizeIndex') ?? 1;
|
||
_showGuideOnStartup = prefs.getBool('showGuideOnStartup') ?? true;
|
||
_cardSizeIndex = prefs.getInt('cardSizeIndex') ?? 1;
|
||
_enableAnimation = prefs.getBool('enableAnimation') ?? true;
|
||
_enableBlurEffect = prefs.getBool('enableBlurEffect') ?? true;
|
||
_enableSystemNavigation =
|
||
prefs.getBool('enableSystemNavigation') ?? false;
|
||
_accentColorIndex = prefs.getInt('accentColorIndex') ?? 0;
|
||
});
|
||
}
|
||
|
||
void _saveSettings() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
prefs.setBool('darkMode', _isDarkMode);
|
||
prefs.setInt('themeColorIndex', _themeColorIndex);
|
||
prefs.setInt('fontSizeIndex', _fontSizeIndex);
|
||
prefs.setBool('showGuideOnStartup', _showGuideOnStartup);
|
||
prefs.setInt('cardSizeIndex', _cardSizeIndex);
|
||
prefs.setBool('enableAnimation', _enableAnimation);
|
||
prefs.setBool('enableBlurEffect', _enableBlurEffect);
|
||
prefs.setBool('enableSystemNavigation', _enableSystemNavigation);
|
||
prefs.setInt('accentColorIndex', _accentColorIndex);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('个性化'),
|
||
backgroundColor: _isDarkMode ? Colors.grey[900] : Colors.white,
|
||
foregroundColor: _isDarkMode ? Colors.white : AppConstants.primaryColor,
|
||
elevation: 0,
|
||
),
|
||
backgroundColor: _isDarkMode ? Colors.grey[900] : Colors.grey[50],
|
||
body: ListView(
|
||
padding: const EdgeInsets.all(16),
|
||
children: [
|
||
// 主题模式
|
||
_buildSection('显示设置'),
|
||
_buildSwitchItem('深色模式', _isDarkMode, (value) {
|
||
setState(() {
|
||
_isDarkMode = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.dark_mode),
|
||
|
||
// 主题颜色
|
||
_buildSection('主题颜色'),
|
||
_buildColorSelector('色彩', _themeColors, _themeColorIndex, (index) {
|
||
setState(() {
|
||
_themeColorIndex = index;
|
||
_saveSettings();
|
||
});
|
||
}),
|
||
|
||
_buildColorSelector('强调色', _accentColors, _accentColorIndex, (index) {
|
||
setState(() {
|
||
_accentColorIndex = index;
|
||
_saveSettings();
|
||
});
|
||
}),
|
||
|
||
// 字体大小
|
||
_buildSection('字体设置'),
|
||
_buildOptionSelector('字体大小', _fontSizes, _fontSizeIndex, (index) {
|
||
if (index != null) {
|
||
setState(() {
|
||
_fontSizeIndex = index;
|
||
_saveSettings();
|
||
});
|
||
}
|
||
}, icon: Icons.font_download),
|
||
|
||
// 卡片大小
|
||
_buildSection('界面设置'),
|
||
_buildOptionSelector('卡片阴影', _cardSizes, _cardSizeIndex, (index) {
|
||
if (index != null) {
|
||
setState(() {
|
||
_cardSizeIndex = index;
|
||
_saveSettings();
|
||
});
|
||
}
|
||
}, icon: Icons.crop_square),
|
||
|
||
// 启动显示
|
||
_buildSwitchItem('启动时显示引导页', _showGuideOnStartup, (value) {
|
||
setState(() {
|
||
_showGuideOnStartup = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.info_outline),
|
||
|
||
// 动画效果
|
||
_buildSwitchItem('启用动画效果', _enableAnimation, (value) {
|
||
setState(() {
|
||
_enableAnimation = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.animation),
|
||
|
||
// 模糊效果
|
||
_buildSwitchItem('启用模糊效果', _enableBlurEffect, (value) {
|
||
setState(() {
|
||
_enableBlurEffect = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.blur_on),
|
||
|
||
// 系统导航
|
||
_buildSwitchItem('软件悬浮球', _enableSystemNavigation, (value) {
|
||
setState(() {
|
||
_enableSystemNavigation = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.navigation),
|
||
|
||
_buildSwitchItem('转场动画', _enableAnimation, (value) {
|
||
setState(() {
|
||
// _enableAnimation = value;
|
||
_saveSettings();
|
||
});
|
||
}, icon: Icons.track_changes),
|
||
|
||
// 设计风格
|
||
_buildSection('设计风格'),
|
||
_buildDesignStyleCard(),
|
||
|
||
const SizedBox(height: 40),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSection(String title) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: _isDarkMode ? Colors.grey[300] : Colors.grey[700],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSwitchItem(
|
||
String title,
|
||
bool value,
|
||
ValueChanged<bool> onChanged, {
|
||
required IconData icon,
|
||
}) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: _isDarkMode ? 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[_themeColorIndex]),
|
||
title: Text(
|
||
title,
|
||
style: TextStyle(color: _isDarkMode ? Colors.white : Colors.black87),
|
||
),
|
||
trailing: Switch(
|
||
value: value,
|
||
onChanged: onChanged,
|
||
activeThumbColor: _themeColors[_themeColorIndex],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildOptionSelector(
|
||
String title,
|
||
List<String> options,
|
||
int selectedIndex,
|
||
ValueChanged<int?> onChanged, {
|
||
required IconData icon,
|
||
}) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: _isDarkMode ? 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[_themeColorIndex]),
|
||
title: Text(
|
||
title,
|
||
style: TextStyle(color: _isDarkMode ? 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: _isDarkMode ? Colors.white : Colors.black87,
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
dropdownColor: _isDarkMode ? Colors.grey[800] : Colors.white,
|
||
style: TextStyle(color: _isDarkMode ? Colors.white : Colors.black87),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildColorSelector(
|
||
String title,
|
||
List<Color> colors,
|
||
int selectedIndex,
|
||
ValueChanged<int> onChanged,
|
||
) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: _isDarkMode ? 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: _isDarkMode ? 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: _isDarkMode ? Colors.white : Colors.black,
|
||
width: 2,
|
||
)
|
||
: null,
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildDesignStyleCard() {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: _isDarkMode ? 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: _isDarkMode ? Colors.teal[300] : Colors.teal[700],
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Text(
|
||
'设计样式&主题风格',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: _isDarkMode ? Colors.white : Colors.black87,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: _toggleScroll,
|
||
child: Container(
|
||
height: 40,
|
||
child: SingleChildScrollView(
|
||
controller: _scrollController,
|
||
scrollDirection: Axis.horizontal,
|
||
child: Row(
|
||
children: [
|
||
_buildStyleChip(
|
||
'Material 2',
|
||
Icons.style,
|
||
isMd: true,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip(
|
||
'Material 3',
|
||
Icons.auto_awesome,
|
||
isMd: true,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip('透明毛玻璃', Icons.blur_on),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip('沉浸式渐变色', Icons.color_lens),
|
||
const SizedBox(width: 8),
|
||
_buildStyleChip('动态光感效果', Icons.lightbulb_outline),
|
||
const SizedBox(width: 8),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
_themeColors[_themeColorIndex].withAlpha(10),
|
||
_themeColors[_themeColorIndex].withAlpha(5),
|
||
],
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
Icons.lightbulb_outline,
|
||
size: 16,
|
||
color: _themeColors[_themeColorIndex],
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'采用现代Material Design设计语言,\n参考透明毛玻璃、沉浸式渐变色和动态光感等效果',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: _isDarkMode
|
||
? Colors.grey[300]
|
||
: Colors.grey[700],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStyleChip(String label, IconData icon, {bool isMd = false}) {
|
||
final color = isMd
|
||
? _themeColors[_themeColorIndex]
|
||
: _isDarkMode
|
||
? 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)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|