431 lines
14 KiB
Dart
431 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../providers/appearance_provider.dart';
|
|
import '../../providers/sm_account_provider.dart';
|
|
import '../../shared/app_info.dart';
|
|
import '../../shared/theme/app_theme.dart';
|
|
import '../../shared/widgets/app_card.dart';
|
|
import '../../shared/widgets/app_sliver_header.dart';
|
|
import '../../shared/widgets/page_background.dart';
|
|
import 'settings_page.dart';
|
|
import 'settings_tab_key.dart';
|
|
|
|
class SettingsFullPage extends ConsumerWidget {
|
|
const SettingsFullPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final account = ref.watch(smAccountProvider).value;
|
|
final appVersion =
|
|
ref.watch(appVersionProvider).value ?? kSmPlayerVersionFallback;
|
|
final appearance = ref.watch(appearanceProvider).value;
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.transparent,
|
|
extendBodyBehindAppBar: true,
|
|
body: Stack(
|
|
children: [
|
|
const Positioned.fill(child: PageBackground()),
|
|
CustomScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(
|
|
parent: BouncingScrollPhysics(),
|
|
),
|
|
slivers: [
|
|
AppSliverHeader(
|
|
title: '设置',
|
|
actions: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Theme.of(
|
|
context,
|
|
).colorScheme.onSurface.withValues(alpha: 0.15),
|
|
),
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Text(
|
|
appVersion,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SliverPadding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 100),
|
|
sliver: SliverList(
|
|
delegate: SliverChildListDelegate.fixed([
|
|
_SmAccountBanner(
|
|
account: account,
|
|
onTap: () => context.push('/account-center'),
|
|
),
|
|
const SizedBox(height: 24),
|
|
const _SectionTitle('常规设置'),
|
|
_SettingsGroupCard(
|
|
children: [
|
|
_SettingsItem(
|
|
icon: Icons.palette_outlined,
|
|
color: const Color(0xFF8B5CF6),
|
|
title: '主题',
|
|
trailing: _ThemeModeSelector(
|
|
currentMode: appearance?.themeMode,
|
|
onChanged: (mode) => ref
|
|
.read(appearanceProvider.notifier)
|
|
.setThemeMode(mode),
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.settings_outlined,
|
|
color: const Color(0xFF6B7280),
|
|
title: '通用',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabGeneral,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
const _SectionTitle('播放设置'),
|
|
_SettingsGroupCard(
|
|
children: [
|
|
_SettingsItem(
|
|
icon: Icons.play_circle_outline_rounded,
|
|
color: const Color(0xFF22C55E),
|
|
title: '播放',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabPlayback,
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.headphones_outlined,
|
|
color: const Color(0xFFA855F7),
|
|
title: '音频',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabAudio,
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.chat_bubble_outline_rounded,
|
|
color: const Color(0xFFEF4444),
|
|
title: '弹幕',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabDanmaku,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
const _SectionTitle('扩展服务'),
|
|
_SettingsGroupCard(
|
|
children: [
|
|
_SettingsItem(
|
|
icon: Icons.extension_rounded,
|
|
color: const Color(0xFF6366F1),
|
|
title: '模块中心',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabModules,
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.movie_filter_outlined,
|
|
color: const Color(0xFF0EA5E9),
|
|
title: 'TMDB',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabTmdb,
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.link_outlined,
|
|
color: const Color(0xFFEF4444),
|
|
title: 'Trakt',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabTrakt,
|
|
),
|
|
),
|
|
_SettingsItem(
|
|
icon: Icons.palette_outlined,
|
|
color: const Color(0xFFF59E0B),
|
|
title: '图标库',
|
|
onTap: () => showSettingsSectionSheet(
|
|
context,
|
|
initialTab: kSettingsTabIconLibrary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SmAccountBanner extends StatelessWidget {
|
|
final SmAccountState? account;
|
|
final VoidCallback onTap;
|
|
|
|
const _SmAccountBanner({required this.account, required this.onTap});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final loggedIn = account?.status == SmAuthStatus.authenticated;
|
|
final email = account?.email ?? '';
|
|
final isVip = account?.isVip ?? false;
|
|
|
|
final title = loggedIn
|
|
? (email.isNotEmpty ? email : 'Player 账号')
|
|
: '登录 Player 账号';
|
|
final subtitle = loggedIn ? (isVip ? 'VIP 会员' : '已登录') : '登录以使用云同步等扩展服务';
|
|
|
|
return AppCard(
|
|
onTap: onTap,
|
|
padding: const EdgeInsets.all(16),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: loggedIn
|
|
? (isVip
|
|
? const Color(0xFFFAAD14).withValues(alpha: 0.15)
|
|
: theme.colorScheme.primary.withValues(alpha: 0.12))
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.08),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
loggedIn
|
|
? (isVip
|
|
? Icons.workspace_premium_rounded
|
|
: Icons.person_rounded)
|
|
: Icons.person_outline_rounded,
|
|
size: 24,
|
|
color: loggedIn
|
|
? (isVip
|
|
? const Color(0xFFFAAD14)
|
|
: theme.colorScheme.primary)
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.textTheme.titleSmall?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 3),
|
|
Text(
|
|
subtitle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.58),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (isVip)
|
|
Container(
|
|
margin: const EdgeInsets.only(right: 8),
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFAAD14),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: const Text(
|
|
'VIP',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
),
|
|
const Icon(Icons.chevron_right_rounded),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionTitle extends StatelessWidget {
|
|
final String text;
|
|
|
|
const _SectionTitle(this.text);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(4, 0, 4, 8),
|
|
child: Text(
|
|
text,
|
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
|
color: context.appTokens.mutedForeground,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SettingsGroupCard extends StatelessWidget {
|
|
final List<Widget> children;
|
|
|
|
const _SettingsGroupCard({required this.children});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AppCard(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
for (var i = 0; i < children.length; i++) ...[
|
|
if (i > 0)
|
|
Divider(
|
|
height: 1,
|
|
indent: 56,
|
|
color: Theme.of(
|
|
context,
|
|
).colorScheme.onSurface.withValues(alpha: 0.08),
|
|
),
|
|
children[i],
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SettingsItem extends StatelessWidget {
|
|
final IconData icon;
|
|
final Color color;
|
|
final String title;
|
|
final Widget? trailing;
|
|
final VoidCallback? onTap;
|
|
|
|
const _SettingsItem({
|
|
required this.icon,
|
|
required this.color,
|
|
required this.title,
|
|
this.trailing,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
|
child: Icon(icon, size: 18, color: Colors.white),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [Text(title, style: theme.textTheme.bodyMedium)],
|
|
),
|
|
),
|
|
if (trailing != null)
|
|
trailing!
|
|
else if (onTap != null)
|
|
const Icon(Icons.chevron_right_rounded, size: 20),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ThemeModeSelector extends StatelessWidget {
|
|
final ThemeMode? currentMode;
|
|
final ValueChanged<ThemeMode> onChanged;
|
|
|
|
const _ThemeModeSelector({
|
|
required this.currentMode,
|
|
required this.onChanged,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final mode = currentMode ?? ThemeMode.system;
|
|
|
|
Widget chip(ThemeMode m, IconData icon) {
|
|
final selected = mode == m;
|
|
return GestureDetector(
|
|
onTap: () => onChanged(m),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: selected
|
|
? theme.colorScheme.primary.withValues(alpha: 0.15)
|
|
: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
size: 18,
|
|
color: selected
|
|
? theme.colorScheme.primary
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.06),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
padding: const EdgeInsets.all(2),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
chip(ThemeMode.light, Icons.light_mode_outlined),
|
|
chip(ThemeMode.system, Icons.settings_outlined),
|
|
chip(ThemeMode.dark, Icons.dark_mode_outlined),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|