diff --git a/lib/common/widgets/icon_button.dart b/lib/common/widgets/icon_button.dart index 6e2ea7ae2..961492d64 100644 --- a/lib/common/widgets/icon_button.dart +++ b/lib/common/widgets/icon_button.dart @@ -6,6 +6,7 @@ Widget iconButton({ required IconData icon, required VoidCallback? onPressed, double size = 36, + double? iconSize, Color? bgColor, Color? iconColor, }) { @@ -17,7 +18,7 @@ Widget iconButton({ onPressed: onPressed, icon: Icon( icon, - size: size / 2, + size: iconSize ?? size / 2, color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer, ), style: IconButton.styleFrom( diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 6c7cf4139..2bd452ad2 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -11,7 +11,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:PiliPalaX/models/github/latest.dart'; -import 'package:PiliPalaX/pages/setting/controller.dart'; import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/utils.dart'; import '../../utils/cache_manage.dart'; @@ -437,7 +436,6 @@ Commit Hash: ${BuildConfig.commitHash}''', } class AboutController extends GetxController { - final SettingController settingController = Get.put(SettingController()); RxString currentVersion = ''.obs; RxString remoteVersion = ''.obs; LatestDataModel? remoteAppInfo; diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 32e875af1..a136c08ce 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -19,7 +19,7 @@ class MinePage extends StatefulWidget { class _MinePageState extends State { final MineController mineController = Get.put(MineController()) - ..themeType.value = ThemeType.values[GStorage.themeType]; + ..themeType.value = ThemeType.values[GStorage.themeTypeInt]; late Future _futureBuilderFuture; StreamSubscription? _listener; diff --git a/lib/pages/setting/controller.dart b/lib/pages/setting/controller.dart deleted file mode 100644 index 0e9c283be..000000000 --- a/lib/pages/setting/controller.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPalaX/http/init.dart'; -import 'package:PiliPalaX/models/common/theme_type.dart'; -import 'package:PiliPalaX/utils/feed_back.dart'; -import 'package:PiliPalaX/utils/login.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'package:hive/hive.dart'; -import '../../models/common/dynamic_badge_mode.dart'; -import '../../models/common/nav_bar_config.dart'; -import '../main/index.dart'; -import 'widgets/select_dialog.dart'; - -class SettingController extends GetxController { - RxBool userLogin = false.obs; - RxBool hiddenSettingUnlocked = false.obs; - RxBool feedBackEnable = false.obs; - RxDouble toastOpacity = (1.0).obs; - RxInt picQuality = 10.obs; - RxInt previewQ = 80.obs; - Rx themeType = ThemeType.system.obs; - dynamic userInfo; - Rx dynamicBadgeType = DynamicBadgeMode.number.obs; - RxInt defaultHomePage = 0.obs; - - Box get setting => GStorage.setting; - - @override - void onInit() { - super.onInit(); - userInfo = GStorage.userInfo.get('userInfoCache'); - userLogin.value = userInfo != null; - hiddenSettingUnlocked.value = - setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false); - feedBackEnable.value = - setting.get(SettingBoxKey.feedBackEnable, defaultValue: false); - toastOpacity.value = - setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0); - picQuality.value = - setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); - themeType.value = ThemeType.values[GStorage.themeType]; - dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( - SettingBoxKey.dynamicBadgeMode, - defaultValue: DynamicBadgeMode.number.code)]; - defaultHomePage.value = - setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0); - previewQ.value = - setting.get(SettingBoxKey.previewQuality, defaultValue: 80); - } - - loginOut(BuildContext context) async { - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('确认要退出登录吗'), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('点错了'), - ), - TextButton( - onPressed: () async { - // 清空cookie - await Request.cookieManager.cookieJar.deleteAll(); - await CookieManager().deleteAllCookies(); - Request.dio.options.headers['cookie'] = ''; - // 清空本地存储的用户标识 - GStorage.userInfo.put('userInfoCache', null); - GStorage.localCache.put(LocalCacheKey.accessKey, - {'mid': -1, 'value': '', 'refresh': ''}); - userLogin.value = false; - if (Get.isRegistered()) { - MainController mainController = Get.find(); - mainController.userLogin.value = false; - } - await LoginUtils.refreshLoginStatus(false); - Get.back(); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - } - - // 开启关闭震动反馈 - onOpenFeedBack() { - feedBack(); - feedBackEnable.value = !feedBackEnable.value; - setting.put(SettingBoxKey.feedBackEnable, feedBackEnable.value); - } - - // 设置动态未读标记 - setDynamicBadgeMode(BuildContext context) async { - DynamicBadgeMode? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '动态未读标记', - value: dynamicBadgeType.value, - values: DynamicBadgeMode.values.map((e) { - return {'title': e.description, 'value': e}; - }).toList(), - ); - }, - ); - if (result != null) { - dynamicBadgeType.value = result; - setting.put(SettingBoxKey.dynamicBadgeMode, result.code); - MainController mainController = Get.put(MainController()); - mainController.dynamicBadgeType = DynamicBadgeMode.values[result.code]; - if (mainController.dynamicBadgeType != DynamicBadgeMode.hidden) { - mainController.getUnreadDynamic(); - } - SmartDialog.showToast('设置成功'); - } - } - - // 设置默认启动页 - setDefaultHomePage(BuildContext context) async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '首页启动页', - value: defaultHomePage.value, - values: defaultNavigationBars.map((e) { - return {'title': e['label'], 'value': e['id']}; - }).toList()); - }, - ); - if (result != null) { - defaultHomePage.value = result; - setting.put(SettingBoxKey.defaultHomePage, result); - SmartDialog.showToast('设置成功,重启生效'); - } - } -} diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index fe23231a3..5925e023e 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -1,621 +1,15 @@ -import 'dart:math'; - -import 'package:PiliPalaX/pages/main/controller.dart'; -import 'package:PiliPalaX/pages/member/new/controller.dart' - show MemberTabType, MemberTabTypeExt; -import 'package:PiliPalaX/utils/global_data.dart'; +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:PiliPalaX/models/common/dynamics_type.dart'; -import 'package:PiliPalaX/models/common/reply_sort_type.dart'; -import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'package:hive/hive.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import '../home/index.dart'; -import 'controller.dart'; -import 'widgets/switch_item.dart'; - -class ExtraSetting extends StatefulWidget { +class ExtraSetting extends StatelessWidget { const ExtraSetting({super.key}); - @override - State createState() => _ExtraSettingState(); -} - -class _ExtraSettingState extends State { - final SettingController settingController = Get.put(SettingController()); - late dynamic defaultReplySort; - late dynamic defaultDynamicType; - late MemberTabType defaultMemberTab; - late dynamic enableSystemProxy; - late String defaultSystemProxyHost; - late String defaultSystemProxyPort; - late double danmakuLineHeight = GStorage.danmakuLineHeight; - bool userLogin = false; - - Box get setting => GStorage.setting; - - @override - void initState() { - super.initState(); - // 默认优先显示最新评论 - defaultReplySort = - setting.get(SettingBoxKey.replySortType, defaultValue: 1); - if (defaultReplySort == 2) { - setting.put(SettingBoxKey.replySortType, 0); - defaultReplySort = 0; - } - // 优先展示全部动态 all - defaultDynamicType = - setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); - defaultMemberTab = GStorage.memberTab; - enableSystemProxy = - setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false); - defaultSystemProxyHost = - setting.get(SettingBoxKey.systemProxyHost, defaultValue: ''); - defaultSystemProxyPort = - setting.get(SettingBoxKey.systemProxyPort, defaultValue: ''); - } - - // 设置代理 - void twoFADialog() { - var systemProxyHost = ''; - var systemProxyPort = ''; - - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('设置代理'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 6), - TextField( - decoration: InputDecoration( - isDense: true, - labelText: defaultSystemProxyHost != '' - ? defaultSystemProxyHost - : '请输入Host,使用 . 分割', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - hintText: defaultSystemProxyHost, - ), - onChanged: (e) { - systemProxyHost = e; - }, - ), - const SizedBox(height: 10), - TextField( - keyboardType: TextInputType.number, - decoration: InputDecoration( - isDense: true, - labelText: defaultSystemProxyPort != '' - ? defaultSystemProxyPort - : '请输入Port', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(6.0), - ), - hintText: defaultSystemProxyPort, - ), - onChanged: (e) { - systemProxyPort = e; - }, - ), - ], - ), - actions: [ - TextButton( - onPressed: () async { - Get.back(); - }, - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - setting.put(SettingBoxKey.systemProxyHost, systemProxyHost); - setting.put(SettingBoxKey.systemProxyPort, systemProxyPort); - Get.back(); - // Request.dio; - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - } - @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('其它设置')), body: ListView( - children: [ - SetSwitchItem( - title: '空降助手', - subTitle: '点击配置', - leading: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.shield_outlined), - Icon( - Icons.play_arrow_rounded, - size: 15, - ), - ], - ), - setKey: SettingBoxKey.enableSponsorBlock, - defaultVal: false, - onTap: () => Get.toNamed('/sponsorBlock'), - ), - SetSwitchItem( - title: '检查未读动态', - subTitle: '点击设置检查周期(min)', - leading: const Icon(Icons.notifications_none), - setKey: SettingBoxKey.checkDynamic, - defaultVal: true, - onChanged: (value) { - Get.find().checkDynamic = value; - }, - onTap: () { - int dynamicPeriod = GStorage.dynamicPeriod; - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('检查周期', style: TextStyle(fontSize: 18)), - content: TextFormField( - autofocus: true, - initialValue: dynamicPeriod.toString(), - keyboardType: TextInputType.numberWithOptions(), - onChanged: (value) { - dynamicPeriod = int.tryParse(value) ?? 5; - }, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'\d+')), - ], - decoration: InputDecoration(suffixText: 'min'), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - setting.put( - SettingBoxKey.dynamicPeriod, dynamicPeriod); - Get.find().dynamicPeriod = - dynamicPeriod; - }, - child: Text('确定'), - ) - ], - ); - }, - ); - }, - ), - SetSwitchItem( - title: '使用gRPC加载评论', - subTitle: '如无法加载评论,可关闭\n非gRPC楼中楼无法定位评论、按热度/时间排序、查看对话', - leading: SizedBox( - height: 24, - width: 24, - child: Icon(MdiIcons.google, size: 20), - ), - setKey: SettingBoxKey.grpcReply, - defaultVal: true, - onChanged: (value) { - GlobalData().grpcReply = value; - }, - ), - SetSwitchItem( - title: '显示视频分段信息', - leading: Transform.rotate( - angle: pi / 2, - child: Icon(MdiIcons.viewHeadline), - ), - setKey: SettingBoxKey.showViewPoints, - defaultVal: true, - ), - SetSwitchItem( - title: '视频页显示相关视频', - leading: Icon(MdiIcons.motionPlayOutline), - setKey: SettingBoxKey.showRelatedVideo, - defaultVal: true, - ), - SetSwitchItem( - title: '显示视频评论', - leading: Icon(MdiIcons.commentTextOutline), - setKey: SettingBoxKey.showVideoReply, - defaultVal: true, - ), - SetSwitchItem( - title: '显示番剧评论', - leading: Icon(MdiIcons.commentTextOutline), - setKey: SettingBoxKey.showBangumiReply, - defaultVal: true, - ), - SetSwitchItem( - title: '默认展开视频简介', - leading: const Icon(Icons.expand_more), - setKey: SettingBoxKey.alwaysExapndIntroPanel, - defaultVal: false, - ), - SetSwitchItem( - title: '横屏自动展开视频简介', - leading: const Icon(Icons.expand_more), - setKey: SettingBoxKey.exapndIntroPanelH, - defaultVal: false, - ), - SetSwitchItem( - title: '横屏分P/合集列表显示在Tab栏', - leading: const Icon(Icons.format_list_numbered_rtl_sharp), - setKey: SettingBoxKey.horizontalSeasonPanel, - defaultVal: false, - ), - SetSwitchItem( - title: '横屏播放页在侧栏打开UP主页', - leading: const Icon(Icons.account_circle_outlined), - setKey: SettingBoxKey.horizontalMemberPage, - defaultVal: false, - ), - ListTile( - title: Text('评论折叠行数', style: titleStyle), - subtitle: Text('0行为不折叠', style: subTitleStyle), - leading: const Icon(Icons.compress), - trailing: Text( - '${GlobalData().replyLengthLimit.toString()}行', - style: Theme.of(context).textTheme.titleSmall, - ), - onTap: () { - String replyLengthLimit = - GlobalData().replyLengthLimit.toString(); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('评论折叠行数', style: TextStyle(fontSize: 18)), - content: TextFormField( - autofocus: true, - initialValue: replyLengthLimit, - keyboardType: TextInputType.numberWithOptions(), - onChanged: (value) { - replyLengthLimit = value; - }, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'\d+')), - ], - decoration: InputDecoration(suffixText: '行'), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () async { - Get.back(); - GlobalData().replyLengthLimit = - int.tryParse(replyLengthLimit) ?? 6; - await setting.put( - SettingBoxKey.replyLengthLimit, - GlobalData().replyLengthLimit, - ); - setState(() {}); - }, - child: Text('确定'), - ) - ], - ); - }, - ); - }, - ), - ListTile( - title: Text('弹幕行高', style: titleStyle), - subtitle: Text('默认1.6', style: subTitleStyle), - leading: const Icon(Icons.subtitles_outlined), - trailing: Text( - danmakuLineHeight.toString(), - style: Theme.of(context).textTheme.titleSmall, - ), - onTap: () { - String danmakuLineHeight = this.danmakuLineHeight.toString(); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('弹幕行高', style: TextStyle(fontSize: 18)), - content: TextFormField( - autofocus: true, - initialValue: danmakuLineHeight, - keyboardType: - TextInputType.numberWithOptions(decimal: true), - onChanged: (value) { - danmakuLineHeight = value; - }, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), - ], - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () async { - Get.back(); - this.danmakuLineHeight = max( - 1.0, - double.tryParse(danmakuLineHeight) ?? 1.6, - ); - await setting.put( - SettingBoxKey.danmakuLineHeight, - this.danmakuLineHeight, - ); - setState(() {}); - }, - child: Text('确定'), - ) - ], - ); - }, - ); - }, - ), - SetSwitchItem( - title: '显示视频警告/争议信息', - leading: const Icon(Icons.warning_amber_rounded), - setKey: SettingBoxKey.showArgueMsg, - defaultVal: true, - ), - SetSwitchItem( - title: '分P/合集:倒序播放从首集开始播放', - subTitle: '开启则自动切换为倒序首集,否则保持当前集', - leading: Icon(MdiIcons.sort), - setKey: SettingBoxKey.reverseFromFirst, - defaultVal: true, - ), - SetSwitchItem( - title: '禁用 SSL 证书验证', - subTitle: '谨慎开启,禁用容易受到中间人攻击', - leading: Icon(Icons.security), - needReboot: true, - setKey: SettingBoxKey.badCertificateCallback, - ), - Obx( - () => ListTile( - enableFeedback: true, - onTap: settingController.onOpenFeedBack, - leading: const Icon(Icons.vibration_outlined), - title: Text('震动反馈', style: titleStyle), - subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; // All other states will use the default thumbIcon. - }), - value: settingController.feedBackEnable.value, - onChanged: (value) => settingController.onOpenFeedBack()), - ), - ), - ), - const SetSwitchItem( - title: '大家都在搜', - subTitle: '是否展示「大家都在搜」', - leading: Icon(Icons.data_thresholding_outlined), - setKey: SettingBoxKey.enableHotKey, - defaultVal: true, - ), - SetSwitchItem( - title: '搜索默认词', - subTitle: '是否展示搜索框默认词', - leading: const Icon(Icons.whatshot_outlined), - setKey: SettingBoxKey.enableSearchWord, - defaultVal: true, - onChanged: (val) { - Get.find().defaultSearch.value = ''; - }, - ), - const SetSwitchItem( - title: '快速收藏', - subTitle: '点按收藏至默认,长按选择文件夹', - leading: Icon(Icons.bookmark_add_outlined), - setKey: SettingBoxKey.enableQuickFav, - defaultVal: false, - ), - const SetSwitchItem( - title: '评论区搜索关键词', - subTitle: '展示评论区搜索关键词', - leading: Icon(Icons.search_outlined), - setKey: SettingBoxKey.enableWordRe, - defaultVal: false, - ), - const SetSwitchItem( - title: '启用AI总结', - subTitle: '视频详情页开启AI总结', - leading: Icon(Icons.engineering_outlined), - setKey: SettingBoxKey.enableAi, - defaultVal: true, - ), - const SetSwitchItem( - title: '消息页禁用"收到的赞"功能', - subTitle: '禁止打开入口,降低网络社交依赖', - leading: Icon(Icons.beach_access_outlined), - setKey: SettingBoxKey.disableLikeMsg, - defaultVal: false, - ), - const SetSwitchItem( - title: '默认展示评论区', - subTitle: '在视频详情页默认切换至评论区页(仅Tab型布局)', - leading: Icon(Icons.mode_comment_outlined), - setKey: SettingBoxKey.defaultShowComment, - defaultVal: false, - ), - ListTile( - dense: false, - title: Text('评论展示', style: titleStyle), - leading: const Icon(Icons.whatshot_outlined), - subtitle: Text( - '当前优先展示「${ReplySortType.values[defaultReplySort].titles}」', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '评论展示', - value: defaultReplySort, - values: ReplySortType.values.map((e) { - return {'title': e.titles, 'value': e.index}; - }).toList()); - }, - ); - if (result != null) { - defaultReplySort = result; - setting.put(SettingBoxKey.replySortType, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('动态展示', style: titleStyle), - leading: const Icon(Icons.dynamic_feed_outlined), - subtitle: Text( - '当前优先展示「${DynamicsType.values[defaultDynamicType].labels}」', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '动态展示', - value: defaultDynamicType, - values: DynamicsType.values.sublist(0, 4).map((e) { - return {'title': e.labels, 'value': e.index}; - }).toList()); - }, - ); - if (result != null) { - defaultDynamicType = result; - setting.put(SettingBoxKey.defaultDynamicType, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('用户页默认展示TAB', style: titleStyle), - leading: const Icon(Icons.tab), - subtitle: Text( - '当前优先展示「${defaultMemberTab.title}」', - style: subTitleStyle, - ), - onTap: () async { - MemberTabType? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '用户页默认展示TAB', - value: defaultMemberTab, - values: MemberTabType.values.map((e) { - return {'title': e.title, 'value': e}; - }).toList()); - }, - ); - if (result != null) { - defaultMemberTab = result; - setting.put(SettingBoxKey.memberTab, result.index); - setState(() {}); - } - }, - ), - ListTile( - enableFeedback: true, - onTap: () => twoFADialog(), - leading: const Icon(Icons.airplane_ticket_outlined), - title: Text('设置代理', style: titleStyle), - subtitle: Text('设置代理 host:port', style: subTitleStyle), - trailing: Transform.scale( - alignment: Alignment.centerRight, - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; // All other states will use the default thumbIcon. - }), - value: enableSystemProxy, - onChanged: (val) { - setting.put( - SettingBoxKey.enableSystemProxy, !enableSystemProxy); - setState(() { - enableSystemProxy = !enableSystemProxy; - }); - }, - ), - ), - ), - const SetSwitchItem( - title: '自动清除缓存', - subTitle: '每次启动时清除缓存', - leading: Icon(Icons.auto_delete_outlined), - setKey: SettingBoxKey.autoClearCache, - defaultVal: false, - ), - // const SetSwitchItem( - // title: '检查更新', - // subTitle: '每次启动时检查是否需要更新', - // leading: Icon(Icons.system_update_alt_outlined), - // setKey: SettingBoxKey.autoUpdate, - // defaultVal: false, - // ), - ], + children: extraSettings.map((item) => item.widget).toList(), ), ); } diff --git a/lib/pages/setting/index.dart b/lib/pages/setting/index.dart deleted file mode 100644 index 30fa06b33..000000000 --- a/lib/pages/setting/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -library setting; - -export './controller.dart'; -export './view.dart'; diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart index 1ec18d3d7..4675bb28b 100644 --- a/lib/pages/setting/pages/color_select.dart +++ b/lib/pages/setting/pages/color_select.dart @@ -1,7 +1,6 @@ import 'package:PiliPalaX/models/common/nav_bar_config.dart'; import 'package:PiliPalaX/models/common/theme_type.dart'; import 'package:PiliPalaX/pages/home/index.dart'; -import 'package:PiliPalaX/pages/setting/controller.dart'; import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter/material.dart'; @@ -10,32 +9,6 @@ import 'package:hive/hive.dart'; import 'package:PiliPalaX/models/common/color_type.dart'; import 'package:PiliPalaX/utils/storage.dart'; -// extension _SchemeExt on DynamicSchemeVariant { -// String get title => [ -// '色调点', //tonalSpot -// '富达', //fidelity -// '单色', //monochrome -// '中性的', //neutral -// '充满活力', //vibrant -// '富有表现力', //expressive -// '内容', //content -// '彩虹', //rainbow -// '水果沙拉', //fruitSalad -// ][index]; -// // from ImageToolbox -// String get subTitle => [ -// '默认调色板样式,它允许自定义所有四种颜色,其他允许您仅设置关键颜色', //tonalSpot -// '与内容方案非常相似的方案', //fidelity -// '单色主题,颜色纯黑/白/灰', //monochrome -// '色彩比单色稍多的风格', //neutral -// '响亮的主题,主要调色板的色彩度最大,其他调色板的色彩度增加', //vibrant -// '有趣的主题 - 源颜色的色调不会出现在主题中', //expressive -// '将源颜色放置在Scheme.primaryContainer中的方案', //content -// '有趣的主题 - 源颜色的色调不会出现在主题中', //rainbow -// '有趣的主题 - 源颜色的色调不会出现在主题中', //fruitSalad -// ][index]; -// } - class ColorSelectPage extends StatefulWidget { const ColorSelectPage({super.key}); @@ -65,7 +38,6 @@ List generateItems(int count) { } class _ColorSelectPageState extends State { - final SettingController settingController = Get.put(SettingController()); final ColorSelectController ctr = Get.put(ColorSelectController()); FlexSchemeVariant _dynamicSchemeVariant = FlexSchemeVariant.values[GStorage.schemeVariant]; @@ -88,14 +60,14 @@ class _ColorSelectPageState extends State { builder: (context) { return SelectDialog( title: '主题模式', - value: settingController.themeType.value, + value: ctr.themeType.value, values: ThemeType.values.map((e) { return {'title': e.description, 'value': e}; }).toList()); }, ); if (result != null) { - settingController.themeType.value = result; + ctr.themeType.value = result; GStorage.setting.put(SettingBoxKey.themeMode, result.index); Get.forceAppUpdate(); } @@ -106,8 +78,7 @@ class _ColorSelectPageState extends State { child: const Icon(Icons.flashlight_on_outlined), ), title: Text('主题模式', style: titleStyle), - subtitle: Obx(() => Text( - '当前模式:${settingController.themeType.value.description}', + subtitle: Obx(() => Text('当前模式:${ctr.themeType.value.description}', style: subTitleStyle)), ), ListTile( @@ -285,16 +256,15 @@ class _ColorSelectPageState extends State { class ColorSelectController extends GetxController { RxBool dynamicColor = true.obs; RxInt type = 0.obs; - late final List> colorThemes; + late final List> colorThemes = colorThemeTypes; RxInt currentColor = 0.obs; RxDouble currentTextScale = 1.0.obs; + Rx themeType = GStorage.themeType.obs; Box get setting => GStorage.setting; @override void onInit() { - colorThemes = colorThemeTypes; - // 默认使用动态取色 dynamicColor.value = setting.get(SettingBoxKey.dynamicColor, defaultValue: true); type.value = dynamicColor.value ? 0 : 1; diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 9d8391478..911cd6b6c 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -217,13 +217,12 @@ class _PlaySpeedPageState extends State { ), ), ListTile( - dense: false, title: const Text('默认倍速'), subtitle: Text(playSpeedDefault.toString()), ), SetSwitchItem( title: '动态长按倍速', - subTitle: '根据默认倍速长按时自动双倍', + subtitle: '根据默认倍速长按时自动双倍', setKey: SettingBoxKey.enableAutoLongPressSpeed, defaultVal: enableAutoLongPressSpeed, onChanged: (val) { @@ -237,7 +236,6 @@ class _PlaySpeedPageState extends State { ), if (!enableAutoLongPressSpeed) ListTile( - dense: false, title: const Text('默认长按倍速'), subtitle: Text(longPressSpeedDefault.toString()), ), diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index beb7c8dd7..3a8c92411 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -1,16 +1,6 @@ -import 'dart:io'; - +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; -import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:PiliPalaX/services/service_locator.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'package:hive/hive.dart'; - -import '../../models/video/play/subtitle.dart'; -import 'widgets/switch_item.dart'; class PlaySetting extends StatefulWidget { const PlaySetting({super.key}); @@ -20,23 +10,6 @@ class PlaySetting extends StatefulWidget { } class _PlaySettingState extends State { - late String defaultSubtitlePreference; - late int defaultFullScreenMode; - late int defaultBtmProgressBehavior; - - Box get setting => GStorage.setting; - - @override - void initState() { - super.initState(); - defaultFullScreenMode = setting.get(SettingBoxKey.fullScreenMode, - defaultValue: FullScreenMode.values.first.code); - defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior, - defaultValue: BtmProgressBehavior.values.first.code); - defaultSubtitlePreference = setting.get(SettingBoxKey.subtitlePreference, - defaultValue: SubtitlePreference.values.first.code); - } - @override void dispose() { super.dispose(); @@ -47,210 +20,10 @@ class _PlaySettingState extends State { @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('播放器设置')), body: ListView( - children: [ - const SetSwitchItem( - title: '弹幕开关', - subTitle: '是否展示弹幕', - leading: Icon(Icons.comment_outlined), - setKey: SettingBoxKey.enableShowDanmaku, - defaultVal: true, - ), - ListTile( - dense: false, - onTap: () => Get.toNamed('/playSpeedSet'), - leading: const Icon(Icons.speed_outlined), - title: Text('倍速设置', style: titleStyle), - subtitle: Text('设置视频播放速度', style: subTitleStyle), - ), - const SetSwitchItem( - title: '自动播放', - subTitle: '进入详情页自动播放', - leading: Icon(Icons.motion_photos_auto_outlined), - setKey: SettingBoxKey.autoPlayEnable, - defaultVal: false, - ), - const SetSwitchItem( - title: '双击快退/快进', - subTitle: '左侧双击快退/右侧双击快进,关闭则双击均为暂停/播放', - leading: Icon(Icons.touch_app_outlined), - setKey: SettingBoxKey.enableQuickDouble, - defaultVal: true, - ), - ListTile( - dense: false, - title: Text('自动启用字幕', style: titleStyle), - leading: const Icon(Icons.closed_caption_outlined), - subtitle: Text( - '当前选择偏好:' - '${SubtitlePreferenceCode.fromCode(defaultSubtitlePreference)!.description}', - style: subTitleStyle), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '字幕选择偏好', - value: setting.get(SettingBoxKey.subtitlePreference, - defaultValue: SubtitlePreference.values.first.code), - values: SubtitlePreference.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - setting.put(SettingBoxKey.subtitlePreference, result); - defaultSubtitlePreference = result; - setState(() {}); - } - }, - ), - const SetSwitchItem( - title: '竖屏扩大展示', - subTitle: '小屏竖屏视频宽高比由16:9扩大至1:1(不支持收起);横屏适配时,扩大至9:16', - leading: Icon(Icons.expand_outlined), - setKey: SettingBoxKey.enableVerticalExpand, - defaultVal: false, - ), - const SetSwitchItem( - title: '自动全屏', - subTitle: '视频开始播放时进入全屏', - leading: Icon(Icons.fullscreen_outlined), - setKey: SettingBoxKey.enableAutoEnter, - defaultVal: false, - ), - const SetSwitchItem( - title: '自动退出全屏', - subTitle: '视频结束播放时退出全屏', - leading: Icon(Icons.fullscreen_exit_outlined), - setKey: SettingBoxKey.enableAutoExit, - defaultVal: true, - ), - const SetSwitchItem( - title: '延长播放控件显示时间', - subTitle: '开启后延长至30秒,便于屏幕阅读器滑动切换控件焦点', - leading: Icon(Icons.timer_outlined), - setKey: SettingBoxKey.enableLongShowControl, - defaultVal: false), - const SetSwitchItem( - title: '全向旋转', - subTitle: '小屏可受重力转为临时全屏,若系统锁定旋转仍触发请关闭,关闭会影响横屏适配', - leading: Icon(Icons.screen_rotation_alt_outlined), - setKey: SettingBoxKey.allowRotateScreen, - defaultVal: true, - ), - const SetSwitchItem( - title: '后台播放', - subTitle: '进入后台时继续播放', - leading: Icon(Icons.motion_photos_pause_outlined), - setKey: SettingBoxKey.continuePlayInBackground, - defaultVal: false, - ), - if (Platform.isAndroid) - SetSwitchItem( - title: '后台画中画', - subTitle: '进入后台时以小窗形式(PiP)播放', - leading: const Icon(Icons.picture_in_picture_outlined), - setKey: SettingBoxKey.autoPiP, - defaultVal: false, - onChanged: (val) { - if (val && - !setting.get(SettingBoxKey.enableBackgroundPlay, - defaultValue: true)) { - SmartDialog.showToast('建议开启后台音频服务'); - } - }), - if (Platform.isAndroid) - const SetSwitchItem( - title: '画中画不加载弹幕', - subTitle: '当弹幕开关开启时,小窗屏蔽弹幕以获得较好的体验', - leading: Icon(Icons.comments_disabled_outlined), - setKey: SettingBoxKey.pipNoDanmaku, - defaultVal: false, - ), - const SetSwitchItem( - title: '全屏手势反向', - subTitle: '默认播放器中部向上滑动进入全屏,向下退出\n开启后向下全屏,向上退出', - leading: Icon(Icons.swap_vert_outlined), - setKey: SettingBoxKey.fullScreenGestureReverse, - defaultVal: false, - ), - const SetSwitchItem( - title: '观看人数', - subTitle: '展示同时在看人数', - leading: Icon(Icons.people_outlined), - setKey: SettingBoxKey.enableOnlineTotal, - defaultVal: true, - ), - ListTile( - dense: false, - title: Text('默认全屏方向', style: titleStyle), - leading: const Icon(Icons.open_with_outlined), - subtitle: Text( - '当前全屏方向:${FullScreenModeCode.fromCode(defaultFullScreenMode)!.description}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认全屏方向', - value: defaultFullScreenMode, - values: FullScreenMode.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultFullScreenMode = result; - setting.put(SettingBoxKey.fullScreenMode, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('底部进度条展示', style: titleStyle), - leading: const Icon(Icons.border_bottom_outlined), - subtitle: Text( - '当前展示方式:${BtmProgresBehaviorCode.fromCode(defaultBtmProgressBehavior)!.description}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '底部进度条展示', - value: defaultBtmProgressBehavior, - values: BtmProgressBehavior.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultBtmProgressBehavior = result; - setting.put(SettingBoxKey.btmProgressBehavior, result); - setState(() {}); - } - }, - ), - const SetSwitchItem( - title: '后台音频服务', - subTitle: '避免画中画没有播放暂停功能', - leading: Icon(Icons.volume_up_outlined), - setKey: SettingBoxKey.enableBackgroundPlay, - defaultVal: true, - ), - ], + children: playSettings.map((item) => item.widget).toList(), ), ); } diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart index 6696a3792..8a715280a 100644 --- a/lib/pages/setting/privacy_setting.dart +++ b/lib/pages/setting/privacy_setting.dart @@ -1,119 +1,15 @@ +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPalaX/http/interceptor_anonymity.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import '../../models/user/info.dart'; -import '../mine/controller.dart'; - -class PrivacySetting extends StatefulWidget { +class PrivacySetting extends StatelessWidget { const PrivacySetting({super.key}); - @override - State createState() => _PrivacySettingState(); -} - -class _PrivacySettingState extends State { - bool userLogin = false; - UserInfoData? userInfo; - late bool hiddenSettingUnlocked; - - @override - void initState() { - super.initState(); - userInfo = GStorage.userInfo.get('userInfoCache'); - userLogin = userInfo != null; - hiddenSettingUnlocked = GStorage.setting - .get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false); - } - @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('隐私设置')), - body: Column( - children: [ - ListTile( - onTap: () { - if (!userLogin) { - SmartDialog.showToast('登录后查看'); - return; - } - Get.toNamed('/blackListPage'); - }, - dense: false, - title: Text('黑名单管理', style: titleStyle), - subtitle: Text('已拉黑用户', style: subTitleStyle), - leading: const Icon(Icons.block), - ), - // ListTile( - // onTap: () async { - // if (!userLogin) { - // SmartDialog.showToast('请先登录'); - // return; - // } - // var res = await MemberHttp.cookieToKey(); - // if (res['status']) { - // SmartDialog.showToast(res['msg']); - // } else { - // SmartDialog.showToast("刷新失败:${res['msg']}"); - // } - // }, - // dense: false, - // title: Text('刷新access_key', style: titleStyle), - // leading: const Icon(Icons.perm_device_info_outlined), - // subtitle: Text( - // '用于app端推荐接口的用户凭证。若app端未推荐个性化内容,可尝试刷新或清除本app数据后重新登录', - // style: subTitleStyle), - // ), - ListTile( - onTap: () { - MineController.onChangeAnonymity(context); - setState(() {}); - }, - leading: const Icon(Icons.privacy_tip_outlined), - dense: false, - title: Text(MineController.anonymity.value ? '退出无痕模式' : '进入无痕模式', - style: titleStyle), - subtitle: Text( - MineController.anonymity.value - ? '已进入无痕模式,搜索、观看视频/直播不携带Cookie与CSRF,其余操作不受影响' - : '未开启无痕模式,将使用账户信息提供完整服务', - style: subTitleStyle, - )), - ListTile( - onTap: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('查看详情'), - content: - Text(AnonymityInterceptor.anonymityList.join('\n')), - actions: [ - TextButton( - onPressed: () async { - Get.back(); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - leading: const Icon(Icons.flag_outlined), - dense: false, - title: Text('了解无痕模式', style: titleStyle), - subtitle: Text('查看无痕模式作用的API列表', style: subTitleStyle), - ), - ], + body: ListView( + children: privacySettings.map((item) => item.widget).toList(), ), ); } diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index 90b13c816..7c64f8e47 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -1,333 +1,16 @@ -import 'package:PiliPalaX/utils/extension.dart'; +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:hive/hive.dart'; -import 'package:PiliPalaX/models/common/rcmd_type.dart'; -import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; -import 'package:PiliPalaX/utils/recommend_filter.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'widgets/switch_item.dart'; - -class RecommendSetting extends StatefulWidget { +class RecommendSetting extends StatelessWidget { const RecommendSetting({super.key}); - @override - State createState() => _RecommendSettingState(); -} - -class _RecommendSettingState extends State { - late dynamic defaultRcmdType; - late dynamic userInfo; - bool userLogin = false; - late dynamic accessKeyInfo; - // late int filterUnfollowedRatio; - late int minDurationForRcmd; - late int minLikeRatioForRecommend; - late String banWordForRecommend; - - Box get setting => GStorage.setting; - - @override - void initState() { - super.initState(); - // 首页默认推荐类型 - defaultRcmdType = - setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app'); - userInfo = GStorage.userInfo.get('userInfoCache'); - userLogin = userInfo != null; - accessKeyInfo = - GStorage.localCache.get(LocalCacheKey.accessKey, defaultValue: null); - // filterUnfollowedRatio = setting - // .get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0); - minDurationForRcmd = - setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); - minLikeRatioForRecommend = - setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); - banWordForRecommend = - setting.get(SettingBoxKey.banWordForRecommend, defaultValue: ''); - } - @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('推荐流设置')), body: ListView( children: [ - ListTile( - dense: false, - title: Text('首页推荐类型', style: titleStyle), - leading: const Icon(Icons.model_training_outlined), - subtitle: Text( - '当前使用「$defaultRcmdType端」推荐¹', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '推荐类型', - value: defaultRcmdType, - values: RcmdType.values.map((e) { - return {'title': e.labels, 'value': e.values}; - }).toList(), - ); - }, - ); - if (result != null) { - if (result == 'app') { - if (accessKeyInfo == null) { - SmartDialog.showToast('尚未登录,无法收到个性化推荐'); - } - } - defaultRcmdType = result; - setting.put(SettingBoxKey.defaultRcmdType, result); - SmartDialog.showToast('下次启动时生效'); - setState(() {}); - } - }, - ), - const SetSwitchItem( - title: '推荐动态', - subTitle: '是否在推荐内容中展示动态(仅app端)', - leading: Icon(Icons.motion_photos_on_outlined), - setKey: SettingBoxKey.enableRcmdDynamic, - defaultVal: true, - ), - const SetSwitchItem( - title: '首页推荐刷新', - subTitle: '下拉刷新时保留上次内容', - leading: Icon(Icons.refresh), - setKey: SettingBoxKey.enableSaveLastData, - defaultVal: false, - needReboot: true, - ), - // 分割线 - const Divider(height: 1), - ListTile( - dense: false, - leading: const Icon(Icons.thumb_up_outlined), - title: Text('点赞率过滤', style: titleStyle), - subtitle: Text( - '过滤掉点赞数/播放量「小于$minLikeRatioForRecommend%」的推荐视频(仅web端)', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '选择点赞率(0即不过滤)', - value: minLikeRatioForRecommend, - values: [0, 1, 2, 3, 4].map((e) { - return {'title': '$e %', 'value': e}; - }).toList()); - }, - ); - if (result != null) { - minLikeRatioForRecommend = result; - setting.put(SettingBoxKey.minLikeRatioForRecommend, result); - RecommendFilter.update(); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - leading: const Icon(Icons.title_outlined), - title: Text('标题关键词过滤', style: titleStyle), - subtitle: Text( - banWordForRecommend.isEmpty ? "点击添加" : banWordForRecommend, - style: subTitleStyle, - ), - onTap: () async { - final TextEditingController textController = - TextEditingController(text: banWordForRecommend); - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text( - '标题关键词过滤', - style: TextStyle(fontSize: 18), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('使用|隔开,如:尝试|测试'), - TextField( - autofocus: true, - controller: textController, - textInputAction: TextInputAction.newline, - minLines: 1, - maxLines: 4, - ) - ], - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - child: const Text('保存'), - onPressed: () async { - Get.back(); - banWordForRecommend = textController.text; - setting.put(SettingBoxKey.banWordForRecommend, - banWordForRecommend); - setState(() {}); - RecommendFilter.update(); - SmartDialog.showToast('已保存'); - }, - ), - ], - ); - }, - ); - }, - ), - ListTile( - dense: false, - title: Text('视频时长过滤', style: titleStyle), - leading: const Icon(Icons.timelapse_outlined), - subtitle: Text( - '过滤掉时长「小于$minDurationForRcmd秒」的推荐视频', - style: subTitleStyle, - ), - onTap: () async { - const List defDurations = [0, 30, 60, 90, 120]; - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '选择时长(0即不过滤)', - value: minDurationForRcmd, - values: [ - ...[ - ...defDurations, - if (defDurations.contains(minDurationForRcmd).not) - minDurationForRcmd, - ]..sort(), - -1, - ].map((e) { - if (e == -1) { - return {'title': '自定义', 'value': e}; - } - return {'title': '$e 秒', 'value': e}; - }).toList()); - }, - ); - if (result != null) { - void updateDuration(int value) { - minDurationForRcmd = value; - setting.put(SettingBoxKey.minDurationForRcmd, value); - RecommendFilter.update(); - setState(() {}); - } - - if (result == -1 && context.mounted) { - showDialog( - context: context, - builder: (context) { - String duration = ''; - return AlertDialog( - title: Text( - '自定义时长', - style: TextStyle(fontSize: 18), - ), - content: TextField( - autofocus: true, - onChanged: (value) => duration = value, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'\d+')), - ], - decoration: const InputDecoration(suffixText: 's'), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () { - Get.back(); - updateDuration(int.tryParse(duration) ?? 0); - }, - child: Text('确定'), - ), - ], - ); - }, - ); - } else { - updateDuration(result); - } - } - }, - ), - SetSwitchItem( - title: '已关注UP豁免推荐过滤', - subTitle: '推荐中已关注用户发布的内容不会被过滤', - leading: const Icon(Icons.favorite_border_outlined), - setKey: SettingBoxKey.exemptFilterForFollowed, - defaultVal: true, - onChanged: (_) => {RecommendFilter.update}, - ), - // ListTile( - // dense: false, - // title: Text('按比例过滤未关注Up', style: titleStyle), - // subtitle: Text( - // '滤除推荐中占比「$filterUnfollowedRatio%」的未关注用户发布的内容', - // style: subTitleStyle, - // ), - // onTap: () async { - // int? result = await showDialog( - // context: context, - // builder: (context) { - // return SelectDialog( - // title: '选择滤除比例(0即不过滤)', - // value: filterUnfollowedRatio, - // values: [0, 16, 32, 48, 64].map((e) { - // return {'title': '$e %', 'value': e}; - // }).toList()); - // }, - // ); - // if (result != null) { - // filterUnfollowedRatio = result; - // setting.put( - // SettingBoxKey.filterUnfollowedRatio, result); - // RecommendFilter.update(); - // setState(() {}); - // } - // }, - // ), - SetSwitchItem( - title: '过滤器也应用于相关视频', - subTitle: '视频详情页的相关视频也进行过滤²', - leading: const Icon(Icons.explore_outlined), - setKey: SettingBoxKey.applyFilterToRelatedVideos, - defaultVal: true, - onChanged: (_) => {RecommendFilter.update}, - ), + ...recommendSettings.map((item) => item.widget), ListTile( dense: true, subtitle: Text( diff --git a/lib/pages/setting/search_page.dart b/lib/pages/setting/search_page.dart new file mode 100644 index 000000000..5e3dbeb6c --- /dev/null +++ b/lib/pages/setting/search_page.dart @@ -0,0 +1,86 @@ +import 'package:PiliPalaX/common/widgets/http_error.dart'; +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class SettingsSearchPage extends StatefulWidget { + const SettingsSearchPage({super.key}); + + @override + State createState() => _SettingsSearchPageState(); +} + +class _SettingsSearchPageState extends State { + final _textEditingController = TextEditingController(); + final RxList _list = [].obs; + + @override + void dispose() { + _textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + actions: [ + IconButton( + onPressed: () { + if (_textEditingController.text.isNotEmpty) { + _textEditingController.clear(); + _list.value = []; + } else { + Get.back(); + } + }, + icon: const Icon(Icons.clear), + ), + const SizedBox(width: 10), + ], + title: TextField( + autofocus: true, + controller: _textEditingController, + textAlignVertical: TextAlignVertical.center, + onChanged: (value) { + if (value.isEmpty) { + _list.value = []; + } else { + _list.value = [ + ...extraSettings, + ...privacySettings, + ...recommendSettings, + ...videoSettings, + ...playSettings, + ...styleSettings, + ] + .where((item) => + (item.title ?? item.getTitle?.call()) + ?.toLowerCase() + .contains(value.toLowerCase()) || + item.subtitle + ?.toLowerCase() + .contains(value.toLowerCase()) == + true) + .toList(); + } + }, + decoration: InputDecoration( + isDense: true, + hintText: '搜索', + border: InputBorder.none, + ), + ), + ), + body: Obx( + () => _list.isEmpty + ? CustomScrollView( + slivers: [HttpError()], + ) + : ListView( + children: _list.map((item) => item.widget).toList(), + ), + ), + ); + } +} diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 6c7455b36..156a7594f 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -1,426 +1,16 @@ -import 'dart:io'; - -import 'package:auto_orientation/auto_orientation.dart'; +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPalaX/models/common/theme_type.dart'; -import 'package:PiliPalaX/pages/setting/pages/color_select.dart'; -import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; -import 'package:PiliPalaX/pages/setting/widgets/slide_dialog.dart'; -import 'package:PiliPalaX/utils/global_data.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'package:hive/hive.dart'; -import '../../models/common/dynamic_badge_mode.dart'; -import '../../models/common/up_panel_position.dart'; -import '../../plugin/pl_player/utils/fullscreen.dart'; -import '../../models/common/nav_bar_config.dart'; -import 'controller.dart'; -import 'widgets/switch_item.dart'; - -class StyleSetting extends StatefulWidget { +class StyleSetting extends StatelessWidget { const StyleSetting({super.key}); - @override - State createState() => _StyleSettingState(); -} - -class _StyleSettingState extends State { - final SettingController settingController = Get.put(SettingController()); - final ColorSelectController colorSelectController = - Get.put(ColorSelectController()); - - late double maxRowWidth; - late UpPanelPosition upPanelPosition; - - Box get setting => GStorage.setting; - - @override - void initState() { - super.initState(); - maxRowWidth = - setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; - upPanelPosition = UpPanelPosition.values[setting.get( - SettingBoxKey.upPanelPosition, - defaultValue: UpPanelPosition.leftFixed.index)]; - } - @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('外观设置')), body: ListView( - children: [ - SetSwitchItem( - title: '横屏适配', - subTitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】', - leading: const Icon(Icons.phonelink_outlined), - setKey: SettingBoxKey.horizontalScreen, - defaultVal: false, - onChanged: (value) { - if (value) { - autoScreen(); - // SmartDialog.showToast('已开启横屏适配'); - } else { - AutoOrientation.portraitUpMode(); - // SmartDialog.showToast('已关闭横屏适配'); - } - }), - const SetSwitchItem( - title: '改用侧边栏', - subTitle: '开启后底栏与顶栏被替换,且相关设置失效', - leading: Icon(Icons.chrome_reader_mode_outlined), - setKey: SettingBoxKey.useSideBar, - defaultVal: false, - needReboot: true, - ), - const SetSwitchItem( - title: 'MD3样式底栏', - subTitle: 'Material You设计规范底栏,关闭可变窄', - leading: Icon(Icons.design_services_outlined), - setKey: SettingBoxKey.enableMYBar, - defaultVal: true, - needReboot: true, - ), - // const SetSwitchItem( - // title: '首页背景渐变', - // setKey: SettingBoxKey.enableGradientBg, - // leading: Icon(Icons.gradient_outlined), - // defaultVal: false, - // needReboot: true, - // ), - ListTile( - onTap: () async { - double? result = await showDialog( - context: context, - builder: (context) { - return SlideDialog( - title: '最大列宽度(默认240dp)', - value: maxRowWidth, - min: 150.0, - max: 500.0, - divisions: 35, - suffix: 'dp', - ); - }); - if (result != null) { - maxRowWidth = result; - setting.put(SettingBoxKey.maxRowWidth, result); - SmartDialog.showToast('重启生效'); - setState(() {}); - } - }, - leading: const Icon(Icons.calendar_view_week_outlined), - dense: false, - title: Text('列表宽度(dp)限制', style: titleStyle), - subtitle: Text( - '当前:${maxRowWidth.toInt()}dp,屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。' - '宽度越小列数越多,横条、大卡会2倍折算', - style: subTitleStyle, - ), - ), - // const SetSwitchItem( - // title: '播放页状态栏显示为背景色', - // subTitle: '关闭则显示为黑色', - // leading: Icon(Icons.border_outer_outlined), - // setKey: SettingBoxKey.videoPlayerShowStatusBarBackgroundColor, - // defaultVal: false, - // needReboot: true, - // ), - const SetSwitchItem( - title: '播放页移除安全边距', - subTitle: '隐藏状态栏、撑满屏幕,但播放控件仍处于安全域内', - leading: Icon(Icons.fit_screen_outlined), - setKey: SettingBoxKey.videoPlayerRemoveSafeArea, - defaultVal: false, - needReboot: true, - ), - const SetSwitchItem( - title: '动态页启用瀑布流', - subTitle: '关闭会显示为单列', - leading: Icon(Icons.view_array_outlined), - setKey: SettingBoxKey.dynamicsWaterfallFlow, - defaultVal: true, - needReboot: true, - ), - ListTile( - dense: false, - title: Text('动态页UP主显示位置', style: titleStyle), - leading: const Icon(Icons.person_outlined), - subtitle: - Text('当前:${upPanelPosition.labels}', style: subTitleStyle), - onTap: () async { - UpPanelPosition? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '动态页UP主显示位置', - value: upPanelPosition, - values: UpPanelPosition.values.map((e) { - return {'title': e.labels, 'value': e}; - }).toList(), - ); - }, - ); - if (result != null) { - upPanelPosition = result; - setting.put(SettingBoxKey.upPanelPosition, result.index); - SmartDialog.showToast('重启生效'); - setState(() {}); - } - }, - ), - const SetSwitchItem( - title: '动态页显示所有已关注UP主', - subTitle: '并以最常访问排序UP', - leading: Icon(Icons.people_alt_outlined), - setKey: SettingBoxKey.dynamicsShowAllFollowedUp, - defaultVal: false, - ), - ListTile( - dense: false, - onTap: () => settingController.setDynamicBadgeMode(context), - title: Text('动态未读标记', style: titleStyle), - leading: const Icon(Icons.motion_photos_on_outlined), - subtitle: Obx(() => Text( - '当前标记样式:${settingController.dynamicBadgeType.value.description}', - style: subTitleStyle)), - ), - const SetSwitchItem( - title: '首页顶栏收起', - subTitle: '首页列表滑动时,收起顶栏', - leading: Icon(Icons.vertical_align_top_outlined), - setKey: SettingBoxKey.hideSearchBar, - defaultVal: true, - needReboot: true, - ), - const SetSwitchItem( - title: '首页底栏收起', - subTitle: '首页列表滑动时,收起底栏', - leading: Icon(Icons.vertical_align_bottom_outlined), - setKey: SettingBoxKey.hideTabBar, - defaultVal: true, - needReboot: true, - ), - ListTile( - dense: false, - onTap: () { - _showQualityDialog( - title: '图片质量', - initValue: settingController.picQuality.value, - callback: (picQuality) { - setting.put(SettingBoxKey.defaultPicQa, picQuality); - settingController.picQuality.value = picQuality; - GlobalData().imgQuality = picQuality; - }); - }, - title: Text('图片质量', style: titleStyle), - subtitle: Text('选择合适的图片清晰度,上限100%', style: subTitleStyle), - leading: const Icon(Icons.image_outlined), - trailing: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Obx( - () => Text( - '${settingController.picQuality.value}%', - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ), - ), - // preview quality - ListTile( - dense: false, - onTap: () { - _showQualityDialog( - title: '查看大图质量', - initValue: settingController.previewQ.value, - callback: (picQuality) { - setting.put(SettingBoxKey.previewQuality, picQuality); - settingController.previewQ.value = picQuality; - }); - }, - title: Text('查看大图质量', style: titleStyle), - subtitle: Text('选择合适的图片清晰度,上限100%', style: subTitleStyle), - leading: const Icon(Icons.image_outlined), - trailing: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Obx( - () => Text( - '${settingController.previewQ.value}%', - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ), - ), - ListTile( - dense: false, - onTap: () async { - double? result = await showDialog( - context: context, - builder: (context) { - return SlideDialog( - title: 'Toast不透明度', - value: settingController.toastOpacity.value, - min: 0.0, - max: 1.0, - divisions: 10, - ); - }, - ); - if (result != null) { - settingController.toastOpacity.value = result; - SmartDialog.showToast('设置成功'); - setting.put(SettingBoxKey.defaultToastOp, result); - } - }, - leading: const Icon(Icons.opacity_outlined), - title: Text('气泡提示不透明度', style: titleStyle), - subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle), - trailing: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Obx(() => Text( - settingController.toastOpacity.value.toStringAsFixed(1), - style: Theme.of(context).textTheme.titleSmall)), - ), - ), - ListTile( - dense: false, - onTap: () async { - ThemeType? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '主题模式', - value: settingController.themeType.value, - values: ThemeType.values.map((e) { - return {'title': e.description, 'value': e}; - }).toList()); - }, - ); - if (result != null) { - settingController.themeType.value = result; - setting.put(SettingBoxKey.themeMode, result.index); - Get.forceAppUpdate(); - } - }, - leading: const Icon(Icons.flashlight_on_outlined), - title: Text('主题模式', style: titleStyle), - subtitle: Obx(() => Text( - '当前模式:${settingController.themeType.value.description}', - style: subTitleStyle)), - ), - ListTile( - dense: false, - onTap: () => Get.toNamed('/colorSetting'), - leading: const Icon(Icons.color_lens_outlined), - title: Text('应用主题', style: titleStyle), - subtitle: Obx(() => Text( - '当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}', - style: subTitleStyle)), - ), - ListTile( - dense: false, - onTap: () => settingController.setDefaultHomePage(context), - leading: const Icon(Icons.home_outlined), - title: Text('默认启动页', style: titleStyle), - subtitle: Obx(() => Text( - '当前启动页:${defaultNavigationBars.firstWhere((e) => e['id'] == settingController.defaultHomePage.value)['label']}', - style: subTitleStyle)), - ), - ListTile( - dense: false, - onTap: () async { - dynamic result = await Get.toNamed('/fontSizeSetting'); - if (result != null) { - colorSelectController.currentTextScale.value = result; - } - }, - title: Text('字体大小', style: titleStyle), - leading: const Icon(Icons.format_size_outlined), - subtitle: Obx(() => Text( - colorSelectController.currentTextScale.value == 1.0 - ? '默认' - : colorSelectController.currentTextScale.value.toString(), - style: subTitleStyle, - )), - ), - ListTile( - dense: false, - onTap: () => Get.toNamed('/tabbarSetting'), - title: Text('首页标签页', style: titleStyle), - subtitle: Text('删除或调换首页标签页', style: subTitleStyle), - leading: const Icon(Icons.toc_outlined), - ), - ListTile( - dense: false, - onTap: () => Get.toNamed('/navbarSetting'), - title: Text('Navbar编辑', style: titleStyle), - leading: const Icon(Icons.toc_outlined), - ), - if (Platform.isAndroid) - ListTile( - dense: false, - onTap: () => Get.toNamed('/displayModeSetting'), - title: Text('屏幕帧率', style: titleStyle), - leading: const Icon(Icons.autofps_select_outlined), - ), - ], + children: styleSettings.map((item) => item.widget).toList(), ), ); } - - void _showQualityDialog({ - required String title, - required int initValue, - required ValueChanged callback, - }) { - showDialog( - context: context, - builder: (context) { - int picQuality = initValue; - return AlertDialog( - title: Text(title), - contentPadding: - const EdgeInsets.only(top: 20, left: 8, right: 8, bottom: 8), - content: SizedBox( - height: 40, - child: Builder( - builder: (context) => Slider( - value: picQuality.toDouble(), - min: 10, - max: 100, - divisions: 9, - label: '$picQuality%', - onChanged: (double val) { - picQuality = val.toInt(); - (context as Element).markNeedsBuild(); - }, - ), - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text('取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline))), - TextButton( - onPressed: () { - Get.back(); - callback(picQuality); - SmartDialog.showToast('设置成功'); - }, - child: const Text('确定'), - ) - ], - ); - }, - ); - } } diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart index 5ffcaca9a..5c5520405 100644 --- a/lib/pages/setting/video_setting.dart +++ b/lib/pages/setting/video_setting.dart @@ -1,412 +1,15 @@ -import 'dart:io'; - +import 'package:PiliPalaX/pages/setting/widgets/model.dart'; import 'package:flutter/material.dart'; -import 'package:hive/hive.dart'; -import 'package:PiliPalaX/models/video/play/quality.dart'; -import 'package:PiliPalaX/models/video/play/CDN.dart'; -import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'widgets/switch_item.dart'; - -class VideoSetting extends StatefulWidget { +class VideoSetting extends StatelessWidget { const VideoSetting({super.key}); - @override - State createState() => _VideoSettingState(); -} - -class _VideoSettingState extends State { - late dynamic defaultVideoQa; - late dynamic defaultVideoQaCellular; - late dynamic defaultAudioQa; - late dynamic defaultAudioQaCellular; - late dynamic defaultDecode; - late dynamic secondDecode; - late dynamic hardwareDecoding; - late dynamic videoSync; - late dynamic defaultCDNService; - - Box get setting => GStorage.setting; - - @override - void initState() { - super.initState(); - defaultVideoQa = setting.get( - SettingBoxKey.defaultVideoQa, - defaultValue: VideoQuality.values.last.code, - ); - defaultVideoQaCellular = setting.get( - SettingBoxKey.defaultVideoQaCellular, - defaultValue: VideoQuality.high1080.code, - ); - defaultAudioQa = setting.get( - SettingBoxKey.defaultAudioQa, - defaultValue: AudioQuality.values.last.code, - ); - defaultAudioQaCellular = setting.get( - SettingBoxKey.defaultAudioQaCellular, - defaultValue: AudioQuality.k192.code, - ); - defaultDecode = setting.get( - SettingBoxKey.defaultDecode, - defaultValue: VideoDecodeFormats.values.last.code, - ); - secondDecode = setting.get( - SettingBoxKey.secondDecode, - defaultValue: VideoDecodeFormats.values[1].code, - ); - hardwareDecoding = setting.get( - SettingBoxKey.hardwareDecoding, - defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto', - ); - videoSync = setting.get( - SettingBoxKey.videoSync, - defaultValue: 'display-resample', - ); - defaultCDNService = setting.get( - SettingBoxKey.CDNService, - defaultValue: CDNService.backupUrl.code, - ); - } - @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; - TextStyle subTitleStyle = Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline); return Scaffold( appBar: AppBar(title: Text('音视频设置')), body: ListView( - children: [ - const SetSwitchItem( - title: '开启硬解', - subTitle: '以较低功耗播放视频,若异常卡死请关闭', - leading: Icon(Icons.flash_on_outlined), - setKey: SettingBoxKey.enableHA, - defaultVal: true, - ), - // const SetSwitchItem( - // title: '亮度记忆', - // subTitle: '返回时自动调整视频亮度', - // leading: Icon(Icons.brightness_6_outlined), - // setKey: SettingBoxKey.enableAutoBrightness, - // defaultVal: true, - // ), - const SetSwitchItem( - title: '免登录1080P', - subTitle: '免登录查看1080P视频', - leading: Icon(Icons.hd_outlined), - setKey: SettingBoxKey.p1080, - defaultVal: true, - ), - ListTile( - title: Text("B站定向流量支持", style: titleStyle), - subtitle: - Text("若套餐含B站定向流量,则会自动使用。可查阅运营商的流量记录确认。", style: subTitleStyle), - leading: const Icon(Icons.perm_data_setting_outlined), - trailing: Transform.scale( - alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.lock_outline_rounded); - } - return null; // All other states will use the default thumbIcon. - }), - value: true, - onChanged: (_) {}, - ), - ), - ), - ListTile( - title: Text('CDN 设置', style: titleStyle), - leading: Icon(MdiIcons.cloudPlusOutline), - subtitle: Text( - '当前使用:${CDNServiceCode.fromCode(defaultCDNService)!.description},部分 CDN 可能失效,如无法播放请尝试切换', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: 'CDN 设置', - value: defaultCDNService, - values: CDNService.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultCDNService = result; - setting.put(SettingBoxKey.CDNService, result); - setState(() {}); - } - }, - ), - SetSwitchItem( - title: '音频不跟随 CDN 设置', - subTitle: '直接采用备用 URL,可解决部分视频无声', - leading: Icon(MdiIcons.musicNotePlus), - setKey: SettingBoxKey.disableAudioCDN, - defaultVal: true, - ), - ListTile( - dense: false, - title: Text('默认画质', style: titleStyle), - leading: const Icon(Icons.video_settings_outlined), - subtitle: Text( - '当前画质:${VideoQualityCode.fromCode(defaultVideoQa)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认画质', - value: defaultVideoQa, - values: VideoQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList(), - ); - }, - ); - if (result != null) { - defaultVideoQa = result; - setting.put(SettingBoxKey.defaultVideoQa, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('蜂窝网络画质', style: titleStyle), - leading: const Icon(Icons.video_settings_outlined), - subtitle: Text( - '当前画质:${VideoQualityCode.fromCode(defaultVideoQaCellular)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '蜂窝网络画质', - value: defaultVideoQaCellular, - values: VideoQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList(), - ); - }, - ); - if (result != null) { - defaultVideoQaCellular = result; - setting.put(SettingBoxKey.defaultVideoQaCellular, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('默认音质', style: titleStyle), - leading: const Icon(Icons.music_video_outlined), - subtitle: Text( - '当前音质:${AudioQualityCode.fromCode(defaultAudioQa)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认音质', - value: defaultAudioQa, - values: AudioQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList(), - ); - }, - ); - if (result != null) { - defaultAudioQa = result; - setting.put(SettingBoxKey.defaultAudioQa, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('蜂窝网络音质', style: titleStyle), - leading: const Icon(Icons.music_video_outlined), - subtitle: Text( - '当前音质:${AudioQualityCode.fromCode(defaultAudioQaCellular)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '蜂窝网络音质', - value: defaultAudioQaCellular, - values: AudioQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList(), - ); - }, - ); - if (result != null) { - defaultAudioQaCellular = result; - setting.put(SettingBoxKey.defaultAudioQaCellular, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('首选解码格式', style: titleStyle), - leading: const Icon(Icons.movie_creation_outlined), - subtitle: Text( - '首选解码格式:${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!},请根据设备支持情况与需求调整', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认解码格式', - value: defaultDecode, - values: VideoDecodeFormats.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultDecode = result; - setting.put(SettingBoxKey.defaultDecode, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('次选解码格式', style: titleStyle), - subtitle: Text( - '非杜比视频次选:${VideoDecodeFormatsCode.fromCode(secondDecode)!.description!},仍无则选择首个提供的解码格式', - style: subTitleStyle, - ), - leading: const Icon(Icons.swap_horizontal_circle_outlined), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '次选解码格式', - value: secondDecode, - values: VideoDecodeFormats.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - secondDecode = result; - setting.put(SettingBoxKey.secondDecode, result); - setState(() {}); - } - }, - ), - if (Platform.isAndroid) - const SetSwitchItem( - title: '优先使用 OpenSL ES 输出音频', - leading: Icon(Icons.speaker_outlined), - subTitle: - '关闭则优先使用AudioTrack输出音频(此项即mpv的--ao),若遇系统音效丢失、无声、音画不同步等问题请尝试关闭。', - setKey: SettingBoxKey.useOpenSLES, - defaultVal: true, - ), - const SetSwitchItem( - title: '扩大缓冲区', - leading: Icon(Icons.storage_outlined), - subTitle: '默认缓冲区为视频3MB/直播16MB,开启后为32MB/64MB,加载时间变长', - setKey: SettingBoxKey.expandBuffer, - defaultVal: false, - ), - //video-sync - ListTile( - dense: false, - title: Text('视频同步', style: titleStyle), - leading: const Icon(Icons.view_timeline_outlined), - subtitle: Text( - '当前:$videoSync(此项即mpv的--video-sync)', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '视频同步', - value: videoSync, - values: [ - 'audio', - 'display-resample', - 'display-resample-vdrop', - 'display-resample-desync', - 'display-tempo', - 'display-vdrop', - 'display-adrop', - 'display-desync', - 'desync' - ].map((e) { - return {'title': e, 'value': e}; - }).toList()); - }, - ); - if (result != null) { - setting.put(SettingBoxKey.videoSync, result); - videoSync = result; - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('硬解模式', style: titleStyle), - leading: const Icon(Icons.memory_outlined), - subtitle: Text( - '当前:$hardwareDecoding(此项即mpv的--hwdec)', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '硬解模式', - value: hardwareDecoding, - values: ['auto', 'auto-copy', 'auto-safe', 'no', 'yes'] - .map((e) { - return {'title': e, 'value': e}; - }).toList()); - }, - ); - if (result != null) { - setting.put(SettingBoxKey.hardwareDecoding, result); - hardwareDecoding = result; - setState(() {}); - } - }, - ), - ], + children: videoSettings.map((item) => item.widget).toList(), ), ); } diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 4e74b98a0..1df601fe1 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -1,13 +1,19 @@ +import 'package:PiliPalaX/pages/main/controller.dart'; +import 'package:PiliPalaX/utils/extension.dart'; +import 'package:PiliPalaX/utils/login.dart'; +import 'package:PiliPalaX/utils/storage.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; -import 'package:PiliPalaX/pages/setting/index.dart'; + +import '../../http/init.dart'; class SettingPage extends StatelessWidget { const SettingPage({super.key}); @override Widget build(BuildContext context) { - final SettingController settingController = Get.put(SettingController()); + RxBool isLogin = GStorage.isLogin.obs; final TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; final TextStyle subTitleStyle = Theme.of(context) .textTheme @@ -17,16 +23,40 @@ class SettingPage extends StatelessWidget { appBar: AppBar(title: Text('设置')), body: ListView( children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: InkWell( + onTap: () => Get.toNamed('/settingsSearch'), + borderRadius: BorderRadius.circular(50), + child: Ink( + padding: const EdgeInsets.symmetric(vertical: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50), + color: Theme.of(context).colorScheme.onInverseSurface, + ), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + size: MediaQuery.textScalerOf(context).scale(18), + Icons.search, + ), + const Text(' 搜索'), + ], + ), + ), + ), + ), + ), ListTile( onTap: () => Get.toNamed('/privacySetting'), - dense: false, leading: const Icon(Icons.privacy_tip_outlined), title: Text('隐私设置', style: titleStyle), subtitle: Text('黑名单、access_key刷新、无痕模式', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/recommendSetting'), - dense: false, leading: const Icon(Icons.explore_outlined), title: Text('推荐流设置', style: titleStyle), subtitle: Text('推荐来源(web/app)、刷新保留内容、过滤器', style: subTitleStyle), @@ -34,21 +64,18 @@ class SettingPage extends StatelessWidget { ListTile( onTap: () => Get.toNamed('/videoSetting'), leading: const Icon(Icons.video_settings_outlined), - dense: false, title: Text('音视频设置', style: titleStyle), subtitle: Text('画质、音质、解码、缓冲、音频输出等', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/playSetting'), leading: const Icon(Icons.touch_app_outlined), - dense: false, title: Text('播放器设置', style: titleStyle), subtitle: Text('双击/长按、全屏、后台播放、弹幕、字幕、底部进度条等', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/styleSetting'), leading: const Icon(Icons.style_outlined), - dense: false, title: Text('外观设置', style: titleStyle), subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等', style: subTitleStyle), @@ -56,36 +83,66 @@ class SettingPage extends StatelessWidget { ListTile( onTap: () => Get.toNamed('/extraSetting'), leading: const Icon(Icons.extension_outlined), - dense: false, title: Text('其它设置', style: titleStyle), subtitle: Text('震动、搜索、收藏、ai、评论、动态、代理、更新检查等', style: subTitleStyle), ), Obx( - () => Visibility( - visible: settingController.hiddenSettingUnlocked.value, - child: ListTile( - leading: const Icon(Icons.developer_board_outlined), - onTap: () => Get.toNamed('/hiddenSetting'), - dense: false, - title: Text('开发人员选项', style: titleStyle), - ), - ), - ), - Obx( - () => Visibility( - visible: settingController.userLogin.value, - child: ListTile( - leading: const Icon(Icons.logout_outlined), - onTap: () => settingController.loginOut(context), - dense: false, - title: Text('退出登录', style: titleStyle), - ), - ), + () => isLogin.value.not + ? const SizedBox.shrink() + : ListTile( + leading: const Icon(Icons.logout_outlined), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认要退出登录吗'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '点错了', + style: TextStyle( + color: + Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + // 清空cookie + await Request.cookieManager.cookieJar + .deleteAll(); + await CookieManager().deleteAllCookies(); + Request.dio.options.headers['cookie'] = ''; + // 清空本地存储的用户标识 + GStorage.userInfo.put('userInfoCache', null); + GStorage.localCache.put( + LocalCacheKey.accessKey, + {'mid': -1, 'value': '', 'refresh': ''}); + isLogin.value = false; + if (Get.isRegistered()) { + MainController mainController = + Get.find(); + mainController.userLogin.value = false; + } + await LoginUtils.refreshLoginStatus(false); + Get.back(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }, + title: Text('退出登录', style: titleStyle), + ), ), ListTile( leading: const Icon(Icons.info_outline), onTap: () => Get.toNamed('/about'), - dense: false, title: Text('关于', style: titleStyle), ), ], diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart new file mode 100644 index 000000000..dc7b5810c --- /dev/null +++ b/lib/pages/setting/widgets/model.dart @@ -0,0 +1,1808 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:PiliPalaX/http/interceptor_anonymity.dart'; +import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart'; +import 'package:PiliPalaX/models/common/dynamics_type.dart'; +import 'package:PiliPalaX/models/common/nav_bar_config.dart'; +import 'package:PiliPalaX/models/common/rcmd_type.dart'; +import 'package:PiliPalaX/models/common/reply_sort_type.dart'; +import 'package:PiliPalaX/models/common/up_panel_position.dart'; +import 'package:PiliPalaX/models/video/play/CDN.dart'; +import 'package:PiliPalaX/models/video/play/quality.dart'; +import 'package:PiliPalaX/models/video/play/subtitle.dart'; +import 'package:PiliPalaX/pages/home/controller.dart'; +import 'package:PiliPalaX/pages/main/controller.dart'; +import 'package:PiliPalaX/pages/member/new/controller.dart'; +import 'package:PiliPalaX/pages/mine/controller.dart'; +import 'package:PiliPalaX/pages/setting/pages/color_select.dart'; +import 'package:PiliPalaX/pages/setting/widgets/normal_item.dart'; +import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; +import 'package:PiliPalaX/pages/setting/widgets/slide_dialog.dart'; +import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart'; +import 'package:PiliPalaX/plugin/pl_player/models/bottom_progress_behavior.dart'; +import 'package:PiliPalaX/plugin/pl_player/models/fullscreen_mode.dart'; +import 'package:PiliPalaX/plugin/pl_player/utils/fullscreen.dart'; +import 'package:PiliPalaX/utils/extension.dart'; +import 'package:PiliPalaX/utils/feed_back.dart'; +import 'package:PiliPalaX/utils/global_data.dart'; +import 'package:PiliPalaX/utils/recommend_filter.dart'; +import 'package:PiliPalaX/utils/storage.dart'; +import 'package:auto_orientation/auto_orientation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; + +extension SettingsModelExt on SettingsModel { + Widget get widget => switch (settingsType) { + SettingsType.normal => NormalItem( + title: title, + getTitle: getTitle, + subtitle: subtitle, + getSubtitle: getSubtitle, + setKey: setKey, + defaultVal: defaultVal, + onChanged: onChanged, + needReboot: needReboot, + leading: leading, + getTrailing: getTrailing, + onTap: onTap, + contentPadding: contentPadding, + titleStyle: titleStyle, + ), + SettingsType.sw1tch => SetSwitchItem( + title: title, + subtitle: subtitle, + setKey: setKey, + defaultVal: defaultVal, + onChanged: onChanged, + needReboot: needReboot, + leading: leading, + onTap: onTap, + contentPadding: contentPadding, + titleStyle: titleStyle, + ), + }; +} + +class SettingsModel { + final SettingsType settingsType; + final String? title; + final Function? getTitle; + final String? subtitle; + final Function? getSubtitle; + final String? setKey; + final bool? defaultVal; + final ValueChanged? onChanged; + final bool? needReboot; + final Widget? leading; + final Function? getTrailing; + final Function? onTap; + final EdgeInsetsGeometry? contentPadding; + final TextStyle? titleStyle; + final bool? enableFeedback; + + SettingsModel({ + required this.settingsType, + this.title, + this.getTitle, + this.subtitle, + this.getSubtitle, + this.setKey, + this.defaultVal, + this.onChanged, + this.needReboot, + this.leading, + this.getTrailing, + this.onTap, + this.contentPadding, + this.titleStyle, + this.enableFeedback, + }); +} + +enum SettingsType { normal, sw1tch } + +List get styleSettings => [ + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '横屏适配', + subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】', + leading: const Icon(Icons.phonelink_outlined), + setKey: SettingBoxKey.horizontalScreen, + defaultVal: false, + onChanged: (value) { + if (value) { + autoScreen(); + // SmartDialog.showToast('已开启横屏适配'); + } else { + AutoOrientation.portraitUpMode(); + // SmartDialog.showToast('已关闭横屏适配'); + } + }), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '改用侧边栏', + subtitle: '开启后底栏与顶栏被替换,且相关设置失效', + leading: Icon(Icons.chrome_reader_mode_outlined), + setKey: SettingBoxKey.useSideBar, + defaultVal: false, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: 'MD3样式底栏', + subtitle: 'Material You设计规范底栏,关闭可变窄', + leading: Icon(Icons.design_services_outlined), + setKey: SettingBoxKey.enableMYBar, + defaultVal: true, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) async { + double? result = await showDialog( + context: Get.context!, + builder: (context) { + return SlideDialog( + title: '最大列宽度(默认240dp)', + value: GStorage.maxRowWidth, + min: 150.0, + max: 500.0, + divisions: 35, + suffix: 'dp', + ); + }); + if (result != null) { + GStorage.setting.put(SettingBoxKey.maxRowWidth, result); + SmartDialog.showToast('重启生效'); + setState(); + } + }, + leading: const Icon(Icons.calendar_view_week_outlined), + title: '列表宽度(dp)限制', + getSubtitle: () => + '当前:${GStorage.maxRowWidth.toInt()}dp,屏幕宽度:${MediaQuery.of(Get.context!).size.width.toPrecision(2)}dp。宽度越小列数越多,横条、大卡会2倍折算', + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '播放页移除安全边距', + subtitle: '隐藏状态栏、撑满屏幕,但播放控件仍处于安全域内', + leading: Icon(Icons.fit_screen_outlined), + setKey: SettingBoxKey.videoPlayerRemoveSafeArea, + defaultVal: false, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '动态页启用瀑布流', + subtitle: '关闭会显示为单列', + leading: Icon(Icons.view_array_outlined), + setKey: SettingBoxKey.dynamicsWaterfallFlow, + defaultVal: true, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '动态页UP主显示位置', + leading: const Icon(Icons.person_outlined), + getSubtitle: () => '当前:${GStorage.upPanelPosition.labels}', + onTap: (setState) async { + UpPanelPosition? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '动态页UP主显示位置', + value: GStorage.upPanelPosition, + values: UpPanelPosition.values.map((e) { + return {'title': e.labels, 'value': e}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.upPanelPosition, result.index); + SmartDialog.showToast('重启生效'); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '动态页显示所有已关注UP主', + subtitle: '并以最常访问排序UP', + leading: Icon(Icons.people_alt_outlined), + setKey: SettingBoxKey.dynamicsShowAllFollowedUp, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) async { + DynamicBadgeMode? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '动态未读标记', + value: GStorage.dynamicBadgeType, + values: DynamicBadgeMode.values.map((e) { + return {'title': e.description, 'value': e}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.dynamicBadgeMode, result.code); + MainController mainController = Get.put(MainController()); + mainController.dynamicBadgeType = + DynamicBadgeMode.values[result.code]; + if (mainController.dynamicBadgeType != DynamicBadgeMode.hidden) { + mainController.getUnreadDynamic(); + } + SmartDialog.showToast('设置成功'); + setState(); + } + }, + title: '动态未读标记', + leading: const Icon(Icons.motion_photos_on_outlined), + getSubtitle: () => '当前标记样式:${GStorage.dynamicBadgeType.description}', + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '首页顶栏收起', + subtitle: '首页列表滑动时,收起顶栏', + leading: Icon(Icons.vertical_align_top_outlined), + setKey: SettingBoxKey.hideSearchBar, + defaultVal: true, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '首页底栏收起', + subtitle: '首页列表滑动时,收起底栏', + leading: Icon(Icons.vertical_align_bottom_outlined), + setKey: SettingBoxKey.hideTabBar, + defaultVal: true, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) { + _showQualityDialog( + context: Get.context!, + title: '图片质量', + initValue: GStorage.picQuality, + callback: (picQuality) { + GStorage.setting.put(SettingBoxKey.defaultPicQa, picQuality); + GlobalData().imgQuality = picQuality; + setState(); + }, + ); + }, + title: '图片质量', + subtitle: '选择合适的图片清晰度,上限100%', + leading: const Icon(Icons.image_outlined), + getTrailing: () => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + '${GStorage.picQuality}%', + style: Theme.of(Get.context!).textTheme.titleSmall, + ), + ), + ), + // preview quality + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) { + _showQualityDialog( + context: Get.context!, + title: '查看大图质量', + initValue: GStorage.previewQ, + callback: (picQuality) { + GStorage.setting.put(SettingBoxKey.previewQuality, picQuality); + setState(); + }, + ); + }, + title: '查看大图质量', + subtitle: '选择合适的图片清晰度,上限100%', + leading: const Icon(Icons.image_outlined), + getTrailing: () => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + '${GStorage.previewQ}%', + style: Theme.of(Get.context!).textTheme.titleSmall, + ), + ), + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) async { + double? result = await showDialog( + context: Get.context!, + builder: (context) { + return SlideDialog( + title: 'Toast不透明度', + value: GStorage.toastOpacity, + min: 0.0, + max: 1.0, + divisions: 10, + ); + }, + ); + if (result != null) { + SmartDialog.showToast('设置成功'); + GStorage.setting.put(SettingBoxKey.defaultToastOp, result); + setState(); + } + }, + leading: const Icon(Icons.opacity_outlined), + title: '气泡提示不透明度', + subtitle: '自定义气泡提示(Toast)不透明度', + getTrailing: () => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + GStorage.toastOpacity.toStringAsFixed(1), + style: Theme.of(Get.context!).textTheme.titleSmall, + ), + ), + ), + // SettingsModel( + // settingsType: SettingsType.normal, + // onTap: (setState) async { + // ThemeType? result = await showDialog( + // context: Get.context!, + // builder: (context) { + // return SelectDialog( + // title: '主题模式', + // value: GStorage.themeType, + // values: ThemeType.values.map( + // (e) { + // return {'title': e.description, 'value': e}; + // }, + // ).toList()); + // }, + // ); + // if (result != null) { + // GStorage.setting.put(SettingBoxKey.themeMode, result.index); + // Get.forceAppUpdate(); + // } + // }, + // leading: const Icon(Icons.flashlight_on_outlined), + // title: '主题模式', + // getSubtitle: () => '当前模式:${GStorage.themeType.description}', + // ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) => Get.toNamed('/colorSetting'), + leading: const Icon(Icons.color_lens_outlined), + title: '应用主题', + getSubtitle: () => + '当前主题:${Get.put(ColorSelectController()).type.value == 0 ? '动态取色' : '指定颜色'}', + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '首页启动页', + value: GStorage.defaultHomePage, + values: defaultNavigationBars.map((e) { + return {'title': e['label'], 'value': e['id']}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultHomePage, result); + SmartDialog.showToast('设置成功,重启生效'); + setState(); + } + }, + leading: const Icon(Icons.home_outlined), + title: '默认启动页', + getSubtitle: () => + '当前启动页:${defaultNavigationBars.firstWhere((e) => e['id'] == GStorage.defaultHomePage)['label']}', + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) async { + dynamic result = await Get.toNamed('/fontSizeSetting'); + if (result != null) { + Get.put(ColorSelectController()).currentTextScale.value = result; + } + }, + title: '字体大小', + leading: const Icon(Icons.format_size_outlined), + getSubtitle: () => + Get.put(ColorSelectController()).currentTextScale.value == 1.0 + ? '默认' + : Get.put(ColorSelectController()) + .currentTextScale + .value + .toString(), + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) => Get.toNamed('/tabbarSetting'), + title: '首页标签页', + subtitle: '删除或调换首页标签页', + leading: const Icon(Icons.toc_outlined), + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) => Get.toNamed('/navbarSetting'), + title: 'Navbar编辑', + leading: const Icon(Icons.toc_outlined), + ), + if (Platform.isAndroid) + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) => Get.toNamed('/displayModeSetting'), + title: '屏幕帧率', + leading: const Icon(Icons.autofps_select_outlined), + ), + ]; + +void _showQualityDialog({ + required BuildContext context, + required String title, + required int initValue, + required ValueChanged callback, +}) { + showDialog( + context: context, + builder: (context) { + int picQuality = initValue; + return AlertDialog( + title: Text(title), + contentPadding: + const EdgeInsets.only(top: 20, left: 8, right: 8, bottom: 8), + content: SizedBox( + height: 40, + child: Builder( + builder: (context) => Slider( + value: picQuality.toDouble(), + min: 10, + max: 100, + divisions: 9, + label: '$picQuality%', + onChanged: (double val) { + picQuality = val.toInt(); + (context as Element).markNeedsBuild(); + }, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text('取消', + style: + TextStyle(color: Theme.of(context).colorScheme.outline))), + TextButton( + onPressed: () { + Get.back(); + callback(picQuality); + SmartDialog.showToast('设置成功'); + }, + child: const Text('确定'), + ) + ], + ); + }, + ); +} + +List get playSettings => [ + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '弹幕开关', + subtitle: '是否展示弹幕', + leading: Icon(Icons.comment_outlined), + setKey: SettingBoxKey.enableShowDanmaku, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) => Get.toNamed('/playSpeedSet'), + leading: const Icon(Icons.speed_outlined), + title: '倍速设置', + subtitle: '设置视频播放速度', + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '自动播放', + subtitle: '进入详情页自动播放', + leading: Icon(Icons.motion_photos_auto_outlined), + setKey: SettingBoxKey.autoPlayEnable, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '双击快退/快进', + subtitle: '左侧双击快退/右侧双击快进,关闭则双击均为暂停/播放', + leading: Icon(Icons.touch_app_outlined), + setKey: SettingBoxKey.enableQuickDouble, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '自动启用字幕', + leading: const Icon(Icons.closed_caption_outlined), + getSubtitle: () => + '当前选择偏好:${SubtitlePreferenceCode.fromCode(GStorage.defaultSubtitlePreference)!.description}', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '字幕选择偏好', + value: GStorage.setting.get(SettingBoxKey.subtitlePreference, + defaultValue: SubtitlePreference.values.first.code), + values: SubtitlePreference.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.subtitlePreference, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '竖屏扩大展示', + subtitle: '小屏竖屏视频宽高比由16:9扩大至1:1(不支持收起);横屏适配时,扩大至9:16', + leading: Icon(Icons.expand_outlined), + setKey: SettingBoxKey.enableVerticalExpand, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '自动全屏', + subtitle: '视频开始播放时进入全屏', + leading: Icon(Icons.fullscreen_outlined), + setKey: SettingBoxKey.enableAutoEnter, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '自动退出全屏', + subtitle: '视频结束播放时退出全屏', + leading: Icon(Icons.fullscreen_exit_outlined), + setKey: SettingBoxKey.enableAutoExit, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '延长播放控件显示时间', + subtitle: '开启后延长至30秒,便于屏幕阅读器滑动切换控件焦点', + leading: Icon(Icons.timer_outlined), + setKey: SettingBoxKey.enableLongShowControl, + defaultVal: false), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '全向旋转', + subtitle: '小屏可受重力转为临时全屏,若系统锁定旋转仍触发请关闭,关闭会影响横屏适配', + leading: Icon(Icons.screen_rotation_alt_outlined), + setKey: SettingBoxKey.allowRotateScreen, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '后台播放', + subtitle: '进入后台时继续播放', + leading: Icon(Icons.motion_photos_pause_outlined), + setKey: SettingBoxKey.continuePlayInBackground, + defaultVal: false, + ), + if (Platform.isAndroid) + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '后台画中画', + subtitle: '进入后台时以小窗形式(PiP)播放', + leading: const Icon(Icons.picture_in_picture_outlined), + setKey: SettingBoxKey.autoPiP, + defaultVal: false, + onChanged: (val) { + if (val && + !GStorage.setting.get(SettingBoxKey.enableBackgroundPlay, + defaultValue: true)) { + SmartDialog.showToast('建议开启后台音频服务'); + } + }), + if (Platform.isAndroid) + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '画中画不加载弹幕', + subtitle: '当弹幕开关开启时,小窗屏蔽弹幕以获得较好的体验', + leading: Icon(Icons.comments_disabled_outlined), + setKey: SettingBoxKey.pipNoDanmaku, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '全屏手势反向', + subtitle: '默认播放器中部向上滑动进入全屏,向下退出\n开启后向下全屏,向上退出', + leading: Icon(Icons.swap_vert_outlined), + setKey: SettingBoxKey.fullScreenGestureReverse, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '观看人数', + subtitle: '展示同时在看人数', + leading: Icon(Icons.people_outlined), + setKey: SettingBoxKey.enableOnlineTotal, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '默认全屏方向', + leading: const Icon(Icons.open_with_outlined), + getSubtitle: () => + '当前全屏方向:${FullScreenModeCode.fromCode(GStorage.defaultFullScreenMode)!.description}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '默认全屏方向', + value: GStorage.defaultFullScreenMode, + values: FullScreenMode.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.fullScreenMode, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '底部进度条展示', + leading: const Icon(Icons.border_bottom_outlined), + getSubtitle: () => + '当前展示方式:${BtmProgresBehaviorCode.fromCode(GStorage.defaultBtmProgressBehavior)!.description}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '底部进度条展示', + value: GStorage.defaultBtmProgressBehavior, + values: BtmProgressBehavior.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.btmProgressBehavior, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '后台音频服务', + subtitle: '避免画中画没有播放暂停功能', + leading: Icon(Icons.volume_up_outlined), + setKey: SettingBoxKey.enableBackgroundPlay, + defaultVal: true, + ), + ]; + +List get videoSettings => [ + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '开启硬解', + subtitle: '以较低功耗播放视频,若异常卡死请关闭', + leading: Icon(Icons.flash_on_outlined), + setKey: SettingBoxKey.enableHA, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '免登录1080P', + subtitle: '免登录查看1080P视频', + leading: Icon(Icons.hd_outlined), + setKey: SettingBoxKey.p1080, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: 'B站定向流量支持', + subtitle: '若套餐含B站定向流量,则会自动使用。可查阅运营商的流量记录确认。', + leading: const Icon(Icons.perm_data_setting_outlined), + getTrailing: () => Transform.scale( + alignment: Alignment.centerRight, + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && states.first == WidgetState.selected) { + return const Icon(Icons.lock_outline_rounded); + } + return null; + }), + value: true, + onChanged: (_) {}, + ), + ), + ), + SettingsModel( + settingsType: SettingsType.normal, + title: 'CDN 设置', + leading: Icon(MdiIcons.cloudPlusOutline), + getSubtitle: () => + '当前使用:${CDNServiceCode.fromCode(GStorage.defaultCDNService)!.description},部分 CDN 可能失效,如无法播放请尝试切换', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: 'CDN 设置', + value: GStorage.defaultCDNService, + values: CDNService.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.CDNService, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '音频不跟随 CDN 设置', + subtitle: '直接采用备用 URL,可解决部分视频无声', + leading: Icon(MdiIcons.musicNotePlus), + setKey: SettingBoxKey.disableAudioCDN, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '默认画质', + leading: const Icon(Icons.video_settings_outlined), + getSubtitle: () => + '当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQa)!.description!}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '默认画质', + value: GStorage.defaultVideoQa, + values: VideoQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultVideoQa, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '蜂窝网络画质', + leading: const Icon(Icons.video_settings_outlined), + getSubtitle: () => + '当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQaCellular)!.description!}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '蜂窝网络画质', + value: GStorage.defaultVideoQaCellular, + values: VideoQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultVideoQaCellular, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '默认音质', + leading: const Icon(Icons.music_video_outlined), + getSubtitle: () => + '当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQa)!.description!}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '默认音质', + value: GStorage.defaultAudioQa, + values: AudioQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultAudioQa, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '蜂窝网络音质', + leading: const Icon(Icons.music_video_outlined), + getSubtitle: () => + '当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQaCellular)!.description!}', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '蜂窝网络音质', + value: GStorage.defaultAudioQaCellular, + values: AudioQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultAudioQaCellular, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '首选解码格式', + leading: const Icon(Icons.movie_creation_outlined), + getSubtitle: () => + '首选解码格式:${VideoDecodeFormatsCode.fromCode(GStorage.defaultDecode)!.description!},请根据设备支持情况与需求调整', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '默认解码格式', + value: GStorage.defaultDecode, + values: VideoDecodeFormats.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultDecode, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '次选解码格式', + getSubtitle: () => + '非杜比视频次选:${VideoDecodeFormatsCode.fromCode(GStorage.secondDecode)!.description!},仍无则选择首个提供的解码格式', + leading: const Icon(Icons.swap_horizontal_circle_outlined), + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '次选解码格式', + value: GStorage.secondDecode, + values: VideoDecodeFormats.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.secondDecode, result); + setState(); + } + }, + ), + if (Platform.isAndroid) + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '优先使用 OpenSL ES 输出音频', + leading: Icon(Icons.speaker_outlined), + subtitle: + '关闭则优先使用AudioTrack输出音频(此项即mpv的--ao),若遇系统音效丢失、无声、音画不同步等问题请尝试关闭。', + setKey: SettingBoxKey.useOpenSLES, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '扩大缓冲区', + leading: Icon(Icons.storage_outlined), + subtitle: '默认缓冲区为视频3MB/直播16MB,开启后为32MB/64MB,加载时间变长', + setKey: SettingBoxKey.expandBuffer, + defaultVal: false, + ), + //video-sync + SettingsModel( + settingsType: SettingsType.normal, + title: '视频同步', + leading: const Icon(Icons.view_timeline_outlined), + getSubtitle: () => '当前:${GStorage.videoSync}(此项即mpv的--video-sync)', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '视频同步', + value: GStorage.videoSync, + values: [ + 'audio', + 'display-resample', + 'display-resample-vdrop', + 'display-resample-desync', + 'display-tempo', + 'display-vdrop', + 'display-adrop', + 'display-desync', + 'desync' + ].map((e) { + return {'title': e, 'value': e}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.videoSync, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '硬解模式', + leading: const Icon(Icons.memory_outlined), + getSubtitle: () => '当前:${GStorage.hardwareDecoding}(此项即mpv的--hwdec)', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '硬解模式', + value: GStorage.hardwareDecoding, + values: + ['auto', 'auto-copy', 'auto-safe', 'no', 'yes'].map((e) { + return {'title': e, 'value': e}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.hardwareDecoding, result); + setState(); + } + }, + ), + ]; + +List get recommendSettings => [ + SettingsModel( + settingsType: SettingsType.normal, + title: '首页推荐类型', + leading: const Icon(Icons.model_training_outlined), + getSubtitle: () => '当前使用「${GStorage.defaultRcmdType}端」推荐¹', + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '推荐类型', + value: GStorage.defaultRcmdType, + values: RcmdType.values.map((e) { + return {'title': e.labels, 'value': e.values}; + }).toList(), + ); + }, + ); + if (result != null) { + if (result == 'app') { + if (GStorage.isLogin.not) { + SmartDialog.showToast('尚未登录,无法收到个性化推荐'); + } + } + GStorage.setting.put(SettingBoxKey.defaultRcmdType, result); + SmartDialog.showToast('下次启动时生效'); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '推荐动态', + subtitle: '是否在推荐内容中展示动态(仅app端)', + leading: Icon(Icons.motion_photos_on_outlined), + setKey: SettingBoxKey.enableRcmdDynamic, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '首页推荐刷新', + subtitle: '下拉刷新时保留上次内容', + leading: Icon(Icons.refresh), + setKey: SettingBoxKey.enableSaveLastData, + defaultVal: false, + needReboot: true, + ), + SettingsModel( + settingsType: SettingsType.normal, + leading: const Icon(Icons.thumb_up_outlined), + title: '点赞率过滤', + getSubtitle: () => + '过滤掉点赞数/播放量「小于${GStorage.minLikeRatioForRecommend}%」的推荐视频(仅web端)', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '选择点赞率(0即不过滤)', + value: GStorage.minLikeRatioForRecommend, + values: [0, 1, 2, 3, 4].map( + (e) { + return {'title': '$e %', 'value': e}; + }, + ).toList()); + }, + ); + if (result != null) { + GStorage.setting + .put(SettingBoxKey.minLikeRatioForRecommend, result); + RecommendFilter.update(); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + leading: const Icon(Icons.title_outlined), + title: '标题关键词过滤', + getSubtitle: () { + String banWordForRecommend = GStorage.banWordForRecommend; + return banWordForRecommend.isEmpty ? "点击添加" : banWordForRecommend; + }, + onTap: (setState) async { + final TextEditingController textController = + TextEditingController(text: GStorage.banWordForRecommend); + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text( + '标题关键词过滤', + style: TextStyle(fontSize: 18), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('使用|隔开,如:尝试|测试'), + TextField( + autofocus: true, + controller: textController, + textInputAction: TextInputAction.newline, + minLines: 1, + maxLines: 4, + ) + ], + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + child: const Text('保存'), + onPressed: () async { + Get.back(); + GStorage.setting.put( + SettingBoxKey.banWordForRecommend, + textController.text, + ); + setState(); + RecommendFilter.update(); + SmartDialog.showToast('已保存'); + }, + ), + ], + ); + }, + ); + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '视频时长过滤', + leading: const Icon(Icons.timelapse_outlined), + getSubtitle: () => '过滤掉时长「小于${GStorage.minDurationForRcmd}秒」的推荐视频', + onTap: (setState) async { + const List defDurations = [0, 30, 60, 90, 120]; + int? result = await showDialog( + context: Get.context!, + builder: (context) { + int minDurationForRcmd = GStorage.minDurationForRcmd; + return SelectDialog( + title: '选择时长(0即不过滤)', + value: minDurationForRcmd, + values: [ + ...[ + ...defDurations, + if (defDurations.contains(minDurationForRcmd).not) + minDurationForRcmd, + ]..sort(), + -1, + ].map((e) { + if (e == -1) { + return {'title': '自定义', 'value': e}; + } + return {'title': '$e 秒', 'value': e}; + }).toList()); + }, + ); + if (result != null) { + void updateDuration(int value) { + GStorage.setting.put(SettingBoxKey.minDurationForRcmd, value); + RecommendFilter.update(); + setState(); + } + + if (result == -1) { + showDialog( + context: Get.context!, + builder: (context) { + String duration = ''; + return AlertDialog( + title: Text( + '自定义时长', + style: TextStyle(fontSize: 18), + ), + content: TextField( + autofocus: true, + onChanged: (value) => duration = value, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'\d+')), + ], + decoration: const InputDecoration(suffixText: 's'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () { + Get.back(); + updateDuration(int.tryParse(duration) ?? 0); + }, + child: Text('确定'), + ), + ], + ); + }, + ); + } else { + updateDuration(result); + } + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '已关注UP豁免推荐过滤', + subtitle: '推荐中已关注用户发布的内容不会被过滤', + leading: const Icon(Icons.favorite_border_outlined), + setKey: SettingBoxKey.exemptFilterForFollowed, + defaultVal: true, + onChanged: (_) => {RecommendFilter.update}, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '过滤器也应用于相关视频', + subtitle: '视频详情页的相关视频也进行过滤²', + leading: const Icon(Icons.explore_outlined), + setKey: SettingBoxKey.applyFilterToRelatedVideos, + defaultVal: true, + onChanged: (_) => {RecommendFilter.update}, + ), + ]; + +List get privacySettings => [ + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) { + if (GStorage.isLogin.not) { + SmartDialog.showToast('登录后查看'); + return; + } + Get.toNamed('/blackListPage'); + }, + title: '黑名单管理', + subtitle: '已拉黑用户', + leading: const Icon(Icons.block), + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) { + MineController.onChangeAnonymity(Get.context!); + setState(); + }, + leading: const Icon(Icons.privacy_tip_outlined), + getTitle: () => MineController.anonymity.value ? '退出无痕模式' : '进入无痕模式', + getSubtitle: () => MineController.anonymity.value + ? '已进入无痕模式,搜索、观看视频/直播不携带Cookie与CSRF,其余操作不受影响' + : '未开启无痕模式,将使用账户信息提供完整服务', + ), + SettingsModel( + settingsType: SettingsType.normal, + onTap: (setState) { + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('查看详情'), + content: Text(AnonymityInterceptor.anonymityList.join('\n')), + actions: [ + TextButton( + onPressed: () async { + Get.back(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }, + leading: const Icon(Icons.flag_outlined), + title: '了解无痕模式', + subtitle: '查看无痕模式作用的API列表', + ), + ]; + +List get extraSettings => [ + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '空降助手', + subtitle: '点击配置', + setKey: SettingBoxKey.enableSponsorBlock, + defaultVal: false, + onTap: () => Get.toNamed('/sponsorBlock'), + leading: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.shield_outlined), + Icon( + Icons.play_arrow_rounded, + size: 15, + ), + ], + ), + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '检查未读动态', + subtitle: '点击设置检查周期(min)', + leading: const Icon(Icons.notifications_none), + setKey: SettingBoxKey.checkDynamic, + defaultVal: true, + onChanged: (value) { + Get.find().checkDynamic = value; + }, + onTap: () { + int dynamicPeriod = GStorage.dynamicPeriod; + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: Text('检查周期', style: TextStyle(fontSize: 18)), + content: TextFormField( + autofocus: true, + initialValue: dynamicPeriod.toString(), + keyboardType: TextInputType.numberWithOptions(), + onChanged: (value) { + dynamicPeriod = int.tryParse(value) ?? 5; + }, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'\d+')), + ], + decoration: InputDecoration(suffixText: 'min'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + GStorage.setting + .put(SettingBoxKey.dynamicPeriod, dynamicPeriod); + Get.find().dynamicPeriod = dynamicPeriod; + }, + child: Text('确定'), + ) + ], + ); + }, + ); + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '使用gRPC加载评论', + subtitle: '如无法加载评论,可关闭\n非gRPC楼中楼无法定位评论、按热度/时间排序、查看对话', + leading: SizedBox( + height: 24, + width: 24, + child: Icon(MdiIcons.google, size: 20), + ), + setKey: SettingBoxKey.grpcReply, + defaultVal: true, + onChanged: (value) { + GlobalData().grpcReply = value; + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '显示视频分段信息', + leading: Transform.rotate( + angle: pi / 2, + child: Icon(MdiIcons.viewHeadline), + ), + setKey: SettingBoxKey.showViewPoints, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '视频页显示相关视频', + leading: Icon(MdiIcons.motionPlayOutline), + setKey: SettingBoxKey.showRelatedVideo, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '显示视频评论', + leading: Icon(MdiIcons.commentTextOutline), + setKey: SettingBoxKey.showVideoReply, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '显示番剧评论', + leading: Icon(MdiIcons.commentTextOutline), + setKey: SettingBoxKey.showBangumiReply, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '默认展开视频简介', + leading: const Icon(Icons.expand_more), + setKey: SettingBoxKey.alwaysExapndIntroPanel, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '横屏自动展开视频简介', + leading: const Icon(Icons.expand_more), + setKey: SettingBoxKey.exapndIntroPanelH, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '横屏分P/合集列表显示在Tab栏', + leading: const Icon(Icons.format_list_numbered_rtl_sharp), + setKey: SettingBoxKey.horizontalSeasonPanel, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '横屏播放页在侧栏打开UP主页', + leading: const Icon(Icons.account_circle_outlined), + setKey: SettingBoxKey.horizontalMemberPage, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '评论折叠行数', + subtitle: '0行为不折叠', + leading: const Icon(Icons.compress), + setKey: SettingBoxKey.replyLengthLimit, + getTrailing: () => Text( + '${GlobalData().replyLengthLimit}行', + style: Theme.of(Get.context!).textTheme.titleSmall, + ), + onTap: (setState) { + String replyLengthLimit = GlobalData().replyLengthLimit.toString(); + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: Text('评论折叠行数', style: TextStyle(fontSize: 18)), + content: TextFormField( + autofocus: true, + initialValue: replyLengthLimit, + keyboardType: TextInputType.numberWithOptions(), + onChanged: (value) { + replyLengthLimit = value; + }, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'\d+')), + ], + decoration: InputDecoration(suffixText: '行'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + GlobalData().replyLengthLimit = + int.tryParse(replyLengthLimit) ?? 6; + await GStorage.setting.put( + SettingBoxKey.replyLengthLimit, + GlobalData().replyLengthLimit, + ); + setState(); + }, + child: Text('确定'), + ) + ], + ); + }, + ); + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '弹幕行高', + subtitle: '默认1.6', + setKey: SettingBoxKey.danmakuLineHeight, + leading: const Icon(Icons.subtitles_outlined), + getTrailing: () => Text( + GStorage.danmakuLineHeight.toString(), + style: Theme.of(Get.context!).textTheme.titleSmall, + ), + onTap: (setState) { + dynamic danmakuLineHeight = GStorage.danmakuLineHeight.toString(); + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: Text('弹幕行高', style: TextStyle(fontSize: 18)), + content: TextFormField( + autofocus: true, + initialValue: danmakuLineHeight, + keyboardType: TextInputType.numberWithOptions(decimal: true), + onChanged: (value) { + danmakuLineHeight = value; + }, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), + ], + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + danmakuLineHeight = max( + 1.0, + double.tryParse(danmakuLineHeight) ?? 1.6, + ); + await GStorage.setting.put( + SettingBoxKey.danmakuLineHeight, + danmakuLineHeight, + ); + setState(); + }, + child: Text('确定'), + ) + ], + ); + }, + ); + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '显示视频警告/争议信息', + leading: const Icon(Icons.warning_amber_rounded), + setKey: SettingBoxKey.showArgueMsg, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '分P/合集:倒序播放从首集开始播放', + subtitle: '开启则自动切换为倒序首集,否则保持当前集', + leading: Icon(MdiIcons.sort), + setKey: SettingBoxKey.reverseFromFirst, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '禁用 SSL 证书验证', + subtitle: '谨慎开启,禁用容易受到中间人攻击', + leading: Icon(Icons.security), + needReboot: true, + setKey: SettingBoxKey.badCertificateCallback, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + enableFeedback: true, + setKey: SettingBoxKey.feedBackEnable, + onChanged: (value) { + feedBack(); + }, + leading: const Icon(Icons.vibration_outlined), + title: '震动反馈', + subtitle: '请确定手机设置中已开启震动反馈', + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '大家都在搜', + subtitle: '是否展示「大家都在搜」', + leading: Icon(Icons.data_thresholding_outlined), + setKey: SettingBoxKey.enableHotKey, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '搜索默认词', + subtitle: '是否展示搜索框默认词', + leading: const Icon(Icons.whatshot_outlined), + setKey: SettingBoxKey.enableSearchWord, + defaultVal: true, + onChanged: (val) { + Get.find().defaultSearch.value = ''; + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '快速收藏', + subtitle: '点按收藏至默认,长按选择文件夹', + leading: Icon(Icons.bookmark_add_outlined), + setKey: SettingBoxKey.enableQuickFav, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '评论区搜索关键词', + subtitle: '展示评论区搜索关键词', + leading: Icon(Icons.search_outlined), + setKey: SettingBoxKey.enableWordRe, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '启用AI总结', + subtitle: '视频详情页开启AI总结', + leading: Icon(Icons.engineering_outlined), + setKey: SettingBoxKey.enableAi, + defaultVal: true, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '消息页禁用"收到的赞"功能', + subtitle: '禁止打开入口,降低网络社交依赖', + leading: Icon(Icons.beach_access_outlined), + setKey: SettingBoxKey.disableLikeMsg, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '默认展示评论区', + subtitle: '在视频详情页默认切换至评论区页(仅Tab型布局)', + leading: Icon(Icons.mode_comment_outlined), + setKey: SettingBoxKey.defaultShowComment, + defaultVal: false, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '评论展示', + setKey: SettingBoxKey.replySortType, + leading: const Icon(Icons.whatshot_outlined), + getSubtitle: () => + '当前优先展示「${ReplySortType.values[GStorage.defaultReplySort].titles}」', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '评论展示', + value: GStorage.defaultReplySort, + values: ReplySortType.values.map((e) { + return {'title': e.titles, 'value': e.index}; + }).toList(), + ); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.replySortType, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '动态展示', + setKey: SettingBoxKey.defaultDynamicType, + leading: const Icon(Icons.dynamic_feed_outlined), + getSubtitle: () => + '当前优先展示「${DynamicsType.values[GStorage.defaultDynamicType].labels}」', + onTap: (setState) async { + int? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '动态展示', + value: GStorage.defaultDynamicType, + values: DynamicsType.values.sublist(0, 4).map((e) { + return {'title': e.labels, 'value': e.index}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.defaultDynamicType, result); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '用户页默认展示TAB', + setKey: SettingBoxKey.memberTab, + leading: const Icon(Icons.tab), + getSubtitle: () => '当前优先展示「${GStorage.memberTab.title}」', + onTap: (setState) async { + MemberTabType? result = await showDialog( + context: Get.context!, + builder: (context) { + return SelectDialog( + title: '用户页默认展示TAB', + value: GStorage.memberTab, + values: MemberTabType.values.map((e) { + return {'title': e.title, 'value': e}; + }).toList()); + }, + ); + if (result != null) { + GStorage.setting.put(SettingBoxKey.memberTab, result.index); + setState(); + } + }, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + onTap: () { + String systemProxyHost = GStorage.defaultSystemProxyHost; + String systemProxyPort = GStorage.defaultSystemProxyPort; + + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('设置代理'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 6), + TextField( + decoration: InputDecoration( + isDense: true, + labelText: systemProxyHost != '' + ? systemProxyHost + : '请输入Host,使用 . 分割', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6.0), + ), + hintText: systemProxyHost, + ), + onChanged: (e) { + systemProxyHost = e; + }, + ), + const SizedBox(height: 10), + TextField( + keyboardType: TextInputType.number, + decoration: InputDecoration( + isDense: true, + labelText: + systemProxyPort != '' ? systemProxyPort : '请输入Port', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6.0), + ), + hintText: systemProxyPort, + ), + onChanged: (e) { + systemProxyPort = e; + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () async { + Get.back(); + }, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + GStorage.setting + .put(SettingBoxKey.systemProxyHost, systemProxyHost); + GStorage.setting + .put(SettingBoxKey.systemProxyPort, systemProxyPort); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }, + leading: const Icon(Icons.airplane_ticket_outlined), + title: '设置代理', + subtitle: '设置代理 host:port', + setKey: SettingBoxKey.enableSystemProxy, + ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '自动清除缓存', + subtitle: '每次启动时清除缓存', + leading: Icon(Icons.auto_delete_outlined), + setKey: SettingBoxKey.autoClearCache, + defaultVal: false, + ), + ]; diff --git a/lib/pages/setting/widgets/normal_item.dart b/lib/pages/setting/widgets/normal_item.dart new file mode 100644 index 000000000..a131709d0 --- /dev/null +++ b/lib/pages/setting/widgets/normal_item.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +class NormalItem extends StatefulWidget { + final String? title; + final Function? getTitle; + final String? subtitle; + final Function? getSubtitle; + final String? setKey; + final bool? defaultVal; + final ValueChanged? onChanged; + final bool? needReboot; + final Widget? leading; + final Function? getTrailing; + final Function? onTap; + final EdgeInsetsGeometry? contentPadding; + final TextStyle? titleStyle; + + const NormalItem({ + this.title, + this.getTitle, + this.subtitle, + this.getSubtitle, + this.setKey, + this.defaultVal, + this.onChanged, + this.needReboot, + this.leading, + this.getTrailing, + this.onTap, + this.contentPadding, + this.titleStyle, + super.key, + }); + + @override + State createState() => _NormalItemState(); +} + +class _NormalItemState extends State { + @override + Widget build(BuildContext context) { + return ListTile( + contentPadding: widget.contentPadding, + onTap: () { + widget.onTap?.call(() { + setState(() {}); + }); + }, + title: Text(widget.title ?? widget.getTitle?.call(), + style: widget.titleStyle ?? Theme.of(context).textTheme.titleMedium!), + subtitle: widget.subtitle != null || widget.getSubtitle != null + ? Text(widget.subtitle ?? widget.getSubtitle?.call(), + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline)) + : null, + leading: widget.leading, + trailing: widget.getTrailing?.call(), + ); + } +} diff --git a/lib/pages/setting/widgets/select_item.dart b/lib/pages/setting/widgets/select_item.dart index 36329e9b3..0232fea45 100644 --- a/lib/pages/setting/widgets/select_item.dart +++ b/lib/pages/setting/widgets/select_item.dart @@ -97,7 +97,6 @@ class _SetSelectItemState extends State { .copyWith(color: Theme.of(context).colorScheme.outline); return ListTile( onTap: () {}, - dense: false, title: Text(widget.title!), subtitle: Text( '当前${widget.title!} $currentVal', diff --git a/lib/pages/setting/widgets/slide_dialog.dart b/lib/pages/setting/widgets/slide_dialog.dart index 94d917de6..6d6d567fe 100644 --- a/lib/pages/setting/widgets/slide_dialog.dart +++ b/lib/pages/setting/widgets/slide_dialog.dart @@ -20,7 +20,7 @@ class SlideDialog extends StatefulWidget { }); @override - _SlideDialogState createState() => _SlideDialogState(); + State> createState() => _SlideDialogState(); } class _SlideDialogState extends State> { @@ -35,7 +35,10 @@ class _SlideDialogState extends State> { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(widget.title), + title: Text( + widget.title, + style: TextStyle(fontSize: 18), + ), contentPadding: const EdgeInsets.only(top: 20, left: 8, right: 8, bottom: 8), content: SizedBox( diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index 11a56f513..287ce3893 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -5,19 +5,19 @@ import 'package:get/get.dart'; class SetSwitchItem extends StatefulWidget { final String? title; - final String? subTitle; + final String? subtitle; final String? setKey; final bool? defaultVal; final ValueChanged? onChanged; final bool? needReboot; final Widget? leading; - final GestureTapCallback? onTap; + final Function? onTap; final EdgeInsetsGeometry? contentPadding; final TextStyle? titleStyle; const SetSwitchItem({ this.title, - this.subTitle, + this.subtitle, this.setKey, this.defaultVal, this.onChanged, @@ -113,8 +113,8 @@ class _SetSwitchItemState extends State { onTap: () => widget.onTap != null ? widget.onTap?.call() : switchChange(null), title: Text(widget.title!, style: titleStyle), - subtitle: widget.subTitle != null - ? Text(widget.subTitle!, style: subTitleStyle) + subtitle: widget.subtitle != null + ? Text(widget.subtitle!, style: subTitleStyle) : null, leading: widget.leading, trailing: Transform.scale( diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index ae3822fd5..768cc5756 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -1,7 +1,9 @@ import 'package:PiliPalaX/pages/member/new/member_page.dart'; import 'package:PiliPalaX/pages/member/new/widget/edit_profile_page.dart'; import 'package:PiliPalaX/pages/setting/navigation_bar_set.dart'; +import 'package:PiliPalaX/pages/setting/search_page.dart'; import 'package:PiliPalaX/pages/setting/sponsor_block_page.dart'; +import 'package:PiliPalaX/pages/setting/view.dart'; import 'package:PiliPalaX/pages/video/detail/introduction/widgets/create_fav_page.dart'; import 'package:PiliPalaX/pages/webview/webview_page.dart'; import 'package:flutter/material.dart'; @@ -39,7 +41,6 @@ import '../pages/msg_feed_top/sys_msg/view.dart'; import '../pages/search/index.dart'; import '../pages/search_result/index.dart'; import '../pages/setting/extra_setting.dart'; -import '../pages/setting/index.dart'; import '../pages/setting/pages/color_select.dart'; import '../pages/setting/pages/display_mode.dart'; import '../pages/setting/pages/font_size_select.dart'; @@ -174,6 +175,8 @@ class Routes { // navigation bar CustomGetPage( name: '/navbarSetting', page: () => const NavigationBarSetPage()), + CustomGetPage( + name: '/settingsSearch', page: () => const SettingsSearchPage()), ]; } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 7d07a579f..54bb9f5b3 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -2,10 +2,17 @@ import 'dart:convert'; import 'dart:io'; import 'package:PiliPalaX/common/widgets/pair.dart'; import 'package:PiliPalaX/http/constants.dart'; +import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart'; import 'package:PiliPalaX/models/common/theme_type.dart'; +import 'package:PiliPalaX/models/common/up_panel_position.dart'; +import 'package:PiliPalaX/models/video/play/CDN.dart'; +import 'package:PiliPalaX/models/video/play/quality.dart'; +import 'package:PiliPalaX/models/video/play/subtitle.dart'; import 'package:PiliPalaX/pages/member/new/controller.dart' show MemberTabType; import 'package:PiliPalaX/pages/video/detail/controller.dart' show SegmentType, SegmentTypeExt, SkipType; +import 'package:PiliPalaX/plugin/pl_player/models/bottom_progress_behavior.dart'; +import 'package:PiliPalaX/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; @@ -66,6 +73,120 @@ class GStorage { .toList(); } + static bool get hiddenSettingUnlocked => + setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false); + + static bool get feedBackEnable => + setting.get(SettingBoxKey.feedBackEnable, defaultValue: false); + + static double get toastOpacity => + setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0); + + static int get picQuality => + setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); + + static ThemeType get themeType => ThemeType.values[GStorage.themeTypeInt]; + + static DynamicBadgeMode get dynamicBadgeType => + DynamicBadgeMode.values[setting.get( + SettingBoxKey.dynamicBadgeMode, + defaultValue: DynamicBadgeMode.number.code, + )]; + + static int get defaultHomePage => + setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0); + + static int get previewQ => + setting.get(SettingBoxKey.previewQuality, defaultValue: 80); + + static double get maxRowWidth => + setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0); + + static UpPanelPosition get upPanelPosition => + UpPanelPosition.values[setting.get(SettingBoxKey.upPanelPosition, + defaultValue: UpPanelPosition.leftFixed.index)]; + + static int get defaultFullScreenMode => + setting.get(SettingBoxKey.fullScreenMode, + defaultValue: FullScreenMode.values.first.code); + + static int get defaultBtmProgressBehavior => + setting.get(SettingBoxKey.btmProgressBehavior, + defaultValue: BtmProgressBehavior.values.first.code); + + static String get defaultSubtitlePreference => + setting.get(SettingBoxKey.subtitlePreference, + defaultValue: SubtitlePreference.values.first.code); + + static int get defaultVideoQa => setting.get( + SettingBoxKey.defaultVideoQa, + defaultValue: VideoQuality.values.last.code, + ); + + static int get defaultVideoQaCellular => setting.get( + SettingBoxKey.defaultVideoQaCellular, + defaultValue: VideoQuality.high1080.code, + ); + + static int get defaultAudioQa => setting.get( + SettingBoxKey.defaultAudioQa, + defaultValue: AudioQuality.values.last.code, + ); + + static int get defaultAudioQaCellular => setting.get( + SettingBoxKey.defaultAudioQaCellular, + defaultValue: AudioQuality.k192.code, + ); + + static String get defaultDecode => setting.get( + SettingBoxKey.defaultDecode, + defaultValue: VideoDecodeFormats.values.last.code, + ); + + static String get secondDecode => setting.get( + SettingBoxKey.secondDecode, + defaultValue: VideoDecodeFormats.values[1].code, + ); + + static String get hardwareDecoding => setting.get( + SettingBoxKey.hardwareDecoding, + defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto', + ); + + static String get videoSync => setting.get( + SettingBoxKey.videoSync, + defaultValue: 'display-resample', + ); + + static String get defaultCDNService => setting.get( + SettingBoxKey.CDNService, + defaultValue: CDNService.backupUrl.code, + ); + + static int get minDurationForRcmd => + setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); + + static String get banWordForRecommend => + setting.get(SettingBoxKey.banWordForRecommend, defaultValue: ''); + + static int get minLikeRatioForRecommend => + setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); + + static String get defaultRcmdType => + setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'app'); + + static String get defaultSystemProxyHost => + setting.get(SettingBoxKey.systemProxyHost, defaultValue: ''); + + static String get defaultSystemProxyPort => + setting.get(SettingBoxKey.systemProxyPort, defaultValue: ''); + + static int get defaultReplySort => + setting.get(SettingBoxKey.replySortType, defaultValue: 1); + + static int get defaultDynamicType => + setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); + static double get blockLimit => setting.get(SettingBoxKey.blockLimit, defaultValue: 0.0); @@ -183,11 +304,11 @@ class GStorage { static MemberTabType get memberTab => MemberTabType .values[setting.get(SettingBoxKey.memberTab, defaultValue: 0)]; - static int get themeType => + static int get themeTypeInt => setting.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code); static ThemeMode get themeMode { - return switch (themeType) { + return switch (themeTypeInt) { 0 => ThemeMode.light, 1 => ThemeMode.dark, _ => ThemeMode.system