代码优化,功能新增

This commit is contained in:
DelLevin-Home
2026-06-26 23:46:37 +08:00
parent f7ef50a677
commit 45137b96d6
59 changed files with 5041 additions and 4937 deletions

View File

@@ -30,6 +30,7 @@ class _NoteDetailPageState extends State<NoteDetailPage> {
return Scaffold(
backgroundColor: colors.surface,
appBar: AppBar(
titleSpacing: 0,
title: Text(
note.title.isNotEmpty
? note.title

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:image_picker/image_picker.dart';
@@ -9,6 +10,7 @@ import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
import '../../widgets/fade_in_local_image.dart';
import '../../widgets/tag_side_panel.dart';
/// 添加/编辑笔记页面 - 极简书写界面
class NoteFormPage extends StatefulWidget {
@@ -30,6 +32,9 @@ class _NoteFormPageState extends State<NoteFormPage> {
final ImagePicker _picker = ImagePicker();
String? _tempNoteId; // 新建模式时使用的临时笔记ID
String _editorMode = 'edit'; // 'edit' | 'preview'
Timer? _autoSaveTimer;
String _saveStatus = ''; // '', 'saved'
Note? _savedNote; // 新建模式首次自动保存后的笔记引用
static const _weekdays = ['', '', '', '', '', '', ''];
@@ -44,15 +49,82 @@ class _NoteFormPageState extends State<NoteFormPage> {
_tags = note != null ? List.from(note.tags) : [];
_images = note != null ? List.from(note.images) : [];
_isEditing = note != null;
_titleController.addListener(_onTextChanged);
_contentController.addListener(_onTextChanged);
}
@override
void dispose() {
_autoSaveTimer?.cancel();
_titleController.dispose();
_contentController.dispose();
super.dispose();
}
void _onTextChanged() {
_autoSaveTimer?.cancel();
_autoSaveTimer = Timer(const Duration(seconds: 2), () {
if (mounted) _autoSave();
});
}
Future<void> _autoSave() async {
final content = _contentController.text.trim();
final title = _titleController.text.trim();
if (title.isEmpty && content.isEmpty) return;
try {
final now = DateTime.now();
if (_isEditing) {
final updatedNote = widget.note!.copyWith(
title: title,
content: content,
tags: _tags,
images: _images,
updatedAt: now,
);
await context.read<AppProvider>().updateNote(updatedNote);
} else if (_savedNote != null) {
final updatedNote = _savedNote!.copyWith(
title: title,
content: content,
tags: _tags,
images: _images,
updatedAt: now,
);
await context.read<AppProvider>().updateNote(updatedNote);
_savedNote = updatedNote;
} else {
final noteId = now.millisecondsSinceEpoch.toString();
List<String> finalImages = [];
if (_images.isNotEmpty) {
final oldNoteId = _tempNoteId ?? noteId;
finalImages = await _moveImagesToNewId(oldNoteId, noteId);
}
final newNote = Note(
id: noteId,
title: title,
content: content,
tags: _tags,
images: finalImages.isNotEmpty ? finalImages : _images,
createdAt: _createdAt,
updatedAt: now,
);
await context.read<AppProvider>().addNote(newNote);
_savedNote = newNote;
_isEditing = true;
}
if (mounted) {
setState(() => _saveStatus = 'saved');
Timer(const Duration(seconds: 3), () {
if (mounted && _saveStatus == 'saved') setState(() => _saveStatus = '');
});
}
} catch (_) {
// 自动保存失败静默处理
}
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
@@ -149,6 +221,19 @@ class _NoteFormPageState extends State<NoteFormPage> {
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface),
),
),
if (_saveStatus == 'saved')
Padding(
padding: const EdgeInsets.only(right: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 6, height: 6,
decoration: BoxDecoration(color: Colors.green, shape: BoxShape.circle)),
const SizedBox(width: 4),
Text('已保存', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
],
),
),
GestureDetector(
onTap: _saveNote,
child: Container(
@@ -501,7 +586,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
Widget _buildAddTagButton() {
final colors = Theme.of(context).colorScheme;
return GestureDetector(
onTap: _showAddTagDialog,
onTap: _showTagPanel,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
@@ -520,184 +605,26 @@ class _NoteFormPageState extends State<NoteFormPage> {
);
}
/// 显示添加标签对话框
Future<void> _showAddTagDialog() async {
final controller = TextEditingController();
// 从 tags 表获取已有标签
/// 显示标签侧边面板
Future<void> _showTagPanel() async {
final provider = context.read<AppProvider>();
final tagRows = await provider.getTags('note_tag');
final allTags = tagRows.map((t) => t['name'] as String).toSet();
// 也从当前笔记内容中收集
for (final note in provider.notes) {
allTags.addAll(note.tags);
}
// 过滤掉已添加的标签
final availableTags = allTags.where((tag) => !_tags.contains(tag)).toList()..sort();
showDialog(
if (!mounted) return;
TagSidePanel.show(
context: context,
builder: (ctx) {
final colors = Theme.of(context).colorScheme;
return StatefulBuilder(
builder: (ctx, setDialogState) => AlertDialog(
backgroundColor: colors.surface,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
title: Text(
'添加标签',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: colors.onSurface,
),
),
contentPadding: const EdgeInsets.fromLTRB(24, 16, 24, 0),
content: SizedBox(
width: double.maxFinite,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 输入框
TextField(
controller: controller,
autofocus: true,
style: TextStyle(fontSize: 14, color: colors.onSurface),
cursorColor: colors.primary,
decoration: InputDecoration(
hintText: '输入新标签名称',
hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.3)),
filled: true,
fillColor: colors.surfaceContainerHigh,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colors.primary, width: 1),
),
suffixIcon: controller.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, size: 16, color: colors.onSurface.withValues(alpha: 0.35)),
onPressed: () => controller.clear(),
)
: null,
),
onChanged: (_) => setDialogState(() {}),
onSubmitted: (value) {
_addTag(value);
controller.clear();
setDialogState(() {});
},
),
// 已有标签列表
if (availableTags.isNotEmpty) ...[
const SizedBox(height: 20),
Text(
'或选择已有标签',
style: TextStyle(
fontSize: 12,
color: colors.onSurface.withValues(alpha: 0.35),
),
),
const SizedBox(height: 12),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 180),
child: SingleChildScrollView(
child: Wrap(
spacing: 8,
runSpacing: 8,
children: availableTags.map((tag) {
return InkWell(
onTap: () {
_addTag(tag);
Navigator.pop(ctx);
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: colors.outline, width: 0.5),
),
child: Text(
tag,
style: TextStyle(
fontSize: 13,
color: colors.onSurface.withValues(alpha: 0.7),
),
),
),
);
}).toList(),
),
),
),
],
if (availableTags.isEmpty)
Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
child: Center(
child: Text(
'暂无已有标签',
style: TextStyle(fontSize: 13, color: Colors.grey[400]),
),
),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
style: TextButton.styleFrom(
foregroundColor: colors.onSurface.withValues(alpha: 0.4),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('取消', style: TextStyle(fontSize: 14)),
),
ElevatedButton(
onPressed: () {
_addTag(controller.text);
Navigator.pop(ctx);
},
style: ElevatedButton.styleFrom(
backgroundColor: colors.primary,
foregroundColor: colors.onPrimary,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
),
child: const Text('添加', style: TextStyle(fontSize: 14)),
),
],
actionsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
),
);
selectedTags: List.from(_tags),
allAvailableTags: allTags.toList()..sort(),
onTagsChanged: (newTags) {
setState(() => _tags = newTags);
},
);
}
/// 获取所有已有标签(从所有笔记中收集)
/// 添加标签
void _addTag(String tag) {
final trimmed = tag.trim();
if (trimmed.isNotEmpty && !_tags.contains(trimmed)) {
setState(() => _tags.add(trimmed));
}
}
/// 标题输入行
Widget _buildTitleInput(ColorScheme colors) {
return Padding(
@@ -729,42 +656,15 @@ class _NoteFormPageState extends State<NoteFormPage> {
return false;
}
/// 离开确认
/// 离开确认(自动保存后直接返回)
Future<bool> _confirmLeave() async {
if (!_hasContent()) return true;
final colors = Theme.of(context).colorScheme;
final result = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: colors.surface,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text('未保存', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
content: Text('当前内容未保存,确定要离开吗?',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))),
),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(
backgroundColor: colors.error,
foregroundColor: colors.onError,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('离开'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
return result ?? false;
_autoSaveTimer?.cancel();
if (_hasContent()) await _autoSave();
return true;
}
Future<void> _saveNote() async {
_autoSaveTimer?.cancel();
final content = _contentController.text.trim();
final title = _titleController.text.trim();

View File

@@ -24,10 +24,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
AppProvider? _provider;
int _layoutStyle = 0;
bool _initialized = false;
int _lastDataCount = -1;
DateTime? _lastUpdatedAt;
int _lastScrollSignal = 0;
String? _selectedTag;
@override
void initState() {
@@ -38,8 +35,6 @@ class _NoteTabPageState extends State<NoteTabPage> {
final provider = context.read<AppProvider>();
_provider = provider;
provider.addListener(_onDataChanged);
_lastDataCount = provider.notes.length;
if (provider.notes.isNotEmpty) _lastUpdatedAt = provider.notes.first.updatedAt;
_loadFirst();
});
}
@@ -55,7 +50,6 @@ class _NoteTabPageState extends State<NoteTabPage> {
if (!_initialized || !mounted) return;
final provider = context.read<AppProvider>();
// 检查回到顶部信号
if (provider.scrollToTopSignal != _lastScrollSignal && provider.scrollToTopSignal > 0) {
_lastScrollSignal = provider.scrollToTopSignal;
if (_scrollController.hasClients) {
@@ -63,15 +57,8 @@ class _NoteTabPageState extends State<NoteTabPage> {
}
}
final count = provider.notes.length;
final latest = provider.notes.isNotEmpty ? provider.notes.first.updatedAt : null;
if (count != _lastDataCount || latest != _lastUpdatedAt) {
_lastDataCount = count;
_lastUpdatedAt = latest;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _loadFirst();
});
}
// 数据变化时刷新列表(排序/评分/新增等)
_loadFirst();
}
void _onScroll() {
@@ -83,12 +70,8 @@ class _NoteTabPageState extends State<NoteTabPage> {
Future<void> _loadFirst() async {
_initialized = true;
setState(() { _isLoading = true; _offset = 0; _hasMore = true; });
List<Note> list = await context.read<AppProvider>().loadNotesPaged(offset: 0);
if (_selectedTag != null) {
final all = context.read<AppProvider>().notes.where((n) => !n.isDeleted && n.tags.contains(_selectedTag)).toList()
..sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
list = all.take(20).toList();
}
final sortMode = UserPrefs().noteSortMode;
final list = await context.read<AppProvider>().loadNotesPaged(offset: 0, sortMode: sortMode);
if (!mounted) return;
setState(() { _items.clear(); _items.addAll(list); _offset = list.length; _hasMore = list.length >= 20; _isLoading = false; });
}
@@ -96,14 +79,8 @@ class _NoteTabPageState extends State<NoteTabPage> {
Future<void> _loadMore() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
List<Note> list;
if (_selectedTag != null) {
final all = context.read<AppProvider>().notes.where((n) => !n.isDeleted && n.tags.contains(_selectedTag)).toList()
..sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
list = all.skip(_offset).take(20).toList();
} else {
list = await context.read<AppProvider>().loadNotesPaged(offset: _offset);
}
final sortMode = UserPrefs().noteSortMode;
final list = await context.read<AppProvider>().loadNotesPaged(offset: _offset, sortMode: sortMode);
if (!mounted) return;
setState(() { _items.addAll(list); _offset += list.length; _hasMore = list.length >= 20; _isLoading = false; });
}
@@ -118,20 +95,11 @@ class _NoteTabPageState extends State<NoteTabPage> {
final colors = Theme.of(context).colorScheme;
return Consumer<AppProvider>(builder: (context, provider, _) {
if (_items.isEmpty && _isLoading) return _buildSkeleton();
if (_items.isEmpty && _selectedTag == null) {
if (_items.isEmpty) {
return RefreshIndicator(onRefresh: _refresh, color: colors.primary, backgroundColor: colors.surface,
child: ListView(physics: const AlwaysScrollableScrollPhysics(), children: [_buildEmptyState(context)]));
}
return Column(
children: [
_buildTagBar(provider),
Expanded(
child: _items.isEmpty
? Center(child: Text('没有"$_selectedTag"相关的笔记', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.4))))
: _buildContent(),
),
],
);
return _buildContent();
});
}
@@ -141,53 +109,6 @@ class _NoteTabPageState extends State<NoteTabPage> {
return _buildListView();
}
Widget _buildTagBar(AppProvider provider) {
final colors = Theme.of(context).colorScheme;
final allTags = <String>{};
for (final n in provider.notes.where((n) => !n.isDeleted)) {
allTags.addAll(n.tags);
}
if (allTags.isEmpty) return const SizedBox.shrink();
final tags = allTags.toList()..sort();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: colors.outlineVariant, width: 0.5)),
),
child: SizedBox(
height: 32,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 12),
itemCount: tags.length,
separatorBuilder: (_, __) => const SizedBox(width: 6),
itemBuilder: (_, i) {
final tag = tags[i];
final selected = _selectedTag == tag;
return GestureDetector(
onTap: () {
setState(() => _selectedTag = selected ? null : tag);
_loadFirst();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: selected ? colors.primary : colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(6),
),
alignment: Alignment.center,
child: Text(tag, style: TextStyle(
fontSize: 12,
color: selected ? colors.onPrimary : colors.onSurface.withValues(alpha: 0.6),
)),
),
);
},
),
),
);
}
Widget _buildSkeleton() {
switch (_layoutStyle) {
case 1: return _buildWaterfallSkeleton();
@@ -246,7 +167,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
final colors = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/note-detail', arguments: note).then((_) => _loadFirst()),
onLongPress: () => _showDeleteDialog(context, note),
onLongPress: () => _showNoteActions(note),
child: IntrinsicHeight(child: Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
SizedBox(width: 40, child: Column(children: [
Container(width: 10, height: 10,
@@ -256,7 +177,10 @@ class _NoteTabPageState extends State<NoteTabPage> {
Expanded(child: Container(margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(14),
decoration: BoxDecoration(color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(12)),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
Text(_formatFullDate(note.updatedAt), style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
Row(children: [
Expanded(child: Text(_formatFullDate(note.updatedAt), style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)))),
if (note.isPinned) Icon(Icons.push_pin, size: 14, color: colors.primary),
]),
if (note.title.isNotEmpty) ...[const SizedBox(height: 6),
Text(note.title, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.onSurface), maxLines: 1, overflow: TextOverflow.ellipsis),
],
@@ -305,7 +229,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
final extraCount = images.length - 1;
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/note-detail', arguments: note).then((_) => _loadFirst()),
onLongPress: () => _showDeleteDialog(context, note),
onLongPress: () => _showNoteActions(note),
child: Container(margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(10),
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 6, offset: const Offset(0, 2))]),
@@ -344,9 +268,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
]),
),
);
}
String _getPreviewText(Note note) {
} String _getPreviewText(Note note) {
final text = note.content.replaceAll(RegExp(r'[#*\[\]\(\)]'), '').trim();
return text.isEmpty ? '(无内容)' : text;
}
@@ -368,23 +290,72 @@ class _NoteTabPageState extends State<NoteTabPage> {
);
}
void _showDeleteDialog(BuildContext context, Note note) {
void _showNoteActions(Note note) {
final colors = Theme.of(context).colorScheme;
showDialog(context: context, builder: (ctx) => AlertDialog(
backgroundColor: colors.surface, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
content: Text('确定要删除这条笔记吗?删除后可在回收站恢复。',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
ElevatedButton(onPressed: () async { await context.read<AppProvider>().removeNote(note.id); Navigator.pop(ctx); _loadFirst(); },
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
child: const Text('删除'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
));
showModalBottomSheet(
context: context,
backgroundColor: colors.surface,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (ctx) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Container(width: 36, height: 4, decoration: BoxDecoration(color: colors.onSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(2))),
const SizedBox(height: 16),
ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(width: 36, height: 36, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)),
child: Icon(note.isPinned ? Icons.push_pin_outlined : Icons.push_pin, size: 20, color: colors.onSurface.withValues(alpha: 0.6))),
title: Text(note.isPinned ? '取消置顶' : '置顶', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface)),
subtitle: Text(note.isPinned ? '取消置顶后按时间排序' : '置顶后始终显示在最前', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
trailing: Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25)),
onTap: () {
Navigator.pop(ctx);
context.read<AppProvider>().toggleNotePin(note.id, !note.isPinned);
},
),
Divider(height: 0.5, color: colors.outlineVariant),
ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(width: 36, height: 36, decoration: BoxDecoration(color: colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(10)),
child: Icon(Icons.delete_outline, size: 20, color: colors.error)),
title: Text('删除', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.error)),
subtitle: Text('删除后可在回收站恢复', style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
trailing: Icon(Icons.chevron_right, color: colors.onSurface.withValues(alpha: 0.25)),
onTap: () {
Navigator.pop(ctx);
_showDeleteDialog(note);
},
),
const SizedBox(height: 12),
]),
),
);
}
void _showDeleteDialog(Note note) {
final colors = Theme.of(context).colorScheme;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: colors.surface, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text('确认删除', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
content: Text('确定要删除这条笔记吗?删除后可在回收站恢复。',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx),
child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
ElevatedButton(
onPressed: () async { await context.read<AppProvider>().removeNote(note.id); Navigator.pop(ctx); _loadFirst(); },
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
child: const Text('删除'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
}
Widget _buildEmptyState(BuildContext context) {