功能优化

This commit is contained in:
DelLevin-Home
2026-03-09 14:14:38 +08:00
parent c894921472
commit 7c970cde49
7 changed files with 496 additions and 705 deletions

View File

@@ -9,7 +9,7 @@ import '../models/data_models.dart';
/// 自定义左侧弹出菜单 - 极简主义设计
class CustomDrawer extends StatelessWidget {
const CustomDrawer({super.key});
CustomDrawer({super.key});
@override
Widget build(BuildContext context) {
@@ -27,6 +27,11 @@ class CustomDrawer extends StatelessWidget {
const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)),
// 日历热力图区域
_buildCalendarSection(context),
const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)),
// 菜单项列表
Expanded(
child: ListView(
@@ -411,6 +416,197 @@ class CustomDrawer extends StatelessWidget {
void _showToast(BuildContext context, String message) {
ToastUtil.show(context, message);
}
/// 构建日历热力图区域
Widget _buildCalendarSection(BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
// 使用 StatefulBuilder 的状态来管理选中的月份
final selectedMonth = _calendarSelectedMonth ?? DateTime.now();
// 合并所有数据按日期
final Map<DateTime, _DailyData> dailyData = {};
for (final movie in provider.movies.where((m) => !m.isDeleted)) {
final date = DateTime(movie.createdAt.year, movie.createdAt.month, movie.createdAt.day);
dailyData.putIfAbsent(date, () => _DailyData()).movies++;
}
for (final book in provider.books.where((b) => !b.isDeleted)) {
final date = DateTime(book.createdAt.year, book.createdAt.month, book.createdAt.day);
dailyData.putIfAbsent(date, () => _DailyData()).books++;
}
for (final note in provider.notes.where((n) => !n.isDeleted)) {
final date = DateTime(note.createdAt.year, note.createdAt.month, note.createdAt.day);
dailyData.putIfAbsent(date, () => _DailyData()).notes++;
}
// 计算最大数量用于颜色强度
int maxCount = 0;
for (final data in dailyData.values) {
final count = data.total;
if (count > maxCount) maxCount = count;
}
if (maxCount == 0) maxCount = 1;
final year = selectedMonth.year;
final month = selectedMonth.month;
final daysInMonth = DateTime(year, month + 1, 0).day;
final firstWeekday = DateTime(year, month, 1).weekday % 7;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题和月份切换
Row(
children: [
const Icon(
Icons.calendar_today,
size: 16,
color: Color(0xFF666666),
),
const SizedBox(width: 8),
// 上个月按钮
GestureDetector(
onTap: () {
setState(() {
_calendarSelectedMonth = DateTime(year, month - 1);
});
},
child: const Icon(
Icons.chevron_left,
size: 20,
color: Color(0xFF666666),
),
),
const SizedBox(width: 8),
Text(
'$year年$month月',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Color(0xFF666666),
),
),
const SizedBox(width: 8),
// 下个月按钮
GestureDetector(
onTap: () {
setState(() {
_calendarSelectedMonth = DateTime(year, month + 1);
});
},
child: const Icon(
Icons.chevron_right,
size: 20,
color: Color(0xFF666666),
),
),
],
),
const SizedBox(height: 12),
// 星期标题 - 使用与日期相同的Wrap布局
Wrap(
spacing: 4,
runSpacing: 4,
children: const ['', '', '', '', '', '', '']
.map((d) => SizedBox(
width: 32,
height: 20,
child: Text(
d,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 10, color: Color(0xFF999999)),
),
))
.toList(),
),
const SizedBox(height: 4),
// 日历网格
Wrap(
spacing: 4,
runSpacing: 4,
children: [
// 空白填充
...List.generate(firstWeekday, (_) => const SizedBox(width: 32, height: 28)),
// 日期
...List.generate(daysInMonth, (index) {
final day = index + 1;
final date = DateTime(year, month, day);
final data = dailyData[date];
final count = data?.total ?? 0;
// 计算颜色强度
double opacity = 0.1;
if (count > 0) {
opacity = 0.3 + (count / maxCount * 0.7);
opacity = opacity.clamp(0.3, 1.0);
}
return Container(
width: 32,
height: 28,
decoration: BoxDecoration(
color: count > 0
? const Color(0xFF1A1A1A).withOpacity(opacity)
: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(4),
),
child: Center(
child: Text(
day.toString(),
style: TextStyle(
fontSize: 10,
color: count > 0 ? Colors.white : const Color(0xFF666666),
),
),
),
);
}),
],
),
// 图例
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Text('', style: TextStyle(fontSize: 10, color: Color(0xFF999999))),
const SizedBox(width: 4),
...List.generate(4, (index) {
final opacity = 0.2 + (index * 0.2);
return Container(
width: 10,
height: 10,
margin: const EdgeInsets.symmetric(horizontal: 1),
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A).withOpacity(opacity),
borderRadius: BorderRadius.circular(2),
),
);
}),
const SizedBox(width: 4),
const Text('', style: TextStyle(fontSize: 10, color: Color(0xFF999999))),
],
),
],
),
);
});
});
}
// 日历选中的月份状态(用于 StatefulBuilder
static DateTime? _calendarSelectedMonth;
}
/// 回顾项数据类
@@ -427,3 +623,12 @@ class _MemoryItem {
this.imagePath,
});
}
/// 每日数据
class _DailyData {
int movies = 0;
int books = 0;
int notes = 0;
int get total => movies + books + notes;
}

