自定义图标

This commit is contained in:
DelLevin-Home
2026-05-20 00:38:30 +08:00
parent 320fd9f692
commit 9560e37a44
19 changed files with 948 additions and 361 deletions

View File

@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import '../../utils/user_prefs.dart';
import '../../utils/toast_util.dart';
import '../../utils/app_icon_channel.dart';
/// 应用图标选择页面
class AppIconPickerPage extends StatefulWidget {
const AppIconPickerPage({super.key});
@override
State<AppIconPickerPage> createState() => _AppIconPickerPageState();
}
class _AppIconPickerPageState extends State<AppIconPickerPage> {
final UserPrefs _userPrefs = UserPrefs();
String _currentIconName = 'app_icon';
// 预定义的图标列表
final List<Map<String, String>> _icons = [
{'name': 'app_icon', 'label': '默认图标'},
{'name': 'app_icon2', 'label': '风格二'},
];
@override
void initState() {
super.initState();
_loadCurrentIcon();
}
Future<void> _loadCurrentIcon() async {
// 先从原生层获取当前实际启用的图标(更准确)
final nativeIcon = await AppIconChannel.getCurrentIcon();
setState(() {
_currentIconName = nativeIcon;
});
}
Future<void> _selectIcon(String iconName) async {
if (iconName == _currentIconName) return;
try {
// 调用原生层切换桌面图标
final success = await AppIconChannel.switchIcon(iconName);
if (success) {
await _userPrefs.setAppIconName(iconName);
setState(() {
_currentIconName = iconName;
});
if (mounted) {
ToastUtil.show(context, '图标已切换,请返回桌面查看');
}
} else {
if (mounted) {
ToastUtil.show(context, '图标切换失败');
}
}
} catch (e) {
if (mounted) {
ToastUtil.show(context, '切换出错: $e');
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('应用图标'),
),
body: GridView.builder(
padding: const EdgeInsets.all(24),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.85,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: _icons.length,
itemBuilder: (context, index) {
final icon = _icons[index];
final isSelected = _currentIconName == icon['name'];
return GestureDetector(
onTap: () => _selectIcon(icon['name']!),
child: Container(
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFF0F0F0) : const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFE8E8E8),
width: isSelected ? 2 : 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标预览
Image.asset(
'assets/icon/${icon['name']}.png',
width: 64,
height: 64,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: const Color(0xFFEEEEEE),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.image_not_supported, color: Color(0xFF999999)),
);
},
),
const SizedBox(height: 12),
// 标签
Text(
icon['label']!,
style: TextStyle(
fontSize: 14,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: const Color(0xFF1A1A1A),
),
),
if (isSelected) ...[
const SizedBox(height: 4),
const Icon(Icons.check_circle, size: 18, color: Color(0xFF1A1A1A)),
],
],
),
),
);
},
),
);
}
}

View File

