mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat: piped instance picker on settings
This commit is contained in:
parent
3aeb026776
commit
bed0d3bd70
@ -79,4 +79,5 @@ abstract class SpotubeIcons {
|
|||||||
static const colorSync = FeatherIcons.activity;
|
static const colorSync = FeatherIcons.activity;
|
||||||
static const language = FeatherIcons.globe;
|
static const language = FeatherIcons.globe;
|
||||||
static const error = FeatherIcons.alertTriangle;
|
static const error = FeatherIcons.alertTriangle;
|
||||||
|
static const piped = FeatherIcons.cloud;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,13 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
|
|
||||||
final Breakpoints breakAfterOr;
|
final Breakpoints breakAfterOr;
|
||||||
|
|
||||||
|
/// Show the smaller value when the breakpoint is reached
|
||||||
|
///
|
||||||
|
/// If false, the control will be hidden when the breakpoint is reached
|
||||||
|
///
|
||||||
|
/// Defaults to `true`
|
||||||
|
final bool showValueWhenUnfolded;
|
||||||
|
|
||||||
const AdaptiveSelectTile({
|
const AdaptiveSelectTile({
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.value,
|
required this.value,
|
||||||
@ -23,6 +30,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.secondary,
|
this.secondary,
|
||||||
this.breakAfterOr = Breakpoints.md,
|
this.breakAfterOr = Breakpoints.md,
|
||||||
|
this.showValueWhenUnfolded = true,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +57,8 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
|
|
||||||
final control = breakpoint >= breakAfterOr
|
final control = breakpoint >= breakAfterOr
|
||||||
? rawControl
|
? rawControl
|
||||||
: Container(
|
: showValueWhenUnfolded
|
||||||
|
? Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
@ -64,7 +73,8 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
),
|
),
|
||||||
child: controlPlaceholder,
|
child: controlPlaceholder,
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -182,5 +182,7 @@
|
|||||||
"success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে",
|
"success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে",
|
||||||
"step_4": "ধাপ 4",
|
"step_4": "ধাপ 4",
|
||||||
"step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন",
|
"step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন",
|
||||||
"something_went_wrong": "কিছু ভুল হয়েছে"
|
"something_went_wrong": "কিছু ভুল হয়েছে",
|
||||||
|
"piped_instance": "Piped সার্ভার এড্রেস",
|
||||||
|
"piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার\n এগুলোর মধ্যে কিছু ভাল কাজ নাও করতে পারে৷ তাই নিজ দায়িত্বে ব্যবহার করুন"
|
||||||
}
|
}
|
@ -182,5 +182,7 @@
|
|||||||
"success_message": "Now you're successfully Logged In with your Spotify account. Good Job, mate!",
|
"success_message": "Now you're successfully Logged In with your Spotify account. Good Job, mate!",
|
||||||
"step_4": "Step 4",
|
"step_4": "Step 4",
|
||||||
"step_4_steps": "Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields",
|
"step_4_steps": "Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields",
|
||||||
"something_went_wrong": "Something went wrong"
|
"something_went_wrong": "Something went wrong",
|
||||||
|
"piped_instance": "Piped Server Instance",
|
||||||
|
"piped_description": "The Piped server instance to use for track matching\nSome of them might not work well. So use at your own risk"
|
||||||
}
|
}
|
@ -182,5 +182,7 @@
|
|||||||
"success_message": "Vous êtes maintenant connecté avec succès à votre compte Spotify. Bon travail, mon ami!",
|
"success_message": "Vous êtes maintenant connecté avec succès à votre compte Spotify. Bon travail, mon ami!",
|
||||||
"step_4": "Étape 4",
|
"step_4": "Étape 4",
|
||||||
"step_4_steps": "Collez les valeurs copiées de \"sp_dc\" et \"sp_key\" dans les champs respectifs",
|
"step_4_steps": "Collez les valeurs copiées de \"sp_dc\" et \"sp_key\" dans les champs respectifs",
|
||||||
"something_went_wrong": "Quelque chose s'est mal passé"
|
"something_went_wrong": "Quelque chose s'est mal passé",
|
||||||
|
"piped_instance": "Instance pipée",
|
||||||
|
"piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes\nCertaines d'entre elles peuvent ne pas fonctionner correctement. Alors utilisez à vos risques et périls"
|
||||||
}
|
}
|
@ -182,5 +182,7 @@
|
|||||||
"success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!",
|
"success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!",
|
||||||
"step_4": "स्टेप 4",
|
"step_4": "स्टेप 4",
|
||||||
"step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" मानों को संबंधित फील्ड में पेस्ट करें",
|
"step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" मानों को संबंधित फील्ड में पेस्ट करें",
|
||||||
"something_went_wrong": "कुछ गलत हो गया"
|
"something_went_wrong": "कुछ गलत हो गया",
|
||||||
|
"piped_instance": "पाइप्ड सर्वर",
|
||||||
|
"piped_description": "पाइप किए गए सर्वर\n गानों का मिलान करने के लिए उपयोग किए जाते हैं, हो सकता है कि उनमें से कुछ के साथ ठीक से काम न करें इसलिए अपने जोखिम पर उपयोग करें"
|
||||||
}
|
}
|
@ -27,7 +27,6 @@ import 'package:spotube/provider/downloader_provider.dart';
|
|||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/youtube.dart';
|
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
import 'package:spotube/utils/custom_toast_handler.dart';
|
import 'package:spotube/utils/custom_toast_handler.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
@ -206,10 +205,6 @@ class SpotubeState extends ConsumerState<Spotube> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
SharedPreferences.getInstance().then(((value) => localStorage = value));
|
SharedPreferences.getInstance().then(((value) => localStorage = value));
|
||||||
|
|
||||||
/// Doing the initialization here to avoid loading time
|
|
||||||
/// when in offline mode
|
|
||||||
PipedSpotube.initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -8,7 +8,6 @@ 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/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';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -67,7 +66,10 @@ class SpotubeTrack extends Track {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<PipedSearchItemStream>> fetchSiblings(Track track) async {
|
static Future<List<PipedSearchItemStream>> fetchSiblings(
|
||||||
|
Track track,
|
||||||
|
PipedClient client,
|
||||||
|
) async {
|
||||||
final artists = (track.artists ?? [])
|
final artists = (track.artists ?? [])
|
||||||
.map((ar) => ar.name)
|
.map((ar) => ar.name)
|
||||||
.toList()
|
.toList()
|
||||||
@ -80,7 +82,7 @@ class SpotubeTrack extends Track {
|
|||||||
onlyCleanArtist: true,
|
onlyCleanArtist: true,
|
||||||
).trim();
|
).trim();
|
||||||
|
|
||||||
final List<PipedSearchItemStream> siblings = await PipedSpotube.client
|
final List<PipedSearchItemStream> siblings = await client
|
||||||
.search(
|
.search(
|
||||||
"$title - ${artists.join(", ")}",
|
"$title - ${artists.join(", ")}",
|
||||||
PipedFilter.musicSongs,
|
PipedFilter.musicSongs,
|
||||||
@ -112,18 +114,19 @@ class SpotubeTrack extends Track {
|
|||||||
static Future<SpotubeTrack> fetchFromTrack(
|
static Future<SpotubeTrack> fetchFromTrack(
|
||||||
Track track,
|
Track track,
|
||||||
UserPreferences preferences,
|
UserPreferences preferences,
|
||||||
|
PipedClient client,
|
||||||
) async {
|
) async {
|
||||||
final matchedCachedTrack = await MatchedTrack.box.get(track.id!);
|
final matchedCachedTrack = await MatchedTrack.box.get(track.id!);
|
||||||
var siblings = <PipedSearchItemStream>[];
|
var siblings = <PipedSearchItemStream>[];
|
||||||
PipedStreamResponse ytVideo;
|
PipedStreamResponse ytVideo;
|
||||||
if (matchedCachedTrack != null) {
|
if (matchedCachedTrack != null) {
|
||||||
ytVideo = await PipedSpotube.client.streams(matchedCachedTrack.youtubeId);
|
ytVideo = await client.streams(matchedCachedTrack.youtubeId);
|
||||||
} else {
|
} else {
|
||||||
siblings = await fetchSiblings(track);
|
siblings = await fetchSiblings(track, client);
|
||||||
if (siblings.isEmpty) {
|
if (siblings.isEmpty) {
|
||||||
throw Exception("Failed to find any results for ${track.name}");
|
throw Exception("Failed to find any results for ${track.name}");
|
||||||
}
|
}
|
||||||
ytVideo = await PipedSpotube.client.streams(siblings.first.id);
|
ytVideo = await client.streams(siblings.first.id);
|
||||||
|
|
||||||
await MatchedTrack.box.put(
|
await MatchedTrack.box.put(
|
||||||
track.id!,
|
track.id!,
|
||||||
@ -167,10 +170,11 @@ class SpotubeTrack extends Track {
|
|||||||
Future<SpotubeTrack?> swappedCopy(
|
Future<SpotubeTrack?> swappedCopy(
|
||||||
PipedSearchItemStream video,
|
PipedSearchItemStream video,
|
||||||
UserPreferences preferences,
|
UserPreferences preferences,
|
||||||
|
PipedClient client,
|
||||||
) async {
|
) async {
|
||||||
if (siblings.none((element) => element.id == video.id)) return null;
|
if (siblings.none((element) => element.id == video.id)) return null;
|
||||||
|
|
||||||
final ytVideo = await PipedSpotube.client.streams(video.id);
|
final ytVideo = await client.streams(video.id);
|
||||||
|
|
||||||
final ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
final ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
||||||
|
|
||||||
@ -225,10 +229,10 @@ class SpotubeTrack extends Track {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpotubeTrack> populatedCopy() async {
|
Future<SpotubeTrack> populatedCopy(PipedClient client) async {
|
||||||
if (this.siblings.isNotEmpty) return this;
|
if (this.siblings.isNotEmpty) return this;
|
||||||
|
|
||||||
final siblings = await fetchSiblings(this);
|
final siblings = await fetchSiblings(this, client);
|
||||||
|
|
||||||
return SpotubeTrack.fromTrack(
|
return SpotubeTrack.fromTrack(
|
||||||
track: this,
|
track: this,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:piped_client/piped_client.dart';
|
||||||
import 'package:spotube/collections/env.dart';
|
import 'package:spotube/collections/env.dart';
|
||||||
import 'package:spotube/collections/language_codes.dart';
|
import 'package:spotube/collections/language_codes.dart';
|
||||||
|
|
||||||
@ -21,6 +23,7 @@ import 'package:spotube/l10n/l10n.dart';
|
|||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/downloader_provider.dart';
|
import 'package:spotube/provider/downloader_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/provider/piped_provider.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class SettingsPage extends HookConsumerWidget {
|
class SettingsPage extends HookConsumerWidget {
|
||||||
@ -286,6 +289,41 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Consumer(builder: (context, ref, child) {
|
||||||
|
final instanceList =
|
||||||
|
ref.watch(pipedInstancesFutureProvider);
|
||||||
|
|
||||||
|
return instanceList.when(
|
||||||
|
data: (data) {
|
||||||
|
return AdaptiveSelectTile<String>(
|
||||||
|
secondary: const Icon(SpotubeIcons.piped),
|
||||||
|
title: Text(context.l10n.piped_instance),
|
||||||
|
subtitle: Text(context.l10n.piped_description),
|
||||||
|
value: preferences.pipedInstance,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: data
|
||||||
|
.sortedBy((e) => e.name)
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: e.apiUrl,
|
||||||
|
child: Text(e.name),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setPipedInstance(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) =>
|
||||||
|
Text(error.toString()),
|
||||||
|
);
|
||||||
|
}),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(SpotubeIcons.download),
|
secondary: const Icon(SpotubeIcons.download),
|
||||||
title: Text(context.l10n.pre_download_play),
|
title: Text(context.l10n.pre_download_play),
|
||||||
|
@ -11,6 +11,7 @@ import 'package:spotify/spotify.dart' hide Image, Queue;
|
|||||||
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
|
import 'package:spotube/provider/piped_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ class Downloader with ChangeNotifier {
|
|||||||
final track = await SpotubeTrack.fetchFromTrack(
|
final track = await SpotubeTrack.fetchFromTrack(
|
||||||
baseTrack,
|
baseTrack,
|
||||||
ref.read(userPreferencesProvider),
|
ref.read(userPreferencesProvider),
|
||||||
|
ref.read(pipedClientProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
_queue.add(() async {
|
_queue.add(() async {
|
||||||
|
18
lib/provider/piped_provider.dart
Normal file
18
lib/provider/piped_provider.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:piped_client/piped_client.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
|
|
||||||
|
PipedClient _defaultClient = PipedClient();
|
||||||
|
|
||||||
|
final pipedClientProvider = Provider((ref) {
|
||||||
|
final instanceUrl =
|
||||||
|
ref.watch(userPreferencesProvider.select((s) => s.pipedInstance));
|
||||||
|
|
||||||
|
if (instanceUrl == "https://pipedapi.kavin.rocks") return _defaultClient;
|
||||||
|
|
||||||
|
return PipedClient(instance: instanceUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
final pipedInstancesFutureProvider = FutureProvider<List<PipedInstance>>(
|
||||||
|
(ref) async => _defaultClient.instanceList(),
|
||||||
|
);
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:piped_client/piped_client.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/matched_track.dart';
|
||||||
@ -11,7 +12,8 @@ import 'package:spotube/services/supabase.dart';
|
|||||||
|
|
||||||
mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
||||||
Future<List<SpotubeTrack>> fetchTracks(
|
Future<List<SpotubeTrack>> fetchTracks(
|
||||||
UserPreferences preferences, {
|
UserPreferences preferences,
|
||||||
|
PipedClient pipedClient, {
|
||||||
int count = 3,
|
int count = 3,
|
||||||
int offset = 0,
|
int offset = 0,
|
||||||
}) async {
|
}) async {
|
||||||
@ -25,7 +27,11 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
|||||||
/// fetch [bareTracks] one by one with 100ms delay
|
/// fetch [bareTracks] one by one with 100ms delay
|
||||||
final fetchedTracks = await Future.wait(
|
final fetchedTracks = await Future.wait(
|
||||||
bareTracks.mapIndexed((i, track) async {
|
bareTracks.mapIndexed((i, track) async {
|
||||||
final future = SpotubeTrack.fetchFromTrack(track, preferences);
|
final future = SpotubeTrack.fetchFromTrack(
|
||||||
|
track,
|
||||||
|
preferences,
|
||||||
|
pipedClient,
|
||||||
|
);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
return await future;
|
return await future;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ 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/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/audio_services/audio_services.dart';
|
import 'package:spotube/services/audio_services/audio_services.dart';
|
||||||
import 'package:spotube/services/youtube.dart';
|
import 'package:spotube/provider/piped_provider.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
late final AudioServices notificationService;
|
late final AudioServices notificationService;
|
||||||
|
|
||||||
UserPreferences get preferences => ref.read(userPreferencesProvider);
|
UserPreferences get preferences => ref.read(userPreferencesProvider);
|
||||||
|
PipedClient get pipedClient => ref.read(pipedClientProvider);
|
||||||
ProxyPlaylist get playlist => state;
|
ProxyPlaylist get playlist => state;
|
||||||
BlackListNotifier get blacklist =>
|
BlackListNotifier get blacklist =>
|
||||||
ref.read(BlackListNotifier.provider.notifier);
|
ref.read(BlackListNotifier.provider.notifier);
|
||||||
@ -157,7 +158,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
|
|
||||||
final nthFetchedTrack = switch (track.runtimeType) {
|
final nthFetchedTrack = switch (track.runtimeType) {
|
||||||
SpotubeTrack => track as SpotubeTrack,
|
SpotubeTrack => track as SpotubeTrack,
|
||||||
_ => await SpotubeTrack.fetchFromTrack(track, preferences),
|
_ => await SpotubeTrack.fetchFromTrack(track, preferences, pipedClient),
|
||||||
};
|
};
|
||||||
|
|
||||||
await audioPlayer.replaceSource(
|
await audioPlayer.replaceSource(
|
||||||
@ -220,6 +221,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
final addableTrack = await SpotubeTrack.fetchFromTrack(
|
final addableTrack = await SpotubeTrack.fetchFromTrack(
|
||||||
tracks.elementAtOrNull(initialIndex) ?? tracks.first,
|
tracks.elementAtOrNull(initialIndex) ?? tracks.first,
|
||||||
preferences,
|
preferences,
|
||||||
|
pipedClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@ -307,7 +309,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
Future<void> populateSibling() async {
|
Future<void> populateSibling() async {
|
||||||
if (state.activeTrack is SpotubeTrack) {
|
if (state.activeTrack is SpotubeTrack) {
|
||||||
final activeTrackWithSiblingsForSure =
|
final activeTrackWithSiblingsForSure =
|
||||||
await (state.activeTrack as SpotubeTrack).populatedCopy();
|
await (state.activeTrack as SpotubeTrack).populatedCopy(pipedClient);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
tracks: mergeTracks([activeTrackWithSiblingsForSure], state.tracks),
|
tracks: mergeTracks([activeTrackWithSiblingsForSure], state.tracks),
|
||||||
@ -321,7 +323,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
if (state.activeTrack is SpotubeTrack && video is PipedSearchItemStream) {
|
if (state.activeTrack is SpotubeTrack && video is PipedSearchItemStream) {
|
||||||
populateSibling();
|
populateSibling();
|
||||||
final newTrack = await (state.activeTrack as SpotubeTrack)
|
final newTrack = await (state.activeTrack as SpotubeTrack)
|
||||||
.swappedCopy(video, preferences);
|
.swappedCopy(video, preferences, pipedClient);
|
||||||
if (newTrack == null) return;
|
if (newTrack == null) return;
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
tracks: mergeTracks([newTrack], state.tracks),
|
tracks: mergeTracks([newTrack], state.tracks),
|
||||||
@ -438,14 +440,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
onInit() async {
|
onInit() async {
|
||||||
if (state.tracks.isEmpty) return null;
|
if (state.tracks.isEmpty) return null;
|
||||||
|
|
||||||
if (await PipedSpotube.initialized) {
|
|
||||||
await load(
|
await load(
|
||||||
state.tracks,
|
state.tracks,
|
||||||
initialIndex: state.active ?? 0,
|
initialIndex: state.active ?? 0,
|
||||||
autoPlay: false,
|
autoPlay: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<ProxyPlaylist> fromJson(Map<String, dynamic> json) {
|
FutureOr<ProxyPlaylist> fromJson(Map<String, dynamic> json) {
|
||||||
|
@ -50,6 +50,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
|
|
||||||
Locale locale;
|
Locale locale;
|
||||||
|
|
||||||
|
String pipedInstance;
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
UserPreferences(
|
UserPreferences(
|
||||||
@ -67,6 +69,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
this.closeBehavior = CloseBehavior.minimizeToTray,
|
this.closeBehavior = CloseBehavior.minimizeToTray,
|
||||||
this.showSystemTrayIcon = true,
|
this.showSystemTrayIcon = true,
|
||||||
this.locale = const Locale("system", "system"),
|
this.locale = const Locale("system", "system"),
|
||||||
|
this.pipedInstance = "https://pipedapi.kavin.rocks",
|
||||||
}) : super() {
|
}) : super() {
|
||||||
if (downloadLocation.isEmpty) {
|
if (downloadLocation.isEmpty) {
|
||||||
_getDefaultDownloadDirectory().then(
|
_getDefaultDownloadDirectory().then(
|
||||||
@ -161,6 +164,12 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
updatePersistence();
|
updatePersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPipedInstance(String instance) {
|
||||||
|
pipedInstance = instance;
|
||||||
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> _getDefaultDownloadDirectory() async {
|
Future<String> _getDefaultDownloadDirectory() async {
|
||||||
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
||||||
|
|
||||||
@ -206,6 +215,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
final localeMap = map["locale"] != null ? jsonDecode(map["locale"]) : null;
|
final localeMap = map["locale"] != null ? jsonDecode(map["locale"]) : null;
|
||||||
locale =
|
locale =
|
||||||
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
||||||
|
|
||||||
|
pipedInstance = map["pipedInstance"] ?? pipedInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -225,6 +236,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
"showSystemTrayIcon": showSystemTrayIcon,
|
"showSystemTrayIcon": showSystemTrayIcon,
|
||||||
"locale":
|
"locale":
|
||||||
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
|
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
|
||||||
|
"pipedInstance": pipedInstance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:piped_client/piped_client.dart';
|
|
||||||
|
|
||||||
PipedClient _defaultClient = PipedClient();
|
|
||||||
|
|
||||||
class PipedSpotube {
|
|
||||||
static final Completer<bool> _initialized = Completer();
|
|
||||||
static Future<bool> get initialized => _initialized.future;
|
|
||||||
|
|
||||||
/// Checks for a working instance of piped.video
|
|
||||||
///
|
|
||||||
/// To distribute the load, in each startup it randomizes public instances
|
|
||||||
/// and selects a working instance and uses that throughout the session
|
|
||||||
static Future<void> initialize() async {
|
|
||||||
final pipedInstances = await _defaultClient.instanceList();
|
|
||||||
pipedInstances.shuffle();
|
|
||||||
for (final instance in pipedInstances) {
|
|
||||||
final client = PipedClient(instance: instance.apiUrl);
|
|
||||||
try {
|
|
||||||
await client.streams("dQw4w9WgXcQ");
|
|
||||||
_defaultClient = client;
|
|
||||||
_initialized.complete(true);
|
|
||||||
break;
|
|
||||||
} catch (e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static PipedClient get client => _defaultClient;
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ ThemeData theme(Color seed, Brightness brightness) {
|
|||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: scheme,
|
colorScheme: scheme,
|
||||||
listTileTheme: ListTileThemeData(
|
listTileTheme: ListTileThemeData(
|
||||||
horizontalTitleGap: 0,
|
horizontalTitleGap: 5,
|
||||||
iconColor: scheme.onSurface,
|
iconColor: scheme.onSurface,
|
||||||
),
|
),
|
||||||
appBarTheme: const AppBarTheme(surfaceTintColor: Colors.transparent),
|
appBarTheme: const AppBarTheme(surfaceTintColor: Colors.transparent),
|
||||||
|
Loading…
Reference in New Issue
Block a user