import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio_background/just_audio_background.dart'; import 'settings/settings_store.dart'; import 'shell/app_shell.dart'; import 'state/providers.dart'; import 'theme/accent.dart'; import 'theme/app_theme.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); // Lock-screen / notification transport is only available where audio_service // has a backend — Android/iOS. Skipping init elsewhere keeps the Linux dev // target (and tests) running. if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { // Never let background-audio setup block the first frame: on failure or // hang we still start the app (playback just loses lock-screen controls). try { await JustAudioBackground.init( androidNotificationChannelId: 'com.laforrestchurch.timbre.audio', androidNotificationChannelName: 'Timbre playback', androidNotificationOngoing: true, ).timeout(const Duration(seconds: 8)); } catch (e, st) { debugPrint('JustAudioBackground.init failed: $e\n$st'); } } runApp(const ProviderScope(child: TimbreApp())); } class TimbreApp extends ConsumerWidget { const TimbreApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // The accent either tracks the album art (default) or is pinned to a // user-chosen static color. The theme rebuilds whenever it changes. final settings = ref.watch(settingsProvider); final accent = settings.useStaticAccent ? settings.staticAccentColor : ref.watch(accentProvider); // Keep the accent synced with the remote track's art while acting as a // remote (bug #6). Alive as long as the app is. ref.watch(remoteAccentSyncProvider); return MaterialApp( title: 'Timbre', debugShowCheckedModeBanner: false, theme: buildTimbreTheme(accent), home: const AppShell(), ); } }