Files
PiliPlus/lib/pages/setting/models/style_settings.dart
dom 07843a5e77 drop
Signed-off-by: dom <githubaccount56556@proton.me>
2026-05-08 21:27:01 +08:00

484 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<SettingsModel> 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<int> onChanged,
}) {
showDialog<double>(
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<String> 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<void> _showTransitionDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<Transition>(
context: context,
builder: (context) => SelectDialog<Transition>(
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<void> _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<void> _showUpPosDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<UpPanelPosition>(
context: context,
builder: (context) => SelectDialog<UpPanelPosition>(
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<void> _showDynBadgeDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<DynamicBadgeMode>(
context: context,
builder: (context) => SelectDialog<DynamicBadgeMode>(
title: '动态未读标记',
value: Pref.dynamicBadgeType,
values: DynamicBadgeMode.values.map((e) => (e, e.desc)).toList(),
),
);
if (res != null) {
final mainController = Get.find<MainController>()
..dynamicBadgeMode = DynamicBadgeMode.values[res.index];
if (mainController.dynamicBadgeMode != DynamicBadgeMode.hidden) {
mainController.getUnreadDynamic();
}
await GStorage.setting.put(
SettingBoxKey.dynamicBadgeMode,
res.index,
);
SmartDialog.showToast('设置成功');
setState();
}
}
Future<void> _showMsgBadgeDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<DynamicBadgeMode>(
context: context,
builder: (context) => SelectDialog<DynamicBadgeMode>(
title: '消息未读标记',
value: Pref.msgBadgeMode,
values: DynamicBadgeMode.values.map((e) => (e, e.desc)).toList(),
),
);
if (res != null) {
final mainController = Get.find<MainController>()
..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<void> _showMsgUnReadDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<Set<MsgUnReadType>>(
context: context,
builder: (context) => MultiSelectDialog<MsgUnReadType>(
title: '消息未读类型',
initValues: Pref.msgUnReadTypeV2,
values: {for (final i in MsgUnReadType.values) i: i.title},
),
);
if (res != null) {
final mainController = Get.find<MainController>()..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<void> _showThemeTypeDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<ThemeType>(
context: context,
builder: (context) => SelectDialog<ThemeType>(
title: '主题模式',
value: Pref.themeType,
values: ThemeType.values.map((e) => (e, e.desc)).toList(),
),
);
if (res != null) {
try {
Get.find<MineController>().themeType.value = res;
} catch (_) {}
GStorage.setting.put(SettingBoxKey.themeMode, res.index);
Get.changeThemeMode(ThemeUtils.themeMode = res.toThemeMode);
setState();
}
}