opt: sponsor block

This commit is contained in:
bggRGjQaUbCoE
2024-10-21 21:12:50 +08:00
parent ef80589636
commit ca28dd374a
11 changed files with 518 additions and 104 deletions

View File

@@ -0,0 +1,8 @@
class Pair<T, R> {
Pair({
required this.first,
required this.second,
});
T first;
R second;
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
class Segment {
final double start;
final double end;
final Color color;
Segment(this.start, this.end, this.color);
}
class SegmentProgressBar extends CustomPainter {
final double progress;
final List<Segment> segmentColors;
SegmentProgressBar({
required this.progress,
required this.segmentColors,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.fill;
for (var segment in segmentColors) {
paint.color = segment.color;
final segmentStart = segment.start * size.width;
final segmentEnd = segment.end * size.width;
final progressEnd = progress * size.width;
if (progressEnd < segmentStart) {
break;
}
final segmentWidth =
(progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart;
if (segmentWidth > 0) {
canvas.drawRect(
Rect.fromLTWH(segmentStart, 0, segmentWidth, size.height),
paint,
);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -139,12 +139,13 @@ class _ExtraSettingState extends State<ExtraSetting> {
),
body: ListView(
children: [
const SetSwitchItem(
SetSwitchItem(
title: 'Sponsor Block',
subTitle: '跳过赞助商广告',
subTitle: '点击配置',
leading: Icon(Icons.block),
setKey: SettingBoxKey.enableSponsorBlock,
defaultVal: false,
onTap: () => Get.toNamed('/sponsorBlock'),
),
Obx(
() => ListTile(

View File

@@ -0,0 +1,160 @@
import 'dart:math';
import 'package:PiliPalaX/common/widgets/pair.dart';
import 'package:PiliPalaX/pages/video/detail/controller.dart'
show SegmentType, SegmentTypeExt, SkipType, SkipTypeExt;
import 'package:PiliPalaX/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SponsorBlockPage extends StatefulWidget {
const SponsorBlockPage({super.key});
@override
State<SponsorBlockPage> createState() => _SponsorBlockPageState();
}
class _SponsorBlockPageState extends State<SponsorBlockPage> {
late double _blockLimit;
late List<Pair<SegmentType, SkipType>> _blockSettings;
@override
void initState() {
super.initState();
_blockLimit = GStorage.blockLimit;
_blockSettings = GStorage.blockSettings;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
titleSpacing: 0,
title: Text(
'Sponsor Block',
style: Theme.of(context).textTheme.titleMedium,
),
),
body: ListView.separated(
itemCount: _blockSettings.length + 1,
itemBuilder: (_, index) => index == 0
? ListTile(
onTap: () {
final textController =
TextEditingController(text: _blockLimit.toString());
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Block Limit'),
content: TextFormField(
keyboardType:
TextInputType.numberWithOptions(decimal: true),
controller: textController,
autofocus: true,
decoration: InputDecoration(suffixText: 's'),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
_blockLimit = max(0.0,
double.tryParse(textController.text) ?? 0.0);
await GStorage.setting
.put(SettingBoxKey.blockLimit, _blockLimit);
setState(() {});
},
child: Text('确定'),
)
],
);
},
);
},
leading: Icon(Icons.av_timer),
title: const Text('Block Limit'),
trailing: Text(
'${_blockLimit}s',
style: TextStyle(fontSize: 13),
),
)
: ListTile(
leading: Container(
height: 24,
width: 24,
alignment: Alignment.center,
child: Container(
height: 10,
width: 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _blockSettings[index - 1].first.color,
),
),
),
title: Text(
_blockSettings[index - 1].first.name,
style: _blockSettings[index - 1].second == SkipType.disable
? TextStyle(
color: Theme.of(context).colorScheme.outline,
)
: null,
),
trailing: PopupMenuButton(
initialValue: _blockSettings[index - 1].second,
onSelected: (item) async {
_blockSettings[index - 1].second = item;
await GStorage.setting.put(
SettingBoxKey.blockSettings,
_blockSettings
.map((item) => item.second.index)
.toList());
setState(() {});
},
itemBuilder: (context) => SkipType.values
.map((item) => PopupMenuItem<SkipType>(
value: item,
child: Text(item.title),
))
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_blockSettings[index - 1].second.title,
style: TextStyle(
fontSize: 13,
color: _blockSettings[index - 1].second ==
SkipType.disable
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.primary,
),
),
Icon(
size: 20,
Icons.keyboard_arrow_right,
color:
_blockSettings[index - 1].second == SkipType.disable
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.primary,
)
],
),
),
),
separatorBuilder: (_, index) => Divider(height: 1),
),
);
}
}

