Files
sm-emby-share/lib/features/settings/settings_full_page.dart
T
2026-07-14 11:11:36 +08:00

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),
],
),
);
}
}