From 60bdf076840a1c19b1c7465316e3bd11636410cc Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Tue, 12 Nov 2024 11:00:07 +0800 Subject: [PATCH] opt: sponsor block Signed-off-by: bggRGjQaUbCoE --- lib/pages/setting/sponsor_block_page.dart | 172 +++++++++++++++++++--- lib/pages/video/detail/controller.dart | 49 +++--- lib/utils/storage.dart | 9 ++ 3 files changed, 188 insertions(+), 42 deletions(-) diff --git a/lib/pages/setting/sponsor_block_page.dart b/lib/pages/setting/sponsor_block_page.dart index 48bcad8b..efca8d27 100644 --- a/lib/pages/setting/sponsor_block_page.dart +++ b/lib/pages/setting/sponsor_block_page.dart @@ -1,6 +1,8 @@ import 'dart:math'; import 'package:PiliPalaX/common/widgets/pair.dart'; +import 'package:PiliPalaX/http/constants.dart'; +import 'package:PiliPalaX/pages/setting/widgets/select_item.dart'; import 'package:PiliPalaX/pages/video/detail/controller.dart' show SegmentType, SegmentTypeExt, SkipType, SkipTypeExt; import 'package:PiliPalaX/utils/storage.dart'; @@ -19,10 +21,13 @@ class SponsorBlockPage extends StatefulWidget { class _SponsorBlockPageState extends State { final _url = 'https://github.com/hanydd/BilibiliSponsorBlock'; + final _textController = TextEditingController(); late double _blockLimit; late List> _blockSettings; late List _blockColor; late String _userId; + late bool _blockToast; + late String _blockServer; @override void initState() { @@ -31,6 +36,14 @@ class _SponsorBlockPageState extends State { _blockSettings = GStorage.blockSettings; _blockColor = GStorage.blockColor; _userId = GStorage.blockUserID; + _blockToast = GStorage.blockToast; + _blockServer = GStorage.blockServer; + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); } TextStyle get _titleStyle => TextStyle(fontSize: 15); @@ -38,17 +51,17 @@ class _SponsorBlockPageState extends State { TextStyle(color: Theme.of(context).colorScheme.outline); Widget get _blockLimitItem => ListTile( + dense: true, onTap: () { - final textController = - TextEditingController(text: _blockLimit.toString()); + _textController.text = _blockLimit.toString(); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('最短片段时长'), + title: Text('最短片段时长', style: _titleStyle), content: TextFormField( keyboardType: TextInputType.numberWithOptions(decimal: true), - controller: textController, + controller: _textController, autofocus: true, decoration: InputDecoration(suffixText: 's'), ), @@ -65,8 +78,8 @@ class _SponsorBlockPageState extends State { TextButton( onPressed: () async { Get.back(); - _blockLimit = - max(0.0, double.tryParse(textController.text) ?? 0.0); + _blockLimit = max( + 0.0, double.tryParse(_textController.text) ?? 0.0); await GStorage.setting .put(SettingBoxKey.blockLimit, _blockLimit); setState(() {}); @@ -90,29 +103,31 @@ class _SponsorBlockPageState extends State { ); Widget get _aboudItem => ListTile( + dense: true, title: Text('关于 SponsorBlock', style: _titleStyle), subtitle: Text(_url, style: _subTitleStyle), onTap: () => Utils.launchURL(_url), ); Widget get _userIdItem => ListTile( + dense: true, title: Text('用户ID', style: _titleStyle), subtitle: Text(_userId, style: _subTitleStyle), onTap: () { final key = GlobalKey(); - final textController = TextEditingController(text: _userId); + _textController.text = _userId; showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: const Text('用户ID'), + title: Text('用户ID', style: _titleStyle), content: Form( key: key, child: TextFormField( minLines: 1, maxLines: 4, autofocus: true, - controller: textController, + controller: _textController, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\d]+')), ], @@ -148,7 +163,7 @@ class _SponsorBlockPageState extends State { onPressed: () async { if (key.currentState?.validate() == true) { Get.back(); - _userId = textController.text; + _userId = _textController.text; await GStorage.setting .put(SettingBoxKey.blockUserID, _userId); setState(() {}); @@ -163,6 +178,109 @@ class _SponsorBlockPageState extends State { }, ); + void _updateBlockToast() async { + _blockToast = !_blockToast; + await GStorage.setting.put(SettingBoxKey.blockToast, _blockToast); + setState(() {}); + } + + Widget get _blockToastItem => ListTile( + dense: true, + onTap: _updateBlockToast, + title: Text( + '显示跳过Toast', + style: _titleStyle, + ), + trailing: Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.isNotEmpty && states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: _blockToast, + onChanged: (val) { + _updateBlockToast(); + }, + ), + )); + + Widget get _blockServerItem => ListTile( + dense: true, + onTap: () { + _textController.text = _blockServer; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('服务器地址', style: _titleStyle), + content: TextFormField( + keyboardType: TextInputType.url, + controller: _textController, + autofocus: true, + ), + actions: [ + TextButton( + onPressed: () async { + Get.back(); + _blockServer = HttpString.sponsorBlockBaseUrl; + await GStorage.setting + .put(SettingBoxKey.blockServer, _blockServer); + setState(() {}); + }, + child: Text('重置'), + ), + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + _blockServer = _textController.text; + await GStorage.setting + .put(SettingBoxKey.blockServer, _blockServer); + setState(() {}); + }, + child: Text('确定'), + ) + ], + ); + }, + ); + }, + title: Text( + '服务器地址', + style: _titleStyle, + ), + subtitle: Text( + _blockServer, + style: _subTitleStyle, + ), + ); + + Widget get _divider => SliverToBoxAdapter( + child: Divider( + height: 1, + color: Theme.of(context).colorScheme.outline.withOpacity(0.1), + ), + ); + + Widget get _dividerL => SliverToBoxAdapter( + child: Divider( + thickness: 16, + color: Theme.of(context).colorScheme.outline.withOpacity(0.1), + ), + ); + @override Widget build(BuildContext context) { return Scaffold( @@ -176,13 +294,15 @@ class _SponsorBlockPageState extends State { ), body: CustomScrollView( slivers: [ - SliverToBoxAdapter(child: _userIdItem), - SliverToBoxAdapter(child: Divider(height: 1)), + _dividerL, SliverToBoxAdapter(child: _blockLimitItem), - SliverToBoxAdapter(child: Divider(height: 1)), + _divider, + SliverToBoxAdapter(child: _blockToastItem), + _dividerL, SliverList.separated( itemCount: _blockSettings.length, itemBuilder: (_, index) => ListTile( + dense: true, enabled: _blockSettings[index].second != SkipType.disable, onTap: () { showDialog( @@ -191,20 +311,18 @@ class _SponsorBlockPageState extends State { clipBehavior: Clip.hardEdge, contentPadding: const EdgeInsets.symmetric(vertical: 16), title: Text.rich( - style: TextStyle(height: 1), - strutStyle: StrutStyle(height: 1, leading: 0), TextSpan( children: [ TextSpan( - text: 'Color Picker\n', - style: TextStyle(fontSize: 18, height: 1.5), + text: 'Color Picker ', + style: TextStyle(fontSize: 15), ), WidgetSpan( alignment: PlaceholderAlignment.middle, child: Container( height: - MediaQuery.textScalerOf(context).scale(16), - width: MediaQuery.textScalerOf(context).scale(16), + MediaQuery.textScalerOf(context).scale(13), + width: MediaQuery.textScalerOf(context).scale(13), alignment: Alignment.center, child: Container( height: 10, @@ -215,11 +333,11 @@ class _SponsorBlockPageState extends State { ), ), ), - style: TextStyle(fontSize: 16, height: 1), + style: TextStyle(fontSize: 13), ), TextSpan( text: ' ${_blockSettings[index].first.title}', - style: TextStyle(fontSize: 16, height: 1), + style: TextStyle(fontSize: 13), ), ], ), @@ -314,10 +432,18 @@ class _SponsorBlockPageState extends State { ), ), ), - separatorBuilder: (_, index) => Divider(height: 1), + separatorBuilder: (_, index) => Divider( + height: 1, + color: Theme.of(context).colorScheme.outline.withOpacity(0.1), + ), ), - SliverToBoxAdapter(child: Divider(height: 1)), + _dividerL, + SliverToBoxAdapter(child: _userIdItem), + _divider, + SliverToBoxAdapter(child: _blockServerItem), + _dividerL, SliverToBoxAdapter(child: _aboudItem), + _dividerL, SliverToBoxAdapter( child: SizedBox( height: 25 + MediaQuery.paddingOf(context).bottom, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index b262b57b..43c394ae 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -52,6 +52,19 @@ extension SegmentTypeExt on SegmentType { '品牌合作', //exclusive_access ][index]; + String get shortTitle => [ + '赞助广告', //sponsor + '推广', //selfpromo + '订阅提醒', //interaction + '开场', //intro + '片尾', //outro + '回顾', //preview + '非音乐', //music_offtopic + '精彩时刻', //poi_highlight + '闲聊', //filler + '品牌合作', //exclusive_access + ][index]; + String get description => [ '付费推广、付费推荐和直接广告。不是自我推广或免费提及他们喜欢的商品/创作者/网站/产品。', //sponsor '类似于 “赞助广告” ,但无报酬或是自我推广。包括有关商品、捐赠的部分或合作者的信息。', //selfpromo @@ -250,19 +263,14 @@ class VideoDetailController extends GetxController _blockColor?[segment.index] ?? segment.color; Future _vote(String uuid, int type) async { - Request() - .post( - '${HttpString.sponsorBlockBaseUrl}/api/voteOnSponsorTime', + Request().post( + '${GStorage.blockServer}/api/voteOnSponsorTime', queryParameters: { 'UUID': uuid, 'userID': GStorage.blockUserID, - 'type': 1, + 'type': type, }, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), - ) - .then((res) { + ).then((res) { SmartDialog.showToast(res.statusCode == 200 ? '投票成功' : '投票失败'); }); } @@ -282,7 +290,7 @@ class VideoDetailController extends GetxController onTap: () { Get.back(); Request().post( - '${HttpString.sponsorBlockBaseUrl}/api/voteOnSponsorTime', + '${GStorage.blockServer}/api/voteOnSponsorTime', queryParameters: { 'UUID': segment.UUID, 'userID': GStorage.blockUserID, @@ -436,7 +444,8 @@ class VideoDetailController extends GetxController item.skipType.title, style: TextStyle(fontSize: 13), ), - if (item.skipType == SkipType.showOnly) + if (item.skipType == SkipType.showOnly && + item.segment.second != 0) SizedBox( width: 36, height: 36, @@ -449,8 +458,10 @@ class VideoDetailController extends GetxController await plPlayerController.videoPlayerController ?.seek(Duration( seconds: item.segment.first)); - _showBlockToast( - '已跳至${Utils.formatDuration(item.segment.first)}'); + if (GStorage.blockToast) { + _showBlockToast( + '已跳至${item.segmentType.shortTitle}'); + } } catch (e) { _showBlockToast('跳转失败: $e'); } @@ -493,7 +504,7 @@ class VideoDetailController extends GetxController Future _sponsorBlock() async { dynamic result = await Request().get( - '${HttpString.sponsorBlockBaseUrl}/api/skipSegments', + '${GStorage.blockServer}/api/skipSegments', data: { 'videoID': bvid, 'cid': cid.value, @@ -553,7 +564,7 @@ class VideoDetailController extends GetxController .clamp(0.0, 1.0); return Segment( start, - start == end ? (end + 0.01).clamp(0.0, 1.0) : end, + (start == end && end != 0) ? (end + 0.01).clamp(0.0, 1.0) : end, _getColor(item.segmentType), ); }).toList(); @@ -589,13 +600,13 @@ class VideoDetailController extends GetxController plPlayerController.danmakuController?.clear(); await plPlayerController.videoPlayerController ?.seek(Duration(seconds: item.segment.second)); - // await plPlayerController - // .seekTo(Duration(seconds: item.segment.second)); - _showBlockToast('已跳过${item.segmentType.title}片段'); item.hasSkipped = true; + if (GStorage.blockToast) { + _showBlockToast('已跳过${item.segmentType.shortTitle}片段'); + } } catch (e) { debugPrint('failed to skip: $e'); - _showBlockToast('${item.segmentType.title}片段跳过失败'); + _showBlockToast('${item.segmentType.shortTitle}片段跳过失败'); } } break; diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index cf5175f6..2c60d0a1 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:ui'; import 'package:PiliPalaX/common/widgets/pair.dart'; +import 'package:PiliPalaX/http/constants.dart'; import 'package:PiliPalaX/models/common/theme_type.dart'; import 'package:PiliPalaX/pages/video/detail/controller.dart' show SegmentType, SegmentTypeExt, SkipType; @@ -60,6 +61,12 @@ class GStorage { return blockUserID; } + static bool get blockToast => + setting.get(SettingBoxKey.blockToast, defaultValue: true); + + static String get blockServer => setting.get(SettingBoxKey.blockServer, + defaultValue: HttpString.sponsorBlockBaseUrl); + static ThemeMode get themeMode { switch (setting.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)) { @@ -237,6 +244,8 @@ class SettingBoxKey { blockLimit = 'blockLimit', blockColor = 'blockColor', blockUserID = 'blockUserID', + blockToast = 'blockToast', + blockServer = 'blockServer', // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细 danmakuWeight = 'danmakuWeight',