View File

@@ -12,6 +12,7 @@ class SetSwitchItem extends StatefulWidget {
final Function? callFn;
final bool? needReboot;
final Widget? leading;
final GestureTapCallback? onTap;
const SetSwitchItem({
this.title,
@@ -21,6 +22,7 @@ class SetSwitchItem extends StatefulWidget {
this.callFn,
this.needReboot,
this.leading,
this.onTap,
Key? key,
}) : super(key: key);
@@ -56,14 +58,19 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
@override
Widget build(BuildContext context) {
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!.copyWith(
color: widget.onTap != null && !val
? Theme.of(context).colorScheme.outline
: null);
TextStyle subTitleStyle = Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline);
return ListTile(
enabled: widget.onTap != null ? val : true,
enableFeedback: true,
onTap: () => switchChange(null),
onTap: () =>
widget.onTap != null ? widget.onTap?.call() : switchChange(null),
title: Text(widget.title!, style: titleStyle),
subtitle: widget.subTitle != null
? Text(widget.subTitle!, style: subTitleStyle)
@@ -73,9 +80,9 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.isNotEmpty && states.first == MaterialState.selected) {
thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((Set<WidgetState> states) {
if (states.isNotEmpty && states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.

View File

@@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:PiliPalaX/common/widgets/pair.dart';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/danmaku.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:dio/dio.dart';
@@ -22,6 +24,55 @@ import 'package:ns_danmaku/models/danmaku_item.dart';
import '../../../utils/id_utils.dart';
import 'widgets/header_control.dart';
enum SegmentType {
sponsor,
selfpromo,
interaction,
intro,
outro,
preview,
music_offtopic,
poi_highlight,
chapter,
filler,
exclusive_access
}
extension SegmentTypeExt on SegmentType {
Color get color => [
Colors.amber,
Colors.blue,
Colors.red,
Colors.indigo,
Colors.pink,
Colors.purple,
Colors.lightGreen,
Colors.teal,
Colors.cyan,
Colors.yellow,
Colors.orange
][index];
}
enum SkipType { alwaysSkip, skipOnce, showOnly, disable }
extension SkipTypeExt on SkipType {
String get title => ['总是跳过', '跳过一次', '仅显示', '禁用'][index];
}
class SegmentModel {
SegmentModel({
required this.segmentType,
required this.segment,
required this.skipType,
required this.hasSkipped,
});
SegmentType segmentType;
Pair<int, int> segment;
SkipType skipType;
bool hasSkipped;
}
class VideoDetailController extends GetxController
with GetSingleTickerProviderStateMixin {
/// 路由传参
@@ -90,8 +141,8 @@ class VideoDetailController extends GetxController
late String cacheSecondDecode;
late int cacheAudioQa;
late final bool _enableSponsorBlock;
PlayerStatus? playerStatus;
StreamSubscription<Duration>? positionSubscription;
@override
@@ -151,12 +202,19 @@ class VideoDetailController extends GetxController
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code);
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
if (setting.get(SettingBoxKey.enableSponsorBlock, defaultValue: false)) {
_sponsorBlock();
_enableSponsorBlock =
setting.get(SettingBoxKey.enableSponsorBlock, defaultValue: false);
if (_enableSponsorBlock) {
_blockLimit = GStorage.blockLimit;
_blockSettings = GStorage.blockSettings;
}
}
List? _segmentList;
int? _lastPos;
double? _blockLimit;
List<Pair<SegmentType, SkipType>>? _blockSettings;
List<SegmentModel>? _segmentList;
List<Segment>? _segmentProgressList;
Future _sponsorBlock() async {
dynamic result = await Request().get(
@@ -175,30 +233,94 @@ class VideoDetailController extends GetxController
),
);
if (result.data is List && result.data.isNotEmpty) {
_segmentList = (result.data as List)
.where((item) => item['category'] == 'sponsor')
.toList()
.map((item) => item['segment'])
.toList();
try {
List<String> list =
SegmentType.values.map((item) => item.name).toList();
List<String> enableList = _blockSettings!
.where((item) => item.second != SkipType.disable)
.toList()
.map((item) => item.first.name)
.toList();
_segmentList = (result.data as List)
.where((item) =>
enableList.contains(item['category']) &&
item['segment'][1] > 0 &&
item['segment'][1] >= item['segment'][0])
.map(
(item) {
SegmentType segmentType =
SegmentType.values[list.indexOf(item['category'])];
SkipType skipType = _blockSettings![segmentType.index].second;
if (skipType != SkipType.showOnly) {
if (item['segment'][1] == item['segment'][0] ||
item['segment'][1] - item['segment'][0] < _blockLimit) {
skipType = SkipType.showOnly;
}
}
return SegmentModel(
segmentType: segmentType,
segment: Pair(
first: _convert(item['segment'][0]),
second: _convert(item['segment'][1]),
),
skipType: skipType,
hasSkipped: false,
);
},
).toList();
_segmentProgressList = _segmentList?.map((item) {
double start = (item.segment.first / ((data.timeLength ?? 0) / 1000))
.clamp(0.0, 1.0);
double end = (item.segment.second / ((data.timeLength ?? 0) / 1000))
.clamp(0.0, 1.0);
return Segment(
start,
start == end ? (end + 0.01).clamp(0.0, 1.0) : end,
item.segmentType.color,
);
}).toList();
} catch (e) {
print(e.toString());
}
}
}
int _convert(value) {
return value is double
? value.round()
: value is int
? value
: -1;
}
void _initSkip() {
if (_segmentList != null && _segmentList!.isNotEmpty) {
positionSubscription = plPlayerController
.videoPlayerController?.stream.position
.listen((position) async {
for (List item in _segmentList!) {
// debugPrint(
// '${position.inSeconds},,${(item.first as double).round()}');
if ((item.first as double).round() == position.inSeconds) {
try {
await plPlayerController
.seekTo(Duration(seconds: (item[1] as double).round()));
SmartDialog.showToast('已跳过赞助商广告');
} catch (e) {
debugPrint('failed to skip: $e');
SmartDialog.showToast('广告跳过失败');
int currentPos = position.inSeconds;
if (currentPos != _lastPos) {
_lastPos = currentPos;
for (SegmentModel item in _segmentList!) {
// 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)) {
try {
plPlayerController.danmakuController?.clear();
await plPlayerController.videoPlayerController
?.seek(Duration(seconds: item.segment.second));
// await plPlayerController
// .seekTo(Duration(seconds: item.segment.second));
SmartDialog.showToast('已跳过${item.segmentType.name}');
item.hasSkipped = true;
} catch (e) {
debugPrint('failed to skip: $e');
SmartDialog.showToast('${item.segmentType.name}跳过失败');
}
}
break;
}
}
}
@@ -384,6 +506,7 @@ class VideoDetailController extends GetxController
'referer': HttpString.baseUrl
},
),
segmentList: _segmentProgressList,
// 硬解
enableHA: enableHA.value,
hwdec: hwdec.value,
@@ -414,6 +537,9 @@ class VideoDetailController extends GetxController
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
if (result['status']) {
data = result['data'];
if (_enableSponsorBlock) {
await _sponsorBlock();
}
if (data.acceptDesc!.isNotEmpty && data.acceptDesc!.contains('试看')) {
SmartDialog.showToast(
'该视频为专属视频,仅提供试看',

View File

@@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -114,6 +115,8 @@ class PlPlayerController {
Timer? _timerForGettingVolume;
Timer? timerForTrackingMouse;
final RxList<Segment> segmentList = <Segment>[].obs;
// final Durations durations;
static List<Map<String, dynamic>> videoFitType = [
@@ -403,6 +406,7 @@ class PlPlayerController {
// 初始化资源
Future<void> setDataSource(
DataSource dataSource, {
List<Segment>? segmentList,
bool autoplay = true,
// 默认不循环
PlaylistMode looping = PlaylistMode.none,
@@ -426,6 +430,7 @@ class PlPlayerController {
}) async {
try {
this.dataSource = dataSource;
this.segmentList.value = segmentList ?? <Segment>[];
_autoPlay = autoplay;
_looping = looping;
// 初始化视频倍速

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:ui';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
import 'package:PiliPalaX/utils/id_utils.dart';
@@ -970,6 +971,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
BottomControl(
controller: widget.controller,
buildBottomControl: buildBottomControl(),
segmentList: _.segmentList,
),
),
),
@@ -1015,47 +1017,62 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
// label: '${(value / max * 100).round()}%',
value: '${(value / max * 100).round()}%',
// enabled: false,
child: ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withOpacity(0.2),
bufferedBarColor:
Theme.of(context).colorScheme.primary.withOpacity(0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: draggingFixedProgressBar.value ? 7 : 2.5,
// onDragStart: (duration) {
// draggingFixedProgressBar.value = true;
// feedBack();
// _.onChangedSliderStart();
// },
// onDragUpdate: (duration) {
// double newProgress = duration.timeStamp.inSeconds / max;
// if ((newProgress - _lastAnnouncedValue).abs() > 0.02) {
// _accessibilityDebounce?.cancel();
// _accessibilityDebounce =
// Timer(const Duration(milliseconds: 200), () {
// SemanticsService.announce(
// "${(newProgress * 100).round()}%",
// TextDirection.ltr);
// _lastAnnouncedValue = newProgress;
// });
// }
// _.onUpdatedSliderProgress(duration.timeStamp);
// },
// onSeek: (duration) {
// draggingFixedProgressBar.value = false;
// _.onChangedSliderEnd();
// _.onChangedSlider(duration.inSeconds.toDouble());
// _.seekTo(Duration(seconds: duration.inSeconds),
// type: 'slider');
// SemanticsService.announce(
// "${(duration.inSeconds / max * 100).round()}%",
// TextDirection.ltr);
// },
child: Stack(
alignment: Alignment.center,
children: [
ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withOpacity(0.2),
bufferedBarColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: draggingFixedProgressBar.value ? 7 : 2.5,
// onDragStart: (duration) {
// draggingFixedProgressBar.value = true;
// feedBack();
// _.onChangedSliderStart();
// },
// onDragUpdate: (duration) {
// double newProgress = duration.timeStamp.inSeconds / max;
// if ((newProgress - _lastAnnouncedValue).abs() > 0.02) {
// _accessibilityDebounce?.cancel();
// _accessibilityDebounce =
// Timer(const Duration(milliseconds: 200), () {
// SemanticsService.announce(
// "${(newProgress * 100).round()}%",
// TextDirection.ltr);
// _lastAnnouncedValue = newProgress;
// });
// }
// _.onUpdatedSliderProgress(duration.timeStamp);
// },
// onSeek: (duration) {
// draggingFixedProgressBar.value = false;
// _.onChangedSliderEnd();
// _.onChangedSlider(duration.inSeconds.toDouble());
// _.seekTo(Duration(seconds: duration.inSeconds),
// type: 'slider');
// SemanticsService.announce(
// "${(duration.inSeconds / max * 100).round()}%",
// TextDirection.ltr);
// },
),
if (_.segmentList.isNotEmpty)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(
progress: 1,
segmentColors: _.segmentList,
),
),
],
),
// SlideTransition(
// position: Tween<Offset>(

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
@@ -12,9 +13,11 @@ import '../../../common/widgets/audio_video_progress_bar.dart';
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
final PlPlayerController? controller;
final List<Widget>? buildBottomControl;
final List<Segment>? segmentList;
const BottomControl({
this.controller,
this.buildBottomControl,
this.segmentList,
Key? key,
}) : super(key: key);
@@ -49,44 +52,59 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
// label: '${(value / max * 100).round()}%',
value: '${(value / max * 100).round()}%',
// enabled: false,
child: ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withOpacity(0.2),
bufferedBarColor: colorTheme.withOpacity(0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: 7,
onDragStart: (duration) {
feedBack();
_.onChangedSliderStart();
},
onDragUpdate: (duration) {
double newProgress = duration.timeStamp.inSeconds / max;
if ((newProgress - _lastAnnouncedValue).abs() > 0.02) {
_accessibilityDebounce?.cancel();
_accessibilityDebounce =
Timer(const Duration(milliseconds: 200), () {
child: Stack(
alignment: Alignment.center,
children: [
ProgressBar(
progress: Duration(seconds: value),
buffered: Duration(seconds: buffer),
total: Duration(seconds: max),
progressBarColor: colorTheme,
baseBarColor: Colors.white.withOpacity(0.2),
bufferedBarColor: colorTheme.withOpacity(0.4),
timeLabelLocation: TimeLabelLocation.none,
thumbColor: colorTheme,
barHeight: 3.5,
thumbRadius: 7,
onDragStart: (duration) {
feedBack();
_.onChangedSliderStart();
},
onDragUpdate: (duration) {
double newProgress =
duration.timeStamp.inSeconds / max;
if ((newProgress - _lastAnnouncedValue).abs() >
0.02) {
_accessibilityDebounce?.cancel();
_accessibilityDebounce =
Timer(const Duration(milliseconds: 200), () {
SemanticsService.announce(
"${(newProgress * 100).round()}%",
TextDirection.ltr);
_lastAnnouncedValue = newProgress;
});
}
_.onUpdatedSliderProgress(duration.timeStamp);
},
onSeek: (duration) {
_.onChangedSliderEnd();
_.onChangedSlider(duration.inSeconds.toDouble());
_.seekTo(Duration(seconds: duration.inSeconds),
type: 'slider');
SemanticsService.announce(
"${(newProgress * 100).round()}%",
"${(duration.inSeconds / max * 100).round()}%",
TextDirection.ltr);
_lastAnnouncedValue = newProgress;
});
}
_.onUpdatedSliderProgress(duration.timeStamp);
},
onSeek: (duration) {
_.onChangedSliderEnd();
_.onChangedSlider(duration.inSeconds.toDouble());
_.seekTo(Duration(seconds: duration.inSeconds),
type: 'slider');
SemanticsService.announce(
"${(duration.inSeconds / max * 100).round()}%",
TextDirection.ltr);
},
},
),
if (segmentList?.isNotEmpty == true)
CustomPaint(
size: Size(double.infinity, 3.5),
painter: SegmentProgressBar(
progress: 1,
segmentColors: segmentList!,
),
),
],
)),
);
},

View File

@@ -1,6 +1,7 @@
// ignore_for_file: must_be_immutable
import 'package:PiliPalaX/pages/member/new/member_page.dart';
import 'package:PiliPalaX/pages/setting/sponsor_block_page.dart';
import 'package:PiliPalaX/pages/webview/webview_page.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -184,6 +185,7 @@ class Routes {
CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()),
// 弹幕屏蔽管理
CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()),
CustomGetPage(name: '/sponsorBlock', page: () => const SponsorBlockPage()),
];
}

View File

@@ -1,7 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:PiliPalaX/common/widgets/pair.dart';
import 'package:PiliPalaX/models/common/theme_type.dart';
import 'package:PiliPalaX/pages/video/detail/controller.dart'
show SegmentType, SkipType;
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
@@ -17,6 +20,22 @@ class GStorage {
static late final Box<dynamic> setting;
static late final Box<dynamic> video;
static List<Pair<SegmentType, SkipType>> get blockSettings {
List<int> list = setting.get(
SettingBoxKey.blockSettings,
defaultValue: List.generate(SegmentType.values.length, (_) => 1),
);
return SegmentType.values
.map((item) => Pair<SegmentType, SkipType>(
first: item,
second: SkipType.values[list[item.index]],
))
.toList();
}
static double get blockLimit =>
setting.get(SettingBoxKey.blockLimit, defaultValue: 0.0);
static ThemeMode get themeMode {
switch (setting.get(SettingBoxKey.themeMode,
defaultValue: ThemeType.system.code)) {
@@ -189,6 +208,8 @@ class SettingBoxKey {
disableLikeMsg = 'disableLikeMsg',
defaultHomePage = 'defaultHomePage',
enableSponsorBlock = 'enableSponsorBlock',
blockSettings = 'blockSettings',
blockLimit = 'blockLimit',
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细
danmakuWeight = 'danmakuWeight',