From 15bd58a95597925637bf66866af59c2780aeff97 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 11:21:32 +0600 Subject: [PATCH 01/10] feat(desktop): implement webview based login --- lib/collections/routes.dart | 13 +- lib/main.dart | 2 +- lib/modules/desktop_login/login_form.dart | 69 ---------- lib/pages/desktop_login/desktop_login.dart | 78 ----------- lib/pages/desktop_login/login_tutorial.dart | 125 ------------------ lib/pages/settings/sections/accounts.dart | 44 ++++-- .../authentication/authentication.dart | 4 + linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile.lock | 6 + pubspec.lock | 9 ++ pubspec.yaml | 5 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 15 files changed, 73 insertions(+), 293 deletions(-) delete mode 100644 lib/modules/desktop_login/login_form.dart delete mode 100644 lib/pages/desktop_login/desktop_login.dart delete mode 100644 lib/pages/desktop_login/login_tutorial.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index b3cba581..3bf1d883 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -34,12 +34,9 @@ import 'package:spotube/pages/stats/streams/streams.dart'; import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; -import 'package:spotube/utils/platform.dart'; import 'package:spotube/components/spotube_page_route.dart'; import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/pages/library/library.dart'; -import 'package:spotube/pages/desktop_login/login_tutorial.dart'; -import 'package:spotube/pages/desktop_login/desktop_login.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; import 'package:spotube/pages/root/root_app.dart'; import 'package:spotube/pages/settings/settings.dart'; @@ -313,16 +310,8 @@ final routerProvider = Provider((ref) { path: "/login", name: WebViewLogin.name, parentNavigatorKey: rootNavigatorKey, - pageBuilder: (context, state) => SpotubePage( - child: kIsMobile ? const WebViewLogin() : const DesktopLoginPage(), - ), - ), - GoRoute( - path: "/login-tutorial", - name: LoginTutorial.name, - parentNavigatorKey: rootNavigatorKey, pageBuilder: (context, state) => const SpotubePage( - child: LoginTutorial(), + child: WebViewLogin(), ), ), GoRoute( diff --git a/lib/main.dart b/lib/main.dart index cb553115..b3a3132f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -65,7 +65,7 @@ Future main(List rawArgs) async { await FlutterDisplayMode.setHighRefreshRate(); } - if (kIsDesktop) { + if (kIsDesktop && !kIsMacOS) { await windowManager.setPreventClose(true); } diff --git a/lib/modules/desktop_login/login_form.dart b/lib/modules/desktop_login/login_form.dart deleted file mode 100644 index e5d31215..00000000 --- a/lib/modules/desktop_login/login_form.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/extensions/context.dart'; - -import 'package:spotube/provider/authentication/authentication.dart'; - -class TokenLoginForm extends HookConsumerWidget { - final void Function()? onDone; - const TokenLoginForm({ - super.key, - this.onDone, - }); - - @override - Widget build(BuildContext context, ref) { - final authenticationNotifier = ref.watch(authenticationProvider.notifier); - final directCodeController = useTextEditingController(); - - final isLoading = useState(false); - - return ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400, - ), - child: Column( - children: [ - TextField( - controller: directCodeController, - decoration: InputDecoration( - hintText: context.l10n.spotify_cookie("\"sp_dc\""), - labelText: context.l10n.cookie_name_cookie("sp_dc"), - ), - keyboardType: TextInputType.visiblePassword, - ), - const SizedBox(height: 10), - FilledButton( - onPressed: isLoading.value - ? null - : () async { - try { - isLoading.value = true; - if (directCodeController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.fill_in_all_fields), - behavior: SnackBarBehavior.floating, - ), - ); - return; - } - final cookieHeader = - "sp_dc=${directCodeController.text.trim()}"; - - await authenticationNotifier.login(cookieHeader); - if (context.mounted) { - onDone?.call(); - } - } finally { - isLoading.value = false; - } - }, - child: Text(context.l10n.submit), - ) - ], - ), - ); - } -} diff --git a/lib/pages/desktop_login/desktop_login.dart b/lib/pages/desktop_login/desktop_login.dart deleted file mode 100644 index 80548898..00000000 --- a/lib/pages/desktop_login/desktop_login.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/modules/desktop_login/login_form.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/mobile_login/mobile_login.dart'; - -class DesktopLoginPage extends HookConsumerWidget { - static const name = WebViewLogin.name; - const DesktopLoginPage({super.key}); - - @override - Widget build(BuildContext context, ref) { - final mediaQuery = MediaQuery.of(context); - final theme = Theme.of(context); - final color = theme.colorScheme.surfaceVariant.withOpacity(.3); - - return SafeArea( - child: Scaffold( - appBar: const PageWindowTitleBar( - leading: BackButton(), - ), - body: SingleChildScrollView( - child: Center( - child: Container( - margin: const EdgeInsets.all(10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - Assets.spotubeLogoPng.image( - width: MediaQuery.of(context).size.width * - (mediaQuery.mdAndDown ? .5 : .3), - ), - Text( - context.l10n.add_spotify_credentials, - style: theme.textTheme.titleMedium, - ), - Text( - context.l10n.credentials_will_not_be_shared_disclaimer, - style: theme.textTheme.labelMedium, - ), - const SizedBox(height: 10), - TokenLoginForm( - onDone: () => GoRouter.of(context).go("/"), - ), - const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text(context.l10n.know_how_to_login), - TextButton( - child: Text( - context.l10n.follow_step_by_step_guide, - ), - onPressed: () => GoRouter.of(context).push( - "/login-tutorial", - ), - ), - ], - ), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/desktop_login/login_tutorial.dart b/lib/pages/desktop_login/login_tutorial.dart deleted file mode 100644 index ec62543c..00000000 --- a/lib/pages/desktop_login/login_tutorial.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:introduction_screen/introduction_screen.dart'; - -import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/modules/desktop_login/login_form.dart'; -import 'package:spotube/components/links/hyper_link.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/home/home.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/utils/service_utils.dart'; - -class LoginTutorial extends ConsumerWidget { - static const name = "login_tutorial"; - const LoginTutorial({super.key}); - - @override - Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); - final key = GlobalKey>(); - final theme = Theme.of(context); - - final pageDecoration = PageDecoration( - bodyTextStyle: theme.textTheme.bodyMedium!, - titleTextStyle: theme.textTheme.headlineMedium!, - ); - return Scaffold( - appBar: PageWindowTitleBar( - leading: TextButton( - child: Text(context.l10n.exit), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: IntroductionScreen( - key: key, - globalBackgroundColor: theme.scaffoldBackgroundColor, - overrideBack: OutlinedButton( - child: Center(child: Text(context.l10n.previous)), - onPressed: () { - (key.currentState as IntroductionScreenState).previous(); - }, - ), - overrideNext: FilledButton( - child: Center(child: Text(context.l10n.next)), - onPressed: () { - (key.currentState as IntroductionScreenState).next(); - }, - ), - showBackButton: true, - overrideDone: FilledButton( - onPressed: auth.asData?.value != null - ? () { - ServiceUtils.pushNamed(context, HomePage.name); - } - : null, - child: Center(child: Text(context.l10n.done)), - ), - pages: [ - PageViewModel( - decoration: pageDecoration, - title: context.l10n.step_1, - image: Assets.tutorial.step1.image(), - bodyWidget: Wrap( - children: [ - Text(context.l10n.first_go_to), - const SizedBox(width: 5), - const Hyperlink( - "accounts.spotify.com ", - "https://accounts.spotify.com", - ), - Text(context.l10n.login_if_not_logged_in), - ], - ), - ), - PageViewModel( - decoration: pageDecoration, - title: context.l10n.step_2, - image: Assets.tutorial.step2.image(), - bodyWidget: - Text(context.l10n.step_2_steps, textAlign: TextAlign.left), - ), - PageViewModel( - decoration: pageDecoration, - title: context.l10n.step_3, - image: Assets.tutorial.step3.image(), - bodyWidget: - Text(context.l10n.step_3_steps, textAlign: TextAlign.left), - ), - if (auth.asData?.value != null) - PageViewModel( - decoration: pageDecoration.copyWith( - bodyAlignment: Alignment.center, - ), - title: context.l10n.success_emoji, - image: Assets.success.image(), - body: context.l10n.success_message, - ) - else - PageViewModel( - decoration: pageDecoration, - title: context.l10n.step_4, - bodyWidget: Column( - children: [ - Text( - context.l10n.step_4_steps, - style: theme.textTheme.labelMedium, - ), - const SizedBox(height: 10), - TokenLoginForm( - onDone: () { - GoRouter.of(context).go("/"); - }, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index b06a67f6..1ec488c9 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -1,4 +1,5 @@ import 'package:auto_size_text/auto_size_text.dart'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -8,10 +9,12 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/pages/mobile_login/mobile_login.dart'; import 'package:spotube/pages/profile/profile.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/service_utils.dart'; class SettingsAccountSection extends HookConsumerWidget { @@ -23,6 +26,7 @@ class SettingsAccountSection extends HookConsumerWidget { final router = GoRouter.of(context); final auth = ref.watch(authenticationProvider); + final authNotifier = ref.watch(authenticationProvider.notifier); final scrobbler = ref.watch(scrobblerProvider); final me = ref.watch(meProvider); final meData = me.asData?.value; @@ -32,6 +36,36 @@ class SettingsAccountSection extends HookConsumerWidget { foregroundColor: Colors.white, ); + void onLogin() async { + if (kIsMobile) { + router.pushNamed(WebViewLogin.name); + return; + } + + final webview = await WebviewWindow.create(); + + webview.setOnUrlRequestCallback((url) { + final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); + + if (exp.hasMatch(url)) { + webview.getAllCookies().then((cookies) async { + final cookieHeader = + "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; + + await authNotifier.login(cookieHeader); + webview.close(); + if (context.mounted) { + context.go("/"); + } + }); + return true; + } + return false; + }); + + webview.launch("https://accounts.spotify.com/"); + } + return SectionCardWithHeading( heading: context.l10n.account, children: [ @@ -70,17 +104,11 @@ class SettingsAccountSection extends HookConsumerWidget { ), ), ), - onTap: constrains.mdAndUp - ? null - : () { - router.push("/login"); - }, + onTap: constrains.mdAndUp ? null : onLogin, trailing: constrains.smAndDown ? null : FilledButton( - onPressed: () { - router.push("/login"); - }, + onPressed: onLogin, style: ButtonStyle( shape: MaterialStateProperty.all( RoundedRectangleBorder( diff --git a/lib/provider/authentication/authentication.dart b/lib/provider/authentication/authentication.dart index 3ea8693b..08e658e8 100644 --- a/lib/provider/authentication/authentication.dart +++ b/lib/provider/authentication/authentication.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:collection/collection.dart'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:drift/drift.dart'; @@ -160,6 +161,9 @@ class AuthenticationNotifier extends AsyncNotifier { WebStorageManager.instance().deleteAllData(); CookieManager.instance().deleteAllCookies(); } + if (kIsDesktop) { + await WebviewWindow.clearAll(); + } } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index b8e26367..2218d110 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -24,6 +25,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dart_discord_rpc_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DartDiscordRpcPlugin"); dart_discord_rpc_plugin_register_with_registrar(dart_discord_rpc_registrar); + g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); + desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 20d4a4dd..bb0776b5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dart_discord_rpc + desktop_webview_window file_selector_linux flutter_secure_storage_linux gtk diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 54546705..8a65bb53 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import app_links import audio_service import audio_session import bonsoir_darwin +import desktop_webview_window import device_info_plus import file_selector_macos import flutter_inappwebview_macos @@ -32,6 +33,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin")) + DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 58d09cd9..9ab2ee38 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -8,6 +8,8 @@ PODS: - bonsoir_darwin (0.0.1): - Flutter - FlutterMacOS + - desktop_webview_window (0.0.1): + - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS - file_selector_macos (0.0.1): @@ -70,6 +72,7 @@ DEPENDENCIES: - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - bonsoir_darwin (from `Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin`) + - desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) @@ -105,6 +108,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos bonsoir_darwin: :path: Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin + desktop_webview_window: + :path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos file_selector_macos: @@ -151,6 +156,7 @@ SPEC CHECKSUMS: audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9 audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72 bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842 + desktop_webview_window: 89bb3d691f4c80314a10be312f4cd35db93a9d5a device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d diff --git a/pubspec.lock b/pubspec.lock index 70b0655c..14f2a5b0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -474,6 +474,15 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + desktop_webview_window: + dependency: "direct main" + description: + path: "packages/desktop_webview_window" + ref: "feat/cookies" + resolved-ref: f20e433d4a948515b35089d40069f7dd9bced9e4 + url: "https://github.com/KRTirtho/flutter-plugins.git" + source: git + version: "0.2.4" device_info_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a923f5a3..a9490746 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,11 @@ dependencies: collection: ^1.15.0 curved_navigation_bar: ^1.0.3 dbus: ^0.7.8 + desktop_webview_window: + git: + url: https://github.com/KRTirtho/flutter-plugins.git + ref: feat/cookies + path: packages/desktop_webview_window device_info_plus: ^10.1.0 dio: ^5.4.3+1 disable_battery_optimization: ^1.1.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b978edb9..4fcf3019 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi")); DartDiscordRpcPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DartDiscordRpcPlugin")); + DesktopWebviewWindowPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fcc467a..d0dd6751 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links bonsoir_windows dart_discord_rpc + desktop_webview_window file_selector_windows flutter_secure_storage_windows local_notifier From 1284b409e72d2e9e5ac2ed94f7884e38bd19657c Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 11:31:47 +0600 Subject: [PATCH 02/10] chore: add linux dependencies and update CI + docker config --- .github/Dockerfile.flutter_distributor | 2 +- CONTRIBUTION.md | 6 +++--- cli/commands/install-dependencies.dart | 2 +- linux/packaging/deb/make_config.yaml | 2 ++ linux/packaging/rpm/make_config.yaml | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/Dockerfile.flutter_distributor b/.github/Dockerfile.flutter_distributor index 952b9158..d842e533 100644 --- a/.github/Dockerfile.flutter_distributor +++ b/.github/Dockerfile.flutter_distributor @@ -4,7 +4,7 @@ ARG FLUTTER_VERSION RUN apt-get clean &&\ apt-get update &&\ - apt-get install -y bash curl file git unzip xz-utils zip libglu1-mesa cmake tar clang ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev rpm && \ + apt-get install -y bash curl file git unzip xz-utils zip libglu1-mesa cmake tar clang ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev rpm libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev && \ rm -rf /var/lib/apt/lists/* WORKDIR /home/flutter diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 0cfff0ca..d4746a1a 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -123,16 +123,16 @@ Do the following: - Install Development dependencies in linux - Debian (>=12/Bookworm)/Ubuntu ```bash - $ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev avahi-daemon avahi-discover avahi-utils libnss-mdns mdns-scan + $ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev avahi-daemon avahi-discover avahi-utils libnss-mdns mdns-scan libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev ``` - Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04) - Arch/Manjaro ```bash - yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify avahi nss-mdns mdns-scan + yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify avahi nss-mdns mdns-scan webkit2gtk-4.1 libsoup3 ``` - Fedora ```bash - dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel avahi mdns-scan nss-mdns + dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel avahi mdns-scan nss-mdns webkit2gtk4.1 webkit2gtk4.1-devel libsoup3 libsoup3-devel ``` - Clone the Repo - Create a `.env` in root of the project following the `.env.example` template diff --git a/cli/commands/install-dependencies.dart b/cli/commands/install-dependencies.dart index 75df28df..6875e35f 100644 --- a/cli/commands/install-dependencies.dart +++ b/cli/commands/install-dependencies.dart @@ -37,7 +37,7 @@ class InstallDependenciesCommand extends Command { await shell.run( """ sudo apt-get update -y - sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev + sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev """, ); break; diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 95777f56..a7bea1aa 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -23,6 +23,8 @@ dependencies: - avahi-utils - libnss-mdns - mdns-scan + - libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-0 + - libsoup-3.0-0 | libsoup-2.4-0 essential: false icon: assets/spotube-logo.png diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index 12b4473e..3d4a3b7e 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -16,6 +16,8 @@ requires: - avahi - mdns-scan - nss-mdns + - webkit2gtk4.1 + - libsoup3 display_name: Spotube From 79b842dad32d64ee5bd968f0c3552634b25f8daa Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 11:55:04 +0600 Subject: [PATCH 03/10] chore: use flutter 3.19.6 to avoid window stretching error in windows --- .github/workflows/spotube-release-binary.yml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 5b74c9b5..e99aebab 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -20,7 +20,7 @@ on: description: Dry run without uploading to release env: - FLUTTER_VERSION: 3.22.1 + FLUTTER_VERSION: 3.19.6 permissions: contents: write diff --git a/pubspec.yaml b/pubspec.yaml index a9490746..58ca0ae9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -132,7 +132,7 @@ dependencies: encrypt: ^5.0.3 dev_dependencies: - build_runner: ^2.4.11 + build_runner: ^2.4.9 crypto: ^3.0.3 envied_generator: ^0.5.4+1 flutter_gen_runner: ^5.4.0 From f2f35bd2fbdd046441f4aa5da7f7572292a40b1a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 13:32:07 +0600 Subject: [PATCH 04/10] chore: fix windows webview2 trying to store data in admin folders --- lib/main.dart | 3 ++- lib/models/database/database.dart | 2 +- lib/pages/settings/sections/accounts.dart | 17 ++++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b3a3132f..e0e34254 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:dart_discord_rpc/dart_discord_rpc.dart'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -65,7 +66,7 @@ Future main(List rawArgs) async { await FlutterDisplayMode.setHighRefreshRate(); } - if (kIsDesktop && !kIsMacOS) { + if (kIsDesktop) { await windowManager.setPreventClose(true); } diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 1c233f84..fbfc59db 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -66,7 +66,7 @@ LazyDatabase _openConnection() { return LazyDatabase(() async { // put the database file, called db.sqlite here, into the documents folder // for your app. - final dbFolder = await getApplicationDocumentsDirectory(); + final dbFolder = await getApplicationSupportDirectory(); final file = File(join(dbFolder.path, 'db.sqlite')); // Also work around limitations on old Android versions diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index 1ec488c9..0c2f3c5e 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -1,8 +1,12 @@ +import 'dart:io'; + import 'package:auto_size_text/auto_size_text.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -42,7 +46,18 @@ class SettingsAccountSection extends HookConsumerWidget { return; } - final webview = await WebviewWindow.create(); + final applicationSupportDir = await getApplicationSupportDirectory(); + final userDataFolder = Directory( + join(applicationSupportDir.path, "webview_window_Webview2")); + + if (!await userDataFolder.exists()) { + await userDataFolder.create(); + } + + final webview = await WebviewWindow.create( + configuration: + CreateConfiguration(userDataFolderWindows: userDataFolder.path), + ); webview.setOnUrlRequestCallback((url) { final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); From 7dd76d24c3c8082a0ac37a4018d807aaeca1c3d2 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 14:54:49 +0600 Subject: [PATCH 05/10] chore: fix windows cookie invalid characters --- lib/main.dart | 6 ++++ lib/pages/settings/sections/accounts.dart | 44 ++++++++++++----------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e0e34254..db7773f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,6 +47,12 @@ import 'package:timezone/data/latest.dart' as tz; import 'package:window_manager/window_manager.dart'; Future main(List rawArgs) async { + WidgetsFlutterBinding.ensureInitialized(); + + if (runWebViewTitleBarWidget(rawArgs)) { + return; + } + final arguments = await startCLI(rawArgs); AppLogger.initialize(arguments["verbose"]); diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index 0c2f3c5e..596599be 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -46,6 +46,7 @@ class SettingsAccountSection extends HookConsumerWidget { return; } + final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); final applicationSupportDir = await getApplicationSupportDirectory(); final userDataFolder = Directory( join(applicationSupportDir.path, "webview_window_Webview2")); @@ -55,30 +56,33 @@ class SettingsAccountSection extends HookConsumerWidget { } final webview = await WebviewWindow.create( - configuration: - CreateConfiguration(userDataFolderWindows: userDataFolder.path), + configuration: CreateConfiguration( + title: "Spotify Login", + titleBarTopPadding: kIsMacOS ? 20 : 0, + windowHeight: 720, + windowWidth: 1280, + userDataFolderWindows: userDataFolder.path, + ), ); + webview + ..setBrightness(theme.colorScheme.brightness) + ..launch("https://accounts.spotify.com/") + ..setOnUrlRequestCallback((url) { + if (exp.hasMatch(url)) { + webview.getAllCookies().then((cookies) async { + final cookieHeader = + "sp_dc=${cookies.firstWhere((element) => element.name.contains("sp_dc")).value.replaceAll("\u0000", "")}"; - webview.setOnUrlRequestCallback((url) { - final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); + await authNotifier.login(cookieHeader); + webview.close(); + if (context.mounted) { + context.go("/"); + } + }); + } - if (exp.hasMatch(url)) { - webview.getAllCookies().then((cookies) async { - final cookieHeader = - "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; - - await authNotifier.login(cookieHeader); - webview.close(); - if (context.mounted) { - context.go("/"); - } - }); return true; - } - return false; - }); - - webview.launch("https://accounts.spotify.com/"); + }); } return SectionCardWithHeading( From 359b918e6bb0a2c4792492b8cc84761af1c8aea4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 5 Jul 2024 15:32:18 +0600 Subject: [PATCH 06/10] chore: fix windows playback not working for loop back ipv4 --- lib/main.dart | 10 ++++------ lib/services/audio_player/audio_player.dart | 5 +++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index db7773f9..45f4462d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,18 +47,16 @@ import 'package:timezone/data/latest.dart' as tz; import 'package:window_manager/window_manager.dart'; Future main(List rawArgs) async { - WidgetsFlutterBinding.ensureInitialized(); - - if (runWebViewTitleBarWidget(rawArgs)) { - return; - } - final arguments = await startCLI(rawArgs); AppLogger.initialize(arguments["verbose"]); AppLogger.runZoned(() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + if (runWebViewTitleBarWidget(rawArgs)) { + return; + } + await registerWindowsScheme("spotify"); tz.initializeTimeZones(); diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index bb1a6203..7915dc3b 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -12,6 +12,7 @@ import 'package:media_kit/media_kit.dart' as mk; import 'package:spotube/services/audio_player/playback_state.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; +import 'package:spotube/utils/platform.dart'; part 'audio_players_streams_mixin.dart'; part 'audio_player_impl.dart'; @@ -28,7 +29,7 @@ class SpotubeMedia extends mk.Media { }) : super( track is LocalTrack ? track.path - : "http://${InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}", + : "http://${kIsWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}", extras: { ...?extras, "track": switch (track) { @@ -42,7 +43,7 @@ class SpotubeMedia extends mk.Media { @override String get uri => track is LocalTrack ? (track as LocalTrack).path - : "http://${InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}"; + : "http://${kIsWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}"; factory SpotubeMedia.fromMedia(mk.Media media) { final track = media.uri.startsWith("http") From 2f46fa32f13da3d500a3593e445c43f9a88ac54d Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 6 Jul 2024 18:31:17 +0600 Subject: [PATCH 07/10] chore: fix webview and app window freezing after successful login --- lib/main.dart | 10 ++++++---- lib/pages/settings/sections/accounts.dart | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 45f4462d..7e8da0f2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,16 +47,18 @@ import 'package:timezone/data/latest.dart' as tz; import 'package:window_manager/window_manager.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(); - if (runWebViewTitleBarWidget(rawArgs)) { - return; - } - await registerWindowsScheme("spotify"); tz.initializeTimeZones(); diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index 596599be..7e37b68b 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -74,6 +74,7 @@ class SettingsAccountSection extends HookConsumerWidget { "sp_dc=${cookies.firstWhere((element) => element.name.contains("sp_dc")).value.replaceAll("\u0000", "")}"; await authNotifier.login(cookieHeader); + webview.close(); if (context.mounted) { context.go("/"); From 2ce4853fd1e12ab4616a52d7827484bffd5012a6 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 6 Jul 2024 19:26:59 +0600 Subject: [PATCH 08/10] chore: fix while loading playlists/album already playing ones doesn't get cleared --- lib/provider/audio_player/audio_player.dart | 4 ++++ lib/provider/authentication/authentication.dart | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index da22b2ce..5323f3c0 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -287,6 +287,10 @@ class AudioPlayerNotifier extends Notifier { await ref.read(sourcedTrackProvider(intendedActiveTrack).future); } + if(medias.isEmpty) return; + + await removeCollections(state.collections); + await audioPlayer.openPlaylist( medias, initialIndex: initialIndex, diff --git a/lib/provider/authentication/authentication.dart b/lib/provider/authentication/authentication.dart index 08e658e8..f7339ef0 100644 --- a/lib/provider/authentication/authentication.dart +++ b/lib/provider/authentication/authentication.dart @@ -97,7 +97,7 @@ class AuthenticationNotifier extends AsyncNotifier { await database .into(database.authenticationTable) - .insert(refreshedCredentials); + .insertOnConflictUpdate(refreshedCredentials); } Future credentialsFromCookie( From ccea4a003d84853a68bffe39796b3d70bd34f0be Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 6 Jul 2024 21:35:56 +0600 Subject: [PATCH 09/10] fix: changed source doesn't get saved and uses the wrong once again --- lib/services/sourced_track/sources/jiosaavn.dart | 11 ++++++++++- lib/services/sourced_track/sources/piped.dart | 11 ++++++++++- lib/services/sourced_track/sources/youtube.dart | 15 +++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart index 865e3d63..1434e4f7 100644 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ b/lib/services/sourced_track/sources/jiosaavn.dart @@ -43,7 +43,12 @@ class JioSaavnSourcedTrack extends SourcedTrack { }) async { final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!))) + ..where((s) => s.trackId.equals(track.id!)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) .getSingleOrNull(); if (cachedSource == null || @@ -215,7 +220,11 @@ class JioSaavnSourcedTrack extends SourcedTrack { trackId: id!, sourceId: info.id, sourceType: const Value(SourceType.jiosaavn), + // Because we're sorting by createdAt in the query + // we have to update it to indicate priority + createdAt: Value(DateTime.now()), ), + mode: InsertMode.replace, ); return JioSaavnSourcedTrack( diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart index d156b26e..d24f110f 100644 --- a/lib/services/sourced_track/sources/piped.dart +++ b/lib/services/sourced_track/sources/piped.dart @@ -52,7 +52,12 @@ class PipedSourcedTrack extends SourcedTrack { }) async { final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!))) + ..where((s) => s.trackId.equals(track.id!)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) .getSingleOrNull(); final preferences = ref.read(userPreferencesProvider); final pipedClient = ref.read(pipedProvider); @@ -278,7 +283,11 @@ class PipedSourcedTrack extends SourcedTrack { trackId: id!, sourceId: newSourceInfo.id, sourceType: const Value(SourceType.youtube), + // Because we're sorting by createdAt in the query + // we have to update it to indicate priority + createdAt: Value(DateTime.now()), ), + mode: InsertMode.replace, ); return PipedSourcedTrack( diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 0501a499..0b5ee71b 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -50,8 +50,14 @@ class YoutubeSourcedTrack extends SourcedTrack { }) async { final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!))) - .getSingleOrNull(); + ..where((s) => s.trackId.equals(track.id!)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) + .get() + .then((s) => s.firstOrNull); if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) { final siblings = await fetchSiblings(ref: ref, track: track); @@ -287,12 +293,17 @@ class YoutubeSourcedTrack extends SourcedTrack { ); final database = ref.read(databaseProvider); + await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( trackId: id!, sourceId: newSourceInfo.id, sourceType: const Value(SourceType.youtube), + // Because we're sorting by createdAt in the query + // we have to update it to indicate priority + createdAt: Value(DateTime.now()), ), + mode: InsertMode.replace, ); return YoutubeSourcedTrack( From 86f5b80177b5c1d3da2ff9fbe7694165c48ac190 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 6 Jul 2024 21:38:36 +0600 Subject: [PATCH 10/10] chore: fix insert failing to invalid conflict check --- lib/provider/authentication/authentication.dart | 2 +- lib/provider/spotify/lyrics/synced.dart | 3 ++- lib/provider/spotify/spotify.dart | 1 + lib/utils/migrations/hive.dart | 9 ++++++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/provider/authentication/authentication.dart b/lib/provider/authentication/authentication.dart index f7339ef0..05a05972 100644 --- a/lib/provider/authentication/authentication.dart +++ b/lib/provider/authentication/authentication.dart @@ -97,7 +97,7 @@ class AuthenticationNotifier extends AsyncNotifier { await database .into(database.authenticationTable) - .insertOnConflictUpdate(refreshedCredentials); + .insert(refreshedCredentials, mode: InsertMode.replace); } Future credentialsFromCookie( diff --git a/lib/provider/spotify/lyrics/synced.dart b/lib/provider/spotify/lyrics/synced.dart index bcf2a162..085fccb7 100644 --- a/lib/provider/spotify/lyrics/synced.dart +++ b/lib/provider/spotify/lyrics/synced.dart @@ -152,11 +152,12 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { } if (cachedLyrics == null || cachedLyrics.lyrics.isEmpty) { - await database.into(database.lyricsTable).insertOnConflictUpdate( + await database.into(database.lyricsTable).insert( LyricsTableCompanion.insert( trackId: track.id!, data: lyrics, ), + mode: InsertMode.replace, ); } diff --git a/lib/provider/spotify/spotify.dart b/lib/provider/spotify/spotify.dart index 63a8ed38..5997a47a 100644 --- a/lib/provider/spotify/spotify.dart +++ b/lib/provider/spotify/spotify.dart @@ -2,6 +2,7 @@ library spotify; import 'dart:async'; +import 'package:drift/drift.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/spotify/utils/json_cast.dart'; diff --git a/lib/utils/migrations/hive.dart b/lib/utils/migrations/hive.dart index e43df1d8..e5781931 100644 --- a/lib/utils/migrations/hive.dart +++ b/lib/utils/migrations/hive.dart @@ -35,13 +35,14 @@ Future migrateAuthenticationInfo() async { if (credentials == null) return; - await _database.into(_database.authenticationTable).insertOnConflictUpdate( + await _database.into(_database.authenticationTable).insert( AuthenticationTableCompanion.insert( accessToken: DecryptedText(credentials.accessToken), cookie: DecryptedText(credentials.cookie), expiration: credentials.expiration, id: const Value(0), ), + mode: InsertMode.insertOrReplace, ); AppLogger.log.i("✅ Migrated authentication info"); @@ -58,7 +59,7 @@ Future migratePreferences() async { if (preferences == null) return; - await _database.into(_database.preferencesTable).insertOnConflictUpdate( + await _database.into(_database.preferencesTable).insert( PreferencesTableCompanion.insert( id: const Value(0), accentColorScheme: Value(preferences.accentColorScheme), @@ -108,6 +109,7 @@ Future migratePreferences() async { systemTitleBar: Value(preferences.systemTitleBar), themeMode: Value(preferences.themeMode), ), + mode: InsertMode.replace, ); AppLogger.log.i("✅ Migrated preferences"); @@ -235,12 +237,13 @@ Future migrateLastFmCredentials() async { if (data == null) return; - await _database.into(_database.scrobblerTable).insertOnConflictUpdate( + await _database.into(_database.scrobblerTable).insert( ScrobblerTableCompanion.insert( id: const Value(0), passwordHash: DecryptedText(data.passwordHash), username: data.username, ), + mode: InsertMode.replace, ); AppLogger.log.i("✅ Migrated Last.fm credentials");