feat(app): D.3 中等优先级 UX 改进 — 保存指示器 + 触摸目标 + 主题持久化
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

D.3.2 三态保存指示器:
- 未保存 (灰色) → 保存中 (琥珀色脉冲点) → 已保存 (绿色点)
- _PulsingDot 动画组件,800ms 呼吸效果
- 点击'完成'时显示保存中状态

D.3.3 工具栏触摸目标:
- BoxConstraints 36x36 → 44x44,符合 WCAG 标准

D.3.4 主题偏好持久化:
- SettingsBloc 接受 SharedPreferences,保存/恢复 themeMode
- NuanjiApp 改为 StatefulWidget,异步初始化 SharedPreferences
- 启动时显示 loading,初始化完成后渲染 app
This commit is contained in:
iven
2026-06-07 13:50:34 +08:00
parent 750605e479
commit ec8a04c80a
4 changed files with 175 additions and 42 deletions

View File

@@ -337,6 +337,9 @@ class _EditorViewState extends State<_EditorView> {
/// 查看模式:打开已有日记时默认只读,点击"编辑"后进入编辑模式
bool _isViewMode = false;
/// 保存中状态 — 用于显示"保存中..."指示器
bool _isSaving = false;
@override
void initState() {
super.initState();
@@ -595,9 +598,16 @@ class _EditorViewState extends State<_EditorView> {
);
}
/// 保存处理
/// 保存处理 — 显示"保存中..."后触发保存
void _handleSave(BuildContext context, EditorState state) {
widget.onSaveComplete();
setState(() => _isSaving = true);
// 短暂延迟让 UI 显示"保存中..."状态
Future.delayed(const Duration(milliseconds: 300), () {
if (mounted) {
setState(() => _isSaving = false);
widget.onSaveComplete();
}
});
}
/// 显示评论列表
@@ -624,7 +634,26 @@ class _EditorViewState extends State<_EditorView> {
}
/// 自动保存状态指示器
/// 保存指示器 — 三态: 未保存 / 保存中 / 已保存
Widget _buildAutosaveIndicator(EditorState state) {
// 保存中 — 琥珀色脉冲点
if (_isSaving) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_PulsingDot(color: AppColors.tertiary),
const SizedBox(width: 4),
Text(
'保存中...',
style: TextStyle(fontSize: 11, color: Colors.amber[700]),
),
],
),
);
}
// 未保存
if (state.lastSavedAt == null) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
@@ -634,6 +663,7 @@ class _EditorViewState extends State<_EditorView> {
),
);
}
// 已保存 — 绿色点
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
@@ -1211,3 +1241,53 @@ class _ImageSourceButton extends StatelessWidget {
);
}
}
/// 脉冲圆点动画 — 用于"保存中..."指示器
class _PulsingDot extends StatefulWidget {
const _PulsingDot({required this.color});
final Color color;
@override
State<_PulsingDot> createState() => _PulsingDotState();
}
class _PulsingDotState extends State<_PulsingDot>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
)..repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, _) {
final scale = 0.6 + 0.4 * _controller.value;
return Transform.scale(
scale: scale,
child: Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: widget.color,
shape: BoxShape.circle,
),
),
);
},
);
}
}

View File

@@ -71,7 +71,7 @@ class EditorToolbar extends StatelessWidget {
onTap: () => onEvent(isActive ? ToolReactivated(tool) : ToolChanged(tool)),
behavior: HitTestBehavior.opaque,
child: Container(
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
constraints: const BoxConstraints(minWidth: 44, minHeight: 44),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Column(
mainAxisSize: MainAxisSize.min,