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? bgColor;
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
final TextAlign? textAlign;
|
final TextAlign? textAlign;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
const SearchText({
|
const SearchText({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -18,6 +19,7 @@ class SearchText extends StatelessWidget {
|
|||||||
this.bgColor,
|
this.bgColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -25,8 +27,6 @@ class SearchText extends StatelessWidget {
|
|||||||
return Material(
|
return Material(
|
||||||
color: bgColor ?? Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor ?? Theme.of(context).colorScheme.onInverseSurface,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onTap?.call(text);
|
onTap?.call(text);
|
||||||
@@ -36,8 +36,8 @@ class SearchText extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: padding ??
|
||||||
const EdgeInsets.only(top: 5, bottom: 5, left: 11, right: 11),
|
const EdgeInsets.symmetric(horizontal: 11, vertical: 5),
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
textAlign: textAlign,
|
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/later.dart';
|
||||||
import 'package:PiliPalaX/models/video/play/subtitle.dart';
|
import 'package:PiliPalaX/models/video/play/subtitle.dart';
|
||||||
import 'package:PiliPalaX/models/video_detail_res.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/introduction/controller.dart';
|
||||||
import 'package:PiliPalaX/pages/video/detail/related/controller.dart';
|
import 'package:PiliPalaX/pages/video/detail/related/controller.dart';
|
||||||
import 'package:PiliPalaX/pages/video/detail/reply/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:canvas_danmaku/models/danmaku_content_item.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -109,10 +111,16 @@ extension SegmentTypeExt on SegmentType {
|
|||||||
][index];
|
][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SkipType { alwaysSkip, skipOnce, showOnly, disable }
|
enum SkipType { alwaysSkip, skipOnce, skipManually, showOnly, disable }
|
||||||
|
|
||||||
extension SkipTypeExt on SkipType {
|
extension SkipTypeExt on SkipType {
|
||||||
String get title => ['总是跳过', '跳过一次', '仅显示', '禁用'][index];
|
String get title => [
|
||||||
|
'总是跳过',
|
||||||
|
'跳过一次',
|
||||||
|
'手动跳过',
|
||||||
|
'仅显示',
|
||||||
|
'禁用',
|
||||||
|
][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
class SegmentModel {
|
class SegmentModel {
|
||||||
@@ -122,14 +130,14 @@ class SegmentModel {
|
|||||||
required this.segmentType,
|
required this.segmentType,
|
||||||
required this.segment,
|
required this.segment,
|
||||||
required this.skipType,
|
required this.skipType,
|
||||||
required this.hasSkipped,
|
this.hasSkipped,
|
||||||
});
|
});
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
String UUID;
|
String UUID;
|
||||||
SegmentType segmentType;
|
SegmentType segmentType;
|
||||||
Pair<int, int> segment;
|
Pair<int, int> segment;
|
||||||
SkipType skipType;
|
SkipType skipType;
|
||||||
bool hasSkipped;
|
bool? hasSkipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostSegmentModel {
|
class PostSegmentModel {
|
||||||
@@ -471,6 +479,10 @@ class VideoDetailController extends GetxController
|
|||||||
Color _getColor(SegmentType segment) =>
|
Color _getColor(SegmentType segment) =>
|
||||||
_blockColor?[segment.index] ?? segment.color;
|
_blockColor?[segment.index] ?? segment.color;
|
||||||
|
|
||||||
|
Timer? skipTimer;
|
||||||
|
late final listKey = GlobalKey<AnimatedListState>();
|
||||||
|
late final listData = <SegmentModel>[];
|
||||||
|
|
||||||
Future _vote(String uuid, int type) async {
|
Future _vote(String uuid, int type) async {
|
||||||
Request()
|
Request()
|
||||||
.post(
|
.post(
|
||||||
@@ -817,13 +829,78 @@ class VideoDetailController extends GetxController
|
|||||||
// debugPrint(
|
// debugPrint(
|
||||||
// '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}');
|
// '${position.inSeconds},,${item.segment.first},,${item.segment.second},,${item.skipType.name},,${item.hasSkipped}');
|
||||||
if (item.segment.first == position.inSeconds) {
|
if (item.segment.first == position.inSeconds) {
|
||||||
if (item.skipType == SkipType.alwaysSkip ||
|
if (item.skipType == SkipType.alwaysSkip) {
|
||||||
(item.skipType == SkipType.skipOnce && !item.hasSkipped)) {
|
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 {
|
try {
|
||||||
plPlayerController.danmakuController?.clear();
|
plPlayerController.danmakuController?.clear();
|
||||||
await plPlayerController.videoPlayerController
|
await plPlayerController.videoPlayerController
|
||||||
?.seek(Duration(seconds: item.segment.second));
|
?.seek(Duration(seconds: item.segment.second));
|
||||||
item.hasSkipped = true;
|
|
||||||
if (GStorage.blockToast) {
|
if (GStorage.blockToast) {
|
||||||
_showBlockToast('已跳过${item.segmentType.shortTitle}片段');
|
_showBlockToast('已跳过${item.segmentType.shortTitle}片段');
|
||||||
}
|
}
|
||||||
@@ -839,13 +916,6 @@ class VideoDetailController extends GetxController
|
|||||||
_showBlockToast('${item.segmentType.shortTitle}片段跳过失败');
|
_showBlockToast('${item.segmentType.shortTitle}片段跳过失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 发送弹幕
|
/// 发送弹幕
|
||||||
void showShootDanmakuSheet() {
|
void showShootDanmakuSheet() {
|
||||||
|
|||||||
@@ -346,6 +346,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
_listenerDetail?.cancel();
|
_listenerDetail?.cancel();
|
||||||
_listenerLoadingState?.cancel();
|
_listenerLoadingState?.cancel();
|
||||||
_listenerCid?.cancel();
|
_listenerCid?.cancel();
|
||||||
|
|
||||||
|
videoDetailController.skipTimer?.cancel();
|
||||||
|
videoDetailController.skipTimer = null;
|
||||||
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
if (!Get.previousRoute.startsWith('/video')) {
|
if (!Get.previousRoute.startsWith('/video')) {
|
||||||
ScreenBrightness().resetApplicationScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
@@ -1358,7 +1362,27 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
manualPlayerWidget,
|
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