mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ class NormalModel extends SettingsModel {
|
||||
final String? title;
|
||||
final ValueGetter<String>? getTitle;
|
||||
final ValueGetter<String>? getSubtitle;
|
||||
final Widget Function()? getTrailing;
|
||||
final Widget Function(ThemeData theme)? getTrailing;
|
||||
final void Function(BuildContext context, VoidCallback setState)? onTap;
|
||||
|
||||
const NormalModel({
|
||||
@@ -125,49 +125,47 @@ SettingsModel getBanWordModel({
|
||||
String editValue = banWord;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
constraints: StyleString.dialogFixedConstraints,
|
||||
title: Text(title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('使用|隔开,如:尝试|测试'),
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
initialValue: editValue,
|
||||
textInputAction: TextInputAction.newline,
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
onChanged: (value) => editValue = value,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('保存'),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
banWord = editValue;
|
||||
setState();
|
||||
onChanged(RegExp(banWord, caseSensitive: false));
|
||||
SmartDialog.showToast('已保存');
|
||||
GStorage.setting.put(key, banWord);
|
||||
},
|
||||
builder: (context) => AlertDialog(
|
||||
constraints: StyleString.dialogFixedConstraints,
|
||||
title: Text(title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('使用|隔开,如:尝试|测试'),
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
initialValue: editValue,
|
||||
textInputAction: TextInputAction.newline,
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
onChanged: (value) => editValue = value,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('保存'),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
banWord = editValue;
|
||||
setState();
|
||||
onChanged(RegExp(banWord, caseSensitive: false));
|
||||
SmartDialog.showToast('已保存');
|
||||
GStorage.setting.put(key, banWord);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -197,57 +195,53 @@ SettingsModel getVideoFilterSelectModel({
|
||||
onTap: (context, setState) async {
|
||||
var result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '选择$title${isFilter ? '(0即不过滤)' : ''}',
|
||||
value: value,
|
||||
values:
|
||||
(values
|
||||
..addIf(!values.contains(value), value)
|
||||
..sort())
|
||||
.map(
|
||||
(e) => (e, suffix == null ? e.toString() : '$e $suffix'),
|
||||
)
|
||||
.toList()
|
||||
..add((-1, '自定义')),
|
||||
);
|
||||
},
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '选择$title${isFilter ? '(0即不过滤)' : ''}',
|
||||
value: value,
|
||||
values:
|
||||
(values
|
||||
..addIf(!values.contains(value), value)
|
||||
..sort())
|
||||
.map(
|
||||
(e) => (e, suffix == null ? e.toString() : '$e $suffix'),
|
||||
)
|
||||
.toList()
|
||||
..add((-1, '自定义')),
|
||||
),
|
||||
);
|
||||
if (result != null) {
|
||||
if (result == -1 && context.mounted) {
|
||||
String valueStr = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String valueStr = '';
|
||||
return AlertDialog(
|
||||
title: Text('自定义$title'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
onChanged: (value) => valueStr = value,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(suffixText: suffix),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('自定义$title'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
onChanged: (value) => valueStr = value,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(suffixText: suffix),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
result = int.tryParse(valueStr) ?? 0;
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
result = int.tryParse(valueStr) ?? 0;
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (result != -1) {
|
||||
@@ -275,9 +269,9 @@ SettingsModel getPopupMenuModel({
|
||||
subtitle: subtitle,
|
||||
leading: leading,
|
||||
// onTap: (context, setState) => globalKey.currentState?.showButtonMenu(),
|
||||
getTrailing: () => Builder(
|
||||
getTrailing: (theme) => Builder(
|
||||
builder: (context) {
|
||||
final color = ColorScheme.of(context).secondary;
|
||||
final color = theme.colorScheme.secondary;
|
||||
final v = values[GStorage.setting.get(key, defaultValue: defaultIndex)];
|
||||
return PopupMenuButton(
|
||||
// key: globalKey,
|
||||
|
||||
@@ -120,25 +120,7 @@ List<SettingsModel> get playSettings => [
|
||||
title: '自动启用字幕',
|
||||
leading: const Icon(Icons.closed_caption_outlined),
|
||||
getSubtitle: () => '当前选择偏好:${Pref.subtitlePreferenceV2.desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<SubtitlePrefType>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<SubtitlePrefType>(
|
||||
title: '字幕选择偏好',
|
||||
value: Pref.subtitlePreferenceV2,
|
||||
values: SubtitlePrefType.values.map((e) => (e, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.subtitlePreferenceV2,
|
||||
result.index,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showSubtitleDialog,
|
||||
),
|
||||
if (PlatformUtils.isDesktop)
|
||||
SwitchModel(
|
||||
@@ -162,22 +144,7 @@ List<SettingsModel> get playSettings => [
|
||||
title: 'SuperChat (醒目留言) 显示类型',
|
||||
leading: const Icon(Icons.live_tv),
|
||||
getSubtitle: () => '当前:「${Pref.superChatType.title}」',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<SuperChatType>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<SuperChatType>(
|
||||
title: 'SuperChat (醒目留言) 显示类型',
|
||||
value: Pref.superChatType,
|
||||
values: SuperChatType.values.map((e) => (e, e.title)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.superChatType, result.index);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showSuperChatDialog,
|
||||
),
|
||||
const SwitchModel(
|
||||
title: '竖屏扩大展示',
|
||||
@@ -267,46 +234,13 @@ List<SettingsModel> get playSettings => [
|
||||
title: '默认全屏方向',
|
||||
leading: const Icon(Icons.open_with_outlined),
|
||||
getSubtitle: () => '当前全屏方向:${Pref.fullScreenMode.desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<FullScreenMode>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<FullScreenMode>(
|
||||
title: '默认全屏方向',
|
||||
value: Pref.fullScreenMode,
|
||||
values: FullScreenMode.values.map((e) => (e, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.fullScreenMode, result.index);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showFullScreenModeDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '底部进度条展示',
|
||||
leading: const Icon(Icons.border_bottom_outlined),
|
||||
getSubtitle: () => '当前展示方式:${Pref.btmProgressBehavior.desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<BtmProgressBehavior>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<BtmProgressBehavior>(
|
||||
title: '底部进度条展示',
|
||||
value: Pref.btmProgressBehavior,
|
||||
values: BtmProgressBehavior.values.map((e) => (e, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.btmProgressBehavior,
|
||||
result.index,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showProgressBehaviorDialog,
|
||||
),
|
||||
if (PlatformUtils.isMobile)
|
||||
SwitchModel(
|
||||
@@ -327,3 +261,81 @@ List<SettingsModel> get playSettings => [
|
||||
defaultVal: false,
|
||||
),
|
||||
];
|
||||
|
||||
Future<void> _showSubtitleDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<SubtitlePrefType>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<SubtitlePrefType>(
|
||||
title: '字幕选择偏好',
|
||||
value: Pref.subtitlePreferenceV2,
|
||||
values: SubtitlePrefType.values.map((e) => (e, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.subtitlePreferenceV2,
|
||||
res.index,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showSuperChatDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<SuperChatType>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<SuperChatType>(
|
||||
title: 'SuperChat (醒目留言) 显示类型',
|
||||
value: Pref.superChatType,
|
||||
values: SuperChatType.values.map((e) => (e, e.title)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.superChatType, res.index);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showFullScreenModeDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<FullScreenMode>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<FullScreenMode>(
|
||||
title: '默认全屏方向',
|
||||
value: Pref.fullScreenMode,
|
||||
values: FullScreenMode.values.map((e) => (e, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.fullScreenMode, res.index);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showProgressBehaviorDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<BtmProgressBehavior>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<BtmProgressBehavior>(
|
||||
title: '底部进度条展示',
|
||||
value: Pref.btmProgressBehavior,
|
||||
values: BtmProgressBehavior.values.map((e) => (e, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.btmProgressBehavior,
|
||||
res.index,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,18 @@ List<SettingsModel> get privacySettings => [
|
||||
onTap: (context, setState) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('账号模式详情'),
|
||||
content: SingleChildScrollView(
|
||||
child: _getAccountDetail(context),
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('账号模式详情'),
|
||||
content: SingleChildScrollView(
|
||||
child: _getAccountDetail(context),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: const Text('确认'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: const Text('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
leading: const Icon(Icons.flag_outlined),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,13 +38,16 @@ List<SettingsModel> get videoSettings => [
|
||||
title: 'B站定向流量支持',
|
||||
subtitle: '若套餐含B站定向流量,则会自动使用。可查阅运营商的流量记录确认。',
|
||||
leading: const Icon(Icons.perm_data_setting_outlined),
|
||||
getTrailing: () => IgnorePointer(
|
||||
getTrailing: (theme) => IgnorePointer(
|
||||
child: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Switch(
|
||||
value: true,
|
||||
onChanged: (_) {},
|
||||
thumbIcon: WidgetStateProperty.all(
|
||||
const Icon(Icons.lock_outline_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -54,68 +57,13 @@ List<SettingsModel> get videoSettings => [
|
||||
leading: const Icon(MdiIcons.cloudPlusOutline),
|
||||
getSubtitle: () =>
|
||||
'当前使用:${VideoUtils.cdnService.desc},部分 CDN 可能失效,如无法播放请尝试切换',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<CDNService>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const CdnSelectDialog();
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
VideoUtils.cdnService = result;
|
||||
await GStorage.setting.put(SettingBoxKey.CDNService, result.name);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showCDNDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '直播 CDN 设置',
|
||||
leading: const Icon(MdiIcons.cloudPlusOutline),
|
||||
getSubtitle: () => '当前使用:${Pref.liveCdnUrl ?? "默认"}',
|
||||
onTap: (context, setState) async {
|
||||
String? result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String host = Pref.liveCdnUrl ?? '';
|
||||
return AlertDialog(
|
||||
title: const Text('输入CDN host'),
|
||||
content: TextFormField(
|
||||
initialValue: host,
|
||||
autofocus: true,
|
||||
onChanged: (value) => host = value,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: ColorScheme.of(context).outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: host),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
if (result.isEmpty) {
|
||||
result = null;
|
||||
await GStorage.setting.delete(SettingBoxKey.liveCdnUrl);
|
||||
} else {
|
||||
if (!result.startsWith('http')) {
|
||||
result = 'https://$result';
|
||||
}
|
||||
await GStorage.setting.put(SettingBoxKey.liveCdnUrl, result);
|
||||
}
|
||||
VideoUtils.liveCdnUrl = result;
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showLiveCDNDialog,
|
||||
),
|
||||
const SwitchModel(
|
||||
title: 'CDN 测速',
|
||||
@@ -130,221 +78,69 @@ List<SettingsModel> get videoSettings => [
|
||||
leading: const Icon(MdiIcons.musicNotePlus),
|
||||
setKey: SettingBoxKey.disableAudioCDN,
|
||||
defaultVal: false,
|
||||
onChanged: (value) {
|
||||
VideoUtils.disableAudioCDN = value;
|
||||
},
|
||||
onChanged: (value) => VideoUtils.disableAudioCDN = value,
|
||||
),
|
||||
NormalModel(
|
||||
title: '默认画质',
|
||||
leading: const Icon(Icons.video_settings_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前画质:${VideoQuality.fromCode(Pref.defaultVideoQa).desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认画质',
|
||||
value: Pref.defaultVideoQa,
|
||||
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultVideoQa, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showVideoQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '蜂窝网络画质',
|
||||
leading: const Icon(Icons.video_settings_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前画质:${VideoQuality.fromCode(Pref.defaultVideoQaCellular).desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '蜂窝网络画质',
|
||||
value: Pref.defaultVideoQaCellular,
|
||||
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.defaultVideoQaCellular,
|
||||
result,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showVideoCellularQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '默认音质',
|
||||
leading: const Icon(Icons.music_video_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前音质:${AudioQuality.fromCode(Pref.defaultAudioQa).desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认音质',
|
||||
value: Pref.defaultAudioQa,
|
||||
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultAudioQa, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showAudioQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '蜂窝网络音质',
|
||||
leading: const Icon(Icons.music_video_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前音质:${AudioQuality.fromCode(Pref.defaultAudioQaCellular).desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '蜂窝网络音质',
|
||||
value: Pref.defaultAudioQaCellular,
|
||||
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.defaultAudioQaCellular,
|
||||
result,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showAudioCellularQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '直播默认画质',
|
||||
leading: const Icon(Icons.video_settings_outlined),
|
||||
getSubtitle: () => '当前画质:${LiveQuality.fromCode(Pref.liveQuality)?.desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '直播默认画质',
|
||||
value: Pref.liveQuality,
|
||||
values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.liveQuality, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showLiveQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '蜂窝网络直播默认画质',
|
||||
leading: const Icon(Icons.video_settings_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前画质:${LiveQuality.fromCode(Pref.liveQualityCellular)?.desc}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '蜂窝网络直播默认画质',
|
||||
value: Pref.liveQualityCellular,
|
||||
values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.liveQualityCellular, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showLiveCellularQaDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '首选解码格式',
|
||||
leading: const Icon(Icons.movie_creation_outlined),
|
||||
getSubtitle: () =>
|
||||
'首选解码格式:${VideoDecodeFormatType.fromCode(Pref.defaultDecode).description},请根据设备支持情况与需求调整',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<String>(
|
||||
title: '默认解码格式',
|
||||
value: Pref.defaultDecode,
|
||||
values: VideoDecodeFormatType.values
|
||||
.map((e) => (e.codes.first, e.description))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultDecode, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showDecodeDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '次选解码格式',
|
||||
getSubtitle: () =>
|
||||
'非杜比视频次选:${VideoDecodeFormatType.fromCode(Pref.secondDecode).description},仍无则选择首个提供的解码格式',
|
||||
leading: const Icon(Icons.swap_horizontal_circle_outlined),
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<String>(
|
||||
title: '次选解码格式',
|
||||
value: Pref.secondDecode,
|
||||
values: VideoDecodeFormatType.values
|
||||
.map((e) => (e.codes.first, e.description))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.secondDecode, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showSecondDecodeDialog,
|
||||
),
|
||||
if (kDebugMode || Platform.isAndroid)
|
||||
NormalModel(
|
||||
title: '音频输出设备',
|
||||
leading: const Icon(Icons.speaker_outlined),
|
||||
getSubtitle: () => '当前:${Pref.audioOutput}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<List<String>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return OrderedMultiSelectDialog<String>(
|
||||
title: '音频输出设备',
|
||||
initValues: Pref.audioOutput.split(','),
|
||||
values: {
|
||||
for (final e in AudioOutput.values) e.name: e.label,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null && result.isNotEmpty) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.audioOutput,
|
||||
result.join(','),
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showAudioOutputDialog,
|
||||
),
|
||||
const SwitchModel(
|
||||
title: '扩大缓冲区',
|
||||
@@ -358,58 +154,298 @@ List<SettingsModel> get videoSettings => [
|
||||
title: '视频同步',
|
||||
leading: const Icon(Icons.view_timeline_outlined),
|
||||
getSubtitle: () => '当前:${Pref.videoSync}(此项即mpv的--video-sync)',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<String>(
|
||||
title: '视频同步',
|
||||
value: Pref.videoSync,
|
||||
values: const [
|
||||
'audio',
|
||||
'display-resample',
|
||||
'display-resample-vdrop',
|
||||
'display-resample-desync',
|
||||
'display-tempo',
|
||||
'display-vdrop',
|
||||
'display-adrop',
|
||||
'display-desync',
|
||||
'desync',
|
||||
].map((e) => (e, e)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.videoSync, result);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showVideoSyncDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '硬解模式',
|
||||
leading: const Icon(Icons.memory_outlined),
|
||||
getSubtitle: () => '当前:${Pref.hardwareDecoding}(此项即mpv的--hwdec)',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<List<String>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return OrderedMultiSelectDialog<String>(
|
||||
title: '硬解模式',
|
||||
initValues: Pref.hardwareDecoding.split(','),
|
||||
values: {
|
||||
for (final e in HwDecType.values)
|
||||
e.hwdec: '${e.hwdec}\n${e.desc}',
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null && result.isNotEmpty) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.hardwareDecoding,
|
||||
result.join(','),
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
onTap: _showHwDecDialog,
|
||||
),
|
||||
];
|
||||
|
||||
Future<void> _showCDNDialog(BuildContext context, VoidCallback setState) async {
|
||||
final res = await showDialog<CDNService>(
|
||||
context: context,
|
||||
builder: (context) => const CdnSelectDialog(),
|
||||
);
|
||||
if (res != null) {
|
||||
VideoUtils.cdnService = res;
|
||||
await GStorage.setting.put(SettingBoxKey.CDNService, res.name);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showLiveCDNDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
String host = Pref.liveCdnUrl ?? '';
|
||||
String? res = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('输入CDN host'),
|
||||
content: TextFormField(
|
||||
initialValue: host,
|
||||
autofocus: true,
|
||||
onChanged: (value) => host = value,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: ColorScheme.of(context).outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: host),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
if (res.isEmpty) {
|
||||
res = null;
|
||||
await GStorage.setting.delete(SettingBoxKey.liveCdnUrl);
|
||||
} else {
|
||||
if (!res.startsWith('http')) {
|
||||
res = 'https://$res';
|
||||
}
|
||||
await GStorage.setting.put(SettingBoxKey.liveCdnUrl, res);
|
||||
}
|
||||
VideoUtils.liveCdnUrl = res;
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showVideoQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '默认画质',
|
||||
value: Pref.defaultVideoQa,
|
||||
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultVideoQa, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showVideoCellularQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '蜂窝网络画质',
|
||||
value: Pref.defaultVideoQaCellular,
|
||||
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.defaultVideoQaCellular,
|
||||
res,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showAudioQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '默认音质',
|
||||
value: Pref.defaultAudioQa,
|
||||
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultAudioQa, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showAudioCellularQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '蜂窝网络音质',
|
||||
value: Pref.defaultAudioQaCellular,
|
||||
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.defaultAudioQaCellular,
|
||||
res,
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showLiveQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '直播默认画质',
|
||||
value: Pref.liveQuality,
|
||||
values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.liveQuality, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showLiveCellularQaDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<int>(
|
||||
title: '蜂窝网络直播默认画质',
|
||||
value: Pref.liveQualityCellular,
|
||||
values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.liveQualityCellular, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showDecodeDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<String>(
|
||||
title: '默认解码格式',
|
||||
value: Pref.defaultDecode,
|
||||
values: VideoDecodeFormatType.values
|
||||
.map((e) => (e.codes.first, e.description))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.defaultDecode, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showSecondDecodeDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<String>(
|
||||
title: '次选解码格式',
|
||||
value: Pref.secondDecode,
|
||||
values: VideoDecodeFormatType.values
|
||||
.map((e) => (e.codes.first, e.description))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.secondDecode, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showAudioOutputDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<List<String>>(
|
||||
context: context,
|
||||
builder: (context) => OrderedMultiSelectDialog<String>(
|
||||
title: '音频输出设备',
|
||||
initValues: Pref.audioOutput.split(','),
|
||||
values: {
|
||||
for (final e in AudioOutput.values) e.name: e.label,
|
||||
},
|
||||
),
|
||||
);
|
||||
if (res != null && res.isNotEmpty) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.audioOutput,
|
||||
res.join(','),
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showVideoSyncDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => SelectDialog<String>(
|
||||
title: '视频同步',
|
||||
value: Pref.videoSync,
|
||||
values: const [
|
||||
'audio',
|
||||
'display-resample',
|
||||
'display-resample-vdrop',
|
||||
'display-resample-desync',
|
||||
'display-tempo',
|
||||
'display-vdrop',
|
||||
'display-adrop',
|
||||
'display-desync',
|
||||
'desync',
|
||||
].map((e) => (e, e)).toList(),
|
||||
),
|
||||
);
|
||||
if (res != null) {
|
||||
await GStorage.setting.put(SettingBoxKey.videoSync, res);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showHwDecDialog(
|
||||
BuildContext context,
|
||||
VoidCallback setState,
|
||||
) async {
|
||||
final res = await showDialog<List<String>>(
|
||||
context: context,
|
||||
builder: (context) => OrderedMultiSelectDialog<String>(
|
||||
title: '硬解模式',
|
||||
initValues: Pref.hardwareDecoding.split(','),
|
||||
values: {
|
||||
for (final e in HwDecType.values) e.hwdec: '${e.hwdec}\n${e.desc}',
|
||||
},
|
||||
),
|
||||
);
|
||||
if (res != null && res.isNotEmpty) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.hardwareDecoding,
|
||||
res.join(','),
|
||||
);
|
||||
setState();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user