spotube/lib/main.dart
2023-02-03 18:49:47 +06:00

343 lines
11 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:catcher/catcher.dart';
import 'package:fl_query/fl_query.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/collections/cache_keys.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
import 'package:spotube/entities/cache_track.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/collections/intents.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/audio_player_provider.dart';
import 'package:spotube/provider/downloader_provider.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/provider/youtube_provider.dart';
import 'package:spotube/services/pocketbase.dart';
import 'package:spotube/themes/dark_theme.dart';
import 'package:spotube/themes/light_theme.dart';
import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
import 'package:window_size/window_size.dart';
final bowl = QueryBowl();
void main(List<String> rawArgs) async {
final parser = ArgParser();
parser.addFlag(
'verbose',
abbr: 'v',
help: 'Verbose mode',
callback: (verbose) {
if (verbose) {
Platform.environment['VERBOSE'] = 'true';
Platform.environment['DEBUG'] = 'true';
Platform.environment['ERROR'] = 'true';
}
},
);
parser.addFlag(
"version",
help: "Print version and exit",
negatable: false,
);
parser.addFlag("help", abbr: "h", negatable: false);
final arguments = parser.parse(rawArgs);
if (arguments["help"] == true) {
print(parser.usage);
exit(0);
}
if (arguments["version"] == true) {
final package = await PackageInfo.fromPlatform();
print("Spotube v${package.version}");
exit(0);
}
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(CacheTrackAdapter());
Hive.registerAdapter(CacheTrackEngagementAdapter());
Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
await Env.configure();
if (kIsDesktop) {
await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions(
center: true,
backgroundColor: Colors.transparent,
titleBarStyle: TitleBarStyle.hidden,
title: "Spotube",
);
setWindowMinSize(const Size(kReleaseMode ? 1020 : 300, 700));
await windowManager.waitUntilReadyToShow(windowOptions, () async {
final localStorage = await SharedPreferences.getInstance();
final rawSize = localStorage.getString(LocalStorageKeys.windowSizeInfo);
final savedSize = rawSize != null ? json.decode(rawSize) : null;
final wasMaximized = savedSize?["maximized"] ?? false;
final double? height = savedSize?["height"];
final double? width = savedSize?["width"];
await windowManager.setResizable(true);
if (wasMaximized) {
await windowManager.maximize();
} else if (height != null && width != null) {
await windowManager.setSize(Size(width, height));
}
await windowManager.show();
});
}
Catcher(
enableLogger: arguments["verbose"] ?? !kReleaseMode,
debugConfig: CatcherOptions(
SilentReportMode(),
[
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
),
FileHandler(await getLogsPath(), printLogs: false),
SnackbarHandler(
const Duration(seconds: 5),
action: SnackBarAction(
label: "Dismiss",
onPressed: () {
ScaffoldMessenger.of(
Catcher.navigatorKey!.currentContext!,
).hideCurrentSnackBar();
},
),
),
],
),
releaseConfig: CatcherOptions(SilentReportMode(), [
if (arguments["verbose"] ?? false)
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
),
FileHandler(
await getLogsPath(),
printLogs: false,
),
]),
runAppFunction: () {
runApp(
Builder(
builder: (context) {
return ProviderScope(
overrides: [
downloaderProvider.overrideWith(
(ref) {
return Downloader(
ref,
queueInstance,
yt: ref.watch(youtubeProvider),
downloadPath: ref.watch(
userPreferencesProvider.select(
(s) => s.downloadLocation,
),
),
onFileExists: (track) {
final logger = getLogger(Downloader);
try {
logger.v(
"[onFileExists] download confirmation for ${track.name}",
);
return showPlatformAlertDialog<bool>(
context,
builder: (_) =>
ReplaceDownloadedDialog(track: track),
).then((s) => s ?? false);
} catch (e, stack) {
Catcher.reportCheckedError(e, stack);
return false;
}
},
);
},
)
],
child: QueryBowlScope(
bowl: bowl,
child: const Spotube(),
),
);
},
),
);
},
);
await initializePocketBase();
}
class Spotube extends StatefulHookConsumerWidget {
const Spotube({Key? key}) : super(key: key);
@override
SpotubeState createState() => SpotubeState();
/// ↓↓ ADDED
/// InheritedWidget style accessor to our State object.
static SpotubeState of(BuildContext context) =>
context.findAncestorStateOfType<SpotubeState>()!;
}
class SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
final logger = getLogger(Spotube);
SharedPreferences? localStorage;
Size? prevSize;
@override
void initState() {
super.initState();
SharedPreferences.getInstance().then(((value) => localStorage = value));
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
appPlatform = Theme.of(context).platform;
});
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() async {
super.didChangeMetrics();
if (kIsMobile) return;
final size = await windowManager.getSize();
final windowSameDimension =
prevSize?.width == size.width && prevSize?.height == size.height;
if (localStorage == null || windowSameDimension) return;
final isMaximized = await windowManager.isMaximized();
localStorage!.setString(
LocalStorageKeys.windowSizeInfo,
jsonEncode({
'maximized': isMaximized,
'width': size.width,
'height': size.height,
}),
);
prevSize = size;
}
TargetPlatform appPlatform = TargetPlatform.android;
void changePlatform(TargetPlatform targetPlatform) {
appPlatform = targetPlatform;
setState(() {});
}
@override
Widget build(BuildContext context) {
final themeMode =
ref.watch(userPreferencesProvider.select((s) => s.themeMode));
final accentMaterialColor =
ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme));
final backgroundMaterialColor = ref
.watch(userPreferencesProvider.select((s) => s.backgroundColorScheme));
final player = ref.watch(audioPlayerProvider);
final youtube = ref.watch(youtubeProvider);
useEffect(() {
return () {
player.dispose();
youtube.close();
};
}, []);
platform = appPlatform;
return PlatformApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
routeInformationProvider: router.routeInformationProvider,
debugShowCheckedModeBanner: false,
title: 'Spotube',
builder: (context, child) {
return DragToResizeArea(child: child!);
},
androidTheme: lightTheme(
accentMaterialColor: accentMaterialColor,
backgroundMaterialColor: backgroundMaterialColor,
),
androidDarkTheme: darkTheme(
accentMaterialColor: accentMaterialColor,
backgroundMaterialColor: backgroundMaterialColor,
),
linuxTheme: linuxTheme,
linuxDarkTheme: linuxDarkTheme,
iosTheme: themeMode == ThemeMode.dark ? iosDarkTheme : iosTheme,
windowsTheme: windowsTheme,
windowsDarkTheme: windowsDarkTheme,
macosTheme: macosTheme,
macosDarkTheme: macosDarkTheme,
themeMode: themeMode,
shortcuts: PlatformProperty.all({
...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.keyB,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.browse),
LogicalKeySet(
LogicalKeyboardKey.keyS,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.search),
LogicalKeySet(
LogicalKeyboardKey.keyL,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.library),
LogicalKeySet(
LogicalKeyboardKey.keyY,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): HomeTabIntent(ref, tab: HomeTabs.lyrics),
LogicalKeySet(
LogicalKeyboardKey.keyW,
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
): CloseAppIntent(),
}),
actions: PlatformProperty.all({
...WidgetsApp.defaultActions,
PlayPauseIntent: PlayPauseAction(),
NavigationIntent: NavigationAction(),
HomeTabIntent: HomeTabAction(),
CloseAppIntent: CloseAppAction(),
}),
);
}
}