Skip to content

Commit a951808

Browse files
committed
优化了性能
1 parent 3d663cb commit a951808

File tree

6 files changed

+297
-144
lines changed

6 files changed

+297
-144
lines changed

lib/themes/fluent/pages/fluent_new_series_page.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
3232
String? _error;
3333
bool _isReversed = false;
3434
String _searchQuery = '';
35+
Map<int, List<BangumiAnime>>? _groupedAnimesCache;
36+
bool _groupCacheDirty = true;
3537

3638
// 分组状态
3739
final Map<int, bool> _expansionStates = {};
@@ -112,6 +114,7 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
112114
setState(() {
113115
_animes = animes;
114116
_isLoading = false;
117+
_groupCacheDirty = true;
115118
});
116119
}
117120
} catch (e) {
@@ -137,6 +140,7 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
137140

138141
/// 按星期几分组番剧并应用筛选
139142
Map<int, List<BangumiAnime>> _groupAnimesByWeekday() {
143+
_groupCacheDirty = false;
140144
final grouped = <int, List<BangumiAnime>>{};
141145

142146
// 应用筛选条件
@@ -190,6 +194,13 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
190194
return grouped;
191195
}
192196

197+
Map<int, List<BangumiAnime>> _getGroupedAnimes() {
198+
if (_groupCacheDirty || _groupedAnimesCache == null) {
199+
_groupedAnimesCache = _groupAnimesByWeekday();
200+
}
201+
return _groupedAnimesCache!;
202+
}
203+
193204
/// 显示番剧详情
194205
Future<void> _showAnimeDetail(BangumiAnime anime) async {
195206
final result = await ThemedAnimeDetail.show(context, anime.id);
@@ -283,6 +294,7 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
283294
_searchQuery = query;
284295
_showOnlyAiring = airing;
285296
_minRating = rating;
297+
_groupCacheDirty = true;
286298
});
287299
},
288300
),
@@ -424,7 +436,7 @@ class _FluentNewSeriesPageState extends State<FluentNewSeriesPage>
424436
);
425437
}
426438

427-
final groupedAnimes = _groupAnimesByWeekday();
439+
final groupedAnimes = _getGroupedAnimes();
428440

429441
if (groupedAnimes.isEmpty) {
430442
return Center(
@@ -1080,4 +1092,4 @@ class _FluentTagSearchDialogState extends State<_FluentTagSearchDialog> {
10801092
],
10811093
);
10821094
}
1083-
}
1095+
}

