mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: sponsorblock: manual skip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -8,6 +8,7 @@ class SearchText extends StatelessWidget {
|
||||
final Color? bgColor;
|
||||
final Color? textColor;
|
||||
final TextAlign? textAlign;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const SearchText({
|
||||
super.key,
|
||||
@@ -18,6 +19,7 @@ class SearchText extends StatelessWidget {
|
||||
this.bgColor,
|
||||
this.textColor,
|
||||
this.textAlign,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -25,8 +27,6 @@ class SearchText extends StatelessWidget {
|
||||
return Material(
|
||||
color: bgColor ?? Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
onTap?.call(text);
|
||||
@@ -36,8 +36,8 @@ class SearchText extends StatelessWidget {
|
||||
},
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5, bottom: 5, left: 11, right: 11),
|
||||
padding: padding ??
|
||||
const EdgeInsets.symmetric(horizontal: 11, vertical: 5),
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: textAlign,
|
||||
@@ -49,7 +49,6 @@ class SearchText extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/models/video/later.dart';
|
||||
import 'package:PiliPalaX/models/video/play/subtitle.dart';
|
||||
import 'package:PiliPalaX/models/video_detail_res.dart';
|
||||
import 'package:PiliPalaX/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/related/controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply/controller.dart';
|
||||
@@ -21,6 +22,7 @@ import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:canvas_danmaku/models/danmaku_content_item.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -109,10 +111,16 @@ extension SegmentTypeExt on SegmentType {
|
||||
][index];
|
||||
}
|
||||
|
||||
enum SkipType { alwaysSkip, skipOnce, showOnly, disable }
|
||||
enum SkipType { alwaysSkip, skipOnce, skipManually, showOnly, disable }
|
||||
|
||||
extension SkipTypeExt on SkipType {
|
||||
String get title => ['总是跳过', '跳过一次', '仅显示', '禁用'][index];
|
||||
String get title => [
|
||||
'总是跳过',
|
||||
'跳过一次',
|
||||
'手动跳过',
|
||||
'仅显示',
|
||||
'禁用',
|
||||
][index];
|
||||
}
|
||||
|
||||
class SegmentModel {
|
||||
@@ -122,14 +130,14 @@ class SegmentModel {
|
||||
required this.segmentType,
|
||||
required this.segment,
|
||||
required this.skipType,
|
||||
required this.hasSkipped,
|
||||
this.hasSkipped,
|
||||
});
|
||||
// ignore: non_constant_identifier_names
|
||||
String UUID;
|
||||
SegmentType segmentType;
|
||||
Pair<int, int> segment;
|
||||
SkipType skipType;
|
||||
bool hasSkipped;
|
||||
bool? hasSkipped;
|
||||
}
|
||||
|
||||
class PostSegmentModel {
|
||||
@@ -471,6 +479,10 @@ class VideoDetailController extends GetxController
|
||||
Color _getColor(SegmentType segment) =>
|
||||
_blockColor?[segment.index] ?? segment.color;
|
||||
|
||||
Timer? skipTimer;
|
||||
late final listKey = GlobalKey<AnimatedListState>();
|
||||
late final listData = <SegmentModel>[];
|
||||
|
||||
Future _vote(String uuid, int type) async {
|
||||
Request()
|
||||
.post(
|
||||
@@ -817,13 +829,78 @@ class VideoDetailController extends GetxController
|
||||
// debugPrint(
|
||||
// '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}');
|
||||
if (item.segment.first == position.inSeconds) {
|
||||
if (item.skipType == SkipType.alwaysSkip ||
|
||||
(item.skipType == SkipType.skipOnce && !item.hasSkipped)) {
|
||||
if (item.skipType == SkipType.alwaysSkip) {
|
||||
onSkip(item);
|
||||
} else if (item.skipType == SkipType.skipOnce &&
|
||||
item.hasSkipped != true) {
|
||||
item.hasSkipped = true;
|
||||
onSkip(item);
|
||||
} else if (item.skipType == SkipType.skipManually) {
|
||||
listData.insert(0, item);
|
||||
listKey.currentState?.insertItem(0);
|
||||
skipTimer ??=
|
||||
Timer.periodic(const Duration(milliseconds: 2500), (_) {
|
||||
if (listData.isNotEmpty) {
|
||||
onRemoveItem(listData.length - 1, listData.last);
|
||||
} else {
|
||||
skipTimer?.cancel();
|
||||
skipTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onRemoveItem(int index, SegmentModel item) {
|
||||
EasyThrottle.throttle('onRemoveItem', const Duration(seconds: 1), () {
|
||||
try {
|
||||
listData.removeAt(index);
|
||||
listKey.currentState?.removeItem(
|
||||
index,
|
||||
(context, animation) => buildItem(item, animation),
|
||||
);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildItem(SegmentModel item, Animation<double> animation) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset(-1, 0),
|
||||
end: Offset(0, 0),
|
||||
).animate(animation),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: SearchText(
|
||||
bgColor: Theme.of(Get.context!)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.7),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
fontSize: 13,
|
||||
text: '跳过: ${item.segmentType.shortTitle}',
|
||||
onTap: (_) {
|
||||
onSkip(item);
|
||||
onRemoveItem(listData.indexOf(item), item);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onSkip(SegmentModel item) async {
|
||||
try {
|
||||
plPlayerController.danmakuController?.clear();
|
||||
await plPlayerController.videoPlayerController
|
||||
?.seek(Duration(seconds: item.segment.second));
|
||||
item.hasSkipped = true;
|
||||
if (GStorage.blockToast) {
|
||||
_showBlockToast('已跳过${item.segmentType.shortTitle}片段');
|
||||
}
|
||||
@@ -839,13 +916,6 @@ class VideoDetailController extends GetxController
|
||||
_showBlockToast('${item.segmentType.shortTitle}片段跳过失败');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送弹幕
|
||||
void showShootDanmakuSheet() {
|
||||
|
||||
@@ -346,6 +346,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
_listenerDetail?.cancel();
|
||||
_listenerLoadingState?.cancel();
|
||||
_listenerCid?.cancel();
|
||||
|
||||
videoDetailController.skipTimer?.cancel();
|
||||
videoDetailController.skipTimer = null;
|
||||
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
if (!Get.previousRoute.startsWith('/video')) {
|
||||
ScreenBrightness().resetApplicationScreenBrightness();
|
||||
@@ -1358,7 +1362,27 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
],
|
||||
|
||||
if (videoDetailController.enableSponsorBlock)
|
||||
Align(
|
||||
alignment: Alignment(-0.9, 0.5),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.textScalerOf(context).scale(120),
|
||||
child: AnimatedList(
|
||||
key: videoDetailController.listKey,
|
||||
reverse: true,
|
||||
shrinkWrap: true,
|
||||
initialItemCount: videoDetailController.listData.length,
|
||||
itemBuilder: (context, index, animation) {
|
||||
return videoDetailController.buildItem(
|
||||
videoDetailController.listData[index],
|
||||
animation,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user