马良AI写作初始化仓库
This commit is contained in:
853
AINoval/lib/screens/settings/widgets/editor_settings_panel.dart
Normal file
853
AINoval/lib/screens/settings/widgets/editor_settings_panel.dart
Normal file
@@ -0,0 +1,853 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ainoval/models/editor_settings.dart';
|
||||
// import 'package:ainoval/widgets/common/settings_widgets.dart';
|
||||
import 'package:ainoval/utils/web_theme.dart';
|
||||
|
||||
/// 编辑器设置面板 - 紧凑版
|
||||
/// 提供完整的编辑器配置选项,优化为一页显示
|
||||
class EditorSettingsPanel extends StatefulWidget {
|
||||
const EditorSettingsPanel({
|
||||
super.key,
|
||||
required this.settings,
|
||||
required this.onSettingsChanged,
|
||||
this.onSave,
|
||||
this.onReset,
|
||||
});
|
||||
|
||||
final EditorSettings settings;
|
||||
final ValueChanged<EditorSettings> onSettingsChanged;
|
||||
final VoidCallback? onSave;
|
||||
final VoidCallback? onReset;
|
||||
|
||||
@override
|
||||
State<EditorSettingsPanel> createState() => _EditorSettingsPanelState();
|
||||
}
|
||||
|
||||
class _EditorSettingsPanelState extends State<EditorSettingsPanel> {
|
||||
late EditorSettings _currentSettings;
|
||||
bool _hasUnsavedChanges = false;
|
||||
bool _isSaving = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentSettings = widget.settings;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EditorSettingsPanel oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
// 🚀 修复:只有当外部设置真正改变且不是用户操作导致的时,才重置状态
|
||||
if (oldWidget.settings != widget.settings) {
|
||||
// 如果当前设置与新的widget设置相同,说明设置已被外部保存
|
||||
if (_currentSettings == widget.settings) {
|
||||
setState(() {
|
||||
_hasUnsavedChanges = false;
|
||||
});
|
||||
} else {
|
||||
// 如果不同,更新基础设置但保持未保存状态
|
||||
setState(() {
|
||||
_currentSettings = widget.settings;
|
||||
_hasUnsavedChanges = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSettings(EditorSettings newSettings) {
|
||||
setState(() {
|
||||
_currentSettings = newSettings;
|
||||
// 🚀 修复保存按钮逻辑:先设置未保存状态,再调用回调
|
||||
_hasUnsavedChanges = true;
|
||||
});
|
||||
// 通知父组件设置已更改(用于实时预览),但不影响保存状态
|
||||
widget.onSettingsChanged(newSettings);
|
||||
}
|
||||
|
||||
Future<void> _handleSave() async {
|
||||
if (_isSaving) return; // 🚀 简化:只检查是否正在保存
|
||||
|
||||
setState(() {
|
||||
_isSaving = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// 🚀 实际调用保存回调
|
||||
widget.onSave?.call();
|
||||
|
||||
// 等待一小段时间确保保存操作完成
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
setState(() {
|
||||
_hasUnsavedChanges = false;
|
||||
});
|
||||
|
||||
// 显示保存成功提示
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('编辑器设置已保存'),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('保存失败: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isSaving = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleReset() {
|
||||
setState(() {
|
||||
_currentSettings = const EditorSettings();
|
||||
_hasUnsavedChanges = true;
|
||||
});
|
||||
widget.onSettingsChanged(_currentSettings);
|
||||
widget.onReset?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// 固定顶部:标题和操作按钮
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.getBackgroundColor(context),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: WebTheme.grey200, width: 1),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 标题行
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.edit_note, size: 24, color: WebTheme.getTextColor(context)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'编辑器设置',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: WebTheme.getTextColor(context),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// 保存状态指示
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: (_hasUnsavedChanges
|
||||
? WebTheme.getPrimaryColor(context)
|
||||
: WebTheme.getSecondaryTextColor(context))
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: (_hasUnsavedChanges
|
||||
? WebTheme.getPrimaryColor(context)
|
||||
: WebTheme.getSecondaryTextColor(context))
|
||||
.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_hasUnsavedChanges ? Icons.settings : Icons.check_circle,
|
||||
size: 12,
|
||||
color: _hasUnsavedChanges
|
||||
? WebTheme.getPrimaryColor(context)
|
||||
: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_hasUnsavedChanges ? '可保存' : '已保存',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: _hasUnsavedChanges
|
||||
? WebTheme.getPrimaryColor(context)
|
||||
: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 操作按钮行
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'自定义编辑器外观和行为',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// 重置按钮
|
||||
TextButton.icon(
|
||||
onPressed: _handleReset,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('重置'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: WebTheme.getSecondaryTextColor(context),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// 保存按钮 - 🚀 修改为一直可点击
|
||||
ElevatedButton.icon(
|
||||
onPressed: !_isSaving ? _handleSave : null,
|
||||
icon: _isSaving
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.save, size: 16),
|
||||
label: Text(_isSaving ? '保存中...' : '保存设置'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: WebTheme.getPrimaryColor(context),
|
||||
foregroundColor: WebTheme.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 可滚动的设置内容
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
// 紧凑的双列布局
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 左列
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCompactCard(
|
||||
title: '字体设置',
|
||||
icon: Icons.text_fields,
|
||||
children: [
|
||||
_buildCompactSlider(
|
||||
'字体大小',
|
||||
_currentSettings.fontSize,
|
||||
12, 32, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(fontSize: value)),
|
||||
),
|
||||
_buildCompactDropdown(
|
||||
'字体',
|
||||
_currentSettings.fontFamily,
|
||||
EditorSettings.availableFontFamilies,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(fontFamily: value)),
|
||||
itemBuilder: (font) {
|
||||
switch (font) {
|
||||
case 'Roboto': return 'Roboto(英文推荐)';
|
||||
case 'serif': return '衬线字体(中文推荐)';
|
||||
case 'sans-serif': return '无衬线字体(中文推荐)';
|
||||
case 'monospace': return '等宽字体';
|
||||
case 'Noto Sans SC': return 'Noto Sans SC(思源黑体)';
|
||||
case 'PingFang SC': return 'PingFang SC(苹方)';
|
||||
case 'Microsoft YaHei': return 'Microsoft YaHei(微软雅黑)';
|
||||
case 'SimHei': return 'SimHei(黑体)';
|
||||
case 'SimSun': return 'SimSun(宋体)';
|
||||
case 'Times New Roman': return 'Times New Roman(英文衬线)';
|
||||
case 'Arial': return 'Arial(英文无衬线)';
|
||||
default: return font;
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildCompactDropdown(
|
||||
'字体粗细',
|
||||
_currentSettings.fontWeight,
|
||||
EditorSettings.availableFontWeights,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(fontWeight: value)),
|
||||
itemBuilder: (weight) {
|
||||
switch (weight) {
|
||||
case FontWeight.w300: return '细体 (300)';
|
||||
case FontWeight.w400: return '正常 (400)';
|
||||
case FontWeight.w500: return '中等 (500)';
|
||||
case FontWeight.w600: return '半粗 (600)';
|
||||
case FontWeight.w700: return '粗体 (700)';
|
||||
default: return '正常 (400)';
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'行间距',
|
||||
_currentSettings.lineSpacing,
|
||||
1.0, 3.0, '倍',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(lineSpacing: value)),
|
||||
formatValue: (value) => '${value.toStringAsFixed(1)}x',
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'字符间距',
|
||||
_currentSettings.letterSpacing,
|
||||
-1.0, 2.0, '像素', // 🚀 缩小调整范围,更适合中文
|
||||
(value) => _updateSettings(_currentSettings.copyWith(letterSpacing: value)),
|
||||
formatValue: (value) => value == 0
|
||||
? '标准'
|
||||
: (value > 0 ? '+${value.toStringAsFixed(1)}px' : '${value.toStringAsFixed(1)}px'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildCompactCard(
|
||||
title: '编辑器行为',
|
||||
icon: Icons.settings,
|
||||
children: [
|
||||
_buildCompactSwitch('自动保存', _currentSettings.autoSaveEnabled,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(autoSaveEnabled: value))),
|
||||
if (_currentSettings.autoSaveEnabled)
|
||||
_buildCompactSlider(
|
||||
'保存间隔',
|
||||
_currentSettings.autoSaveIntervalMinutes.toDouble(),
|
||||
1, 15, '分钟',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(autoSaveIntervalMinutes: value.round())),
|
||||
formatValue: (value) => '${value.toInt()}分钟',
|
||||
),
|
||||
_buildCompactSwitch('拼写检查', _currentSettings.spellCheckEnabled,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(spellCheckEnabled: value))),
|
||||
_buildCompactSwitch('显示字数', _currentSettings.showWordCount,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(showWordCount: value))),
|
||||
_buildCompactSwitch('显示行号', _currentSettings.showLineNumbers,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(showLineNumbers: value))),
|
||||
_buildCompactSwitch('高亮当前行', _currentSettings.highlightActiveLine,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(highlightActiveLine: value))),
|
||||
_buildCompactSwitch('Vim模式', _currentSettings.enableVimMode,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(enableVimMode: value))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 🚀 移动导出设置到左列
|
||||
_buildCompactCard(
|
||||
title: '导出设置',
|
||||
icon: Icons.download,
|
||||
children: [
|
||||
_buildCompactDropdown(
|
||||
'默认导出格式',
|
||||
_currentSettings.defaultExportFormat,
|
||||
EditorSettings.availableExportFormats,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(defaultExportFormat: value)),
|
||||
itemBuilder: (format) {
|
||||
switch (format) {
|
||||
case 'markdown': return 'Markdown (.md)';
|
||||
case 'docx': return 'Word文档 (.docx)';
|
||||
case 'pdf': return 'PDF文档 (.pdf)';
|
||||
case 'txt': return '纯文本 (.txt)';
|
||||
case 'html': return 'HTML文档 (.html)';
|
||||
default: return format.toUpperCase();
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildCompactSwitch('包含元数据', _currentSettings.includeMetadata,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(includeMetadata: value))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// 右列
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCompactCard(
|
||||
title: '布局间距',
|
||||
icon: Icons.format_align_center,
|
||||
children: [
|
||||
_buildCompactSlider(
|
||||
'水平边距',
|
||||
_currentSettings.paddingHorizontal,
|
||||
8, 48, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(paddingHorizontal: value)),
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'垂直边距',
|
||||
_currentSettings.paddingVertical,
|
||||
8, 32, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(paddingVertical: value)),
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'段落间距',
|
||||
_currentSettings.paragraphSpacing,
|
||||
4, 24, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(paragraphSpacing: value)),
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'缩进大小',
|
||||
_currentSettings.indentSize,
|
||||
16, 64, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(indentSize: value)),
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'最大行宽',
|
||||
_currentSettings.maxLineWidth,
|
||||
400, 1500, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(maxLineWidth: value)),
|
||||
),
|
||||
_buildCompactSlider(
|
||||
'最小编辑器高度',
|
||||
_currentSettings.minEditorHeight,
|
||||
1200, 3000, '像素',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(minEditorHeight: value)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildCompactCard(
|
||||
title: '视觉效果',
|
||||
icon: Icons.visibility,
|
||||
children: [
|
||||
_buildCompactSwitch('暗色模式', _currentSettings.darkModeEnabled,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(darkModeEnabled: value))),
|
||||
_buildCompactSwitch('平滑滚动', _currentSettings.smoothScrolling,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(smoothScrolling: value))),
|
||||
_buildCompactSwitch('淡入动画', _currentSettings.fadeInAnimation,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(fadeInAnimation: value))),
|
||||
_buildCompactSwitch('打字机模式', _currentSettings.useTypewriterMode,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(useTypewriterMode: value))),
|
||||
_buildCompactSwitch('显示小地图', _currentSettings.showMiniMap,
|
||||
(value) => _updateSettings(_currentSettings.copyWith(showMiniMap: value))),
|
||||
_buildCompactSlider(
|
||||
'光标闪烁速度',
|
||||
_currentSettings.cursorBlinkRate,
|
||||
0.5, 3.0, '秒',
|
||||
(value) => _updateSettings(_currentSettings.copyWith(cursorBlinkRate: value)),
|
||||
formatValue: (value) => '${value.toStringAsFixed(1)}s',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 🚀 保留选择和光标设置卡片在右列
|
||||
_buildCompactCard(
|
||||
title: '选择和光标',
|
||||
icon: Icons.colorize,
|
||||
children: [
|
||||
_buildColorPicker(
|
||||
'选择高亮颜色',
|
||||
Color(_currentSettings.selectionHighlightColor),
|
||||
(color) => _updateSettings(_currentSettings.copyWith(selectionHighlightColor: color.value)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 预览区域
|
||||
_buildPreviewCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactCard({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required List<Widget> children,
|
||||
}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.getSurfaceColor(context),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: WebTheme.grey200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 卡片标题 - 🚀 减少内边距
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.grey50,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: WebTheme.getTextColor(context)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: WebTheme.getTextColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 卡片内容 - 🚀 减少内边距
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactSlider(
|
||||
String label,
|
||||
double value,
|
||||
double min,
|
||||
double max,
|
||||
String unit,
|
||||
ValueChanged<double> onChanged, {
|
||||
String Function(double)? formatValue,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatValue?.call(value) ?? '${value.toStringAsFixed(value % 1 == 0 ? 0 : 1)}$unit',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: WebTheme.getSecondaryTextColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: Slider(
|
||||
value: value.clamp(min, max).toDouble(),
|
||||
min: min,
|
||||
max: max,
|
||||
divisions: ((max - min) * (unit == '倍' ? 10 : 1)).round(),
|
||||
onChanged: onChanged,
|
||||
activeColor: WebTheme.getPrimaryColor(context),
|
||||
inactiveColor: WebTheme.grey300,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactSwitch(
|
||||
String label,
|
||||
bool value,
|
||||
ValueChanged<bool> onChanged,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center, // 🚀 对齐优化
|
||||
children: [
|
||||
Expanded( // 🚀 让文字可以自动换行
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8), // 🚀 添加间距
|
||||
// 🚀 优化开关大小,与文字高度匹配
|
||||
Transform.scale(
|
||||
scale: 0.8, // 缩小开关
|
||||
child: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
activeColor: WebTheme.getPrimaryColor(context),
|
||||
inactiveThumbColor: WebTheme.grey400,
|
||||
inactiveTrackColor: Colors.grey[300],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactDropdown<T>(
|
||||
String label,
|
||||
T value,
|
||||
List<T> items,
|
||||
ValueChanged<T?> onChanged, {
|
||||
String Function(T)? itemBuilder,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: DropdownButtonFormField<T>(
|
||||
value: value,
|
||||
items: items.map((item) {
|
||||
return DropdownMenuItem<T>(
|
||||
value: item,
|
||||
child: Text(
|
||||
itemBuilder?.call(item) ?? item.toString(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: BorderSide(color: WebTheme.grey300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: BorderSide(color: WebTheme.grey300),
|
||||
),
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 🚀 构建颜色选择器
|
||||
Widget _buildColorPicker(
|
||||
String label,
|
||||
Color currentColor,
|
||||
ValueChanged<Color> onColorChanged,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
GestureDetector(
|
||||
onTap: () => _showColorPicker(currentColor, onColorChanged),
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: currentColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: WebTheme.grey300),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: currentColor,
|
||||
borderRadius: const BorderRadius.horizontal(left: Radius.circular(4)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.getSurfaceColor(context),
|
||||
borderRadius: const BorderRadius.horizontal(right: Radius.circular(4)),
|
||||
),
|
||||
child: Text(
|
||||
'#${currentColor.value.toRadixString(16).substring(2).toUpperCase()}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示颜色选择对话框
|
||||
void _showColorPicker(Color currentColor, ValueChanged<Color> onColorChanged) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('选择颜色'),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Colors.red,
|
||||
Colors.pink,
|
||||
Colors.purple,
|
||||
Colors.deepPurple,
|
||||
Colors.indigo,
|
||||
Colors.blue,
|
||||
Colors.lightBlue,
|
||||
Colors.cyan,
|
||||
Colors.teal,
|
||||
Colors.green,
|
||||
Colors.lightGreen,
|
||||
Colors.lime,
|
||||
Colors.yellow,
|
||||
Colors.amber,
|
||||
Colors.orange,
|
||||
Colors.deepOrange,
|
||||
Colors.brown,
|
||||
Colors.grey,
|
||||
Colors.blueGrey,
|
||||
Colors.black,
|
||||
].map((color) => GestureDetector(
|
||||
onTap: () {
|
||||
onColorChanged(color);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: currentColor == color ? Colors.white : Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)).toList(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewCard() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.getSurfaceColor(context),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: WebTheme.grey200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: WebTheme.grey50,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.preview, size: 18, color: WebTheme.getTextColor(context)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'预览效果',
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: WebTheme.getTextColor(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
constraints: const BoxConstraints(maxWidth: 800),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: _currentSettings.paddingHorizontal,
|
||||
vertical: _currentSettings.paddingVertical,
|
||||
),
|
||||
child: Text(
|
||||
'这是预览文本,展示当前字体设置的效果。您可以看到字体大小、行间距、字体样式等设置的实际显示效果。',
|
||||
style: TextStyle(
|
||||
fontFamily: _currentSettings.fontFamily,
|
||||
fontSize: _currentSettings.fontSize,
|
||||
fontWeight: _currentSettings.fontWeight,
|
||||
height: _currentSettings.lineSpacing,
|
||||
letterSpacing: _currentSettings.letterSpacing,
|
||||
color: WebTheme.getTextColor(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user