mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Merge pull request #1688 from KRTirtho/feat/desktop-webview
feat(desktop): webview based login support
This commit is contained in:
commit
15211123aa
2
.github/Dockerfile.flutter_distributor
vendored
2
.github/Dockerfile.flutter_distributor
vendored
@ -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
|
||||
|
2
.github/workflows/spotube-release-binary.yml
vendored
2
.github/workflows/spotube-release-binary.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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"]);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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("/");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dart_discord_rpc
|
||||
desktop_webview_window
|
||||
file_selector_linux
|
||||
flutter_secure_storage_linux
|
||||
gtk
|
||||
|
@ -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
|
||||
|
@ -16,6 +16,8 @@ requires:
|
||||
- avahi
|
||||
- mdns-scan
|
||||
- nss-mdns
|
||||
- webkit2gtk4.1
|
||||
- libsoup3
|
||||
|
||||
display_name: Spotube
|
||||
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user