generated from dellevin/template
优化统计界面
This commit is contained in:
435
lib/pages/encounter_page.dart
Normal file
435
lib/pages/encounter_page.dart
Normal file
@@ -0,0 +1,435 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
|
||||
/// 相遇统计页
|
||||
class EncounterPage extends StatelessWidget {
|
||||
const EncounterPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final userPrefs = UserPrefs();
|
||||
final firstUse = userPrefs.firstUseDate;
|
||||
final now = DateTime.now();
|
||||
final days = DateTime(now.year, now.month, now.day)
|
||||
.difference(DateTime(firstUse.year, firstUse.month, firstUse.day))
|
||||
.inDays + 1;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
appBar: AppBar(title: const Text('统计')),
|
||||
body: Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final movies = provider.movies.where((m) => !m.isDeleted).toList();
|
||||
final books = provider.books.where((b) => !b.isDeleted).toList();
|
||||
final notes = provider.notes.where((n) => !n.isDeleted).toList();
|
||||
|
||||
final noteWords = notes.fold<int>(0, (sum, n) => sum + n.content.length);
|
||||
int imageCount = 0;
|
||||
for (final m in movies) {
|
||||
if (m.posterPath != null && m.posterPath!.isNotEmpty) imageCount++;
|
||||
}
|
||||
for (final b in books) {
|
||||
if (b.coverPath != null && b.coverPath!.isNotEmpty) imageCount++;
|
||||
}
|
||||
for (final n in notes) {
|
||||
imageCount += n.images.length;
|
||||
}
|
||||
|
||||
final totalRecords = movies.length + books.length + notes.length;
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// 上半部分:与你 + 天数
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 48),
|
||||
Text(
|
||||
'与你',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colors.onSurface,
|
||||
letterSpacing: 4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '相遇的第',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '$days',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: colors.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '天',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${firstUse.year}年${firstUse.month}月${firstUse.day}日 — ${now.year}年${now.month}月${now.day}日',
|
||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.3)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 下半部分:用 SliverFillRemaining 推到底部
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Divider(color: colors.outlineVariant, thickness: 0.5),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_statItem(context, '${movies.length}', '影视', Icons.movie_outlined),
|
||||
_statItem(context, '${books.length}', '书籍', Icons.menu_book_outlined),
|
||||
_statItem(context, '${notes.length}', '笔记', Icons.note_outlined),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Divider(color: colors.outlineVariant, thickness: 0.5),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'已记录',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface.withValues(alpha: 0.5),
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_recordItem(context, '$totalRecords', '条记录'),
|
||||
_recordItem(context, _formatCount(noteWords), '文字'),
|
||||
_recordItem(context, '$imageCount', '张图片'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Divider(color: colors.outlineVariant, thickness: 0.5),
|
||||
const SizedBox(height: 32),
|
||||
_buildCuteAnimation(colors),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatCount(int count) {
|
||||
if (count >= 10000) return '${(count / 10000).toStringAsFixed(1)}万';
|
||||
if (count >= 1000) return '${(count / 1000).toStringAsFixed(1)}k';
|
||||
return '$count';
|
||||
}
|
||||
|
||||
Widget _statItem(BuildContext context, String value, String label, IconData icon) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Column(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: colors.onSurface.withValues(alpha: 0.3)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: colors.onSurface),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _recordItem(BuildContext context, String value, String label) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.primary),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 慢速滚动城市天际线
|
||||
Widget _buildCuteAnimation(ColorScheme colors) {
|
||||
return SizedBox(
|
||||
height: 72,
|
||||
child: _CityScape(colors: colors),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CityScape extends StatefulWidget {
|
||||
final ColorScheme colors;
|
||||
const _CityScape({required this.colors});
|
||||
|
||||
@override
|
||||
State<_CityScape> createState() => _CityScapeState();
|
||||
}
|
||||
|
||||
class _CityScapeState extends State<_CityScape> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 20),
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, _) {
|
||||
return CustomPaint(
|
||||
size: const Size(double.infinity, 72),
|
||||
painter: _CityPainter(_controller.value, widget.colors),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CityPainter extends CustomPainter {
|
||||
final double t;
|
||||
final ColorScheme colors;
|
||||
_CityPainter(this.t, this.colors);
|
||||
|
||||
// 后层建筑数据(高度比, 宽度, 是否尖顶)
|
||||
static const _buildings = <(double, double, bool)>[
|
||||
(0.30, 16, false),
|
||||
(0.48, 10, true),
|
||||
(0.22, 20, false),
|
||||
(0.55, 10, true),
|
||||
(0.35, 14, false),
|
||||
(0.42, 10, true),
|
||||
(0.25, 18, false),
|
||||
];
|
||||
|
||||
// 前层绿植数据(高度比, 宽度, 类型: 0=圆冠, 1=松树, 2=灌木)
|
||||
static const _plants = <(double, double, int)>[
|
||||
(0.35, 10, 0),
|
||||
(0.50, 8, 1),
|
||||
(0.22, 14, 2),
|
||||
(0.45, 8, 0),
|
||||
(0.30, 12, 1),
|
||||
(0.20, 10, 2),
|
||||
(0.52, 8, 1),
|
||||
(0.28, 14, 2),
|
||||
];
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..style = PaintingStyle.fill;
|
||||
final groundY = size.height - 2;
|
||||
|
||||
// 星星
|
||||
_drawStars(canvas, paint, size, t);
|
||||
|
||||
// 后层建筑(慢 0.3x,浅色)
|
||||
_drawBuildings(canvas, paint, size, groundY, t * 0.6);
|
||||
|
||||
// 前层绿植(快 1.5x,深色)
|
||||
_drawPlants(canvas, paint, size, groundY, t * 1.0);
|
||||
|
||||
// 地面线
|
||||
paint.color = colors.onSurface.withValues(alpha: 0.10);
|
||||
canvas.drawRect(Rect.fromLTWH(0, groundY, size.width, 1), paint);
|
||||
}
|
||||
|
||||
// ── 后层:建筑 ──
|
||||
|
||||
void _drawBuildings(Canvas canvas, Paint paint, Size size, double groundY, double scrollT) {
|
||||
double totalW = 0;
|
||||
for (final b in _buildings) {
|
||||
totalW += b.$2 + 4;
|
||||
}
|
||||
final offset = (scrollT * totalW) % totalW;
|
||||
|
||||
double x = -offset;
|
||||
int i = 0;
|
||||
while (x < size.width + 20) {
|
||||
final (hR, w, spire) = _buildings[i % _buildings.length];
|
||||
final h = hR * (size.height - 8);
|
||||
final bx = x;
|
||||
final by = groundY - h;
|
||||
|
||||
if (bx + w > -10 && bx < size.width + 10) {
|
||||
final a = 0.06 + hR * 0.05;
|
||||
paint.color = colors.onSurface.withValues(alpha: a);
|
||||
canvas.drawRect(Rect.fromLTWH(bx, by, w, h), paint);
|
||||
|
||||
// 窗户
|
||||
if (h > 18) {
|
||||
paint.color = colors.onSurface.withValues(alpha: 0.04);
|
||||
for (int r = 0; r < ((h - 6) / 5).floor(); r++) {
|
||||
for (int c = 0; c < ((w - 4) / 4).floor(); c++) {
|
||||
if ((i * 13 + r * 7 + c * 11) % 4 == 0) continue;
|
||||
canvas.drawRect(Rect.fromLTWH(bx + 3 + c * 4.0, by + 4 + r * 5.0, 2, 2), paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spire) {
|
||||
paint.color = colors.onSurface.withValues(alpha: a);
|
||||
final sh = h * 0.18;
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(bx + w / 2 - 2, by)
|
||||
..lineTo(bx + w / 2, by - sh)
|
||||
..lineTo(bx + w / 2 + 2, by)
|
||||
..close(),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
if (!spire && hR > 0.4 && i % 3 == 0) {
|
||||
paint.color = colors.onSurface.withValues(alpha: a * 0.5);
|
||||
canvas.drawRect(Rect.fromLTWH(bx + w / 2 - 0.5, by - 6, 1, 6), paint);
|
||||
canvas.drawCircle(Offset(bx + w / 2, by - 6), 1.2, paint);
|
||||
}
|
||||
}
|
||||
x += w + 16;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 前层:绿植 ──
|
||||
|
||||
void _drawPlants(Canvas canvas, Paint paint, Size size, double groundY, double scrollT) {
|
||||
double totalW = 0;
|
||||
for (final p in _plants) {
|
||||
totalW += p.$2 + 6;
|
||||
}
|
||||
final offset = (scrollT * totalW) % totalW;
|
||||
|
||||
double x = -offset;
|
||||
int i = 0;
|
||||
while (x < size.width + 20) {
|
||||
final (hR, w, type) = _plants[i % _plants.length];
|
||||
final h = hR * (size.height - 10);
|
||||
final bx = x;
|
||||
final by = groundY;
|
||||
|
||||
if (bx + w > -10 && bx < size.width + 10) {
|
||||
final alpha = 0.18 + hR * 0.10;
|
||||
|
||||
if (type == 0) {
|
||||
// 圆冠树
|
||||
final trunkH = h * 0.4;
|
||||
final crownR = w * 0.45;
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha * 0.7);
|
||||
canvas.drawRect(Rect.fromLTWH(bx + w / 2 - 1.5, by - trunkH, 3, trunkH), paint);
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(center: Offset(bx + w / 2, by - trunkH - crownR * 0.6), width: crownR * 2, height: crownR * 1.6),
|
||||
paint,
|
||||
);
|
||||
} else if (type == 1) {
|
||||
// 松树
|
||||
final trunkH = h * 0.25;
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha * 0.7);
|
||||
canvas.drawRect(Rect.fromLTWH(bx + w / 2 - 1.5, by - trunkH, 3, trunkH), paint);
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha);
|
||||
for (int layer = 0; layer < 3; layer++) {
|
||||
final layerW = w * (1.0 - layer * 0.2);
|
||||
final layerBottom = by - trunkH - layer * (h * 0.2);
|
||||
final layerTop = layerBottom - h * 0.28;
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(bx + w / 2 - layerW / 2, layerBottom)
|
||||
..lineTo(bx + w / 2, layerTop)
|
||||
..lineTo(bx + w / 2 + layerW / 2, layerBottom)
|
||||
..close(),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 灌木
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha);
|
||||
canvas.drawOval(Rect.fromLTWH(bx, by - h, w, h), paint);
|
||||
paint.color = colors.onSurface.withValues(alpha: alpha * 0.8);
|
||||
canvas.drawOval(Rect.fromLTWH(bx + w * 0.2, by - h * 0.7, w * 0.6, h * 0.6), paint);
|
||||
}
|
||||
}
|
||||
x += w + 14;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void _drawStars(Canvas canvas, Paint paint, Size size, double t) {
|
||||
const stars = [
|
||||
(12.0, 5.0, 1.2), (38.0, 12.0, 0.8), (65.0, 3.0, 1.0),
|
||||
(95.0, 16.0, 1.4), (130.0, 7.0, 0.9), (165.0, 14.0, 1.1),
|
||||
(200.0, 4.0, 1.3), (235.0, 18.0, 0.7), (270.0, 9.0, 1.0),
|
||||
(310.0, 2.0, 1.2), (345.0, 15.0, 0.9), (380.0, 6.0, 1.1),
|
||||
(420.0, 11.0, 0.8), (460.0, 3.0, 1.0), (500.0, 17.0, 1.3),
|
||||
];
|
||||
for (int i = 0; i < stars.length; i++) {
|
||||
final (sx, sy, r) = stars[i];
|
||||
if (sx > size.width) continue;
|
||||
final flicker = 0.15 + 0.12 * sin(t * 2 * pi + i * 1.1);
|
||||
paint.color = colors.onSurface.withValues(alpha: flicker);
|
||||
canvas.drawCircle(Offset(sx, sy), r, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CityPainter old) => old.t != t;
|
||||
}
|
||||
@@ -900,6 +900,18 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const MainContentSettingsPage())),
|
||||
),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildNavigationItem(
|
||||
icon: Icons.view_sidebar_outlined,
|
||||
title: '侧边栏功能设置',
|
||||
subtitle: '控制侧边栏显示的功能模块',
|
||||
onTap: () => Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => const SidebarSettingsPage())),
|
||||
),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
@@ -2415,3 +2427,160 @@ class _WebViewPageState extends State<WebViewPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 侧边栏功能设置 ───
|
||||
|
||||
class SidebarSettingsPage extends StatefulWidget {
|
||||
const SidebarSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SidebarSettingsPage> createState() => _SidebarSettingsPageState();
|
||||
}
|
||||
|
||||
class _SidebarSettingsPageState extends State<SidebarSettingsPage> {
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
bool _showHeatmap = true;
|
||||
bool _showRecent = true;
|
||||
bool _showEncounter = true;
|
||||
bool _showStroll = true;
|
||||
bool _showCalendar = true;
|
||||
bool _showPerson = true;
|
||||
bool _showTags = true;
|
||||
bool _showMdReader = true;
|
||||
bool _showEpub = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
void _loadSettings() {
|
||||
setState(() {
|
||||
_showHeatmap = _userPrefs.showSidebarHeatmap;
|
||||
_showRecent = _userPrefs.showSidebarRecent;
|
||||
_showEncounter = _userPrefs.showSidebarEncounter;
|
||||
_showStroll = _userPrefs.showSidebarStroll;
|
||||
_showCalendar = _userPrefs.showSidebarCalendar;
|
||||
_showPerson = _userPrefs.showSidebarPerson;
|
||||
_showTags = _userPrefs.showSidebarTags;
|
||||
_showMdReader = _userPrefs.showSidebarMdReader;
|
||||
_showEpub = _userPrefs.showSidebarEpub;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
appBar: AppBar(title: const Text('侧边栏功能设置')),
|
||||
body: ListView(
|
||||
children: [
|
||||
_buildSectionHeader('信息模块'),
|
||||
_buildSwitchItem(Icons.calendar_today, '热力图', '显示创作活跃度热力图',
|
||||
_showHeatmap, (v) async {
|
||||
await _userPrefs.setShowSidebarHeatmap(v);
|
||||
setState(() => _showHeatmap = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.schedule, '最近添加', '显示最近添加的记录',
|
||||
_showRecent, (v) async {
|
||||
await _userPrefs.setShowSidebarRecent(v);
|
||||
setState(() => _showRecent = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.favorite_border, '统计', '与应用相遇的天数和数据概览',
|
||||
_showEncounter, (v) async {
|
||||
await _userPrefs.setShowSidebarEncounter(v);
|
||||
setState(() => _showEncounter = v);
|
||||
}),
|
||||
_buildSectionHeader('快捷功能'),
|
||||
_buildSwitchItem(Icons.explore_outlined, '漫步', '随机发现内容',
|
||||
_showStroll, (v) async {
|
||||
await _userPrefs.setShowSidebarStroll(v);
|
||||
setState(() => _showStroll = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.calendar_month_outlined, '书影日历', '按日历查看记录',
|
||||
_showCalendar, (v) async {
|
||||
await _userPrefs.setShowSidebarCalendar(v);
|
||||
setState(() => _showCalendar = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.people_outline, '角色信息', '管理影视和书籍中的角色',
|
||||
_showPerson, (v) async {
|
||||
await _userPrefs.setShowSidebarPerson(v);
|
||||
setState(() => _showPerson = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.label_outline, '标签管理', '管理所有标签',
|
||||
_showTags, (v) async {
|
||||
await _userPrefs.setShowSidebarTags(v);
|
||||
setState(() => _showTags = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.description_outlined, 'MD阅读', 'Markdown 文件阅读器',
|
||||
_showMdReader, (v) async {
|
||||
await _userPrefs.setShowSidebarMdReader(v);
|
||||
setState(() => _showMdReader = v);
|
||||
}),
|
||||
Divider(height: 0.5, indent: 24, endIndent: 24, color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.auto_stories_outlined, 'EPUB阅读', 'EPUB 电子书阅读器',
|
||||
_showEpub, (v) async {
|
||||
await _userPrefs.setShowSidebarEpub(v);
|
||||
setState(() => _showEpub = v);
|
||||
}),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Text('关闭后对应功能将从侧边栏中隐藏。',
|
||||
style: TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.3))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 20, 24, 8),
|
||||
child: Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSwitchItem(IconData icon, String title, String subtitle,
|
||||
bool value, ValueChanged<bool> onChanged) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(icon,
|
||||
color: colors.onSurface.withValues(alpha: 0.6), size: 18)),
|
||||
title: Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colors.onSurface)),
|
||||
subtitle: Text(subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||
trailing: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: colors.primary,
|
||||
activeTrackColor: colors.primary.withValues(alpha: 0.3),
|
||||
inactiveThumbColor: colors.surface,
|
||||
inactiveTrackColor: colors.outline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ class UserPrefs {
|
||||
final oldValue = _prefs!.getBool('isDarkMode') ?? false;
|
||||
await _prefs!.setInt('themeMode', oldValue ? 2 : 0);
|
||||
}
|
||||
// 记录首次使用日期
|
||||
if (!_prefs!.containsKey('firstUseDate')) {
|
||||
await _prefs!.setString('firstUseDate', DateTime.now().toIso8601String());
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取实例
|
||||
@@ -27,7 +31,14 @@ class UserPrefs {
|
||||
}
|
||||
|
||||
// ========== 用户信息 ==========
|
||||
|
||||
|
||||
/// 首次使用日期
|
||||
DateTime get firstUseDate {
|
||||
final str = prefs.getString('firstUseDate');
|
||||
if (str != null) return DateTime.tryParse(str) ?? DateTime.now();
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// 昵称
|
||||
String get nickname => prefs.getString('nickname') ?? 'Mook';
|
||||
Future<bool> setNickname(String value) => prefs.setString('nickname', value);
|
||||
@@ -101,6 +112,35 @@ class UserPrefs {
|
||||
int get defaultMainTabIndex => prefs.getInt('defaultMainTabIndex') ?? 0;
|
||||
Future<bool> setDefaultMainTabIndex(int value) => prefs.setInt('defaultMainTabIndex', value);
|
||||
|
||||
// ─── 侧边栏功能开关 ───
|
||||
|
||||
bool get showSidebarHeatmap => prefs.getBool('showSidebarHeatmap') ?? true;
|
||||
Future<bool> setShowSidebarHeatmap(bool value) => prefs.setBool('showSidebarHeatmap', value);
|
||||
|
||||
bool get showSidebarRecent => prefs.getBool('showSidebarRecent') ?? true;
|
||||
Future<bool> setShowSidebarRecent(bool value) => prefs.setBool('showSidebarRecent', value);
|
||||
|
||||
bool get showSidebarEncounter => prefs.getBool('showSidebarEncounter') ?? true;
|
||||
Future<bool> setShowSidebarEncounter(bool value) => prefs.setBool('showSidebarEncounter', value);
|
||||
|
||||
bool get showSidebarStroll => prefs.getBool('showSidebarStroll') ?? true;
|
||||
Future<bool> setShowSidebarStroll(bool value) => prefs.setBool('showSidebarStroll', value);
|
||||
|
||||
bool get showSidebarCalendar => prefs.getBool('showSidebarCalendar') ?? true;
|
||||
Future<bool> setShowSidebarCalendar(bool value) => prefs.setBool('showSidebarCalendar', value);
|
||||
|
||||
bool get showSidebarPerson => prefs.getBool('showSidebarPerson') ?? true;
|
||||
Future<bool> setShowSidebarPerson(bool value) => prefs.setBool('showSidebarPerson', value);
|
||||
|
||||
bool get showSidebarTags => prefs.getBool('showSidebarTags') ?? true;
|
||||
Future<bool> setShowSidebarTags(bool value) => prefs.setBool('showSidebarTags', value);
|
||||
|
||||
bool get showSidebarMdReader => prefs.getBool('showSidebarMdReader') ?? true;
|
||||
Future<bool> setShowSidebarMdReader(bool value) => prefs.setBool('showSidebarMdReader', value);
|
||||
|
||||
bool get showSidebarEpub => prefs.getBool('showSidebarEpub') ?? true;
|
||||
Future<bool> setShowSidebarEpub(bool value) => prefs.setBool('showSidebarEpub', value);
|
||||
|
||||
/// 笔记布局样式 (0: 列表, 1: 瀑布流, 2: 时间线)
|
||||
int get noteLayoutStyle => prefs.getInt('noteLayoutStyle') ?? 0;
|
||||
Future<bool> setNoteLayoutStyle(int value) => prefs.setInt('noteLayoutStyle', value);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../pages/encounter_page.dart';
|
||||
import '../pages/stroll_page.dart';
|
||||
import '../pages/media_calendar_page.dart';
|
||||
import '../pages/person_list_page.dart';
|
||||
@@ -28,6 +29,16 @@ class CustomDrawer extends StatefulWidget {
|
||||
class _CustomDrawerState extends State<CustomDrawer> {
|
||||
String _version = '0.1.5';
|
||||
|
||||
// 热力图缓存
|
||||
List<Movie>? _cachedMovies;
|
||||
List<Book>? _cachedBooks;
|
||||
List<Note>? _cachedNotes;
|
||||
Map<DateTime, int>? _cachedDailyCounts;
|
||||
int? _cachedMaxCount;
|
||||
|
||||
// 最近添加缓存
|
||||
List<_RecentItem>? _cachedRecentItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -42,6 +53,17 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final userPrefs = UserPrefs();
|
||||
final showHeatmap = userPrefs.showSidebarHeatmap;
|
||||
final showRecent = userPrefs.showSidebarRecent;
|
||||
final showTools = userPrefs.showSidebarEncounter ||
|
||||
userPrefs.showSidebarStroll ||
|
||||
userPrefs.showSidebarCalendar ||
|
||||
userPrefs.showSidebarPerson ||
|
||||
userPrefs.showSidebarTags ||
|
||||
userPrefs.showSidebarMdReader ||
|
||||
userPrefs.showSidebarEpub;
|
||||
|
||||
return Drawer(
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
child: SafeArea(
|
||||
@@ -50,12 +72,18 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildProfileCard(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildCalendarSection(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildRecentSection(context),
|
||||
const SizedBox(height: 16),
|
||||
_buildToolsCard(context),
|
||||
if (showHeatmap) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildCalendarSection(context),
|
||||
],
|
||||
if (showRecent) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildRecentSection(context),
|
||||
],
|
||||
if (showTools) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildToolsCard(context),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 32),
|
||||
child: Center(
|
||||
@@ -162,6 +190,19 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
|
||||
Widget _buildToolsCard(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final userPrefs = UserPrefs();
|
||||
|
||||
final items = <(IconData, String, Widget)>[];
|
||||
if (userPrefs.showSidebarEncounter) items.add((Icons.favorite_border, '统计', const EncounterPage()));
|
||||
if (userPrefs.showSidebarStroll) items.add((Icons.explore_outlined, '漫步', const StrollPage()));
|
||||
if (userPrefs.showSidebarCalendar) items.add((Icons.calendar_month_outlined, '书影日历', const MediaCalendarPage()));
|
||||
if (userPrefs.showSidebarPerson) items.add((Icons.people_outline, '角色信息', const PersonListPage()));
|
||||
if (userPrefs.showSidebarTags) items.add((Icons.label_outline, '标签管理', const TagManagementPage()));
|
||||
if (userPrefs.showSidebarMdReader) items.add((Icons.description_outlined, 'MD阅读', const MdReaderTabPage()));
|
||||
if (userPrefs.showSidebarEpub) items.add((Icons.auto_stories_outlined, 'EPUB阅读', const EpubLibraryPage()));
|
||||
|
||||
if (items.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -169,37 +210,17 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildToolItem(Icons.explore_outlined, '漫步', () {
|
||||
children: List.generate(items.length * 2 - 1, (i) {
|
||||
if (i.isOdd) {
|
||||
return Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant);
|
||||
}
|
||||
final idx = i ~/ 2;
|
||||
final (icon, title, page) = items[idx];
|
||||
return _buildToolItem(icon, title, () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const StrollPage()));
|
||||
}, topRounded: true),
|
||||
Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant),
|
||||
_buildToolItem(Icons.calendar_month_outlined, '书影日历', () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const MediaCalendarPage()));
|
||||
}),
|
||||
Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant),
|
||||
_buildToolItem(Icons.people_outline, '角色信息', () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const PersonListPage()));
|
||||
}),
|
||||
Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant),
|
||||
_buildToolItem(Icons.label_outline, '标签管理', () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const TagManagementPage()));
|
||||
}),
|
||||
Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant),
|
||||
_buildToolItem(Icons.description_outlined, 'MD阅读', () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const MdReaderTabPage()));
|
||||
}),
|
||||
Divider(height: 1, indent: 52, endIndent: 20, color: colors.outlineVariant),
|
||||
_buildToolItem(Icons.auto_stories_outlined, 'EPUB阅读', () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const EpubLibraryPage()));
|
||||
}, bottomRounded: true),
|
||||
],
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => page));
|
||||
}, topRounded: idx == 0, bottomRounded: idx == items.length - 1);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -233,29 +254,75 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
|
||||
// ─── 热力图 ──────────────────────────────────────────────────────────
|
||||
|
||||
Widget _buildCalendarSection(BuildContext context) {
|
||||
return Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final Map<DateTime, int> dailyCounts = {};
|
||||
for (final movie in provider.movies.where((m) => !m.isDeleted)) {
|
||||
final date = DateTime(movie.createdAt.year, movie.createdAt.month, movie.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
for (final book in provider.books.where((b) => !b.isDeleted)) {
|
||||
final date = DateTime(book.createdAt.year, book.createdAt.month, book.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
for (final note in provider.notes.where((n) => !n.isDeleted)) {
|
||||
final date = DateTime(note.createdAt.year, note.createdAt.month, note.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
// ─── 热力图缓存计算 ───
|
||||
|
||||
int maxCount = 0;
|
||||
for (final c in dailyCounts.values) {
|
||||
if (c > maxCount) maxCount = c;
|
||||
}
|
||||
if (maxCount == 0) maxCount = 1;
|
||||
(int, Map<DateTime, int>) _computeDailyCounts(List<Movie> movies, List<Book> books, List<Note> notes) {
|
||||
if (identical(movies, _cachedMovies) &&
|
||||
identical(books, _cachedBooks) &&
|
||||
identical(notes, _cachedNotes) &&
|
||||
_cachedDailyCounts != null) {
|
||||
return (_cachedMaxCount!, _cachedDailyCounts!);
|
||||
}
|
||||
|
||||
final dailyCounts = <DateTime, int>{};
|
||||
for (final movie in movies.where((m) => !m.isDeleted)) {
|
||||
final date = DateTime(movie.createdAt.year, movie.createdAt.month, movie.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
for (final book in books.where((b) => !b.isDeleted)) {
|
||||
final date = DateTime(book.createdAt.year, book.createdAt.month, book.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
for (final note in notes.where((n) => !n.isDeleted)) {
|
||||
final date = DateTime(note.createdAt.year, note.createdAt.month, note.createdAt.day);
|
||||
dailyCounts[date] = (dailyCounts[date] ?? 0) + 1;
|
||||
}
|
||||
|
||||
int maxCount = 0;
|
||||
for (final c in dailyCounts.values) {
|
||||
if (c > maxCount) maxCount = c;
|
||||
}
|
||||
if (maxCount == 0) maxCount = 1;
|
||||
|
||||
_cachedMovies = movies;
|
||||
_cachedBooks = books;
|
||||
_cachedNotes = notes;
|
||||
_cachedDailyCounts = dailyCounts;
|
||||
_cachedMaxCount = maxCount;
|
||||
return (maxCount, dailyCounts);
|
||||
}
|
||||
|
||||
List<_RecentItem> _computeRecentItems(List<Movie> movies, List<Book> books, List<Note> notes) {
|
||||
if (identical(movies, _cachedMovies) &&
|
||||
identical(books, _cachedBooks) &&
|
||||
identical(notes, _cachedNotes) &&
|
||||
_cachedRecentItems != null) {
|
||||
return _cachedRecentItems!;
|
||||
}
|
||||
|
||||
final items = <_RecentItem>[];
|
||||
for (final m in movies.where((m) => !m.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'movie', title: m.title, date: m.createdAt, data: m));
|
||||
}
|
||||
for (final b in books.where((b) => !b.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'book', title: b.title, date: b.createdAt, data: b));
|
||||
}
|
||||
for (final n in notes.where((n) => !n.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'note', title: n.title.isNotEmpty ? n.title : '随手记', date: n.createdAt, data: n));
|
||||
}
|
||||
items.sort((a, b) => b.date.compareTo(a.date));
|
||||
|
||||
_cachedRecentItems = items;
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget _buildCalendarSection(BuildContext context) {
|
||||
final movies = context.select<AppProvider, List<Movie>>((p) => p.movies);
|
||||
final books = context.select<AppProvider, List<Book>>((p) => p.books);
|
||||
final notes = context.select<AppProvider, List<Note>>((p) => p.notes);
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
final (maxCount, dailyCounts) = _computeDailyCounts(movies, books, notes);
|
||||
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
@@ -348,8 +415,6 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Color _heatmapColor(int count, int maxCount) {
|
||||
@@ -369,71 +434,55 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
||||
// ─── 最近添加 ────────────────────────────────────────────────────────
|
||||
|
||||
Widget _buildRecentSection(BuildContext context) {
|
||||
return Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final recent = _getRecentItems(provider);
|
||||
if (recent.isEmpty) return const SizedBox.shrink();
|
||||
final movies = context.select<AppProvider, List<Movie>>((p) => p.movies);
|
||||
final books = context.select<AppProvider, List<Book>>((p) => p.books);
|
||||
final notes = context.select<AppProvider, List<Note>>((p) => p.notes);
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final recent = _computeRecentItems(movies, books, notes);
|
||||
if (recent.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.schedule, size: 14, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||
const SizedBox(width: 8),
|
||||
Text('最近添加', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
...recent.take(4).map((item) => InkWell(
|
||||
onTap: () => _openRecentItem(context, item),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, top: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
item.type == 'movie' ? Icons.movie_outlined : item.type == 'book' ? Icons.menu_book_outlined : Icons.note_outlined,
|
||||
size: 14, color: colors.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(item.title, maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.75))),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(_recentTimeAgo(item.date), style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.25))),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
Icon(Icons.schedule, size: 14, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||
const SizedBox(width: 8),
|
||||
Text('最近添加', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
const SizedBox(height: 14),
|
||||
...recent.take(4).map((item) => InkWell(
|
||||
onTap: () => _openRecentItem(context, item),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, top: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
item.type == 'movie' ? Icons.movie_outlined : item.type == 'book' ? Icons.menu_book_outlined : Icons.note_outlined,
|
||||
size: 14, color: colors.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(item.title, maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.75))),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(_recentTimeAgo(item.date), style: TextStyle(fontSize: 10, color: colors.onSurface.withValues(alpha: 0.25))),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<_RecentItem> _getRecentItems(AppProvider provider) {
|
||||
final items = <_RecentItem>[];
|
||||
for (final m in provider.movies.where((m) => !m.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'movie', title: m.title, date: m.createdAt, data: m));
|
||||
}
|
||||
for (final b in provider.books.where((b) => !b.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'book', title: b.title, date: b.createdAt, data: b));
|
||||
}
|
||||
for (final n in provider.notes.where((n) => !n.isDeleted)) {
|
||||
items.add(_RecentItem(type: 'note', title: n.title.isNotEmpty ? n.title : '随手记', date: n.createdAt, data: n));
|
||||
}
|
||||
items.sort((a, b) => b.date.compareTo(a.date));
|
||||
return items;
|
||||
}
|
||||
|
||||
void _openRecentItem(BuildContext context, _RecentItem item) {
|
||||
Navigator.pop(context);
|
||||
switch (item.type) {
|
||||
|
||||
Reference in New Issue
Block a user