Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 103 additions & 36 deletions lib/bean/card/bangumi_history_card.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'dart:io';
import 'package:kazumi/bean/card/network_img_layer.dart';
import 'package:kazumi/bean/dialog/dialog_helper.dart';
import 'package:kazumi/bean/widget/collect_button.dart';
import 'package:kazumi/modules/history/history_module.dart';
import 'package:kazumi/services/local_video_picker_service.dart';
import 'package:kazumi/pages/collect/collect_controller.dart';
import 'package:kazumi/pages/history/history_controller.dart';
import 'package:kazumi/pages/video/video_controller.dart';
Expand Down Expand Up @@ -36,12 +38,50 @@ class _BangumiHistoryCardVState extends State<BangumiHistoryCardV> {
final PluginsController pluginsController = Modular.get<PluginsController>();
final HistoryController historyController = Modular.get<HistoryController>();
final CollectController collectController = Modular.get<CollectController>();
bool _opening = false;

Future<void> _onTap() async {
if (_opening) {
return;
}
if (widget.showDelete) {
KazumiDialog.showToast(message: '编辑模式');
return;
}
_opening = true;
if (widget.historyItem.isLocalVideo) {
final progress =
widget.historyItem.progresses[widget.historyItem.lastWatchEpisode];
final localPath = (progress?.localPath.isNotEmpty ?? false)
? progress!.localPath
: widget.historyItem.localVideoPath.isNotEmpty
? widget.historyItem.localVideoPath
: widget.historyItem.lastSrc;
if (localPath.isEmpty || !File(localPath).existsSync()) {
KazumiDialog.showToast(message: '本地文件不存在或已移动');
_opening = false;
return;
}
final episodeTitle = (progress?.episodeTitle.isNotEmpty ?? false)
? progress!.episodeTitle
: widget.historyItem.lastWatchEpisodeName.isNotEmpty
? widget.historyItem.lastWatchEpisodeName
: widget.historyItem.localVideoTitle;
videoPageController.initForLocalFilePlayback(
context: LocalVideoPickerService().buildContext(localPath).copyWith(
title: episodeTitle.isEmpty
? widget.historyItem.localVideoTitle
: episodeTitle,
),
boundBangumiItem: widget.historyItem.isBoundLocalVideo
? widget.historyItem.bangumiItem
: null,
episodeNumber: widget.historyItem.lastWatchEpisode,
);
Modular.to.pushNamed('/video/');
_opening = false;
return;
}
KazumiDialog.showLoading(
msg: '获取中',
barrierDismissible: Utils.isDesktop(),
Expand All @@ -60,22 +100,24 @@ class _BangumiHistoryCardVState extends State<BangumiHistoryCardV> {
if (!flag) {
KazumiDialog.dismiss();
KazumiDialog.showToast(message: '未找到关联番剧源');
_opening = false;
return;
}
videoPageController.bangumiItem = widget.historyItem.bangumiItem;
videoPageController.title =
widget.historyItem.bangumiItem.nameCn == ''
? widget.historyItem.bangumiItem.name
: widget.historyItem.bangumiItem.nameCn;
videoPageController.title = widget.historyItem.bangumiItem.nameCn == ''
? widget.historyItem.bangumiItem.name
: widget.historyItem.bangumiItem.nameCn;
videoPageController.src = widget.historyItem.lastSrc;
try {
await videoPageController.queryRoads(widget.historyItem.lastSrc,
videoPageController.currentPlugin.name);
await videoPageController.queryRoads(
widget.historyItem.lastSrc, videoPageController.currentPlugin.name);
KazumiDialog.dismiss();
Modular.to.pushNamed('/video/');
_opening = false;
} catch (_) {
KazumiLogger().w("QueryManager: failed to query roads");
KazumiDialog.dismiss();
_opening = false;
}
}

Expand All @@ -85,13 +127,33 @@ class _BangumiHistoryCardVState extends State<BangumiHistoryCardV> {
final colorScheme = theme.colorScheme;
final double imageWidth = 80;
final double imageHeight = 108;
final String title = widget.historyItem.bangumiItem.nameCn == ''
? widget.historyItem.bangumiItem.name
: widget.historyItem.bangumiItem.nameCn;
final String episodeText =
widget.historyItem.lastWatchEpisodeName.isEmpty
? '第${widget.historyItem.lastWatchEpisode}话'
: widget.historyItem.lastWatchEpisodeName;
final progress =
widget.historyItem.progresses[widget.historyItem.lastWatchEpisode];
final localPath = (progress?.localPath.isNotEmpty ?? false)
? progress!.localPath
: widget.historyItem.localVideoPath.isNotEmpty
? widget.historyItem.localVideoPath
: widget.historyItem.lastSrc;
final localFileName = widget.historyItem.localVideoFileName.isNotEmpty
? widget.historyItem.localVideoFileName
: localPath.split(RegExp(r'[\\/]')).last;
final String title = widget.historyItem.isLocalVideo
? (localFileName.isNotEmpty
? localFileName
: widget.historyItem.localVideoTitle)
: widget.historyItem.bangumiItem.nameCn == ''
? widget.historyItem.bangumiItem.name
: widget.historyItem.bangumiItem.nameCn;
final String episodeText = widget.historyItem.lastWatchEpisodeName.isEmpty
? '第${widget.historyItem.lastWatchEpisode}集'
: widget.historyItem.lastWatchEpisodeName;
final showEpisodeText = !widget.historyItem.isLocalVideo ||
widget.historyItem.isBoundLocalVideo;
final sourceText =
widget.historyItem.isLocalVideo ? '本地' : widget.historyItem.adapterName;
final sourceIcon = widget.historyItem.isLocalVideo
? Icons.movie_outlined
: Icons.extension_outlined;

return Dismissible(
key: ValueKey(widget.historyItem.key),
Expand Down Expand Up @@ -151,39 +213,41 @@ class _BangumiHistoryCardVState extends State<BangumiHistoryCardV> {
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
const SizedBox(height: 6),
Row(
children: [
Icon(
Icons.play_circle_outline,
size: 14,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Flexible(
child: Text(
episodeText,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
if (showEpisodeText) ...[
const SizedBox(height: 6),
Row(
children: [
Icon(
Icons.play_circle_outline,
size: 14,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Flexible(
child: Text(
episodeText,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
],
),
],
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.extension_outlined,
sourceIcon,
size: 14,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Flexible(
child: Text(
widget.historyItem.adapterName,
sourceText,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
Expand All @@ -203,8 +267,11 @@ class _BangumiHistoryCardVState extends State<BangumiHistoryCardV> {
),
const SizedBox(width: 4),
Text(
Utils.formatTimestampToRelativeTime(
widget.historyItem.lastWatchTime.millisecondsSinceEpoch ~/ 1000),
Utils.formatTimestampToRelativeTime(widget
.historyItem
.lastWatchTime
.millisecondsSinceEpoch ~/
1000),
style: theme.textTheme.labelSmall?.copyWith(
color: colorScheme.outline,
),
Expand Down
21 changes: 17 additions & 4 deletions lib/bean/card/network_img_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ class NetworkImgLayer extends StatelessWidget {

//// We need this to shink memory usage
int? memCacheWidth, memCacheHeight;
double aspectRatio = (width / height).toDouble();

void setMemCacheSizes() {
if (!width.isFinite || !height.isFinite || width <= 0 || height <= 0) {
return;
}

final double aspectRatio = (width / height).toDouble();
if (aspectRatio > 1) {
memCacheHeight = height.cacheSize(context);
} else if (aspectRatio < 1) {
Expand All @@ -54,7 +58,12 @@ class NetworkImgLayer extends StatelessWidget {
setMemCacheSizes();

if (memCacheWidth == null && memCacheHeight == null) {
memCacheWidth = width.toInt();
if (width.isFinite && width > 0) {
final fallbackWidth = width.toInt();
if (fallbackWidth > 0) {
memCacheWidth = fallbackWidth;
}
}
}

return src != '' && src != null
Expand All @@ -80,7 +89,8 @@ class NetworkImgLayer extends StatelessWidget {
fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.high,
errorListener: (e) {
KazumiLogger().w("NetworkImage: network image load error", error: e);
KazumiLogger()
.w("NetworkImage: network image load error", error: e);
},
errorWidget: (BuildContext context, String url, Object error) =>
placeholder(context),
Expand All @@ -96,7 +106,10 @@ class NetworkImgLayer extends StatelessWidget {
height: height,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withValues(alpha: 0.4),
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(type == 'avatar'
? 50
: type == 'emote'
Expand Down
50 changes: 45 additions & 5 deletions lib/modules/history/history_module.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:hive_ce/hive.dart';
import 'package:kazumi/modules/bangumi/bangumi_item.dart';
import 'package:kazumi/modules/playback/playback_source.dart';

part 'history_module.g.dart';

Expand All @@ -26,12 +27,39 @@ class History {
@HiveField(6, defaultValue: '')
String lastWatchEpisodeName;

String get key => adapterName + bangumiItem.id.toString();
@HiveField(7, defaultValue: 'online')
String sourceTypeName;

History(
this.bangumiItem, this.lastWatchEpisode, this.adapterName, this.lastWatchTime, this.lastSrc, this.lastWatchEpisodeName);
@HiveField(8, defaultValue: '')
String localVideoPath;

static String getKey(String n, BangumiItem s) => n + s.id.toString();
@HiveField(9, defaultValue: '')
String localVideoTitle;

@HiveField(10, defaultValue: '')
String localVideoFileName;

bool get isLocalVideo => sourceTypeName == PlaybackSourceType.localFile.name;

bool get isBoundLocalVideo => isLocalVideo && bangumiItem.id > 0;

String get key => isLocalVideo
? '$adapterName$localVideoPath'
: adapterName + bangumiItem.id.toString();

History(this.bangumiItem, this.lastWatchEpisode, this.adapterName,
this.lastWatchTime, this.lastSrc, this.lastWatchEpisodeName,
{this.sourceTypeName = 'online',
this.localVideoPath = '',
this.localVideoTitle = '',
this.localVideoFileName = ''});

static String getKey(String n, BangumiItem s,
{String sourceTypeName = 'online', String localVideoPath = ''}) {
return sourceTypeName == PlaybackSourceType.localFile.name
? '$n$localVideoPath'
: n + s.id.toString();
}

@override
String toString() {
Expand All @@ -50,11 +78,23 @@ class Progress {
@HiveField(2)
int _progressInMilli;

@HiveField(3, defaultValue: '')
String localPath;

@HiveField(4, defaultValue: '')
String episodeTitle;

Duration get progress => Duration(milliseconds: _progressInMilli);

set progress(Duration d) => _progressInMilli = d.inMilliseconds;

Progress(this.episode, this.road, this._progressInMilli);
Progress(
this.episode,
this.road,
this._progressInMilli, {
this.localPath = '',
this.episodeTitle = '',
});

@override
String toString() {
Expand Down
Loading