import 'dart:async'; import 'dart:ui'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart'; import 'package:flutter_discord_rpc/flutter_discord_rpc.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get_it/get_it.dart'; import 'package:home_widget/home_widget.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:local_notifier/local_notifier.dart'; import 'package:media_kit/media_kit.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:smtc_windows/smtc_windows.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/initializers.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/hooks/configurators/use_close_behavior.dart'; import 'package:spotube/hooks/configurators/use_deep_linking.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; import 'package:spotube/hooks/configurators/use_fix_window_stretching.dart'; import 'package:spotube/hooks/configurators/use_get_storage_perms.dart'; import 'package:spotube/hooks/configurators/use_has_touch.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/provider/audio_player/audio_player_streams.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/glance/glance.dart'; import 'package:spotube/provider/server/bonsoir.dart'; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/provider/tray_manager/tray_manager.dart'; import 'package:spotube/l10n/l10n.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/cli/cli.dart'; import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/wm_tools/wm_tools.dart'; import 'package:spotube/utils/migrations/sandbox.dart'; import 'package:spotube/utils/platform.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:window_manager/window_manager.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:yt_dlp_dart/yt_dlp_dart.dart'; import 'package:flutter_new_pipe_extractor/flutter_new_pipe_extractor.dart'; Future main(List rawArgs) async { if (rawArgs.contains("web_view_title_bar")) { WidgetsFlutterBinding.ensureInitialized(); if (runWebViewTitleBarWidget(rawArgs)) { return; } } final arguments = await startCLI(rawArgs); AppLogger.initialize(arguments["verbose"]); AppLogger.runZoned(() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); GetIt.I.registerSingleton( await SharedPreferences.getInstance(), ); GetIt.I.registerSingletonWithDependencies( () => KVStoreService.init(), dependsOn: [SharedPreferences], ); await registerWindowsScheme("spotify"); tz.initializeTimeZones(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); MediaKit.ensureInitialized(); await migrateMacOsFromSandboxToNoSandbox(); // force High Refresh Rate on some Android devices (like One Plus) if (kIsAndroid) { await FlutterDisplayMode.setHighRefreshRate(); await NewPipeExtractor.init(); } if (!kIsWeb) { MetadataGod.initialize(); } if (kIsDesktop) { await windowManager.setPreventClose(true); await YtDlp.instance .setBinaryLocation( KVStoreService().getYoutubeEnginePath(YoutubeClientEngine.ytDlp) ?? "yt-dlp${kIsWindows ? '.exe' : ''}", ) .catchError((e, stack) => null); await FlutterDiscordRPC.initialize(Env.discordAppId); } if (kIsWindows) { await SMTCWindows.initialize(); } await EncryptedKvStoreService.initialize(); final database = AppDatabase(); if (kIsDesktop) { await localNotifier.setup(appName: "Spotube"); await WindowManagerTools.initialize(); } if (kIsIOS) { HomeWidget.setAppGroupId("group.spotube_home_player_widget"); } runApp( ProviderScope( overrides: [ databaseProvider.overrideWith((ref) => database), ], observers: const [ AppLoggerProviderObserver(), ], child: const Spotube(), ), ); }); } class Spotube extends HookConsumerWidget { const Spotube({super.key}); @override Widget build(BuildContext context, ref) { final themeMode = ref.watch(userPreferencesProvider.select((s) => s.themeMode)); final locale = ref.watch(userPreferencesProvider.select((s) => s.locale)); final accentMaterialColor = ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme)); final router = useMemoized(() => AppRouter(ref), []); final hasTouchSupport = useHasTouch(); ref.listen(audioPlayerStreamListenersProvider, (_, __) {}); ref.listen(bonsoirProvider, (_, __) {}); ref.listen(connectClientsProvider, (_, __) {}); ref.listen(serverProvider, (_, __) {}); ref.listen(trayManagerProvider, (_, __) {}); useFixWindowStretching(); useDisableBatteryOptimizations(); useDeepLinking(ref, router); useCloseBehavior(ref); useGetStoragePermissions(ref); useEffect(() { FlutterNativeSplash.remove(); if (kIsMobile) { HomeWidget.registerInteractivityCallback(glanceBackgroundCallback); } return () { /// For enabling hot reload for audio player if (!kDebugMode) return; audioPlayer.dispose(); }; }, []); return ShadcnApp.router( supportedLocales: L10n.all, locale: locale.languageCode == "system" ? null : locale, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], routerConfig: router.config(), debugShowCheckedModeBanner: false, title: 'Spotube', builder: (context, child) { child = ScrollConfiguration( behavior: ScrollConfiguration.of(context).copyWith( dragDevices: hasTouchSupport ? { PointerDeviceKind.touch, PointerDeviceKind.stylus, PointerDeviceKind.invertedStylus, } : null, ), child: child!, ); if (kIsLinux) { child = DragToResizeArea( resizeEdgeSize: 2.5, child: child, ); } return child; }, scaling: const AdaptiveScaling(1), theme: ThemeData( radius: .5, iconTheme: const IconThemeProperties(), colorScheme: colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ?? ColorSchemes.lightOrange(), surfaceOpacity: .8, surfaceBlur: 10, ), darkTheme: ThemeData( radius: .5, iconTheme: const IconThemeProperties(), colorScheme: colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ?? ColorSchemes.darkOrange(), surfaceOpacity: .8, surfaceBlur: 10, ), materialTheme: material.ThemeData( brightness: switch (themeMode) { ThemeMode.system => MediaQuery.platformBrightnessOf(context), ThemeMode.light => Brightness.light, ThemeMode.dark => Brightness.dark, }, splashFactory: material.NoSplash.splashFactory, appBarTheme: const material.AppBarTheme( surfaceTintColor: Colors.transparent, scrolledUnderElevation: 0, shadowColor: Colors.transparent, elevation: 0, ), ), themeMode: themeMode, shortcuts: { ...WidgetsApp.defaultShortcuts.map((key, value) { return MapEntry( LogicalKeySet.fromSet(key.triggers?.toSet() ?? {}), value, ); }), LogicalKeySet(LogicalKeyboardKey.space): PlayPauseIntent(ref), LogicalKeySet(LogicalKeyboardKey.comma, LogicalKeyboardKey.control): NavigationIntent(router, "/settings"), LogicalKeySet( LogicalKeyboardKey.digit1, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.browse), LogicalKeySet( LogicalKeyboardKey.digit2, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.search), LogicalKeySet( LogicalKeyboardKey.digit3, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.lyrics), LogicalKeySet( LogicalKeyboardKey.digit4, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.userPlaylists), LogicalKeySet( LogicalKeyboardKey.digit5, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.userArtists), LogicalKeySet( LogicalKeyboardKey.digit6, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.userAlbums), LogicalKeySet( LogicalKeyboardKey.digit7, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.userLocalLibrary), LogicalKeySet( LogicalKeyboardKey.digit8, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): HomeTabIntent(router, tab: HomeTabs.userDownloads), LogicalKeySet( LogicalKeyboardKey.keyW, LogicalKeyboardKey.control, LogicalKeyboardKey.shift, ): CloseAppIntent(), }, actions: { ...WidgetsApp.defaultActions, PlayPauseIntent: PlayPauseAction(), NavigationIntent: NavigationAction(), HomeTabIntent: HomeTabAction(), CloseAppIntent: CloseAppAction(), }, ); } }