generated from dellevin/template
ok了老铁
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import '../models/data_models.dart';
|
||||
|
||||
/// 自定义左侧弹出菜单 - 极简主义设计
|
||||
class CustomDrawer extends StatelessWidget {
|
||||
@@ -20,6 +22,11 @@ class CustomDrawer extends StatelessWidget {
|
||||
|
||||
const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)),
|
||||
|
||||
// 回顾功能区域
|
||||
_buildMemorySection(context),
|
||||
|
||||
const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)),
|
||||
|
||||
// 菜单项列表
|
||||
Expanded(
|
||||
child: ListView(
|
||||
@@ -44,6 +51,219 @@ class CustomDrawer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建回顾功能区域
|
||||
Widget _buildMemorySection(BuildContext context) {
|
||||
return Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final memoryItem = _getRandomMemoryItem(provider);
|
||||
|
||||
if (memoryItem == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final memoryText = _buildMemoryText(memoryItem);
|
||||
final timeAgo = _getTimeAgoText(memoryItem.date);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.history,
|
||||
size: 16,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'回顾',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 内容卡片(带头图)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 头图(影视/书籍显示,笔记不显示)
|
||||
if (memoryItem.imagePath != null && memoryItem.imagePath!.isNotEmpty)
|
||||
Container(
|
||||
width: 60,
|
||||
height: 80,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Image.file(
|
||||
File(memoryItem.imagePath!),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => const Icon(
|
||||
Icons.image,
|
||||
color: Color(0xFFCCCCCC),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (memoryItem.type != 'note')
|
||||
Container(
|
||||
width: 60,
|
||||
height: 80,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Icon(
|
||||
memoryItem.type == 'movie' ? Icons.movie : Icons.menu_book,
|
||||
color: const Color(0xFFCCCCCC),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
|
||||
// 文字内容
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 时间标签
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
timeAgo,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// 内容文字
|
||||
Text(
|
||||
memoryText,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF1A1A1A),
|
||||
height: 1.5,
|
||||
),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取随机回顾项
|
||||
_MemoryItem? _getRandomMemoryItem(AppProvider provider) {
|
||||
final now = DateTime.now();
|
||||
final candidates = <_MemoryItem>[];
|
||||
|
||||
// 收集所有未删除的影视、书籍(去掉笔记)
|
||||
for (final movie in provider.movies.where((m) => !m.isDeleted)) {
|
||||
candidates.add(_MemoryItem(
|
||||
type: 'movie',
|
||||
title: movie.title,
|
||||
date: movie.createdAt,
|
||||
imagePath: movie.posterPath,
|
||||
));
|
||||
}
|
||||
|
||||
for (final book in provider.books.where((b) => !b.isDeleted)) {
|
||||
candidates.add(_MemoryItem(
|
||||
type: 'book',
|
||||
title: book.title,
|
||||
date: book.createdAt,
|
||||
imagePath: book.coverPath,
|
||||
));
|
||||
}
|
||||
|
||||
if (candidates.isEmpty) return null;
|
||||
|
||||
// 优先选择1个月、3个月、6个月、1年前的数据
|
||||
final oneMonthAgo = now.subtract(const Duration(days: 30));
|
||||
final threeMonthsAgo = now.subtract(const Duration(days: 90));
|
||||
final sixMonthsAgo = now.subtract(const Duration(days: 180));
|
||||
final oneYearAgo = now.subtract(const Duration(days: 365));
|
||||
|
||||
final memoryCandidates = candidates.where((item) {
|
||||
return _isInTimeRange(item.date, oneMonthAgo, threeMonthsAgo, sixMonthsAgo, oneYearAgo);
|
||||
}).toList();
|
||||
|
||||
// 如果有符合时间范围的,从中随机选择;否则从所有数据中随机选择
|
||||
final random = Random();
|
||||
final selectedList = memoryCandidates.isNotEmpty ? memoryCandidates : candidates;
|
||||
return selectedList[random.nextInt(selectedList.length)];
|
||||
}
|
||||
|
||||
/// 检查时间是否在范围内(1月、3月、6月、1年前)
|
||||
bool _isInTimeRange(DateTime date, DateTime oneMonth, DateTime threeMonths,
|
||||
DateTime sixMonths, DateTime oneYear) {
|
||||
// 检查是否在1个月前左右(±7天)
|
||||
if (_isCloseTo(date, oneMonth)) return true;
|
||||
// 检查是否在3个月前左右(±14天)
|
||||
if (_isCloseTo(date, threeMonths, days: 14)) return true;
|
||||
// 检查是否在6个月前左右(±30天)
|
||||
if (_isCloseTo(date, sixMonths, days: 30)) return true;
|
||||
// 检查是否在1年前左右(±30天)
|
||||
if (_isCloseTo(date, oneYear, days: 30)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 检查两个日期是否接近
|
||||
bool _isCloseTo(DateTime date, DateTime target, {int days = 7}) {
|
||||
final diff = date.difference(target).inDays.abs();
|
||||
return diff <= days;
|
||||
}
|
||||
|
||||
/// 构建回顾文本
|
||||
String _buildMemoryText(_MemoryItem item) {
|
||||
// 只显示标题,不添加描述前缀
|
||||
return '《${item.title}》';
|
||||
}
|
||||
|
||||
/// 获取时间描述文本
|
||||
String _getTimeAgoText(DateTime date) {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(date);
|
||||
|
||||
if (diff.inDays >= 365) {
|
||||
return '1年前';
|
||||
} else if (diff.inDays >= 180) {
|
||||
return '6个月前';
|
||||
} else if (diff.inDays >= 90) {
|
||||
return '3个月前';
|
||||
} else if (diff.inDays >= 30) {
|
||||
return '1个月前';
|
||||
} else {
|
||||
return '${diff.inDays}天前';
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建头部
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Consumer<AppProvider>(
|
||||
@@ -192,3 +412,18 @@ class CustomDrawer extends StatelessWidget {
|
||||
ToastUtil.show(context, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 回顾项数据类
|
||||
class _MemoryItem {
|
||||
final String type; // 'movie', 'book', 'note'
|
||||
final String title;
|
||||
final DateTime date;
|
||||
final String? imagePath; // 头图路径(影视/书籍有,笔记无)
|
||||
|
||||
_MemoryItem({
|
||||
required this.type,
|
||||
required this.title,
|
||||
required this.date,
|
||||
this.imagePath,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user