Files
MookNote/lib/pages/note/note_tab_page.dart
2026-03-22 15:54:28 +08:00

296 lines
9.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../models/data_models.dart';
import '../../widgets/note_list_item.dart';
/// 笔记标签页
class NoteTabPage extends StatefulWidget {
const NoteTabPage({super.key});
@override
State<NoteTabPage> createState() => _NoteTabPageState();
}
class _NoteTabPageState extends State<NoteTabPage> {
// 使用分页加载
static const int _pageSize = 50;
final List<Note> _displayedNotes = [];
bool _isLoading = false;
bool _hasMore = true;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
// 延迟加载初始数据避免阻塞UI
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadMoreNotes();
});
}
// 用于检测Provider数据变化
int _lastNotesCount = 0;
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 使用watch监听Provider数据变化
final provider = context.watch<AppProvider>();
final allNotes = provider.notes;
// 如果Provider中的笔记数量发生变化且本地列表已加载过数据
if (allNotes.length != _lastNotesCount && _displayedNotes.isNotEmpty) {
_lastNotesCount = allNotes.length;
// 清空本地列表并重新加载
setState(() {
_displayedNotes.clear();
_hasMore = true;
});
// 延迟加载避免setState冲突
Future.microtask(() => _loadMoreNotes());
} else {
_lastNotesCount = allNotes.length;
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMoreNotes();
}
}
Future<void> _loadMoreNotes() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
// 使用微任务延迟加载避免阻塞UI
await Future.microtask(() {
final provider = context.read<AppProvider>();
final allNotes = provider.notes;
final startIndex = _displayedNotes.length;
final endIndex = (startIndex + _pageSize).clamp(0, allNotes.length);
if (startIndex >= allNotes.length) {
_hasMore = false;
} else {
final newNotes = allNotes.sublist(startIndex, endIndex);
_displayedNotes.addAll(newNotes);
_hasMore = endIndex < allNotes.length;
}
});
if (mounted) {
setState(() => _isLoading = false);
}
}
Future<void> _refresh() async {
final provider = context.read<AppProvider>();
await provider.loadNotes();
setState(() {
_displayedNotes.clear();
_hasMore = true;
});
await _loadMoreNotes();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 笔记列表(分页加载)
Expanded(
child: _buildNoteList(context),
),
],
);
}
/// 构建笔记列表
Widget _buildNoteList(BuildContext context) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
final allNotes = provider.notes;
// 当Provider数据变化时同步更新本地显示列表
// 过滤掉已删除的笔记,保持顺序
_syncDisplayedNotes(allNotes);
if (allNotes.isEmpty && _displayedNotes.isEmpty) {
return _buildEmptyState(context);
}
return RefreshIndicator(
onRefresh: _refresh,
color: const Color(0xFF1A1A1A),
backgroundColor: Colors.white,
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 100),
itemCount: _displayedNotes.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= _displayedNotes.length) {
// 底部加载指示器
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Color(0xFF1A1A1A),
),
),
),
);
}
return NoteListItem(note: _displayedNotes[index]);
},
),
);
},
);
}
/// 同步显示列表与Provider数据
void _syncDisplayedNotes(List<Note> allNotes) {
// 获取当前所有有效笔记的ID集合
final validNoteIds = allNotes.map((n) => n.id).toSet();
// 移除已删除的笔记不在Provider中的
final initialLength = _displayedNotes.length;
_displayedNotes.removeWhere((note) => !validNoteIds.contains(note.id));
// 检查是否有新增笔记Provider中有但本地列表中没有的
final displayedIds = _displayedNotes.map((n) => n.id).toSet();
final hasNewNotes = allNotes.any((note) => !displayedIds.contains(note.id));
// 检查已存在的笔记是否有更新通过比较updatedAt
bool hasUpdates = false;
for (int i = 0; i < _displayedNotes.length; i++) {
final localNote = _displayedNotes[i];
final providerNote = allNotes.firstWhere((n) => n.id == localNote.id);
if (localNote.updatedAt != providerNote.updatedAt) {
hasUpdates = true;
break;
}
}
// 如果有笔记被移除、有新增、或有更新,更新列表
if (_displayedNotes.length < initialLength || hasNewNotes || hasUpdates) {
// 清空并重新加载所有数据
_displayedNotes.clear();
_displayedNotes.addAll(allNotes);
_hasMore = false; // 已经有全部数据了
}
}
/// 构建空状态提示
Widget _buildEmptyState(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.note_outlined,
size: 40,
color: Color(0xFFCCCCCC),
),
),
const SizedBox(height: 20),
const Text(
'暂无笔记',
style: TextStyle(
fontSize: 16,
color: Color(0xFF999999),
),
),
const SizedBox(height: 24),
InkWell(
onTap: () {
Navigator.pushNamed(context, '/note-form');
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'添加记录',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
),
);
}
/// 获取示例笔记数据
List<Note> _getSampleNotes() {
final now = DateTime.now();
// 示例数据(实际应从数据库获取)
return [
Note(
id: '1',
content: '今天开始学习 Flutter 框架,感觉和 Vue 有很多相似之处,都是声明式 UI组件化开发。Widget 的概念很有趣,一切皆 Widget。',
tags: ['学习', 'Flutter', '编程'],
createdAt: now.subtract(const Duration(days: 2)),
updatedAt: now.subtract(const Duration(days: 2)),
),
Note(
id: '2',
content: '余华的《活着》真的是一部让人深思的作品。福贵的一生经历了太多的苦难,但他依然坚强地活着。生命的意义或许就在于活着本身。',
tags: ['阅读', '感悟', '书籍'],
createdAt: now.subtract(const Duration(days: 5)),
updatedAt: now.subtract(const Duration(days: 5)),
),
Note(
id: '3',
content: '诺兰的电影总是充满想象力。《星际穿越》将科幻与亲情完美结合,五维空间的呈现方式令人震撼。配乐也是一绝。',
tags: ['观影', '科幻', '电影'],
createdAt: now.subtract(const Duration(days: 10)),
updatedAt: now.subtract(const Duration(days: 10)),
),
Note(
id: '4',
content: 'Pandas 库的 DataFrame 操作非常强大,可以方便地进行数据清洗和分析。需要多练习熟练掌握常用操作。',
tags: ['Python', '数据分析', '技术'],
createdAt: now.subtract(const Duration(days: 30)),
updatedAt: now.subtract(const Duration(days: 30)),
),
Note(
id: '5',
content: '春天来了,天气渐暖。周末去公园散步,看到花开得很好。生活中的小确幸值得记录。',
tags: ['生活', '随笔'],
createdAt: now.subtract(const Duration(hours: 5)),
updatedAt: now.subtract(const Duration(hours: 5)),
),
];
}
}