feat: supabase integration

This commit is contained in:
Kingkor Roy Tirtho 2023-05-16 12:36:49 +06:00
parent fb780da327
commit 8bcce9282e
14 changed files with 280 additions and 4 deletions

View File

@ -1,3 +1,6 @@
SUPABASE_URL=
SUPABASE_API_KEY=
# The format: # The format:
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2 # SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
SPOTIFY_SECRETS= SPOTIFY_SECRETS=

View File

@ -4,6 +4,12 @@ part 'env.g.dart';
@Envied(obfuscate: true, requireEnvFile: true, path: ".env") @Envied(obfuscate: true, requireEnvFile: true, path: ".env")
abstract class Env { abstract class Env {
@EnviedField(varName: 'SUPABASE_URL')
static final supabaseUrl = _Env.supabaseUrl;
@EnviedField(varName: 'SUPABASE_API_KEY')
static final supabaseAnonKey = _Env.supabaseAnonKey;
@EnviedField(varName: 'SPOTIFY_SECRETS') @EnviedField(varName: 'SPOTIFY_SECRETS')
static final spotifySecrets = _Env.spotifySecrets.split(',').map((e) { static final spotifySecrets = _Env.spotifySecrets.split(',').map((e) {
final secrets = e.trim().split(":").map((e) => e.trim()); final secrets = e.trim().split(":").map((e) => e.trim());

View File

@ -59,6 +59,16 @@ class UniversalImage extends HookWidget {
height: height, height: height,
width: width, width: width,
placeholder: AssetImage(placeholder ?? Assets.placeholder.path), placeholder: AssetImage(placeholder ?? Assets.placeholder.path),
imageErrorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholder ?? Assets.placeholder.path,
width: width,
height: height,
cacheHeight: height?.toInt(),
cacheWidth: width?.toInt(),
scale: scale,
);
},
fit: fit, fit: fit,
); );
} else if (Uri.tryParse(path) != null && !path.startsWith("assets")) { } else if (Uri.tryParse(path) != null && !path.startsWith("assets")) {

View File

@ -15,6 +15,7 @@ import 'package:media_kit/media_kit.dart';
import 'package:metadata_god/metadata_god.dart'; import 'package:metadata_god/metadata_god.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart'; import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/routes.dart';
import 'package:spotube/collections/intents.dart'; import 'package:spotube/collections/intents.dart';
@ -28,6 +29,7 @@ import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/youtube.dart'; import 'package:spotube/services/youtube.dart';
import 'package:spotube/themes/theme.dart'; import 'package:spotube/themes/theme.dart';
import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/persisted_state_notifier.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:system_theme/system_theme.dart'; import 'package:system_theme/system_theme.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:spotube/hooks/use_init_sys_tray.dart'; import 'package:spotube/hooks/use_init_sys_tray.dart';
@ -71,6 +73,11 @@ Future<void> main(List<String> rawArgs) async {
exit(0); exit(0);
} }
await Supabase.initialize(
url: Env.supabaseUrl,
anonKey: Env.supabaseAnonKey,
);
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);

View File

@ -9,9 +9,37 @@ class MatchedTrack {
@HiveField(1) @HiveField(1)
String spotifyId; String spotifyId;
String? id;
DateTime? createdAt;
bool get isSynced => id != null;
static const boxName = "oss.krtirtho.spotube.matched_tracks"; static const boxName = "oss.krtirtho.spotube.matched_tracks";
static LazyBox<MatchedTrack> get box => Hive.lazyBox<MatchedTrack>(boxName); static LazyBox<MatchedTrack> get box => Hive.lazyBox<MatchedTrack>(boxName);
MatchedTrack({required this.youtubeId, required this.spotifyId}); MatchedTrack({
required this.youtubeId,
required this.spotifyId,
this.id,
this.createdAt,
});
factory MatchedTrack.fromJson(Map<String, dynamic> json) {
return MatchedTrack(
youtubeId: json["youtube_id"],
spotifyId: json["spotify_id"],
id: json["id"],
createdAt: DateTime.parse(json["created_at"]),
);
}
Map<String, dynamic> toJson() {
return {
"youtube_id": youtubeId,
"spotify_id": spotifyId,
"id": id,
"created_at": createdAt?.toString()
}..removeWhere((key, value) => value == null);
}
} }

View File

@ -8,6 +8,7 @@ import 'package:spotube/extensions/album_simple.dart';
import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/models/matched_track.dart'; import 'package:spotube/models/matched_track.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/supabase.dart';
import 'package:spotube/services/youtube.dart'; import 'package:spotube/services/youtube.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';

View File

