Merge pull request #1688 from KRTirtho/feat/desktop-webview

feat(desktop): webview based login support
This commit is contained in:
Kingkor Roy Tirtho 2024-07-06 21:43:29 +06:00 committed by GitHub
commit 15211123aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 158 additions and 309 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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(

View File

@ -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';
@ -46,6 +47,12 @@ import 'package:timezone/data/latest.dart' as tz;
import 'package:window_manager/window_manager.dart';
Future<void> main(List<String> rawArgs) async {
if (rawArgs.contains("web_view_title_bar")) {
WidgetsFlutterBinding.ensureInitialized();
if (runWebViewTitleBarWidget(rawArgs)) {
return;
}
}
final arguments = await startCLI(rawArgs);
AppLogger.initialize(arguments["verbose"]);

View File

@ -65,7 +65,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

View File

@ -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),
)
],
),
);
}
}

View File

@ -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",
),
),
],
),
],
),
),
),
),
),
);
}
}

View File

@ -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<State<IntroductionScreen>>();
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("/");
},
),
],
),
),
],
),
);
}
}

View File

@ -1,17 +1,24 @@
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';
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 +30,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 +40,52 @@ class SettingsAccountSection extends HookConsumerWidget {
foregroundColor: Colors.white,
);
void onLogin() async {
if (kIsMobile) {
router.pushNamed(WebViewLogin.name);
return;
}
final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status");
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(
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", "")}";
await authNotifier.login(cookieHeader);
webview.close();
if (context.mounted) {
context.go("/");
}
});
}
return true;
});
}
return SectionCardWithHeading(
heading: context.l10n.account,
children: [
@ -70,17 +124,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(

View File

@ -287,6 +287,10 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
await ref.read(sourcedTrackProvider(intendedActiveTrack).future);
}
if(medias.isEmpty) return;
await removeCollections(state.collections);
await audioPlayer.openPlaylist(
medias,
initialIndex: initialIndex,

View File

@ -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';
@ -96,7 +97,7 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
await database
.into(database.authenticationTable)
.insert(refreshedCredentials);
.insert(refreshedCredentials, mode: InsertMode.replace);
}
Future<AuthenticationTableCompanion> credentialsFromCookie(
@ -160,6 +161,9 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
WebStorageManager.instance().deleteAllData();
CookieManager.instance().deleteAllCookies();
}
if (kIsDesktop) {
await WebviewWindow.clearAll();
}
}
}

View File

@ -152,11 +152,12 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier<SubtitleSimple, Track?> {
}
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,
);
}

View File

@ -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';

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -35,13 +35,14 @@ Future<void> 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<void> 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<void> migratePreferences() async {
systemTitleBar: Value(preferences.systemTitleBar),
themeMode: Value(preferences.themeMode),
),
mode: InsertMode.replace,
);
AppLogger.log.i("✅ Migrated preferences");
@ -235,12 +237,13 @@ Future<void> 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");

View File

@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <gtk/gtk_plugin.h>
@ -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);

View File

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dart_discord_rpc
desktop_webview_window
file_selector_linux
flutter_secure_storage_linux
gtk

View File

@ -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

View File

@ -16,6 +16,8 @@ requires:
- avahi
- mdns-scan
- nss-mdns
- webkit2gtk4.1
- libsoup3
display_name: Spotube

View File

@ -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"))

View File

@ -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

View File

@ -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:

View File

@ -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
@ -127,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

View File

@ -9,6 +9,7 @@
#include <app_links/app_links_plugin_c_api.h>
#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_notifier/local_notifier_plugin.h>
@ -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(

View File

@ -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