Skip to content

Commit

Permalink
feat: supabase integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Kingkor Roy Tirtho committed May 16, 2023
1 parent fb780da commit 8bcce92
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
SUPABASE_URL=
SUPABASE_API_KEY=

# The format:
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
SPOTIFY_SECRETS=
Expand Down
6 changes: 6 additions & 0 deletions lib/collections/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ part 'env.g.dart';

@Envied(obfuscate: true, requireEnvFile: true, path: ".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')
static final spotifySecrets = _Env.spotifySecrets.split(',').map((e) {
final secrets = e.trim().split(":").map((e) => e.trim());
Expand Down
10 changes: 10 additions & 0 deletions lib/components/shared/image/universal_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ class UniversalImage extends HookWidget {
height: height,
width: width,
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,
);
} else if (Uri.tryParse(path) != null && !path.startsWith("assets")) {
Expand Down
7 changes: 7 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:media_kit/media_kit.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:package_info_plus/package_info_plus.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/collections/routes.dart';
import 'package:spotube/collections/intents.dart';
Expand All @@ -28,6 +29,7 @@ import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/youtube.dart';
import 'package:spotube/themes/theme.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:path_provider/path_provider.dart';
import 'package:spotube/hooks/use_init_sys_tray.dart';
Expand Down Expand Up @@ -71,6 +73,11 @@ Future<void> main(List<String> rawArgs) async {
exit(0);
}

await Supabase.initialize(
url: Env.supabaseUrl,
anonKey: Env.supabaseAnonKey,
);

final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();

FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
Expand Down
30 changes: 29 additions & 1 deletion lib/models/matched_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,37 @@ class MatchedTrack {
@HiveField(1)
String spotifyId;

String? id;
DateTime? createdAt;

bool get isSynced => id != null;

static const boxName = "oss.krtirtho.spotube.matched_tracks";

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);
}
}
1 change: 1 addition & 0 deletions lib/models/spotube_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:spotube/extensions/album_simple.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/models/matched_track.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/supabase.dart';
import 'package:spotube/services/youtube.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart';
Expand Down
18 changes: 18 additions & 0 deletions lib/provider/proxy_playlist/next_fetcher_mixin.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'package:catcher/catcher.dart';
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.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/provider/proxy_playlist/proxy_playlist.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/supabase.dart';

mixin NextFetcher on StateNotifier<ProxyPlaylist> {
Future<List<SpotubeTrack>> fetchTracks(
Expand Down Expand Up @@ -78,4 +81,19 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
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);
}
}
}
44 changes: 41 additions & 3 deletions lib/provider/proxy_playlist/proxy_playlist_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
isPreSearching = true;

// TODO: Make repeat mode sensitive changes later
final oldTrack =
state.tracks.elementAtOrNull(audioPlayer.currentIndex);
final track =
await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);

Expand All @@ -92,6 +94,13 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
if (audioPlayer.isPaused) {
await audioPlayer.resume();
}

if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
} finally {
isPreSearching = false;
}
Expand Down Expand Up @@ -120,9 +129,10 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
return null;
}

final nthFetchedTrack = nthTrack is SpotubeTrack
? nthTrack
: await SpotubeTrack.fetchFromTrack(nthTrack, preferences);
final nthFetchedTrack = switch (nthTrack.runtimeType) {
SpotubeTrack => nthTrack as SpotubeTrack,
_ => await SpotubeTrack.fetchFromTrack(nthTrack, preferences),
};

if (nthSource == nthFetchedTrack.ytUri) return null;

Expand Down Expand Up @@ -196,14 +206,27 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
initialIndex: initialIndex,
autoPlay: autoPlay,
);

await storeTrack(
tracks[initialIndex],
addableTrack,
);
}

Future<void> jumpTo(int index) async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex);
final track = await ensureNthSourcePlayable(index);
if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
}
await audioPlayer.jumpTo(index);

if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
}

Future<void> jumpToTrack(Track track) async {
Expand Down Expand Up @@ -234,19 +257,34 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
Future<void> swapSibling(PipedSearchItem video) async {}

Future<void> next() async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex + 1);
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
}
await audioPlayer.skipToNext();

if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
}

Future<void> previous() async {
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex - 1);
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex - 1);
if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
}
await audioPlayer.skipToPrevious();
if (oldTrack != null && track != null) {
await storeTrack(
oldTrack,
track,
);
}
}

Future<void> stop() async {
Expand Down
12 changes: 12 additions & 0 deletions lib/services/supabase.dart
Original file line number Diff line number Diff line change
@@ -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();
4 changes: 4 additions & 0 deletions macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation

import app_links
import audio_service
import audio_session
import catcher
Expand All @@ -16,6 +17,7 @@ import package_info_plus
import path_provider_foundation
import screen_retriever
import shared_preferences_foundation
import sign_in_with_apple
import sqflite
import system_theme
import system_tray
Expand All @@ -24,6 +26,7 @@ import window_manager
import window_size

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
Expand All @@ -35,6 +38,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin"))
SystemTrayPlugin.register(with: registry.registrar(forPlugin: "SystemTrayPlugin"))
Expand Down
Loading

0 comments on commit 8bcce92

Please sign in to comment.