mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
feat: search settings item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -6,6 +6,7 @@ Widget iconButton({
|
|||||||
required IconData icon,
|
required IconData icon,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
double size = 36,
|
double size = 36,
|
||||||
|
double? iconSize,
|
||||||
Color? bgColor,
|
Color? bgColor,
|
||||||
Color? iconColor,
|
Color? iconColor,
|
||||||
}) {
|
}) {
|
||||||
@@ -17,7 +18,7 @@ Widget iconButton({
|
|||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
icon,
|
icon,
|
||||||
size: size / 2,
|
size: iconSize ?? size / 2,
|
||||||
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
|
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:PiliPalaX/models/github/latest.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/storage.dart';
|
||||||
import 'package:PiliPalaX/utils/utils.dart';
|
import 'package:PiliPalaX/utils/utils.dart';
|
||||||
import '../../utils/cache_manage.dart';
|
import '../../utils/cache_manage.dart';
|
||||||
@@ -437,7 +436,6 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AboutController extends GetxController {
|
class AboutController extends GetxController {
|
||||||
final SettingController settingController = Get.put(SettingController());
|
|
||||||
RxString currentVersion = ''.obs;
|
RxString currentVersion = ''.obs;
|
||||||
RxString remoteVersion = ''.obs;
|
RxString remoteVersion = ''.obs;
|
||||||
LatestDataModel? remoteAppInfo;
|
LatestDataModel? remoteAppInfo;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class MinePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _MinePageState extends State<MinePage> {
|
class _MinePageState extends State<MinePage> {
|
||||||
final MineController mineController = Get.put(MineController())
|
final MineController mineController = Get.put(MineController())
|
||||||
..themeType.value = ThemeType.values[GStorage.themeType];
|
..themeType.value = ThemeType.values[GStorage.themeTypeInt];
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
StreamSubscription? _listener;
|
StreamSubscription? _listener;
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ThemeType.system.obs;
|
|
||||||
dynamic userInfo;
|
|
||||||
Rx<DynamicBadgeMode> 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 mainController = Get.find<MainController>();
|
|
||||||
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<DynamicBadgeMode>(
|
|
||||||
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<int>(
|
|
||||||
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('设置成功,重启生效');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,621 +1,15 @@
|
|||||||
import 'dart:math';
|
import 'package:PiliPalaX/pages/setting/widgets/model.dart';
|
||||||
|
|
||||||
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:flutter/material.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';
|
class ExtraSetting extends StatelessWidget {
|
||||||
import 'controller.dart';
|
|
||||||
import 'widgets/switch_item.dart';
|
|
||||||
|
|
||||||
class ExtraSetting extends StatefulWidget {
|
|
||||||
const ExtraSetting({super.key});
|
const ExtraSetting({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<ExtraSetting> createState() => _ExtraSettingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ExtraSettingState extends State<ExtraSetting> {
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('其它设置')),
|
appBar: AppBar(title: Text('其它设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: extraSettings.map((item) => item.widget).toList(),
|
||||||
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<MainController>().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<MainController>().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<Icon?>(
|
|
||||||
(Set<WidgetState> states) {
|
|
||||||
if (states.isNotEmpty &&
|
|
||||||
states.first == WidgetState.selected) {
|
|
||||||
return const Icon(Icons.done);
|
|
||||||
}
|
|
||||||
return null; // All other states will use the default thumbIcon.
|
|
||||||
}),
|
|
||||||
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<HomeController>().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<int>(
|
|
||||||
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<int>(
|
|
||||||
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<MemberTabType>(
|
|
||||||
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<Icon?>(
|
|
||||||
(Set<WidgetState> states) {
|
|
||||||
if (states.isNotEmpty &&
|
|
||||||
states.first == WidgetState.selected) {
|
|
||||||
return const Icon(Icons.done);
|
|
||||||
}
|
|
||||||
return null; // All other states will use the default thumbIcon.
|
|
||||||
}),
|
|
||||||
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,
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
library setting;
|
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:PiliPalaX/models/common/nav_bar_config.dart';
|
import 'package:PiliPalaX/models/common/nav_bar_config.dart';
|
||||||
import 'package:PiliPalaX/models/common/theme_type.dart';
|
import 'package:PiliPalaX/models/common/theme_type.dart';
|
||||||
import 'package:PiliPalaX/pages/home/index.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:PiliPalaX/pages/setting/widgets/select_dialog.dart';
|
||||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||||
import 'package:flutter/material.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/models/common/color_type.dart';
|
||||||
import 'package:PiliPalaX/utils/storage.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 {
|
class ColorSelectPage extends StatefulWidget {
|
||||||
const ColorSelectPage({super.key});
|
const ColorSelectPage({super.key});
|
||||||
|
|
||||||
@@ -65,7 +38,6 @@ List<Item> generateItems(int count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ColorSelectPageState extends State<ColorSelectPage> {
|
class _ColorSelectPageState extends State<ColorSelectPage> {
|
||||||
final SettingController settingController = Get.put(SettingController());
|
|
||||||
final ColorSelectController ctr = Get.put(ColorSelectController());
|
final ColorSelectController ctr = Get.put(ColorSelectController());
|
||||||
FlexSchemeVariant _dynamicSchemeVariant =
|
FlexSchemeVariant _dynamicSchemeVariant =
|
||||||
FlexSchemeVariant.values[GStorage.schemeVariant];
|
FlexSchemeVariant.values[GStorage.schemeVariant];
|
||||||
@@ -88,14 +60,14 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SelectDialog<ThemeType>(
|
return SelectDialog<ThemeType>(
|
||||||
title: '主题模式',
|
title: '主题模式',
|
||||||
value: settingController.themeType.value,
|
value: ctr.themeType.value,
|
||||||
values: ThemeType.values.map((e) {
|
values: ThemeType.values.map((e) {
|
||||||
return {'title': e.description, 'value': e};
|
return {'title': e.description, 'value': e};
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
settingController.themeType.value = result;
|
ctr.themeType.value = result;
|
||||||
GStorage.setting.put(SettingBoxKey.themeMode, result.index);
|
GStorage.setting.put(SettingBoxKey.themeMode, result.index);
|
||||||
Get.forceAppUpdate();
|
Get.forceAppUpdate();
|
||||||
}
|
}
|
||||||
@@ -106,8 +78,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
child: const Icon(Icons.flashlight_on_outlined),
|
child: const Icon(Icons.flashlight_on_outlined),
|
||||||
),
|
),
|
||||||
title: Text('主题模式', style: titleStyle),
|
title: Text('主题模式', style: titleStyle),
|
||||||
subtitle: Obx(() => Text(
|
subtitle: Obx(() => Text('当前模式:${ctr.themeType.value.description}',
|
||||||
'当前模式:${settingController.themeType.value.description}',
|
|
||||||
style: subTitleStyle)),
|
style: subTitleStyle)),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -285,16 +256,15 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
class ColorSelectController extends GetxController {
|
class ColorSelectController extends GetxController {
|
||||||
RxBool dynamicColor = true.obs;
|
RxBool dynamicColor = true.obs;
|
||||||
RxInt type = 0.obs;
|
RxInt type = 0.obs;
|
||||||
late final List<Map<String, dynamic>> colorThemes;
|
late final List<Map<String, dynamic>> colorThemes = colorThemeTypes;
|
||||||
RxInt currentColor = 0.obs;
|
RxInt currentColor = 0.obs;
|
||||||
RxDouble currentTextScale = 1.0.obs;
|
RxDouble currentTextScale = 1.0.obs;
|
||||||
|
Rx<ThemeType> themeType = GStorage.themeType.obs;
|
||||||
|
|
||||||
Box get setting => GStorage.setting;
|
Box get setting => GStorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
colorThemes = colorThemeTypes;
|
|
||||||
// 默认使用动态取色
|
|
||||||
dynamicColor.value =
|
dynamicColor.value =
|
||||||
setting.get(SettingBoxKey.dynamicColor, defaultValue: true);
|
setting.get(SettingBoxKey.dynamicColor, defaultValue: true);
|
||||||
type.value = dynamicColor.value ? 0 : 1;
|
type.value = dynamicColor.value ? 0 : 1;
|
||||||
|
|||||||
@@ -217,13 +217,12 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
|
||||||
title: const Text('默认倍速'),
|
title: const Text('默认倍速'),
|
||||||
subtitle: Text(playSpeedDefault.toString()),
|
subtitle: Text(playSpeedDefault.toString()),
|
||||||
),
|
),
|
||||||
SetSwitchItem(
|
SetSwitchItem(
|
||||||
title: '动态长按倍速',
|
title: '动态长按倍速',
|
||||||
subTitle: '根据默认倍速长按时自动双倍',
|
subtitle: '根据默认倍速长按时自动双倍',
|
||||||
setKey: SettingBoxKey.enableAutoLongPressSpeed,
|
setKey: SettingBoxKey.enableAutoLongPressSpeed,
|
||||||
defaultVal: enableAutoLongPressSpeed,
|
defaultVal: enableAutoLongPressSpeed,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
@@ -237,7 +236,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
),
|
),
|
||||||
if (!enableAutoLongPressSpeed)
|
if (!enableAutoLongPressSpeed)
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
|
||||||
title: const Text('默认长按倍速'),
|
title: const Text('默认长按倍速'),
|
||||||
subtitle: Text(longPressSpeedDefault.toString()),
|
subtitle: Text(longPressSpeedDefault.toString()),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'package:PiliPalaX/pages/setting/widgets/model.dart';
|
||||||
|
|
||||||
import 'package:flutter/material.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/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 {
|
class PlaySetting extends StatefulWidget {
|
||||||
const PlaySetting({super.key});
|
const PlaySetting({super.key});
|
||||||
@@ -20,23 +10,6 @@ class PlaySetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PlaySettingState extends State<PlaySetting> {
|
class _PlaySettingState extends State<PlaySetting> {
|
||||||
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -47,210 +20,10 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('播放器设置')),
|
appBar: AppBar(title: Text('播放器设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: playSettings.map((item) => item.widget).toList(),
|
||||||
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<String>(
|
|
||||||
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<int>(
|
|
||||||
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<int>(
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,15 @@
|
|||||||
|
import 'package:PiliPalaX/pages/setting/widgets/model.dart';
|
||||||
import 'package:flutter/material.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';
|
class PrivacySetting extends StatelessWidget {
|
||||||
import '../mine/controller.dart';
|
|
||||||
|
|
||||||
class PrivacySetting extends StatefulWidget {
|
|
||||||
const PrivacySetting({super.key});
|
const PrivacySetting({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<PrivacySetting> createState() => _PrivacySettingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PrivacySettingState extends State<PrivacySetting> {
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('隐私设置')),
|
appBar: AppBar(title: Text('隐私设置')),
|
||||||
body: Column(
|
body: ListView(
|
||||||
children: [
|
children: privacySettings.map((item) => item.widget).toList(),
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/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 StatelessWidget {
|
||||||
|
|
||||||
class RecommendSetting extends StatefulWidget {
|
|
||||||
const RecommendSetting({super.key});
|
const RecommendSetting({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<RecommendSetting> createState() => _RecommendSettingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecommendSettingState extends State<RecommendSetting> {
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('推荐流设置')),
|
appBar: AppBar(title: Text('推荐流设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
...recommendSettings.map((item) => item.widget),
|
||||||
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<String>(
|
|
||||||
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<int>(
|
|
||||||
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: <Widget>[
|
|
||||||
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<int> defDurations = [0, 30, 60, 90, 120];
|
|
||||||
int? result = await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return SelectDialog<int>(
|
|
||||||
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<int>(
|
|
||||||
// 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},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
|
|||||||
86
lib/pages/setting/search_page.dart
Normal file
86
lib/pages/setting/search_page.dart
Normal file
@@ -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<SettingsSearchPage> createState() => _SettingsSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsSearchPageState extends State<SettingsSearchPage> {
|
||||||
|
final _textEditingController = TextEditingController();
|
||||||
|
final RxList<SettingsModel> _list = <SettingsModel>[].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 = <SettingsModel>[];
|
||||||
|
} 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 = <SettingsModel>[];
|
||||||
|
} 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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,426 +1,16 @@
|
|||||||
import 'dart:io';
|
import 'package:PiliPalaX/pages/setting/widgets/model.dart';
|
||||||
|
|
||||||
import 'package:auto_orientation/auto_orientation.dart';
|
|
||||||
import 'package:flutter/material.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';
|
class StyleSetting extends StatelessWidget {
|
||||||
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 {
|
|
||||||
const StyleSetting({super.key});
|
const StyleSetting({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<StyleSetting> createState() => _StyleSettingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StyleSettingState extends State<StyleSetting> {
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('外观设置')),
|
appBar: AppBar(title: Text('外观设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: styleSettings.map((item) => item.widget).toList(),
|
||||||
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<double>(
|
|
||||||
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<UpPanelPosition>(
|
|
||||||
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<double>(
|
|
||||||
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<ThemeType>(
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showQualityDialog({
|
|
||||||
required String title,
|
|
||||||
required int initValue,
|
|
||||||
required ValueChanged<int> 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('确定'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,412 +1,15 @@
|
|||||||
import 'dart:io';
|
import 'package:PiliPalaX/pages/setting/widgets/model.dart';
|
||||||
|
|
||||||
import 'package:flutter/material.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 StatelessWidget {
|
||||||
|
|
||||||
class VideoSetting extends StatefulWidget {
|
|
||||||
const VideoSetting({super.key});
|
const VideoSetting({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<VideoSetting> createState() => _VideoSettingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _VideoSettingState extends State<VideoSetting> {
|
|
||||||
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('音视频设置')),
|
appBar: AppBar(title: Text('音视频设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: videoSettings.map((item) => item.widget).toList(),
|
||||||
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<Icon?>(
|
|
||||||
(Set<WidgetState> 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<String>(
|
|
||||||
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<int>(
|
|
||||||
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<int>(
|
|
||||||
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<int>(
|
|
||||||
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<int>(
|
|
||||||
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<String>(
|
|
||||||
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<String>(
|
|
||||||
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<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(() {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPalaX/pages/setting/index.dart';
|
|
||||||
|
import '../../http/init.dart';
|
||||||
|
|
||||||
class SettingPage extends StatelessWidget {
|
class SettingPage extends StatelessWidget {
|
||||||
const SettingPage({super.key});
|
const SettingPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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 titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||||
final TextStyle subTitleStyle = Theme.of(context)
|
final TextStyle subTitleStyle = Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@@ -17,16 +23,40 @@ class SettingPage extends StatelessWidget {
|
|||||||
appBar: AppBar(title: Text('设置')),
|
appBar: AppBar(title: Text('设置')),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
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(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/privacySetting'),
|
onTap: () => Get.toNamed('/privacySetting'),
|
||||||
dense: false,
|
|
||||||
leading: const Icon(Icons.privacy_tip_outlined),
|
leading: const Icon(Icons.privacy_tip_outlined),
|
||||||
title: Text('隐私设置', style: titleStyle),
|
title: Text('隐私设置', style: titleStyle),
|
||||||
subtitle: Text('黑名单、access_key刷新、无痕模式', style: subTitleStyle),
|
subtitle: Text('黑名单、access_key刷新、无痕模式', style: subTitleStyle),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/recommendSetting'),
|
onTap: () => Get.toNamed('/recommendSetting'),
|
||||||
dense: false,
|
|
||||||
leading: const Icon(Icons.explore_outlined),
|
leading: const Icon(Icons.explore_outlined),
|
||||||
title: Text('推荐流设置', style: titleStyle),
|
title: Text('推荐流设置', style: titleStyle),
|
||||||
subtitle: Text('推荐来源(web/app)、刷新保留内容、过滤器', style: subTitleStyle),
|
subtitle: Text('推荐来源(web/app)、刷新保留内容、过滤器', style: subTitleStyle),
|
||||||
@@ -34,21 +64,18 @@ class SettingPage extends StatelessWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/videoSetting'),
|
onTap: () => Get.toNamed('/videoSetting'),
|
||||||
leading: const Icon(Icons.video_settings_outlined),
|
leading: const Icon(Icons.video_settings_outlined),
|
||||||
dense: false,
|
|
||||||
title: Text('音视频设置', style: titleStyle),
|
title: Text('音视频设置', style: titleStyle),
|
||||||
subtitle: Text('画质、音质、解码、缓冲、音频输出等', style: subTitleStyle),
|
subtitle: Text('画质、音质、解码、缓冲、音频输出等', style: subTitleStyle),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/playSetting'),
|
onTap: () => Get.toNamed('/playSetting'),
|
||||||
leading: const Icon(Icons.touch_app_outlined),
|
leading: const Icon(Icons.touch_app_outlined),
|
||||||
dense: false,
|
|
||||||
title: Text('播放器设置', style: titleStyle),
|
title: Text('播放器设置', style: titleStyle),
|
||||||
subtitle: Text('双击/长按、全屏、后台播放、弹幕、字幕、底部进度条等', style: subTitleStyle),
|
subtitle: Text('双击/长按、全屏、后台播放、弹幕、字幕、底部进度条等', style: subTitleStyle),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/styleSetting'),
|
onTap: () => Get.toNamed('/styleSetting'),
|
||||||
leading: const Icon(Icons.style_outlined),
|
leading: const Icon(Icons.style_outlined),
|
||||||
dense: false,
|
|
||||||
title: Text('外观设置', style: titleStyle),
|
title: Text('外观设置', style: titleStyle),
|
||||||
subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等',
|
subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等',
|
||||||
style: subTitleStyle),
|
style: subTitleStyle),
|
||||||
@@ -56,36 +83,66 @@ class SettingPage extends StatelessWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/extraSetting'),
|
onTap: () => Get.toNamed('/extraSetting'),
|
||||||
leading: const Icon(Icons.extension_outlined),
|
leading: const Icon(Icons.extension_outlined),
|
||||||
dense: false,
|
|
||||||
title: Text('其它设置', style: titleStyle),
|
title: Text('其它设置', style: titleStyle),
|
||||||
subtitle: Text('震动、搜索、收藏、ai、评论、动态、代理、更新检查等', style: subTitleStyle),
|
subtitle: Text('震动、搜索、收藏、ai、评论、动态、代理、更新检查等', style: subTitleStyle),
|
||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => Visibility(
|
() => isLogin.value.not
|
||||||
visible: settingController.hiddenSettingUnlocked.value,
|
? const SizedBox.shrink()
|
||||||
child: ListTile(
|
: ListTile(
|
||||||
leading: const Icon(Icons.developer_board_outlined),
|
leading: const Icon(Icons.logout_outlined),
|
||||||
onTap: () => Get.toNamed('/hiddenSetting'),
|
onTap: () {
|
||||||
dense: false,
|
showDialog(
|
||||||
title: Text('开发人员选项', style: titleStyle),
|
context: context,
|
||||||
),
|
builder: (context) {
|
||||||
),
|
return AlertDialog(
|
||||||
),
|
title: const Text('提示'),
|
||||||
Obx(
|
content: const Text('确认要退出登录吗'),
|
||||||
() => Visibility(
|
actions: [
|
||||||
visible: settingController.userLogin.value,
|
TextButton(
|
||||||
child: ListTile(
|
onPressed: Get.back,
|
||||||
leading: const Icon(Icons.logout_outlined),
|
child: Text(
|
||||||
onTap: () => settingController.loginOut(context),
|
'点错了',
|
||||||
dense: false,
|
style: TextStyle(
|
||||||
title: Text('退出登录', style: titleStyle),
|
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 mainController =
|
||||||
|
Get.find<MainController>();
|
||||||
|
mainController.userLogin.value = false;
|
||||||
|
}
|
||||||
|
await LoginUtils.refreshLoginStatus(false);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
title: Text('退出登录', style: titleStyle),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.info_outline),
|
leading: const Icon(Icons.info_outline),
|
||||||
onTap: () => Get.toNamed('/about'),
|
onTap: () => Get.toNamed('/about'),
|
||||||
dense: false,
|
|
||||||
title: Text('关于', style: titleStyle),
|
title: Text('关于', style: titleStyle),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
1808
lib/pages/setting/widgets/model.dart
Normal file
1808
lib/pages/setting/widgets/model.dart
Normal file
File diff suppressed because it is too large
Load Diff
62
lib/pages/setting/widgets/normal_item.dart
Normal file
62
lib/pages/setting/widgets/normal_item.dart
Normal file
@@ -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<bool>? 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<NormalItem> createState() => _NormalItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NormalItemState extends State<NormalItem> {
|
||||||
|
@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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,7 +97,6 @@ class _SetSelectItemState extends State<SetSelectItem> {
|
|||||||
.copyWith(color: Theme.of(context).colorScheme.outline);
|
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
dense: false,
|
|
||||||
title: Text(widget.title!),
|
title: Text(widget.title!),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'当前${widget.title!} $currentVal',
|
'当前${widget.title!} $currentVal',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SlideDialog<T extends num> extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SlideDialogState<T> createState() => _SlideDialogState<T>();
|
State<SlideDialog<T>> createState() => _SlideDialogState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
|
class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
|
||||||
@@ -35,7 +35,10 @@ class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(widget.title),
|
title: Text(
|
||||||
|
widget.title,
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.only(top: 20, left: 8, right: 8, bottom: 8),
|
const EdgeInsets.only(top: 20, left: 8, right: 8, bottom: 8),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import 'package:get/get.dart';
|
|||||||
|
|
||||||
class SetSwitchItem extends StatefulWidget {
|
class SetSwitchItem extends StatefulWidget {
|
||||||
final String? title;
|
final String? title;
|
||||||
final String? subTitle;
|
final String? subtitle;
|
||||||
final String? setKey;
|
final String? setKey;
|
||||||
final bool? defaultVal;
|
final bool? defaultVal;
|
||||||
final ValueChanged<bool>? onChanged;
|
final ValueChanged<bool>? onChanged;
|
||||||
final bool? needReboot;
|
final bool? needReboot;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final GestureTapCallback? onTap;
|
final Function? onTap;
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
final TextStyle? titleStyle;
|
final TextStyle? titleStyle;
|
||||||
|
|
||||||
const SetSwitchItem({
|
const SetSwitchItem({
|
||||||
this.title,
|
this.title,
|
||||||
this.subTitle,
|
this.subtitle,
|
||||||
this.setKey,
|
this.setKey,
|
||||||
this.defaultVal,
|
this.defaultVal,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
@@ -113,8 +113,8 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
onTap: () =>
|
onTap: () =>
|
||||||
widget.onTap != null ? widget.onTap?.call() : switchChange(null),
|
widget.onTap != null ? widget.onTap?.call() : switchChange(null),
|
||||||
title: Text(widget.title!, style: titleStyle),
|
title: Text(widget.title!, style: titleStyle),
|
||||||
subtitle: widget.subTitle != null
|
subtitle: widget.subtitle != null
|
||||||
? Text(widget.subTitle!, style: subTitleStyle)
|
? Text(widget.subtitle!, style: subTitleStyle)
|
||||||
: null,
|
: null,
|
||||||
leading: widget.leading,
|
leading: widget.leading,
|
||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:PiliPalaX/pages/member/new/member_page.dart';
|
import 'package:PiliPalaX/pages/member/new/member_page.dart';
|
||||||
import 'package:PiliPalaX/pages/member/new/widget/edit_profile_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/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/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/video/detail/introduction/widgets/create_fav_page.dart';
|
||||||
import 'package:PiliPalaX/pages/webview/webview_page.dart';
|
import 'package:PiliPalaX/pages/webview/webview_page.dart';
|
||||||
import 'package:flutter/material.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/index.dart';
|
||||||
import '../pages/search_result/index.dart';
|
import '../pages/search_result/index.dart';
|
||||||
import '../pages/setting/extra_setting.dart';
|
import '../pages/setting/extra_setting.dart';
|
||||||
import '../pages/setting/index.dart';
|
|
||||||
import '../pages/setting/pages/color_select.dart';
|
import '../pages/setting/pages/color_select.dart';
|
||||||
import '../pages/setting/pages/display_mode.dart';
|
import '../pages/setting/pages/display_mode.dart';
|
||||||
import '../pages/setting/pages/font_size_select.dart';
|
import '../pages/setting/pages/font_size_select.dart';
|
||||||
@@ -174,6 +175,8 @@ class Routes {
|
|||||||
// navigation bar
|
// navigation bar
|
||||||
CustomGetPage(
|
CustomGetPage(
|
||||||
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
|
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/settingsSearch', page: () => const SettingsSearchPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:PiliPalaX/common/widgets/pair.dart';
|
import 'package:PiliPalaX/common/widgets/pair.dart';
|
||||||
import 'package:PiliPalaX/http/constants.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/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/member/new/controller.dart' show MemberTabType;
|
||||||
import 'package:PiliPalaX/pages/video/detail/controller.dart'
|
import 'package:PiliPalaX/pages/video/detail/controller.dart'
|
||||||
show SegmentType, SegmentTypeExt, SkipType;
|
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:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -66,6 +73,120 @@ class GStorage {
|
|||||||
.toList();
|
.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 =>
|
static double get blockLimit =>
|
||||||
setting.get(SettingBoxKey.blockLimit, defaultValue: 0.0);
|
setting.get(SettingBoxKey.blockLimit, defaultValue: 0.0);
|
||||||
|
|
||||||
@@ -183,11 +304,11 @@ class GStorage {
|
|||||||
static MemberTabType get memberTab => MemberTabType
|
static MemberTabType get memberTab => MemberTabType
|
||||||
.values[setting.get(SettingBoxKey.memberTab, defaultValue: 0)];
|
.values[setting.get(SettingBoxKey.memberTab, defaultValue: 0)];
|
||||||
|
|
||||||
static int get themeType =>
|
static int get themeTypeInt =>
|
||||||
setting.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code);
|
setting.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code);
|
||||||
|
|
||||||
static ThemeMode get themeMode {
|
static ThemeMode get themeMode {
|
||||||
return switch (themeType) {
|
return switch (themeTypeInt) {
|
||||||
0 => ThemeMode.light,
|
0 => ThemeMode.light,
|
||||||
1 => ThemeMode.dark,
|
1 => ThemeMode.dark,
|
||||||
_ => ThemeMode.system
|
_ => ThemeMode.system
|
||||||
|
|||||||
Reference in New Issue
Block a user