opt settings

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-02-05 18:11:02 +08:00
parent cfa925549e
commit 1ad710c1cf
8 changed files with 242 additions and 226 deletions

View File

@@ -84,12 +84,15 @@ List<SettingsModel> get extraSettings => [
],
),
),
getPopupMenuModel(
PopupModel<SkipType>(
title: '番剧片头/片尾跳过类型',
leading: const Icon(MdiIcons.debugStepOver),
key: SettingBoxKey.pgcSkipType,
values: SkipType.values,
defaultIndex: SkipType.skipOnce.index,
value: () => Pref.pgcSkipType,
items: SkipType.values,
onSelected: (value, setState) async {
await GStorage.setting.put(SettingBoxKey.pgcSkipType, value.index);
setState();
},
),
SwitchModel(
title: '检查未读动态',
@@ -295,7 +298,7 @@ List<SettingsModel> get extraSettings => [
title: '超分辨率',
leading: const Icon(Icons.stay_current_landscape_outlined),
getSubtitle: () =>
'当前:「${Pref.superResolutionType.title}\n默认设置对番剧生效, 其他视频默认关闭\n超分辨率需要启用硬件解码, 若启用硬件解码后仍然不生效, 尝试切换硬件解码器为 auto-copy',
'当前:「${Pref.superResolutionType.label}\n默认设置对番剧生效, 其他视频默认关闭\n超分辨率需要启用硬件解码, 若启用硬件解码后仍然不生效, 尝试切换硬件解码器为 auto-copy',
onTap: _showSuperResolutionDialog,
),
const SwitchModel(
@@ -964,7 +967,6 @@ Future<void> _showRefreshDragDialog(
kDragContainerExtentPercentage = res;
await GStorage.setting.put(SettingBoxKey.refreshDragPercentage, res);
Get.forceAppUpdate();
setState();
}
}
@@ -986,7 +988,6 @@ Future<void> _showRefreshDialog(
displacement = res;
await GStorage.setting.put(SettingBoxKey.refreshDisplacement, res);
Get.forceAppUpdate();
setState();
}
}
@@ -999,7 +1000,7 @@ Future<void> _showSuperResolutionDialog(
builder: (context) => SelectDialog<SuperResolutionType>(
title: '超分辨率',
value: Pref.superResolutionType,
values: SuperResolutionType.values.map((e) => (e, e.title)).toList(),
values: SuperResolutionType.values.map((e) => (e, e.label)).toList(),
),
);
if (res != null) {

View File

@@ -1,14 +1,14 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/pages/setting/widgets/normal_item.dart';
import 'package:PiliPlus/pages/setting/widgets/popup_item.dart';
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
import 'package:PiliPlus/pages/setting/widgets/switch_item.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide PopupMenuItemSelected;
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
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';
@immutable
sealed class SettingsModel {
@@ -30,6 +30,45 @@ sealed class SettingsModel {
});
}
class PopupModel<T extends EnumWithLabel> extends SettingsModel {
const PopupModel({
required this.title,
super.subtitle,
super.leading,
super.contentPadding,
super.titleStyle,
required this.value,
required this.items,
required this.onSelected,
});
@override
String? get effectiveSubtitle => null;
@override
String get effectiveTitle => title;
@override
final String title;
final ValueGetter<T> value;
final List<T> items;
final PopupMenuItemSelected<T> onSelected;
@override
Widget get widget => PopupListTile<T>(
safeArea: false,
leading: leading,
title: Text(title),
value: () {
final v = value();
return (v, v.label);
},
itemBuilder: (_) => enumItemBuilder(items),
onSelected: onSelected,
);
}
class NormalModel extends SettingsModel {
@override
final String? title;
@@ -254,61 +293,3 @@ SettingsModel getVideoFilterSelectModel({
},
);
}
SettingsModel getPopupMenuModel({
required String title,
Widget? leading,
String? subtitle,
required String key,
required List<EnumWithLabel> values,
int defaultIndex = 0,
}) {
// final globalKey = GlobalKey<PopupMenuButtonState<EnumWithLabel>>();
return NormalModel(
title: title,
subtitle: subtitle,
leading: leading,
// onTap: (context, setState) => globalKey.currentState?.showButtonMenu(),
getTrailing: (theme) => Builder(
builder: (context) {
final color = theme.colorScheme.secondary;
final v = values[GStorage.setting.get(key, defaultValue: defaultIndex)];
return PopupMenuButton(
// key: globalKey,
padding: .zero,
initialValue: v,
onSelected: (value) async {
await GStorage.setting.put(key, value.index);
if (context.mounted) {
(context as Element).markNeedsBuild();
}
},
itemBuilder: (context) => values
.map((i) => PopupMenuItem(value: i, child: Text(i.label)))
.toList(),
child: Padding(
padding: const .symmetric(vertical: 8),
child: Text.rich(
style: TextStyle(fontSize: 14, height: 1, color: color),
strutStyle: const StrutStyle(leading: 0, height: 1, fontSize: 14),
TextSpan(
children: [
TextSpan(text: v.label),
WidgetSpan(
alignment: .middle,
child: Icon(
size: 14,
MdiIcons.unfoldMoreHorizontal,
color: color,
),
),
],
style: TextStyle(color: color),
),
),
),
);
},
),
);
}

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
import 'package:PiliPlus/models/common/theme/theme_type.dart';
import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/pages/setting/widgets/popup_item.dart';
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -76,11 +77,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
Get.changeThemeMode(result.toThemeMode);
}
},
leading: Container(
width: 40,
alignment: Alignment.center,
child: const Icon(Icons.flashlight_on_outlined),
),
leading: const Icon(Icons.flashlight_on_outlined),
title: Text('主题模式', style: titleStyle),
subtitle: Obx(
() => Text(
@@ -90,94 +87,57 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
),
),
Obx(
() => ListTile(
() => PopupListTile<FlexSchemeVariant>(
enabled: !ctr.dynamicColor.value,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('调色板风格'),
PopupMenuButton(
enabled: !ctr.dynamicColor.value,
initialValue: _dynamicSchemeVariant,
onSelected: (item) {
_dynamicSchemeVariant = item;
GStorage.setting.put(
SettingBoxKey.schemeVariant,
item.index,
);
Get.forceAppUpdate();
},
itemBuilder: (context) => FlexSchemeVariant.values
.map(
(item) => PopupMenuItem<FlexSchemeVariant>(
value: item,
child: Text(item.variantName),
),
)
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_dynamicSchemeVariant.variantName,
style: TextStyle(
height: 1,
fontSize: 13,
color: ctr.dynamicColor.value
? theme.colorScheme.outline.withValues(
alpha: 0.8,
)
: theme.colorScheme.secondary,
),
strutStyle: const StrutStyle(leading: 0, height: 1),
),
Icon(
size: 20,
Icons.keyboard_arrow_right,
color: ctr.dynamicColor.value
? theme.colorScheme.outline.withValues(
alpha: 0.8,
)
: theme.colorScheme.secondary,
),
],
),
),
],
),
leading: Container(
width: 40,
alignment: Alignment.center,
child: const Icon(Icons.palette_outlined),
),
subtitle: Text(
_dynamicSchemeVariant.description,
style: const TextStyle(fontSize: 12),
),
leading: const Icon(Icons.palette_outlined),
title: const Text('调色板风格'),
value: () =>
(_dynamicSchemeVariant, _dynamicSchemeVariant.variantName),
itemBuilder: (_) => FlexSchemeVariant.values
.map(
(e) => PopupMenuItem(value: e, child: Text(e.variantName)),
)
.toList(),
onSelected: (value, setState) {
_dynamicSchemeVariant = value;
GStorage.setting.put(SettingBoxKey.schemeVariant, value.index);
Get.forceAppUpdate();
},
),
),
if (!Platform.isIOS)
Obx(
() => CheckboxListTile(
title: const Text('动态取色'),
controlAffinity: ListTileControlAffinity.leading,
value: ctr.dynamicColor.value,
onChanged: (val) async {
ctr
..dynamicColor.value = val!
..setting.put(SettingBoxKey.dynamicColor, val);
if (val) {
if (await MyApp.initPlatformState()) {
Get.forceAppUpdate();
() {
final dynamicColor = ctr.dynamicColor.value;
return ListTile(
title: const Text('动态取色'),
leading: Checkbox(
value: dynamicColor,
onChanged: (value) {},
materialTapTargetSize: .shrinkWrap,
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
),
),
onTap: () async {
final val = !dynamicColor;
if (val) {
if (await MyApp.initPlatformState()) {
Get.forceAppUpdate();
} else {
SmartDialog.showToast('该设备可能不支持动态取色');
return;
}
} else {
SmartDialog.showToast('该设备可能不支持动态取色');
ctr.dynamicColor.value = false;
Get.forceAppUpdate();
}
} else {
Get.forceAppUpdate();
}
},
),
ctr
..dynamicColor.value = val
..setting.put(SettingBoxKey.dynamicColor, val);
},
);
},
),
Padding(
padding: padding,

View File

@@ -0,0 +1,120 @@
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart' hide ListTile;
typedef PopupMenuItemSelected<T> =
void Function(T value, VoidCallback setState);
List<PopupMenuEntry<T>> enumItemBuilder<T extends EnumWithLabel>(
List<T> items,
) => items.map((e) => PopupMenuItem(value: e, child: Text(e.label))).toList();
enum DescPosType { subtitle, title, trailing }
class PopupListTile<T> extends StatefulWidget {
const PopupListTile({
super.key,
this.dense,
this.safeArea = true,
this.enabled = true,
this.leading,
required this.title,
this.descPosType = .subtitle,
required this.value,
required this.itemBuilder,
required this.onSelected,
});
final bool? dense;
final bool safeArea;
final bool enabled;
final Widget? leading;
final Widget title;
final DescPosType descPosType;
final ValueGetter<(T, String)> value;
final PopupMenuItemBuilder<T> itemBuilder;
final PopupMenuItemSelected<T> onSelected;
@override
State<PopupListTile<T>> createState() => _PopupListTileState<T>();
}
class _PopupListTileState<T> extends State<PopupListTile<T>> {
final _key = GlobalKey();
void _showButtonMenu(TapUpDetails details, T value) {
final box = context.findRenderObject() as RenderBox;
final offset = box.localToGlobal(box.size.topLeft(.zero));
final double dx;
if (PlatformUtils.isDesktop) {
dx = details.globalPosition.dx + 1;
} else {
final box = _key.currentContext!.findRenderObject() as RenderBox;
final offset = box.localToGlobal(box.size.topLeft(.zero));
dx = offset.dx;
}
showMenu<T?>(
context: context,
position: RelativeRect.fromLTRB(dx, offset.dy + 5, dx, 0),
items: widget.itemBuilder(context),
initialValue: value,
requestFocus: false,
).then<void>((T? newValue) {
if (!mounted) {
return;
}
if (newValue == null || newValue == value) {
return;
}
widget.onSelected(newValue, _refresh);
});
}
void _refresh() {
if (mounted) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final (value, descStr) = widget.value();
Widget title = Builder(key: _key, builder: (_) => widget.title);
Widget? subtitle;
Widget? trailing;
final desc = Text(
descStr,
style: TextStyle(
fontSize: 13,
color: widget.enabled
? theme.colorScheme.secondary
: theme.disabledColor,
),
);
switch (widget.descPosType) {
case DescPosType.subtitle:
subtitle = desc;
case DescPosType.title:
title = Row(
spacing: 12,
mainAxisSize: .min,
children: [title, desc],
);
case DescPosType.trailing:
trailing = desc;
}
return ListTile(
dense: widget.dense,
safeArea: widget.safeArea,
enabled: widget.enabled,
onTapUp: (details) => _showButtonMenu(details, value),
leading: widget.leading,
title: title,
subtitle: subtitle,
trailing: trailing,
);
}
}