import 'dart:math' as math; import 'package:PiliPlus/common/widgets/color_palette.dart'; import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/common/msg/msg_unread_type.dart'; import 'package:PiliPlus/models/common/theme/theme_color_type.dart'; import 'package:PiliPlus/models/common/theme/theme_type.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/widgets/dual_slider_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/multi_select_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/slider_dialog.dart'; import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:flutter/material.dart' hide StatefulBuilder; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; List get styleSettings => [ SwitchModel( title: '横屏适配', subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】', leading: const Icon(Icons.phonelink_outlined), setKey: SettingBoxKey.horizontalScreen, defaultVal: Pref.horizontalScreen, onChanged: (value) { if (value) { fullMode(); } else { portraitUpMode(); } }, ), NormalModel( title: '页面过渡动画', leading: const Icon(Icons.animation), getSubtitle: () => '当前:${Pref.pageTransition.name}', onTap: _showTransitionDialog, ), const SwitchModel( title: '悬浮底栏', leading: Icon(MdiIcons.soundbar), setKey: SettingBoxKey.floatingNavBar, defaultVal: false, needReboot: true, ), NormalModel( leading: const Icon(Icons.calendar_view_week_outlined), title: '列表宽度(dp)限制', getSubtitle: () => '当前: 主页${Pref.recommendCardWidth.toInt()}dp 其他${Pref.smallCardWidth.toInt()}dp,屏幕宽度:${MediaQuery.widthOf(Get.context!).toPrecision(2)}dp。宽度越小列数越多。', onTap: _showCardWidthDialog, ), SwitchModel( title: '动态页启用瀑布流', subtitle: '关闭会显示为单列', leading: const Icon(Icons.view_array_outlined), setKey: SettingBoxKey.dynamicsWaterfallFlow, defaultVal: Pref.horizontalScreen, needReboot: true, ), NormalModel( title: '动态页UP主显示位置', leading: const Icon(Icons.person_outlined), getSubtitle: () => '当前:${Pref.upPanelPosition.label}', onTap: _showUpPosDialog, ), NormalModel( title: '动态未读标记', leading: const Icon(Icons.motion_photos_on_outlined), getSubtitle: () => '当前标记样式:${Pref.dynamicBadgeType.desc}', onTap: _showDynBadgeDialog, ), NormalModel( title: '消息未读标记', leading: const Icon(MdiIcons.bellBadgeOutline), getSubtitle: () => '当前标记样式:${Pref.msgBadgeMode.desc}', onTap: _showMsgBadgeDialog, ), NormalModel( onTap: _showMsgUnReadDialog, title: '消息未读类型', leading: const Icon(MdiIcons.bellCogOutline), getSubtitle: () => '当前消息类型:${Pref.msgUnReadTypeV2.map((item) => item.title).join('、')}', ), NormalModel( onTap: (context, setState) => _showQualityDialog( context: context, title: '图片质量', initValue: Pref.picQuality, onChanged: (picQuality) async { GlobalData().imgQuality = picQuality; await GStorage.setting.put(SettingBoxKey.defaultPicQa, picQuality); setState(); }, ), title: '图片质量', subtitle: '选择合适的图片清晰度,上限100%', leading: const Icon(Icons.image_outlined), getTrailing: (theme) => Text( '${Pref.picQuality}%', style: theme.textTheme.titleSmall, ), ), NormalModel( onTap: (context, setState) => _showQualityDialog( context: context, title: '查看大图质量', initValue: Pref.previewQ, onChanged: (picQuality) async { await GStorage.setting.put(SettingBoxKey.previewQuality, picQuality); setState(); }, ), title: '查看大图质量', subtitle: '选择合适的图片清晰度,上限100%', leading: const Icon(Icons.image_outlined), getTrailing: (theme) => Text( '${Pref.previewQ}%', style: theme.textTheme.titleSmall, ), ), NormalModel( onTap: _showThemeTypeDialog, leading: const Icon(Icons.flashlight_on_outlined), title: '主题模式', getSubtitle: () => '当前模式:${Pref.themeType.desc}', ), NormalModel( onTap: (context, setState) => Get.toNamed('/colorSetting'), leading: const Icon(Icons.color_lens_outlined), title: '应用主题', getSubtitle: () => '当前主题:${Pref.dynamicColor ? '动态取色' : '指定颜色'}', getTrailing: (theme) => Pref.dynamicColor ? Icon(Icons.color_lens_rounded, color: theme.colorScheme.primary) : SizedBox.square( dimension: 20, child: ColorPalette( colorScheme: colorThemeTypes[Pref.customColor].color .asColorSchemeSeed(Pref.schemeVariant, theme.brightness), selected: false, showBgColor: false, ), ), ), const NormalModel( title: '滑动动画弹簧参数', leading: Icon(Icons.chrome_reader_mode_outlined), onTap: _showSpringDialog, ), ]; void _showQualityDialog({ required BuildContext context, required String title, required int initValue, required ValueChanged onChanged, }) { showDialog( context: context, builder: (context) => SliderDialog( value: initValue.toDouble(), title: title, min: 10, max: 100, divisions: 9, suffix: '%', precise: 0, ), ).then((result) { if (result != null) { SmartDialog.showToast('设置成功'); onChanged(result.toInt()); } }); } void _showSpringDialog(BuildContext context, _) { final List springDescription = Pref.springDescription .map((i) => i.toString()) .toList(growable: false); bool physicalMode = true; void physical2Duration() { final mass = double.parse(springDescription[0]); final stiffness = double.parse(springDescription[1]); final damping = double.parse(springDescription[2]); final duration = math.sqrt(4 * math.pi * math.pi * mass / stiffness); final dampingRatio = damping / (2.0 * math.sqrt(mass * stiffness)); final bounce = dampingRatio < 1.0 ? 1.0 - dampingRatio : 1.0 / dampingRatio - 1; springDescription[0] = duration.toString(); springDescription[1] = bounce.toString(); } /// from [SpringDescription.withDurationAndBounce] but with higher precision void duration2Physical() { final duration = double.parse(springDescription[0]); final bounce = double.parse(springDescription[1]).clamp(-1.0, 1.0); final stiffness = 4 * math.pi * math.pi / math.pow(duration, 2); final dampingRatio = bounce > 0 ? 1.0 - bounce : 1.0 / (bounce + 1); final damping = 2 * math.sqrt(stiffness) * dampingRatio; springDescription[0] = '1'; springDescription[1] = stiffness.toString(); springDescription[2] = damping.toString(); } showDialog( context: context, builder: (context) => AlertDialog( title: Row( mainAxisAlignment: .spaceBetween, children: [ const Text('弹簧参数'), TextButton( style: TextButton.styleFrom( visualDensity: .compact, tapTargetSize: .shrinkWrap, ), onPressed: () { try { if (physicalMode) { physical2Duration(); } else { duration2Physical(); } physicalMode = !physicalMode; (context as Element).markNeedsBuild(); } catch (e) { SmartDialog.showToast(e.toString()); } }, child: Text(physicalMode ? '滑动时间' : '物理参数'), ), ], ), content: Column( key: ValueKey(physicalMode), mainAxisSize: .min, children: List.generate( physicalMode ? 3 : 2, (index) => TextFormField( autofocus: index == 0, initialValue: springDescription[index], keyboardType: .numberWithOptions( signed: !physicalMode && index == 1, decimal: true, ), onChanged: (value) => springDescription[index] = value, inputFormatters: [ !physicalMode && index == 1 ? FilteringTextInputFormatter.allow(RegExp(r'[-\d\.]+')) : FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')), ], decoration: InputDecoration( labelText: (physicalMode ? const ['mass', 'stiffness', 'damping'] : const ['duration', 'bounce'])[index], suffixText: !physicalMode && index == 0 ? 's' : null, ), ), ), ), actions: [ TextButton( onPressed: () { Get.back(); GStorage.setting.delete(SettingBoxKey.springDescription); SmartDialog.showToast('重置成功,重启生效'); }, child: const Text('重置'), ), TextButton( onPressed: Get.back, child: Text( '取消', style: TextStyle(color: ColorScheme.of(context).outline), ), ), TextButton( onPressed: () { try { if (!physicalMode) { duration2Physical(); } final res = springDescription.map(double.parse).toList(); Get.back(); GStorage.setting.put(SettingBoxKey.springDescription, res); SmartDialog.showToast('设置成功,重启生效'); } catch (e) { SmartDialog.showToast(e.toString()); } }, child: const Text('确定'), ), ], ), ); } Future _showTransitionDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '页面过渡动画', value: Pref.pageTransition, values: Transition.values.map((e) => (e, e.name)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.pageTransition, res.index); SmartDialog.showToast('重启生效'); setState(); } } Future _showCardWidthDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog<(double, double)>( context: context, builder: (context) => DualSliderDialog( title: '列表最大列宽度(默认240dp)', value1: Pref.recommendCardWidth, value2: Pref.smallCardWidth, description1: '主页推荐流', description2: '其他', min: 150.0, max: 500.0, divisions: 35, suffix: 'dp', ), ); if (res != null) { await GStorage.setting.putAll({ SettingBoxKey.recommendCardWidth: res.$1, SettingBoxKey.smallCardWidth: res.$2, }); SmartDialog.showToast('重启生效'); setState(); } } Future _showUpPosDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '动态页UP主显示位置', value: Pref.upPanelPosition, values: UpPanelPosition.values.map((e) => (e, e.label)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.upPanelPosition, res.index); SmartDialog.showToast('重启生效'); setState(); } } Future _showDynBadgeDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '动态未读标记', value: Pref.dynamicBadgeType, values: DynamicBadgeMode.values.map((e) => (e, e.desc)).toList(), ), ); if (res != null) { final mainController = Get.find() ..dynamicBadgeMode = DynamicBadgeMode.values[res.index]; if (mainController.dynamicBadgeMode != DynamicBadgeMode.hidden) { mainController.getUnreadDynamic(); } await GStorage.setting.put( SettingBoxKey.dynamicBadgeMode, res.index, ); SmartDialog.showToast('设置成功'); setState(); } } Future _showMsgBadgeDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '消息未读标记', value: Pref.msgBadgeMode, values: DynamicBadgeMode.values.map((e) => (e, e.desc)).toList(), ), ); if (res != null) { final mainController = Get.find() ..msgBadgeMode = DynamicBadgeMode.values[res.index]; if (mainController.msgBadgeMode != DynamicBadgeMode.hidden) { mainController.queryUnreadMsg(true); } else { mainController.msgUnReadCount.value = ''; } await GStorage.setting.put(SettingBoxKey.msgBadgeMode, res.index); SmartDialog.showToast('设置成功'); setState(); } } Future _showMsgUnReadDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog>( context: context, builder: (context) => MultiSelectDialog( title: '消息未读类型', initValues: Pref.msgUnReadTypeV2, values: {for (final i in MsgUnReadType.values) i: i.title}, ), ); if (res != null) { final mainController = Get.find()..msgUnReadTypes = res; if (mainController.msgBadgeMode != DynamicBadgeMode.hidden) { mainController.queryUnreadMsg(); } await GStorage.setting.put( SettingBoxKey.msgUnReadTypeV2, res.map((item) => item.index).toList()..sort(), ); SmartDialog.showToast('设置成功'); setState(); } } Future _showThemeTypeDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '主题模式', value: Pref.themeType, values: ThemeType.values.map((e) => (e, e.desc)).toList(), ), ); if (res != null) { try { Get.find().themeType.value = res; } catch (_) {} GStorage.setting.put(SettingBoxKey.themeMode, res.index); Get.changeThemeMode(ThemeUtils.themeMode = res.toThemeMode); setState(); } }