generated from dellevin/template
功能优化
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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')}';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user