feat: 调整设置,支持导入导出,代码优化

This commit is contained in:
orz12
2024-04-27 02:05:50 +08:00
parent a4d3713b05
commit ea8f44f4da
41 changed files with 882 additions and 424 deletions

View File

@@ -21,7 +21,6 @@ class ExtraSetting extends StatefulWidget {
class _ExtraSettingState extends State<ExtraSetting> {
Box setting = GStrorage.setting;
final SettingController settingController = Get.put(SettingController());
static Box localCache = GStrorage.localCache;
late dynamic defaultReplySort;
late dynamic defaultDynamicType;
late dynamic enableSystemProxy;
@@ -45,9 +44,9 @@ class _ExtraSettingState extends State<ExtraSetting> {
enableSystemProxy =
setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
defaultSystemProxyHost =
localCache.get(LocalCacheKey.systemProxyHost, defaultValue: '');
setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
defaultSystemProxyPort =
localCache.get(LocalCacheKey.systemProxyPort, defaultValue: '');
setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
}
// 设置代理
@@ -111,8 +110,8 @@ class _ExtraSettingState extends State<ExtraSetting> {
),
TextButton(
onPressed: () async {
localCache.put(LocalCacheKey.systemProxyHost, systemProxyHost);
localCache.put(LocalCacheKey.systemProxyPort, systemProxyPort);
setting.put(SettingBoxKey.systemProxyHost, systemProxyHost);
setting.put(SettingBoxKey.systemProxyPort, systemProxyPort);
SmartDialog.dismiss();
// Request.dio;
},
@@ -143,9 +142,10 @@ class _ExtraSettingState extends State<ExtraSetting> {
body: ListView(
children: [
Obx(
() => ListTile(
() => ListTile(
enableFeedback: true,
onTap: () => settingController.onOpenFeedBack(),
leading: const Icon(Icons.vibration_outlined),
title: Text('震动反馈', style: titleStyle),
subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle),
trailing: Transform.scale(
@@ -153,13 +153,13 @@ class _ExtraSettingState extends State<ExtraSetting> {
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.isNotEmpty &&
states.first == MaterialState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
}),
(Set<MaterialState> states) {
if (states.isNotEmpty &&
states.first == MaterialState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
}),
value: settingController.feedBackEnable.value,
onChanged: (value) => settingController.onOpenFeedBack()),
),
@@ -168,12 +168,14 @@ class _ExtraSettingState extends State<ExtraSetting> {
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,
callFn: (val) {
@@ -183,30 +185,35 @@ class _ExtraSettingState extends State<ExtraSetting> {
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,
),
ListTile(
dense: false,
title: Text('评论展示', style: titleStyle),
leading: const Icon(Icons.whatshot_outlined),
subtitle: Text(
'当前优先展示「${ReplySortType.values[defaultReplySort].titles}',
style: subTitleStyle,
@@ -233,6 +240,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
ListTile(
dense: false,
title: Text('动态展示', style: titleStyle),
leading: const Icon(Icons.dynamic_feed_outlined),
subtitle: Text(
'当前优先展示「${DynamicsType.values[defaultDynamicType].labels}',
style: subTitleStyle,
@@ -259,6 +267,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
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(
@@ -287,12 +296,14 @@ class _ExtraSettingState extends State<ExtraSetting> {
const SetSwitchItem(
title: '自动清除缓存',
subTitle: '每次启动时清除缓存',
leading: Icon(Icons.auto_delete_outlined),
setKey: SettingBoxKey.autoClearCache,
defaultVal: false,
defaultVal: true,
),
const SetSwitchItem(
title: '检查更新',
subTitle: '每次启动时检查是否需要更新',
leading: Icon(Icons.system_update_alt_outlined),
setKey: SettingBoxKey.autoUpdate,
defaultVal: false,
),

View File

@@ -22,10 +22,6 @@ class _HiddenSettingState extends State<HiddenSetting> {
@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(
centerTitle: false,

View File

@@ -65,30 +65,35 @@ class _PlaySettingState extends State<PlaySetting> {
const SetSwitchItem(
title: '弹幕开关',
subTitle: '是否展示弹幕',
leading: Icon(Icons.comment_outlined),
setKey: SettingBoxKey.enableShowDanmaku,
defaultVal: false,
),
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: true,
),
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}',
@@ -116,30 +121,35 @@ class _PlaySettingState extends State<PlaySetting> {
const SetSwitchItem(
title: '竖屏扩大展示',
subTitle: '小屏竖屏视频宽高比由16:9扩大至4:5暂不支持临时收起',
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: '非全屏时可受重力转为临时全屏,若系统锁定旋转仍异常触发请关闭,无异常可保持开启',
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: true,
),
@@ -147,6 +157,7 @@ class _PlaySettingState extends State<PlaySetting> {
SetSwitchItem(
title: '后台画中画',
subTitle: '进入后台时以小窗形式PiP播放',
leading: const Icon(Icons.picture_in_picture_outlined),
setKey: SettingBoxKey.autoPiP,
defaultVal: false,
callFn: (val) {
@@ -160,26 +171,30 @@ class _PlaySettingState extends State<PlaySetting> {
const SetSwitchItem(
title: '画中画不加载弹幕',
subTitle: '当弹幕开关开启时,小窗屏蔽弹幕以获得较好的体验',
leading: Icon(Icons.comments_disabled_outlined),
setKey: SettingBoxKey.pipNoDanmaku,
defaultVal: true,
),
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: false,
),
ListTile(
dense: false,
title: Text('默认全屏方', style: titleStyle),
title: Text('默认全屏方', style: titleStyle),
leading: const Icon(Icons.open_with_outlined),
subtitle: Text(
'当前全屏方${FullScreenModeCode.fromCode(defaultFullScreenMode)!.description}',
'当前全屏方${FullScreenModeCode.fromCode(defaultFullScreenMode)!.description}',
style: subTitleStyle,
),
onTap: () async {
@@ -187,7 +202,7 @@ class _PlaySettingState extends State<PlaySetting> {
context: context,
builder: (context) {
return SelectDialog<int>(
title: '默认全屏方',
title: '默认全屏方',
value: defaultFullScreenMode,
values: FullScreenMode.values.map((e) {
return {'title': e.description, 'value': e.code};
@@ -204,6 +219,7 @@ class _PlaySettingState extends State<PlaySetting> {
ListTile(
dense: false,
title: Text('底部进度条展示', style: titleStyle),
leading: const Icon(Icons.border_bottom_outlined),
subtitle: Text(
'当前展示方式:${BtmProgresBehaviorCode.fromCode(defaultBtmProgressBehavior)!.description}',
style: subTitleStyle,
@@ -230,6 +246,7 @@ class _PlaySettingState extends State<PlaySetting> {
const SetSwitchItem(
title: '后台音频服务',
subTitle: '避免画中画没有播放暂停功能',
leading: Icon(Icons.volume_up_outlined),
setKey: SettingBoxKey.enableBackgroundPlay,
defaultVal: true,
),

View File

@@ -1,4 +1,6 @@
import 'package:PiliPalaX/utils/cookie.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';
@@ -6,6 +8,11 @@ import 'package:PiliPalaX/http/interceptor_anonymity.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/utils/storage.dart';
import '../../http/user.dart';
import '../../models/user/info.dart';
import '../../utils/login.dart';
import '../home/controller.dart';
import '../media/controller.dart';
import '../mine/controller.dart';
class PrivacySetting extends StatefulWidget {
@@ -18,13 +25,16 @@ class PrivacySetting extends StatefulWidget {
class _PrivacySettingState extends State<PrivacySetting> {
bool userLogin = false;
Box userInfoCache = GStrorage.userInfo;
var userInfo;
UserInfoData? userInfo;
late bool hiddenSettingUnlocked;
@override
void initState() {
super.initState();
userInfo = userInfoCache.get('userInfoCache');
userLogin = userInfo != null;
hiddenSettingUnlocked = GStrorage.setting
.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false);
}
@override
@@ -56,6 +66,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
dense: false,
title: Text('黑名单管理', style: titleStyle),
subtitle: Text('已拉黑用户', style: subTitleStyle),
leading: const Icon(Icons.block),
),
ListTile(
onTap: () {
@@ -66,15 +77,33 @@ class _PrivacySettingState extends State<PrivacySetting> {
},
dense: false,
title: Text('刷新access_key', style: titleStyle),
leading: const Icon(Icons.perm_device_info_outlined),
subtitle: Text(
'access_key是app端的用户凭证用于推荐接口。刷新将使用cookie请求新的access_key小概率导致其他设备下线。若发现app端推荐内容不是个性化内容,可尝试刷新',
'用于app端推荐接口的用户凭证。刷新有小概率导致其他设备下线。若app端推荐个性化内容,可尝试刷新或清除本app数据后重新登录',
style: subTitleStyle),
),
if (hiddenSettingUnlocked)
ListTile(
title: Text(
'导入/导出cookie',
style: titleStyle,
),
subtitle: Text(
'cookie代表您的登录状态仅供高级用户使用',
style: subTitleStyle,
),
leading: const Icon(Icons.cookie_outlined),
dense: false,
onTap: () {
import_export_cookies(titleStyle, subTitleStyle);
},
),
ListTile(
onTap: () {
MineController.onChangeAnonymity(context);
setState(() {});
},
leading: const Icon(Icons.privacy_tip_outlined),
dense: false,
title: Text(MineController.anonymity ? '退出无痕模式' : '进入无痕模式',
style: titleStyle),
@@ -104,6 +133,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
},
);
},
leading: const Icon(Icons.flag_outlined),
dense: false,
title: Text('了解无痕模式', style: titleStyle),
subtitle: Text('查看无痕模式作用的API列表', style: subTitleStyle),
@@ -112,4 +142,146 @@ class _PrivacySettingState extends State<PrivacySetting> {
),
);
}
void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) {
SmartDialog.show(
useSystem: true,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)),
children: [
ListTile(
title: Text(
'导出cookie至剪贴板',
style: titleStyle.copyWith(color: Colors.red),
),
leading: const Icon(
Icons.warning_amber,
color: Colors.red,
),
subtitle: Text(
'泄露账号cookie等同于绕过账号密码与验证码直接登录可导致隐私泄露、风控、毁号、盗号等各类问题。\n'
'你应妥善保管该cookie且仅供自己使用。你承诺不会利用本服务进行任何违法或不当的活动。你承诺对所进行的一切活动'
'(包括但不限于网上点击同意或提交各类规则协议或购买服务、分享资讯或图片等)负全部责任。\n'
'你承诺、理解、同意并确认,在你的账户遭到未获授权的使用,或者发生其他任何安全问题时,'
'作者不对上述情形产生的任何直接或间接的遗失或损害承担责任。',
style: subTitleStyle.copyWith(color: Colors.redAccent),
),
dense: false,
onTap: () async {
await SmartDialog.dismiss();
if (!userLogin) {
SmartDialog.showToast('请先登录');
return;
}
final String cookie = await CookieTool.exportCookie();
await SmartDialog.show(
builder: (context) {
return AlertDialog(
title: const Text('导出cookie危险',
style: TextStyle(color: Colors.red)),
content: Text(cookie),
actions: [
TextButton(
onPressed: () async {
await SmartDialog.dismiss();
await Clipboard.setData(
ClipboardData(text: cookie));
},
child: const Text('复制(危险)',
style: TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () async {
await SmartDialog.dismiss();
},
child: const Text('取消'),
),
],
);
},
);
}),
ListTile(
title: Text(
'从剪贴板导入cookie',
style: titleStyle,
),
leading: const Icon(
Icons.warning_amber,
color: Colors.red,
),
subtitle: Text(
'导入将覆盖当前登录状态,你应自行对利用服务从事的所有行为及结果承担责任,请慎用',
style: subTitleStyle,
),
dense: false,
onTap: () async {
await SmartDialog.dismiss();
ClipboardData? data = await Clipboard.getData('text/plain');
if (data == null || data.text == null || data.text == '') {
SmartDialog.showToast('未检测到剪贴板内容');
return;
}
await SmartDialog.show(
builder: (context) {
return AlertDialog(
title: const Text('导入剪贴板中的cookie'),
content: Text(data.text!),
actions: [
TextButton(
onPressed: () async {
await SmartDialog.dismiss();
},
child: const Text('取消'),
),
TextButton(
onPressed: () async {
await SmartDialog.dismiss();
final String cookie = data.text!;
try {
await CookieTool.importCookie(cookie);
await SmartDialog.showToast('已导入');
await CookieTool.onSet();
final result = await UserHttp.userInfo();
if (result['status'] &&
result['data'].isLogin) {
SmartDialog.showToast('登录成功,当前采用「'
'${GStrorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
'端」推荐');
Box userInfoCache = GStrorage.userInfo;
await userInfoCache.put(
'userInfoCache', result['data']);
final HomeController homeCtr =
Get.find<HomeController>();
homeCtr.updateLoginStatus(true);
homeCtr.userFace.value = result['data'].face;
final MediaController mediaCtr =
Get.find<MediaController>();
mediaCtr.mid = result['data'].mid;
await LoginUtils.refreshLoginStatus(true);
Get.back();
} else {
// 获取用户信息失败
SmartDialog.showNotify(
msg:
'登录失败请检查cookie是否正确${result['message']}',
notifyType: NotifyType.warning);
}
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确认'),
),
],
);
},
);
}),
],
);
},
);
}
}

View File

@@ -57,7 +57,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
centerTitle: false,
titleSpacing: 0,
title: Text(
'推荐设置',
'推荐设置',
style: Theme.of(context).textTheme.titleMedium,
),
),
@@ -66,6 +66,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
ListTile(
dense: false,
title: Text('首页推荐类型', style: titleStyle),
leading: const Icon(Icons.model_training_outlined),
subtitle: Text(
'当前使用「$defaultRcmdType端」推荐¹',
style: subTitleStyle,
@@ -131,12 +132,14 @@ class _RecommendSettingState extends State<RecommendSetting> {
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,
),
@@ -144,6 +147,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
const Divider(height: 1),
ListTile(
dense: false,
leading: const Icon(Icons.thumb_up_outlined),
title: Text('点赞率过滤', style: titleStyle),
subtitle: Text(
'过滤掉点赞数/播放量「小于$minLikeRatioForRecommend%」的推荐视频(仅web端)',
@@ -172,6 +176,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
ListTile(
dense: false,
title: Text('视频时长过滤', style: titleStyle),
leading: const Icon(Icons.timelapse_outlined),
subtitle: Text(
'过滤掉时长「小于$minDurationForRcmd秒」的推荐视频',
style: subTitleStyle,
@@ -199,6 +204,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
SetSwitchItem(
title: '已关注Up豁免推荐过滤',
subTitle: '推荐中已关注用户发布的内容不会被过滤',
leading: const Icon(Icons.favorite_border_outlined),
setKey: SettingBoxKey.exemptFilterForFollowed,
defaultVal: true,
callFn: (_) => {RecommendFilter.update},
@@ -234,6 +240,7 @@ class _RecommendSettingState extends State<RecommendSetting> {
SetSwitchItem(
title: '过滤器也应用于相关视频',
subTitle: '视频详情页的相关视频也进行过滤²',
leading: const Icon(Icons.explore_outlined),
setKey: SettingBoxKey.applyFilterToRelatedVideos,
defaultVal: true,
callFn: (_) => {RecommendFilter.update},

View File

@@ -63,7 +63,8 @@ class _StyleSettingState extends State<StyleSetting> {
children: [
SetSwitchItem(
title: '横屏适配',
subTitle: '开启该项启用横屏布局与逻辑(测试)',
subTitle: '启用横屏布局与逻辑,适用于平板等设备',
leading: const Icon(Icons.phonelink_outlined),
setKey: SettingBoxKey.horizontalScreen,
defaultVal: false,
callFn: (value) {
@@ -75,15 +76,24 @@ class _StyleSettingState extends State<StyleSetting> {
SmartDialog.showToast('已关闭横屏适配');
}
}),
const SetSwitchItem(
title: '自适应底栏/侧边栏',
subTitle: '横竖屏自动切换(其它底栏设置失效)',
leading: Icon(Icons.chrome_reader_mode_outlined),
setKey: SettingBoxKey.adaptiveNavBar,
defaultVal: false,
),
const SetSwitchItem(
title: 'MD3样式底栏',
subTitle: '符合Material You设计规范底栏,关闭可使底栏变窄',
subTitle: 'Material You设计规范底栏关闭可变窄',
leading: Icon(Icons.design_services_outlined),
setKey: SettingBoxKey.enableMYBar,
defaultVal: true,
),
const SetSwitchItem(
title: '首页顶栏收起',
subTitle: '首页列表滑动时,收起顶栏',
leading: Icon(Icons.vertical_align_top_outlined),
setKey: SettingBoxKey.hideSearchBar,
defaultVal: false,
needReboot: true,
@@ -91,6 +101,7 @@ class _StyleSettingState extends State<StyleSetting> {
const SetSwitchItem(
title: '首页底栏收起',
subTitle: '首页列表滑动时,收起底栏',
leading: Icon(Icons.vertical_align_bottom_outlined),
setKey: SettingBoxKey.hideTabBar,
defaultVal: false,
needReboot: true,
@@ -98,6 +109,7 @@ class _StyleSettingState extends State<StyleSetting> {
const SetSwitchItem(
title: '首页背景渐变',
setKey: SettingBoxKey.enableGradientBg,
leading: Icon(Icons.gradient_outlined),
defaultVal: true,
needReboot: true,
),
@@ -123,11 +135,12 @@ class _StyleSettingState extends State<StyleSetting> {
setState(() {});
}
},
leading: const Icon(Icons.calendar_view_week_outlined),
dense: false,
title: Text('最大列宽dp基准', style: titleStyle),
title: Text('列表宽度dp上限', style: titleStyle),
subtitle: Text(
'当前${maxRowWidth.toInt()}dp屏幕宽度${MediaQuery.of(context).size.width.toPrecision(2)}dp。'
'该值决定列表在不同屏宽下的列数,部分列表会根据系数折算宽度',
'当前:${maxRowWidth.toInt()}dp屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。'
'宽度越小列数越多横条、大卡会2倍折算',
style: subTitleStyle,
),
),
@@ -187,6 +200,7 @@ class _StyleSettingState extends State<StyleSetting> {
},
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(
@@ -218,8 +232,9 @@ class _StyleSettingState extends State<StyleSetting> {
setting.put(SettingBoxKey.defaultToastOp, result);
}
},
title: Text('Toast不透明度', style: titleStyle),
subtitle: Text('自定义Toast不透明度', style: subTitleStyle),
leading: const Icon(Icons.opacity_outlined),
title: Text('气泡提示不透明度', style: titleStyle),
subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle),
),
ListTile(
dense: false,
@@ -242,6 +257,7 @@ class _StyleSettingState extends State<StyleSetting> {
Get.forceAppUpdate();
}
},
leading: const Icon(Icons.flashlight_on_outlined),
title: Text('主题模式', style: titleStyle),
subtitle: Obx(() => Text(
'当前模式:${settingController.themeType.value.description}',
@@ -251,6 +267,7 @@ class _StyleSettingState extends State<StyleSetting> {
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)),
@@ -258,6 +275,7 @@ class _StyleSettingState extends State<StyleSetting> {
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 ? '动态取色' : '指定颜色'}',
@@ -266,12 +284,14 @@ class _StyleSettingState extends State<StyleSetting> {
const SetSwitchItem(
title: '默认展示评论区',
subTitle: '在视频详情页默认切换至评论区页',
leading: Icon(Icons.mode_comment_outlined),
setKey: SettingBoxKey.defaultShowComment,
defaultVal: false,
),
ListTile(
dense: false,
onTap: () => settingController.seteDefaultHomePage(context),
leading: const Icon(Icons.home_outlined),
title: Text('默认启动页', style: titleStyle),
subtitle: Obx(() => Text(
'当前启动页:${defaultNavigationBars.firstWhere((e) => e['id'] == settingController.defaultHomePage.value)['label']}',
@@ -281,18 +301,21 @@ class _StyleSettingState extends State<StyleSetting> {
dense: false,
onTap: () => Get.toNamed('/fontSizeSetting'),
title: Text('字体大小', style: titleStyle),
leading: const Icon(Icons.format_size_outlined),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/tabbarSetting'),
title: Text('首页标签页', style: titleStyle),
subtitle: Text('删除或调换首页标签页', style: subTitleStyle),
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),
)
],
),

View File

@@ -21,6 +21,8 @@ class _VideoSettingState extends State<VideoSetting> {
late dynamic defaultAudioQa;
late dynamic defaultDecode;
late dynamic secondDecode;
late dynamic hardwareDecoding;
late dynamic videoSync;
@override
void initState() {
@@ -33,6 +35,10 @@ class _VideoSettingState extends State<VideoSetting> {
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');
}
@override
@@ -55,31 +61,36 @@ class _VideoSettingState extends State<VideoSetting> {
children: [
const SetSwitchItem(
title: '开启硬解',
subTitle: '以较低功耗播放视频,若异常卡死,请尝试关闭',
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: false,
),
const SetSwitchItem(
title: '免登录1080P',
subTitle: '免登录查看1080P视频',
leading: Icon(Icons.hd_outlined),
setKey: SettingBoxKey.p1080,
defaultVal: true,
),
const SetSwitchItem(
title: 'CDN优化',
subTitle: '使用优质CDN线路',
leading: Icon(Icons.network_check_outlined),
setKey: SettingBoxKey.enableCDN,
defaultVal: true,
),
ListTile(
dense: false,
title: Text('默认画质', style: titleStyle),
leading: const Icon(Icons.video_settings_outlined),
subtitle: Text(
'当前画质:${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
style: subTitleStyle,
@@ -106,6 +117,7 @@ class _VideoSettingState extends State<VideoSetting> {
ListTile(
dense: false,
title: Text('默认音质', style: titleStyle),
leading: const Icon(Icons.audiotrack_outlined),
subtitle: Text(
'当前音质:${AudioQualityCode.fromCode(defaultAudioQa)!.description!}',
style: subTitleStyle,
@@ -132,6 +144,7 @@ class _VideoSettingState extends State<VideoSetting> {
ListTile(
dense: false,
title: Text('首选解码格式', style: titleStyle),
leading: const Icon(Icons.movie_creation_outlined),
subtitle: Text(
'首选解码格式:${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!},请根据设备支持情况与需求调整',
style: subTitleStyle,
@@ -162,6 +175,7 @@ class _VideoSettingState extends State<VideoSetting> {
'非杜比视频次选:${VideoDecodeFormatsCode.fromCode(secondDecode)!.description!},仍无则选择首个提供的解码格式',
style: subTitleStyle,
),
leading: const Icon(Icons.swap_horizontal_circle_outlined),
onTap: () async {
String? result = await showDialog(
context: context,
@@ -184,16 +198,84 @@ class _VideoSettingState extends State<VideoSetting> {
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: '扩大缓冲区',
subTitle: '默认缓冲区为视频5MB/直播32MB开启后为视频32MB/直播64MB但会延长首次加载时间',
leading: Icon(Icons.storage_outlined),
subTitle: '默认缓冲区为视频5MB/直播32MB开启后为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<String>(
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<String>(
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(() {});
}
},
),
],
),
);

View File

@@ -69,7 +69,7 @@ class SettingPage extends StatelessWidget {
() => Visibility(
visible: settingController.hiddenSettingUnlocked.value,
child: ListTile(
leading: const Icon(Icons.developer_mode_outlined),
leading: const Icon(Icons.developer_board_outlined),
onTap: () => Get.toNamed('/hiddenSetting'),
dense: false,
title: const Text('开发人员选项'),

View File

@@ -11,6 +11,7 @@ class SetSwitchItem extends StatefulWidget {
final bool? defaultVal;
final Function? callFn;
final bool? needReboot;
final Widget? leading;
const SetSwitchItem({
this.title,
@@ -19,6 +20,7 @@ class SetSwitchItem extends StatefulWidget {
this.defaultVal,
this.callFn,
this.needReboot,
this.leading,
Key? key,
}) : super(key: key);
@@ -66,6 +68,7 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
subtitle: widget.subTitle != null
? Text(widget.subTitle!, style: subTitleStyle)
: null,
leading: widget.leading,
trailing: Transform.scale(
alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
scale: 0.8,