mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-10 03:57:49 +08:00
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
120
lib/pages/setting/widgets/popup_item.dart
Normal file
120
lib/pages/setting/widgets/popup_item.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user