View File

@@ -11,6 +11,21 @@ class NoteListItem extends StatelessWidget {
const NoteListItem({super.key, required this.note});
@override
Widget build(BuildContext context) {
// 使用 RepaintBoundary 减少重绘
return RepaintBoundary(
child: _NoteListItemContent(note: note),
);
}
}
/// 笔记列表项内容 - 分离出来便于优化
class _NoteListItemContent extends StatelessWidget {
final Note note;
const _NoteListItemContent({required this.note});
@override
Widget build(BuildContext context) {
final isPlainText = note.contentType == 'plain_text';
@@ -50,9 +65,9 @@ class NoteListItem extends StatelessWidget {
),
),
const SizedBox(width: 8),
// 时间
// 时间 - 使用缓存的格式化结果
Text(
_formatDate(note.updatedAt),
_formatDateCached(note.updatedAt),
style: const TextStyle(
fontSize: 11,
color: Color(0xFF999999),
@@ -100,18 +115,11 @@ class NoteListItem extends StatelessWidget {
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: note.images.length > 2 ? 2 : note.images.length,
physics: const NeverScrollableScrollPhysics(), // 禁用滚动,提高性能
itemBuilder: (context, index) {
return Container(
width: 60,
height: 60,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: Image.file(
File(note.images[index]),
fit: BoxFit.cover,
),
return _NoteImage(
imagePath: note.images[index],
index: index,
);
},
),
@@ -148,26 +156,6 @@ class NoteListItem extends StatelessWidget {
);
}
/// 格式化日期
String _formatDate(DateTime date) {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays == 0) {
if (difference.inHours == 0) {
if (difference.inMinutes == 0) {
return '刚刚';
}
return '${difference.inMinutes}分钟前';
}
return '${difference.inHours}小时前';
} else if (difference.inDays < 7) {
return '${difference.inDays}天前';
} else {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
}
/// 显示删除确认对话框
void _showDeleteDialog(BuildContext context) {
showDialog(
@@ -196,3 +184,75 @@ class NoteListItem extends StatelessWidget {
);
}
}
/// 笔记图片组件 - 独立出来便于优化
class _NoteImage extends StatelessWidget {
final String imagePath;
final int index;
const _NoteImage({
required this.imagePath,
required this.index,
});
@override
Widget build(BuildContext context) {
return Container(
width: 60,
height: 60,
margin: EdgeInsets.only(right: index < 1 ? 8 : 0),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: Image.file(
File(imagePath),
fit: BoxFit.cover,
// 使用低内存缓存
cacheWidth: 120,
cacheHeight: 120,
errorBuilder: (_, __, ___) => const Icon(
Icons.broken_image,
size: 24,
color: Color(0xFFCCCCCC),
),
),
);
}
}
// 日期格式化缓存
final Map<DateTime, String> _dateFormatCache = {};
/// 格式化日期(带缓存)
String _formatDateCached(DateTime date) {
// 使用日期部分作为缓存键(忽略时分秒)
final cacheKey = DateTime(date.year, date.month, date.day);
if (_dateFormatCache.containsKey(cacheKey)) {
return _dateFormatCache[cacheKey]!;
}
final result = _formatDate(date);
_dateFormatCache[cacheKey] = result;
return result;
}
/// 格式化日期
String _formatDate(DateTime date) {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays == 0) {
if (difference.inHours == 0) {
if (difference.inMinutes == 0) {
return '刚刚';
}
return '${difference.inMinutes}分钟前';
}
return '${difference.inHours}小时前';
} else if (difference.inDays < 7) {
return '${difference.inDays}天前';
} else {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
}