@ -1,10 +1,13 @@
import 'package:catcher/catcher.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/local_track.dart';
import 'package:spotube/models/matched_track.dart';
import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/supabase.dart';
mixin NextFetcher on StateNotifier<ProxyPlaylist> { mixin NextFetcher on StateNotifier<ProxyPlaylist> {
Future<List<SpotubeTrack>> fetchTracks( Future<List<SpotubeTrack>> fetchTracks(
@ -78,4 +81,19 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
return "https://youtube.com/unplayable.m4a?id=${track.id}"; return "https://youtube.com/unplayable.m4a?id=${track.id}";
} }
} }
/// This method must be called after any playback operation as
/// it can increase the latency
Future<void> storeTrack(Track track, SpotubeTrack spotubeTrack) async {
if (track is! SpotubeTrack) {
await supabase
.insertTrack(
MatchedTrack(
youtubeId: spotubeTrack.ytTrack.id,
spotifyId: spotubeTrack.id!,
),
)
.catchError(Catcher.reportCheckedError);
}
}
} }

View File

@ -75,6 +75,8 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
isPreSearching = true; isPreSearching = true;
// TODO: Make repeat mode sensitive changes later // TODO: Make repeat mode sensitive changes later
final oldTrack =
state.tracks.elementAtOrNull(audioPlayer.currentIndex);
final track = final track =
await ensureNthSourcePlayable(audioPlayer.currentIndex + 1); await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
@ -92,6 +94,13 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
if (audioPlayer.isPaused) { if (audioPlayer.isPaused) {
await audioPlayer.resume(); await audioPlayer.resume();
} }
if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
} finally { } finally {
isPreSearching = false; isPreSearching = false;
} }
@ -120,9 +129,10 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
return null; return null;
} }
final nthFetchedTrack = nthTrack is SpotubeTrack final nthFetchedTrack = switch (nthTrack.runtimeType) {
? nthTrack SpotubeTrack => nthTrack as SpotubeTrack,
: await SpotubeTrack.fetchFromTrack(nthTrack, preferences); _ => await SpotubeTrack.fetchFromTrack(nthTrack, preferences),
};
if (nthSource == nthFetchedTrack.ytUri) return null; if (nthSource == nthFetchedTrack.ytUri) return null;
@ -196,14 +206,27 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
initialIndex: initialIndex, initialIndex: initialIndex,
autoPlay: autoPlay, autoPlay: autoPlay,
); );
await storeTrack(
tracks[initialIndex],
addableTrack,
);
} }
Future<void> jumpTo(int index) async { Future<void> jumpTo(int index) async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex);
final track = await ensureNthSourcePlayable(index); final track = await ensureNthSourcePlayable(index);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(tracks: mergeTracks([track], state.tracks));
} }
await audioPlayer.jumpTo(index); await audioPlayer.jumpTo(index);
if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
} }
Future<void> jumpToTrack(Track track) async { Future<void> jumpToTrack(Track track) async {
@ -234,19 +257,34 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
Future<void> swapSibling(PipedSearchItem video) async {} Future<void> swapSibling(PipedSearchItem video) async {}
Future<void> next() async { Future<void> next() async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex + 1);
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex + 1); final track = await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(tracks: mergeTracks([track], state.tracks));
} }
await audioPlayer.skipToNext(); await audioPlayer.skipToNext();
if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
} }
Future<void> previous() async { Future<void> previous() async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex - 1);
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex - 1); final track = await ensureNthSourcePlayable(audioPlayer.currentIndex - 1);
if (track != null) { if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks)); state = state.copyWith(tracks: mergeTracks([track], state.tracks));
} }
await audioPlayer.skipToPrevious(); await audioPlayer.skipToPrevious();
if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
} }
Future<void> stop() async { Future<void> stop() async {

View File

@ -0,0 +1,12 @@
import 'package:spotube/models/matched_track.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class SupabaseService {
static SupabaseClient get api => Supabase.instance.client;
Future<void> insertTrack(MatchedTrack track) async {
await api.from("tracks").insert(track.toJson());
}
}
final supabase = SupabaseService();

View File

@ -5,6 +5,7 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import app_links
import audio_service import audio_service
import audio_session import audio_session
import catcher import catcher
@ -16,6 +17,7 @@ import package_info_plus
import path_provider_foundation import path_provider_foundation
import screen_retriever import screen_retriever
import shared_preferences_foundation import shared_preferences_foundation
import sign_in_with_apple
import sqflite import sqflite
import system_theme import system_theme
import system_tray import system_tray
@ -24,6 +26,7 @@ import window_manager
import window_size import window_size
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin")) CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
@ -35,6 +38,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin")) SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin"))
SystemTrayPlugin.register(with: registry.registrar(forPlugin: "SystemTrayPlugin")) SystemTrayPlugin.register(with: registry.registrar(forPlugin: "SystemTrayPlugin"))

View File

