Files
PiliPlus/lib/pages/setting/view.dart
bggRGjQaUbCoE 3a78ead3a6 opt type
opt ua

opt subtitle

opt playertype

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-07-26 20:11:26 +08:00

313 lines
10 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 'package:PiliPlus/http/login.dart';
import 'package:PiliPlus/models/common/setting_type.dart';
import 'package:PiliPlus/pages/about/view.dart';
import 'package:PiliPlus/pages/login/controller.dart';
import 'package:PiliPlus/pages/setting/extra_setting.dart';
import 'package:PiliPlus/pages/setting/play_setting.dart';
import 'package:PiliPlus/pages/setting/privacy_setting.dart';
import 'package:PiliPlus/pages/setting/recommend_setting.dart';
import 'package:PiliPlus/pages/setting/style_setting.dart';
import 'package:PiliPlus/pages/setting/video_setting.dart';
import 'package:PiliPlus/pages/setting/widgets/multi_select_dialog.dart';
import 'package:PiliPlus/pages/webdav/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.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';
class _SettingsModel {
final SettingType type;
final String? subtitle;
final IconData icon;
const _SettingsModel({
required this.type,
this.subtitle,
required this.icon,
});
}
class SettingPage extends StatefulWidget {
const SettingPage({super.key});
@override
State<SettingPage> createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
late SettingType _type = SettingType.privacySetting;
final RxBool _noAccount = Accounts.accountMode.isEmpty.obs;
late bool _isPortrait;
final List<_SettingsModel> _items = [
const _SettingsModel(
type: SettingType.privacySetting,
subtitle: '黑名单、无痕模式',
icon: Icons.privacy_tip_outlined,
),
const _SettingsModel(
type: SettingType.recommendSetting,
subtitle: '推荐来源web/app、刷新保留内容、过滤器',
icon: Icons.explore_outlined,
),
const _SettingsModel(
type: SettingType.videoSetting,
subtitle: '画质、音质、解码、缓冲、音频输出等',
icon: Icons.video_settings_outlined,
),
const _SettingsModel(
type: SettingType.playSetting,
subtitle: '双击/长按、全屏、后台播放、弹幕、字幕、底部进度条等',
icon: Icons.touch_app_outlined,
),
const _SettingsModel(
type: SettingType.styleSetting,
subtitle: '横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等',
icon: Icons.style_outlined,
),
const _SettingsModel(
type: SettingType.extraSetting,
subtitle: '震动、搜索、收藏、ai、评论、动态、代理、更新检查等',
icon: Icons.extension_outlined,
),
const _SettingsModel(
type: SettingType.webdavSetting,
icon: MdiIcons.databaseCogOutline,
),
const _SettingsModel(
type: SettingType.about,
icon: Icons.info_outline,
),
];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
_isPortrait = context.orientation == Orientation.portrait;
return Scaffold(
appBar: AppBar(
title: _isPortrait ? const Text('设置') : Text(_type.title),
),
body: _isPortrait
? _buildList(theme)
: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: MediaQuery.removePadding(
context: context,
removeRight: true,
removeTop: true,
child: _buildList(theme),
),
),
VerticalDivider(
width: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
Expanded(
flex: 6,
child: MediaQuery.removePadding(
context: context,
removeLeft: true,
removeTop: true,
child: switch (_type) {
SettingType.privacySetting => const PrivacySetting(
showAppBar: false,
),
SettingType.recommendSetting => const RecommendSetting(
showAppBar: false,
),
SettingType.videoSetting => const VideoSetting(
showAppBar: false,
),
SettingType.playSetting => const PlaySetting(
showAppBar: false,
),
SettingType.styleSetting => const StyleSetting(
showAppBar: false,
),
SettingType.extraSetting => const ExtraSetting(
showAppBar: false,
),
SettingType.webdavSetting => const WebDavSettingPage(
showAppBar: false,
),
SettingType.about => const AboutPage(showAppBar: false),
},
),
),
],
),
);
}
void _toPage(SettingType type) {
if (_isPortrait) {
Get.toNamed('/${type.name}');
} else {
_type = type;
setState(() {});
}
}
Color? _getTileColor(ThemeData theme, SettingType type) {
if (_isPortrait) {
return null;
} else {
return type == _type ? theme.colorScheme.onInverseSurface : null;
}
}
Widget _buildList(ThemeData theme) {
TextStyle titleStyle = theme.textTheme.titleMedium!;
TextStyle subTitleStyle = theme.textTheme.labelMedium!.copyWith(
color: theme.colorScheme.outline,
);
return ListView(
children: [
_buildSearchItem(theme),
..._items
.sublist(0, _items.length - 1)
.map(
(item) => ListTile(
tileColor: _getTileColor(theme, item.type),
onTap: () => _toPage(item.type),
leading: Icon(item.icon),
title: Text(item.type.title, style: titleStyle),
subtitle: item.subtitle == null
? null
: Text(item.subtitle!, style: subTitleStyle),
),
),
ListTile(
onTap: () => LoginPageController.switchAccountDialog(context),
leading: const Icon(Icons.switch_account_outlined),
title: const Text('设置账号模式'),
),
Obx(
() => _noAccount.value
? const SizedBox.shrink()
: ListTile(
leading: const Icon(Icons.logout_outlined),
onTap: () => _logoutDialog(context),
title: Text('退出登录', style: titleStyle),
),
),
ListTile(
tileColor: _getTileColor(theme, _items.last.type),
onTap: () => _toPage(_items.last.type),
leading: Icon(_items.last.icon),
title: Text(_items.last.type.title, style: titleStyle),
),
SizedBox(height: MediaQuery.paddingOf(context).bottom + 80),
],
);
}
Future<void> _logoutDialog(BuildContext context) async {
final result = await showDialog<Set<LoginAccount>>(
context: context,
builder: (context) {
return MultiSelectDialog<LoginAccount>(
title: '选择要登出的账号uid',
initValues: const Iterable.empty(),
values: {for (var i in Accounts.account.values) i: i.mid.toString()},
);
},
);
if (!context.mounted || result.isNullOrEmpty) return;
Future<void> logout() {
_noAccount.value = result!.length == Accounts.account.length;
return Accounts.deleteAll(result);
}
showDialog(
context: context,
builder: (context) {
final theme = Theme.of(context);
return AlertDialog(
title: const Text('提示'),
content: Text(
"确认要退出以下账号登录吗\n\n${result!.map((i) => i.mid.toString()).join('\n')}",
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'点错了',
style: TextStyle(
color: theme.colorScheme.outline,
),
),
),
TextButton(
onPressed: () {
Get.back();
logout();
},
child: Text(
'仅登出',
style: TextStyle(color: theme.colorScheme.error),
),
),
TextButton(
onPressed: () async {
SmartDialog.showLoading();
final res = await LoginHttp.logout(Accounts.main);
if (res['status']) {
SmartDialog.dismiss();
logout();
Get.back();
} else {
SmartDialog.dismiss();
SmartDialog.showToast(res['msg'].toString());
}
},
child: const Text('确认'),
),
],
);
},
);
}
Widget _buildSearchItem(ThemeData theme) => Padding(
padding: EdgeInsets.only(
left: 16 + MediaQuery.paddingOf(context).left,
right: 16,
bottom: 8,
),
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => Get.toNamed('/settingsSearch'),
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: Ink(
padding: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(50)),
color: theme.colorScheme.onInverseSurface,
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
size: MediaQuery.textScalerOf(context).scale(18),
Icons.search,
),
const Text(' 搜索'),
],
),
),
),
),
),
);
}