lib/themes/fluent/pages/fluent_settings/fluent_watch_history_page.dart

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class FluentWatchHistoryPage extends StatefulWidget {
2222
class _FluentWatchHistoryPageState extends State<FluentWatchHistoryPage> {
2323
bool _isAutoMatching = false;
2424
bool _autoMatchDialogVisible = false;
25+
final Map<String, Future<Uint8List?>> _thumbnailFutures = {};
26+
List<WatchHistoryItem> _cachedValidHistory = const [];
27+
int _lastHistoryHash = 0;
2528

2629
@override
2730
Widget build(BuildContext context) {
@@ -38,8 +41,7 @@ class _FluentWatchHistoryPageState extends State<FluentWatchHistoryPage> {
3841
);
3942
}
4043

41-
// 过滤出真正被观看过的记录(与主页仪表盘保持一致)
42-
final validHistory = historyProvider.history.where((item) => item.duration > 0).toList();
44+
final validHistory = _getValidHistory(historyProvider.history);
4345

4446
if (validHistory.isEmpty) {
4547
return _buildEmptyState();
@@ -112,35 +114,33 @@ class _FluentWatchHistoryPageState extends State<FluentWatchHistoryPage> {
112114
}
113115

114116
Widget _buildThumbnail(WatchHistoryItem item) {
115-
if (item.thumbnailPath != null) {
116-
final thumbnailFile = File(item.thumbnailPath!);
117-
if (thumbnailFile.existsSync()) {
118-
return FutureBuilder<Uint8List>(
119-
future: thumbnailFile.readAsBytes(),
120-
builder: (context, snapshot) {
121-
if (snapshot.hasData) {
122-
return Container(
123-
width: 80,
124-
height: 45,
125-
decoration: BoxDecoration(
126-
borderRadius: BorderRadius.circular(4),
127-
),
128-
child: ClipRRect(
129-
borderRadius: BorderRadius.circular(4),
130-
child: Image.memory(
131-
snapshot.data!,
132-
fit: BoxFit.cover,
133-
errorBuilder: (context, error, stackTrace) {
134-
return _buildDefaultThumbnail();
135-
},
136-
),
117+
final path = item.thumbnailPath;
118+
if (path != null) {
119+
return FutureBuilder<Uint8List?>(
120+
future: _getThumbnailBytes(path),
121+
builder: (context, snapshot) {
122+
if (snapshot.hasData && snapshot.data != null) {
123+
return Container(
124+
width: 80,
125+
height: 45,
126+
decoration: BoxDecoration(
127+
borderRadius: BorderRadius.circular(4),
128+
),
129+
child: ClipRRect(
130+
borderRadius: BorderRadius.circular(4),
131+
child: Image.memory(
132+
snapshot.data!,
133+
fit: BoxFit.cover,
134+
errorBuilder: (context, error, stackTrace) {
135+
return _buildDefaultThumbnail();
136+
},
137137
),
138-
);
139-
}
140-
return _buildDefaultThumbnail();
141-
},
142-
);
143-
}
138+
),
139+
);
140+
}
141+
return _buildDefaultThumbnail();
142+
},
143+
);
144144
}
145145
return _buildDefaultThumbnail();
146146
}
@@ -325,6 +325,37 @@ class _FluentWatchHistoryPageState extends State<FluentWatchHistoryPage> {
325325
}
326326
}
327327

328+
List<WatchHistoryItem> _getValidHistory(List<WatchHistoryItem> history) {
329+
final hash = _historyHash(history);
330+
if (hash != _lastHistoryHash) {
331+
_cachedValidHistory =
332+
history.where((item) => item.duration > 0).toList(growable: false);
333+
_lastHistoryHash = hash;
334+
}
335+
return _cachedValidHistory;
336+
}
337+
338+
Future<Uint8List?> _getThumbnailBytes(String path) {
339+
return _thumbnailFutures.putIfAbsent(path, () async {
340+
try {
341+
final file = File(path);
342+
if (!await file.exists()) return null;
343+
return await file.readAsBytes();
344+
} catch (_) {
345+
return null;
346+
}
347+
});
348+
}
349+
350+
int _historyHash(List<WatchHistoryItem> history) {
351+
int hash = history.length;
352+
final sample = history.length > 5 ? history.take(5) : history;
353+
for (final item in sample) {
354+
hash = hash ^ item.filePath.hashCode ^ item.lastWatchTime.millisecondsSinceEpoch;
355+
}
356+
return hash;
357+
}
358+
328359
void _updateAutoMatchingState(bool value) {
329360
if (!mounted) {
330361
_isAutoMatching = value;
@@ -404,4 +435,4 @@ class _FluentWatchHistoryPageState extends State<FluentWatchHistoryPage> {
404435
},
405436
);
406437
}
407-
}
438+
}

lib/themes/fluent/widgets/fluent_watch_history_list.dart

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart';
22
import 'package:nipaplay/models/watch_history_model.dart';
33
import 'package:nipaplay/themes/fluent/widgets/fluent_history_card.dart';
44

5-
class FluentWatchHistoryList extends StatelessWidget {
5+
class FluentWatchHistoryList extends StatefulWidget {
66
final List<WatchHistoryItem> history;
77
final Function(WatchHistoryItem) onItemTap;
88
final VoidCallback onShowMore;
@@ -15,46 +15,82 @@ class FluentWatchHistoryList extends StatelessWidget {
1515
});
1616

1717
@override
18-
Widget build(BuildContext context) {
19-
final validHistoryItems = history.where((item) => item.duration > 0).toList();
20-
if (validHistoryItems.isEmpty) {
21-
return _buildEmptyState();
22-
}
18+
State<FluentWatchHistoryList> createState() => _FluentWatchHistoryListState();
19+
}
20+
21+
class _FluentWatchHistoryListState extends State<FluentWatchHistoryList> {
22+
List<WatchHistoryItem> _validHistoryItems = const [];
23+
String? _latestUpdatedPath;
24+
int _displayItemCount = 0;
25+
bool _showViewMoreButton = false;
26+
double _lastKnownWidth = 0;
27+
List<WatchHistoryItem>? _lastHistoryRef;
28+
29+
@override
30+
void didChangeDependencies() {
31+
super.didChangeDependencies();
32+
_recomputeIfNeeded();
33+
}
34+
35+
@override
36+
void didUpdateWidget(covariant FluentWatchHistoryList oldWidget) {
37+
super.didUpdateWidget(oldWidget);
38+
_recomputeIfNeeded(force: widget.history != _lastHistoryRef);
39+
}
40+
41+
void _recomputeIfNeeded({bool force = false}) {
42+
final width = MediaQuery.of(context).size.width;
43+
if (!force && width == _lastKnownWidth && widget.history == _lastHistoryRef) return;
44+
45+
_lastKnownWidth = width;
46+
_lastHistoryRef = widget.history;
2347

24-
String? latestUpdatedPath;
48+
_validHistoryItems = widget.history.where((item) => item.duration > 0).toList();
49+
50+
_latestUpdatedPath = null;
2551
DateTime latestTime = DateTime(2000);
26-
for (var item in validHistoryItems) {
52+
for (var item in _validHistoryItems) {
2753
if (item.lastWatchTime.isAfter(latestTime)) {
2854
latestTime = item.lastWatchTime;
29-
latestUpdatedPath = item.filePath;
55+
_latestUpdatedPath = item.filePath;
3056
}
3157
}
3258

33-
final screenWidth = MediaQuery.of(context).size.width;
3459
const cardWidth = 166.0; // Card width (150) + padding (16)
35-
final visibleCards = (screenWidth / cardWidth).floor();
36-
final showViewMoreButton = validHistoryItems.length > visibleCards + 2;
37-
final displayItemCount = showViewMoreButton ? visibleCards + 2 : validHistoryItems.length;
60+
final visibleCards = (_lastKnownWidth / cardWidth).floor();
61+
_showViewMoreButton = _validHistoryItems.length > visibleCards + 2;
62+
_displayItemCount =
63+
_showViewMoreButton ? visibleCards + 2 : _validHistoryItems.length;
64+
setState(() {});
65+
}
66+
67+
@override
68+
Widget build(BuildContext context) {
69+
if (_validHistoryItems.isEmpty) {
70+
return _buildEmptyState();
71+
}
3872

3973
return ListView.builder(
4074
scrollDirection: Axis.horizontal,
4175
padding: const EdgeInsets.symmetric(horizontal: 16),
42-
itemCount: showViewMoreButton ? displayItemCount + 1 : validHistoryItems.length,
76+
itemCount:
77+
_showViewMoreButton ? _displayItemCount + 1 : _validHistoryItems.length,
4378
itemBuilder: (context, index) {
44-
if (showViewMoreButton && index == displayItemCount) {
79+
if (_showViewMoreButton && index == _displayItemCount) {
4580
return _buildShowMoreButton(context);
4681
}
4782

48-
if (index < validHistoryItems.length) {
49-
final item = validHistoryItems[index];
50-
final isLatestUpdated = item.filePath == latestUpdatedPath;
83+
if (index < _validHistoryItems.length) {
84+
final item = _validHistoryItems[index];
85+
final isLatestUpdated = item.filePath == _latestUpdatedPath;
5186
return Padding(
52-
key: ValueKey('${item.filePath}_${item.lastWatchTime.millisecondsSinceEpoch}'),
87+
key: ValueKey(
88+
'${item.filePath}_${item.lastWatchTime.millisecondsSinceEpoch}'),
5389
padding: const EdgeInsets.only(right: 16),
5490
child: FluentHistoryCard(
5591
item: item,
5692
isLatestUpdated: isLatestUpdated,
57-
onTap: () => onItemTap(item),
93+
onTap: () => widget.onItemTap(item),
5894
),
5995
);
6096
}
@@ -82,7 +118,7 @@ class FluentWatchHistoryList extends StatelessWidget {
82118
child: SizedBox(
83119
width: 150,
84120
child: Button(
85-
onPressed: onShowMore,
121+
onPressed: widget.onShowMore,
86122
child: const Center(
87123
child: Column(
88124
mainAxisAlignment: MainAxisAlignment.center,

0 commit comments

Comments
 (0)