@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.0" version: "5.4.0"
app_links:
dependency: transitive
description:
name: app_links
sha256: d572dcdff49c4cfcfa6f315e2683e518ec6eb54e084d01e51d9631a4dcc1b5e8
url: "https://pub.dev"
source: hosted
version: "3.4.2"
app_package_maker: app_package_maker:
dependency: transitive dependency: transitive
description: description:
@ -777,6 +785,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
functions_client:
dependency: transitive
description:
name: functions_client
sha256: "26059c5fb000ffd0986ae3144d43c2a6f54931610fd61c2584e18e308c7eaa52"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
fuzzywuzzy: fuzzywuzzy:
dependency: "direct main" dependency: "direct main"
description: description:
@ -801,6 +817,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.6" version: "6.0.6"
gotrue:
dependency: transitive
description:
name: gotrue
sha256: c08f5ac76dcae2dd06cc7f8e80a8ede12c66454fef06caac3b191c8c7a603811
url: "https://pub.dev"
source: hosted
version: "1.7.1"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -966,6 +990,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.7" version: "0.4.7"
jwt_decode:
dependency: transitive
description:
name: jwt_decode
sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb
url: "https://pub.dev"
source: hosted
version: "0.3.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -1312,6 +1344,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.8+1" version: "0.2.8+1"
postgrest:
dependency: transitive
description:
name: postgrest
sha256: "7b91eb7b40621d07aaae687f47f3032f30e1b86a9ccebfcfca52d001223f8b6e"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -1376,6 +1416,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
realtime_client:
dependency: transitive
description:
name: realtime_client
sha256: "0f2614f72e5639ddd7abc3dede336f23554f9f744d0b064d41009f9ca94a53d2"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
retry:
dependency: transitive
description:
name: retry
sha256: a8a1e475a100a0bdc73f529ca8ea1e9c9c76bec8ad86a1f47780139a34ce7aea
url: "https://pub.dev"
source: hosted
version: "3.1.1"
riverpod: riverpod:
dependency: transitive dependency: transitive
description: description:
@ -1512,6 +1568,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.15.0"
sign_in_with_apple:
dependency: transitive
description:
name: sign_in_with_apple
sha256: ac3b113767dfdd765078c507dad9d4d9fe96b669cc7bd88fc36fc15376fb3400
url: "https://pub.dev"
source: hosted
version: "4.3.0"
sign_in_with_apple_platform_interface:
dependency: transitive
description:
name: sign_in_with_apple_platform_interface
sha256: a5883edee09ed6be19de19e7d9f618a617fe41a6fa03f76d082dfb787e9ea18d
url: "https://pub.dev"
source: hosted
version: "1.0.0"
sign_in_with_apple_web:
dependency: transitive
description:
name: sign_in_with_apple_web
sha256: "44b66528f576e77847c14999d5e881e17e7223b7b0625a185417829e5306f47a"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
simple_circular_progress_bar: simple_circular_progress_bar:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1606,6 +1686,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2+1" version: "0.7.2+1"
storage_client:
dependency: transitive
description:
name: storage_client
sha256: "4ed4dc8a990d178c96962319d6d8c267c3e206fca2c2b98660bad6e001220ffc"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -1630,6 +1718,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
supabase:
dependency: transitive
description:
name: supabase
sha256: "403739cdfea48ba633450e5b191ceeaae81ac10ec89166c0e109235b3e1532f3"
url: "https://pub.dev"
source: hosted
version: "1.8.1"
supabase_flutter:
dependency: "direct main"
description:
name: supabase_flutter
sha256: "7cbdd9a7264dd5b7ab5a6e2da63346054b8e5ddf358467c7f2bc23d5c14d732c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
sync_http: sync_http:
dependency: transitive dependency: transitive
description: description:
@ -1878,6 +1982,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.2"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "1acea8def62592123e2fbbca164ed8681a98a890bdcbb88f916d5b4a22687759"
url: "https://pub.dev"
source: hosted
version: "3.7.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "78715dc442b7849dbde74e92bb67de1cecf5addf95531c5fb474e72f5fe9a507"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "4646bb68297803bdbb96d46853e8fcb560d6cb5e04153fa64581535767875dfe"
url: "https://pub.dev"
source: hosted
version: "3.4.3"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -1935,6 +2071,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
yet_another_json_isolate:
dependency: transitive
description:
name: yet_another_json_isolate
sha256: "94ba4947ac1ce44bd6a1634d9df712e07b9b5025ba12abd6750be77ba5c08f18"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.10.0"

View File

@ -101,6 +101,7 @@ dependencies:
git: git:
url: https://github.com/KRTirtho/piped_client url: https://github.com/KRTirtho/piped_client
ref: 2036a78d3414a0fc7fe3b081f1029dd086352fcd ref: 2036a78d3414a0fc7fe3b081f1029dd086352fcd
supabase_flutter: ^1.9.1
dev_dependencies: dev_dependencies:
build_runner: ^2.3.2 build_runner: ^2.3.2

View File

@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
#include <catcher/catcher_plugin.h> #include <catcher/catcher_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_notifier/local_notifier_plugin.h> #include <local_notifier/local_notifier_plugin.h>
@ -19,6 +20,8 @@
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
CatcherPluginRegisterWithRegistrar( CatcherPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("CatcherPlugin")); registry->GetRegistrarForPlugin("CatcherPlugin"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links
catcher catcher
flutter_secure_storage_windows flutter_secure_storage_windows
local_notifier local_notifier