128 lines
4.1 KiB
Dart
128 lines
4.1 KiB
Dart
|
|
import 'dart:async';
|
||
|
|
|
||
|
|
import 'package:flutter/foundation.dart'
|
||
|
|
show defaultTargetPlatform, TargetPlatform;
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
import 'package:forui/forui.dart';
|
||
|
|
|
||
|
|
import 'features/update/update_dialog.dart';
|
||
|
|
import 'providers/appearance_provider.dart';
|
||
|
|
import 'providers/di_providers.dart';
|
||
|
|
import 'providers/server_sync_provider.dart';
|
||
|
|
import 'providers/sm_account_provider.dart';
|
||
|
|
import 'providers/update_provider.dart';
|
||
|
|
import 'router/app_router.dart' show appRouterProvider, rootNavigatorKey;
|
||
|
|
import 'shared/theme/app_scroll_behavior.dart';
|
||
|
|
import 'shared/theme/app_theme.dart';
|
||
|
|
import 'shared/utils/app_logger.dart';
|
||
|
|
|
||
|
|
class SmPlayerApp extends ConsumerStatefulWidget {
|
||
|
|
const SmPlayerApp({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
ConsumerState<SmPlayerApp> createState() => _SmPlayerAppState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _SmPlayerAppState extends ConsumerState<SmPlayerApp> {
|
||
|
|
bool _updateDialogShown = false;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||
|
|
try {
|
||
|
|
final queue = await ref.read(traktSyncQueueProvider.future);
|
||
|
|
await queue.drain();
|
||
|
|
} catch (e) {
|
||
|
|
AppLogger.debug('Trakt', 'startup drain skipped', e);
|
||
|
|
}
|
||
|
|
unawaited(ref.read(updateProvider.notifier).silentCheck());
|
||
|
|
unawaited(_autoSyncPull());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _autoSyncPull() async {
|
||
|
|
try {
|
||
|
|
final account = await ref.read(smAccountProvider.future);
|
||
|
|
if (!account.hasVip) return;
|
||
|
|
final outcome = await ref.read(serverSyncProvider.notifier).pull();
|
||
|
|
if (outcome.isError) {
|
||
|
|
AppLogger.debug('CloudSync', 'auto-pull failed: ${outcome.message}');
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
AppLogger.debug('CloudSync', 'auto-pull skipped', e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final router = ref.watch(appRouterProvider);
|
||
|
|
final appearance = ref.watch(appearanceProvider);
|
||
|
|
final flutterMode = appearance.maybeWhen(
|
||
|
|
data: (d) => d.themeMode,
|
||
|
|
orElse: () => ThemeMode.system,
|
||
|
|
);
|
||
|
|
|
||
|
|
return MaterialApp.router(
|
||
|
|
title: 'smPlayer',
|
||
|
|
debugShowCheckedModeBanner: false,
|
||
|
|
locale: const Locale('zh', 'CN'),
|
||
|
|
supportedLocales: const [Locale('zh', 'CN'), Locale('en')],
|
||
|
|
localizationsDelegates: const [
|
||
|
|
GlobalMaterialLocalizations.delegate,
|
||
|
|
GlobalCupertinoLocalizations.delegate,
|
||
|
|
GlobalWidgetsLocalizations.delegate,
|
||
|
|
],
|
||
|
|
theme: AppTheme.light(),
|
||
|
|
darkTheme: AppTheme.dark(),
|
||
|
|
themeMode: flutterMode,
|
||
|
|
scrollBehavior: const AppScrollBehavior(),
|
||
|
|
routerConfig: router,
|
||
|
|
builder: (context, child) {
|
||
|
|
final zinc = Theme.of(context).brightness == Brightness.dark
|
||
|
|
? FThemes.zinc.dark
|
||
|
|
: FThemes.zinc.light;
|
||
|
|
return FTheme(
|
||
|
|
data: _isTouchPlatform ? zinc.touch : zinc.desktop,
|
||
|
|
child: FToaster(
|
||
|
|
child: Consumer(
|
||
|
|
builder: (context, ref, _) {
|
||
|
|
ref.listen<UpdateState>(updateProvider, (previous, next) {
|
||
|
|
if (_updateDialogShown ||
|
||
|
|
next.status != UpdateStatus.available ||
|
||
|
|
!next.silent) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_updateDialogShown = true;
|
||
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
|
|
if (!mounted) return;
|
||
|
|
final navContext = rootNavigatorKey.currentContext;
|
||
|
|
if (navContext == null) return;
|
||
|
|
showFDialog(
|
||
|
|
context: navContext,
|
||
|
|
builder: (_, _, _) => const UpdateDialog(),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
return child ?? const SizedBox();
|
||
|
|
},
|
||
|
|
child: child,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool get _isTouchPlatform => switch (defaultTargetPlatform) {
|
||
|
|
TargetPlatform.android ||
|
||
|
|
TargetPlatform.iOS ||
|
||
|
|
TargetPlatform.fuchsia => true,
|
||
|
|
_ => false,
|
||
|
|
};
|