77 lines
3 KiB
Dart
77 lines
3 KiB
Dart
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';
|
|
import 'theme/tokens.dart';
|
|
import 'widgets/splash_screen.dart';
|
|
|
|
Future<void> 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) {
|
|
final settings = ref.watch(settingsProvider);
|
|
|
|
// Point the token getters at the selected theme's palette before the theme
|
|
// (and the widget tree) read them.
|
|
final palette = settings.appTheme == AppTheme.lavender
|
|
? TimbrePalette.lavender
|
|
: TimbrePalette.dark;
|
|
TimbreColors.applyPalette(palette);
|
|
|
|
// The accent either tracks the album art (default theme, dynamic mode) or
|
|
// is pinned — to the user's static color, or to the theme's own accent when
|
|
// the theme locks it (Lavender). The theme rebuilds whenever it changes.
|
|
final Color accent;
|
|
if (settings.appTheme == AppTheme.lavender) {
|
|
accent = palette.accentDefault; // locked lavender primary
|
|
} else if (settings.useStaticAccent) {
|
|
accent = settings.staticAccentColor;
|
|
} else {
|
|
accent = 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),
|
|
// AppShell mounts under the splash and boots behind it; the splash fades
|
|
// out after a couple of seconds. Keying AppShell by the theme forces a
|
|
// remount on theme switch so every widget re-reads the (now runtime)
|
|
// TimbreColors getters; providers live above TimbreApp, so playback and
|
|
// library state survive the remount.
|
|
home: SplashGate(child: AppShell(key: ValueKey(settings.appTheme))),
|
|
);
|
|
}
|
|
}
|