@@ -14,6 +14,7 @@ import 'recycle_bin_page.dart';
import 'sync/backup_page.dart';
import 'statistics_page.dart';
import 'sync/cloud_sync_page.dart';
import 'app_icon_picker_page.dart';
/// 个人中心页面 - 极简主义设计
class ProfilePage extends StatefulWidget {
@@ -695,7 +696,21 @@ class SettingsPage extends StatelessWidget {
const Divider(height: 0.5, indent: 24, endIndent: 24),
// 主界面功能显示入口
_buildSectionHeader('主界面显示'),
_buildSectionHeader('个性化设置'),
_buildNavigationItem(
icon: Icons.apps_outlined,
title: '应用图标',
subtitle: '更换桌面应用图标',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AppIconPickerPage(),
),
);
},
),
const Divider(height: 0.5, indent: 24, endIndent: 24),
_buildNavigationItem(
icon: Icons.view_list_outlined,
title: '主界面功能显示',

View File

@@ -21,6 +21,8 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
bool _isLoading = false;
bool _isConfigured = false;
bool _obscurePassword = true;
bool _isAutoSyncEnabled = false;
int _autoSyncInterval = 5; // 默认5分钟
SyncDirection _syncDirection = SyncDirection.upload;
@override
@@ -50,6 +52,14 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
_isConfigured = true;
});
}
// 加载自动同步设置
final autoSyncEnabled = await WebDAVService.instance.isAutoSyncEnabled();
final autoSyncInterval = await WebDAVService.instance.getAutoSyncInterval();
setState(() {
_isAutoSyncEnabled = autoSyncEnabled;
_autoSyncInterval = autoSyncInterval;
});
}
/// 保存配置
@@ -112,6 +122,13 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
}
}
/// 安全显示 Toast
void _safeShowToast(String message) {
if (mounted) {
ToastUtil.show(context, message);
}
}
/// 执行同步
Future<void> _syncData() async {
setState(() => _isLoading = true);
@@ -176,6 +193,94 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
);
}
/// 切换自动同步
Future<void> _toggleAutoSync(bool value) async {
setState(() => _isLoading = true);
try {
if (value) {
await WebDAVService.instance.startAutoSync();
if (mounted) {
ToastUtil.show(context, '自动同步已开启,每 $_autoSyncInterval 分钟同步一次');
}
} else {
await WebDAVService.instance.stopAutoSync();
if (mounted) {
ToastUtil.show(context, '自动同步已关闭');
}
}
setState(() => _isAutoSyncEnabled = value);
} catch (e) {
if (mounted) {
ToastUtil.show(context, '设置失败: $e');
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
/// 显示间隔选择器
Future<void> _showIntervalPicker() async {
final intervals = [1, 3, 5, 10, 15, 30, 60];
final selected = await showDialog<int>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
elevation: 0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
title: const Text('选择同步间隔'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: intervals.length,
itemBuilder: (context, index) {
final interval = intervals[index];
return ListTile(
title: Text('$interval 分钟'),
trailing: _autoSyncInterval == interval
? const Icon(Icons.check, color: Color(0xFF1A1A1A))
: null,
onTap: () => Navigator.pop(context, interval),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消', style: TextStyle(color: Color(0xFF666666))),
),
],
),
);
if (selected != null && selected != _autoSyncInterval) {
setState(() => _isLoading = true);
try {
await WebDAVService.instance.setAutoSyncInterval(selected);
setState(() => _autoSyncInterval = selected);
if (mounted) {
ToastUtil.show(context, '同步间隔已设置为 $selected 分钟');
}
} catch (e) {
if (mounted) {
ToastUtil.show(context, '设置失败: $e');
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
}
/// 清除配置
Future<void> _clearConfig() async {
final confirmed = await showDialog<bool>(
@@ -374,8 +479,109 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
if (_isConfigured) ...[
const SizedBox(height: 32),
// 自动同步设置
_buildSectionTitle('自动同步'),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'启用自动同步',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(height: 4),
Text(
'修改后自动上传,每 $_autoSyncInterval 分钟同步一次',
style: const TextStyle(
fontSize: 12,
color: Color(0xFF666666),
),
),
],
),
),
Switch(
value: _isAutoSyncEnabled,
onChanged: _isLoading ? null : _toggleAutoSync,
activeTrackColor: const Color(0xFF1A1A1A),
),
],
),
if (_isAutoSyncEnabled) ...[
const SizedBox(height: 16),
const Divider(height: 1, color: Color(0xFFE8E8E8)),
const SizedBox(height: 16),
Row(
children: [
const Text(
'同步间隔',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(width: 16),
Expanded(
child: GestureDetector(
onTap: _isLoading ? null : _showIntervalPicker,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE8E8E8)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'$_autoSyncInterval 分钟',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
const Icon(
Icons.arrow_forward_ios,
size: 14,
color: Color(0xFF999999),
),
],
),
),
),
),
],
),
],
],
),
),
const SizedBox(height: 32),
// 同步操作区域
_buildSectionTitle('数据同步'),
_buildSectionTitle('手动同步'),
const SizedBox(height: 16),
Container(
@@ -550,9 +756,9 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
const SizedBox(height: 10),
_buildInfoItem('服务器地址需包含协议http:// 或 https://'),
const SizedBox(height: 10),
_buildInfoItem('同步前请确保服务器可用且空间充足'),
_buildInfoItem('数据文件和图片分开存储,支持多设备同步'),
const SizedBox(height: 10),
_buildInfoItem('首次同步将上传所有数据,后续只同步变更'),
_buildInfoItem('开启自动同步后,修改将自动上传云端'),
],
),
);