mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-07 15:59:42 +00:00
Compare commits
8 Commits
94773c09f7
...
391bd607dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
391bd607dd | ||
|
|
ba27dc70e4 | ||
|
|
0ec9f3535b | ||
|
|
df72ba6960 | ||
|
|
d9057dae57 | ||
|
|
e61b79585e | ||
|
|
a9e5636e96 | ||
|
|
7b85ab1617 |
@ -1,6 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
## [4.0.2](https://github.com/krtirtho/spotube/compare/v4.0.1...v4.0.2) (2025-03-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- invalid access token exception #2525
|
||||
|
||||
## [4.0.1](https://github.com/krtirtho/spotube/compare/v4.0.0...v4.0.1) (2025-03-15)
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
||||
import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
@ -122,8 +121,9 @@ class TrackOptions extends HookConsumerWidget {
|
||||
final playlist = ref.read(audioPlayerProvider);
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
final query = "${track.name} Radio";
|
||||
final pages =
|
||||
await spotify.search.get(query, types: [SearchType.playlist]).first();
|
||||
final pages = await spotify.invoke(
|
||||
(api) => api.search.get(query, types: [SearchType.playlist]).first(),
|
||||
);
|
||||
|
||||
final radios = pages
|
||||
.expand((e) => e.items?.cast<PlaylistSimple>().toList() ?? [])
|
||||
@ -165,8 +165,9 @@ class TrackOptions extends HookConsumerWidget {
|
||||
await playback.addTrack(track);
|
||||
}
|
||||
|
||||
final tracks =
|
||||
await spotify.playlists.getTracksByPlaylistId(radio.id!).all();
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.playlists.getTracksByPlaylistId(radio.id!).all(),
|
||||
);
|
||||
|
||||
await playback.addTracks(
|
||||
tracks.toList()
|
||||
|
||||
@ -191,8 +191,7 @@ class TrackTile extends HookConsumerWidget {
|
||||
const SizedBox(
|
||||
width: 26,
|
||||
height: 26,
|
||||
child:
|
||||
CircularProgressIndicator(size: 1.5),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
(_, _, true, _, _) => Icon(
|
||||
SpotubeIcons.pause,
|
||||
|
||||
@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/routes.dart';
|
||||
import 'package:spotube/collections/routes.gr.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
@ -27,7 +27,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
|
||||
|
||||
switch (url.pathSegments.first) {
|
||||
case "album":
|
||||
final album = await spotify.albums.get(url.pathSegments.last);
|
||||
final album = await spotify.invoke((api) {
|
||||
return api.albums.get(url.pathSegments.last);
|
||||
});
|
||||
router.navigate(
|
||||
AlbumRoute(id: album.id!, album: album),
|
||||
);
|
||||
@ -36,7 +38,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
|
||||
router.navigate(ArtistRoute(artistId: url.pathSegments.last));
|
||||
break;
|
||||
case "playlist":
|
||||
final playlist = await spotify.playlists.get(url.pathSegments.last);
|
||||
final playlist = await spotify.invoke((api) {
|
||||
return api.playlists.get(url.pathSegments.last);
|
||||
});
|
||||
router
|
||||
.navigate(PlaylistRoute(id: playlist.id!, playlist: playlist));
|
||||
break;
|
||||
@ -65,7 +69,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
|
||||
|
||||
switch (startSegment) {
|
||||
case "spotify:album":
|
||||
final album = await spotify.albums.get(endSegment);
|
||||
final album = await spotify.invoke((api) {
|
||||
return api.albums.get(endSegment);
|
||||
});
|
||||
await router.navigate(
|
||||
AlbumRoute(id: album.id!, album: album),
|
||||
);
|
||||
@ -77,7 +83,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) {
|
||||
await router.navigate(TrackRoute(trackId: endSegment));
|
||||
break;
|
||||
case "spotify:playlist":
|
||||
final playlist = await spotify.playlists.get(endSegment);
|
||||
final playlist = await spotify.invoke((api) {
|
||||
return api.playlists.get(endSegment);
|
||||
});
|
||||
await router.navigate(
|
||||
PlaylistRoute(id: playlist.id!, playlist: playlist),
|
||||
);
|
||||
|
||||
@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
|
||||
@ -28,8 +28,8 @@ void useEndlessPlayback(WidgetRef ref) {
|
||||
final track = playlist.tracks.last;
|
||||
|
||||
final query = "${track.name} Radio";
|
||||
final pages = await spotify.search
|
||||
.get(query, types: [SearchType.playlist]).first();
|
||||
final pages = await spotify.invoke((api) =>
|
||||
api.search.get(query, types: [SearchType.playlist]).first());
|
||||
|
||||
final radios = pages
|
||||
.expand((e) => e.items?.toList() ?? <PlaylistSimple>[])
|
||||
@ -50,8 +50,8 @@ void useEndlessPlayback(WidgetRef ref) {
|
||||
orElse: () => radios.first,
|
||||
);
|
||||
|
||||
final tracks =
|
||||
await spotify.playlists.getTracksByPlaylistId(radio.id!).all();
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.playlists.getTracksByPlaylistId(radio.id!).all());
|
||||
|
||||
await playback.addTracks(
|
||||
tracks.toList()
|
||||
|
||||
405
lib/l10n/app_pa.arb
Normal file
405
lib/l10n/app_pa.arb
Normal file
@ -0,0 +1,405 @@
|
||||
{
|
||||
"guest": "ਮਹਿਮਾਨ",
|
||||
"browse": "ਬ੍ਰਾਊਜ਼ ਕਰੋ",
|
||||
"search": "ਖੋਜੋ",
|
||||
"library": "ਲਾਇਬ੍ਰੇਰੀ",
|
||||
"lyrics": "ਗੀਤਾਂ ਦੇ ਬੋਲ",
|
||||
"settings": "ਸੈਟਿੰਗਾਂ",
|
||||
"genre_categories_filter": "ਸ਼੍ਰੇਣੀਆਂ ਜਾਂ ਸ਼ੈਲੀਆਂ ਨੂੰ ਛਾਣੋ...",
|
||||
"genre": "ਸ਼ੈਲੀ",
|
||||
"personalized": "ਵਿਅਕਤੀਗਤ",
|
||||
"featured": "ਵਿਸ਼ੇਸ਼ ਰੂਪ ਨਾਲ ਪ੍ਰਦਰਸ਼ਿਤ",
|
||||
"new_releases": "ਨਵੀਂ ਰਿਲੀਜ਼",
|
||||
"songs": "ਗੀਤ",
|
||||
"playing_track": "{track} ਚੱਲ ਰਿਹਾ ਹੈ",
|
||||
"queue_clear_alert": "ਇਹ ਮੌਜੂਦਾ ਕਤਾਰ ਨੂੰ ਸਾਫ਼ ਕਰ ਦੇਵੇਗਾ। {track_length} ਟ੍ਰੈਕ ਹਟਾ ਦਿੱਤੇ ਜਾਣਗੇ\nਕੀ ਤੁਸੀਂ ਜਾਰੀ ਰੱਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"load_more": "ਹੋਰ ਲੋਡ ਕਰੋ",
|
||||
"playlists": "ਪਲੇਸੂਚੀ",
|
||||
"artists": "ਕਲਾਕਾਰ",
|
||||
"albums": "ਐਲਬਮ",
|
||||
"tracks": "ਟ੍ਰੈਕ",
|
||||
"downloads": "ਡਾਊਨਲੋਡ",
|
||||
"filter_playlists": "ਆਪਣੀਆਂ ਪਲੇਸੂਚੀਆਂ ਨੂੰ ਛਾਣੋ...",
|
||||
"liked_tracks": "ਪਸੰਦੀਦਾ ਟ੍ਰੈਕ",
|
||||
"liked_tracks_description": "ਤੁਹਾਡੇ ਸਾਰੇ ਪਸੰਦੀਦਾ ਟ੍ਰੈਕ",
|
||||
"create_playlist": "ਪਲੇਸੂਚੀ ਬਣਾਉ",
|
||||
"create_a_playlist": "ਇੱਕ ਪਲੇਸੂਚੀ ਬਣਾਉ",
|
||||
"create": "ਬਣਾਉ",
|
||||
"cancel": "ਰੱਦ ਕਰੋ",
|
||||
"playlist_name": "ਪਲੇਸੂਚੀ ਦਾ ਨਾਮ",
|
||||
"name_of_playlist": "ਪਲੇਸੂਚੀ ਦਾ ਨਾਮ",
|
||||
"description": "ਵੇਰਵਾ",
|
||||
"public": "ਜਨਤਕ",
|
||||
"collaborative": "ਸਹਿਯੋਗੀ",
|
||||
"search_local_tracks": "ਸਥਾਨਕ ਟ੍ਰੈਕ ਖੋਜੋ...",
|
||||
"play": "ਚਲਾਉ",
|
||||
"delete": "ਹਟਾਉ",
|
||||
"none": "ਕੋਈ ਨਹੀਂ",
|
||||
"sort_a_z": "A-Z ਦਿਖਾਉ",
|
||||
"sort_z_a": "Z-A ਦਿਖਾਉ",
|
||||
"sort_artist": "ਕਲਾਕਾਰ ਅਨੁਸਾਰ ਦਿਖਾਉ",
|
||||
"sort_album": "ਐਲਬਮ ਅਨੁਸਾਰ ਦਿਖਾਉ",
|
||||
"sort_tracks": "ਟ੍ਰੈਕ ਅਨੁਸਾਰ ਦਿਖਾਉ",
|
||||
"currently_downloading": "ਹੁਣ ਡਾਊਨਲੋਡ ਹੋ ਰਿਹਾ ਹੈ ({tracks_length})",
|
||||
"cancel_all": "ਸਾਰਿਆਂ ਨੂੰ ਰੱਦ ਕਰੋ",
|
||||
"filter_artist": "ਕਲਾਕਾਰ ਨੂੰ ਛਾਣੋ...",
|
||||
"followers": "{followers} ਫ਼ਾਲੋਅਰ",
|
||||
"add_artist_to_blacklist": "ਕਲਾਕਾਰ ਨੂੰ ਕਾਲੀਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"top_tracks": "ਚੋਟੀ ਦਾ ਟ੍ਰੈਕ",
|
||||
"fans_also_like": "ਫੈਨਸ ਨੂੰ ਇਹ ਵੀ ਪਸੰਦ ਹੈ",
|
||||
"loading": "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ...",
|
||||
"artist": "ਕਲਾਕਾਰ",
|
||||
"blacklisted": "ਕਾਲੀਸੂਚੀ ਵਿੱਚ ਹੈ",
|
||||
"following": "ਫ਼ਾਲੋ ਕਰ ਰਿਹਾ ਹੈ",
|
||||
"follow": "ਫ਼ਾਲੋ ਕਰੋ",
|
||||
"artist_url_copied": "ਕਲਾਕਾਰ ਯੂਆਰਐਲ ਕਲਿੱਪਬੋਰਡ ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ",
|
||||
"added_to_queue": "{tracks} ਟ੍ਰੈਕ ਕਤਾਰ ਵਿੱਚ ਜੋੜੇ ਗਏ",
|
||||
"filter_albums": "ਐਲਬਮਾਂ ਨੂੰ ਛਾਣੋ...",
|
||||
"synced": "ਸਿੰਕ ਕੀਤਾ ਗਿਆ",
|
||||
"plain": "ਸਾਦਾ",
|
||||
"shuffle": "ਸ਼ਫਲ",
|
||||
"search_tracks": "ਟ੍ਰੈਕ ਖੋਜੋ...",
|
||||
"released": "ਜਾਰੀ ਹੋਇਆ",
|
||||
"error": "ਤਰੁੱਟੀ {error}",
|
||||
"title": "ਸਿਰਲੇਖ",
|
||||
"time": "ਸਮਾਂ",
|
||||
"more_actions": "ਹੋਰ ਕਾਰਵਾਈ",
|
||||
"download_count": "ਡਾਊਨਲੋਡ ({count})",
|
||||
"add_count_to_playlist": "({count}) ਨੂੰ ਪਲੇਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"add_count_to_queue": "({count}) ਨੂੰ ਕਤਾਰ ਵਿੱਚ ਜੋੜੋ",
|
||||
"play_count_next": "({count}) ਅਗਲਾ ਚਲਾਉ",
|
||||
"album": "ਐਲਬਮ",
|
||||
"copied_to_clipboard": "{data} ਕਲਿੱਪਬੋਰਡ ਉੱਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ",
|
||||
"add_to_following_playlists": "{track} ਨੂੰ ਹੇਠ ਲਿਖੀ ਪਲੇਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"add": "ਜੋੜੋ",
|
||||
"added_track_to_queue": "{track} ਨੂੰ ਕਤਾਰ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ",
|
||||
"add_to_queue": "ਕਤਾਰ ਵਿੱਚ ਜੋੜੋ",
|
||||
"track_will_play_next": "{track} ਅਗਲਾ ਚਲਾਉ",
|
||||
"play_next": "ਅਗਲਾ ਚਲਾਉ",
|
||||
"removed_track_from_queue": "{track} ਨੂੰ ਕਤਾਰ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ",
|
||||
"remove_from_queue": "ਕਤਾਰ ਵਿੱਚ ਹਟਾਉ",
|
||||
"remove_from_favorites": "ਪਸੰਦੀਦਾ ਵਿੱਚੋਂ ਹਟਾਉ",
|
||||
"save_as_favorite": "ਪਸੰਦੀਦਾ ਵਜੋਂ ਸੰਭਾਲੋ",
|
||||
"add_to_playlist": "ਪਲੇਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"remove_from_playlist": "ਪਲੇਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾਉ",
|
||||
"add_to_blacklist": "ਕਾਲੀਸੂਚੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"remove_from_blacklist": "ਕਾਲੀਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾਉ",
|
||||
"share": "ਸਾਂਝਾ ਕਰੋ",
|
||||
"mini_player": "ਮਿੰਨੀ ਪਲੇਅਰ",
|
||||
"slide_to_seek": "ਅੱਗੇ ਜਾਂ ਪਿੱਛੇ ਖੋਜਣ ਲਈ ਸਲਾਈਡ ਕਰੋ",
|
||||
"shuffle_playlist": "ਪਲੇਸੂਚੀ ਸ਼ਫਲ ਕਰੋ",
|
||||
"unshuffle_playlist": "ਪਲੇਸੂਚੀ ਅਣਸ਼ਫਲ ਕਰੋ",
|
||||
"previous_track": "ਪਿਛਲਾ ਟ੍ਰੈਕ",
|
||||
"next_track": "ਅਗਲਾ ਟ੍ਰੈਕ",
|
||||
"pause_playback": "ਪਲੇਬੈਕ ਰੋਕੋ",
|
||||
"resume_playback": "ਪਲੇਬੈਕ ਚਲਾਉ",
|
||||
"loop_track": "ਲੂਪ ਟ੍ਰੈਕ",
|
||||
"repeat_playlist": "ਪਲੇਸੂਚੀ ਦੁਹਰਾਉ",
|
||||
"queue": "ਕਤਾਰ",
|
||||
"alternative_track_sources": "ਵਿਕਲਪਕ ਟ੍ਰੈਕ ਸਰੋਤ",
|
||||
"download_track": "ਟ੍ਰੈਕ ਡਾਊਨਲੋਡ ਕਰੋ",
|
||||
"tracks_in_queue": "{tracks} ਟ੍ਰੈਕ ਕਤਾਰ ਵਿੱਚ ਹਨ",
|
||||
"clear_all": "ਸਾਰੇ ਹਟਾਉ",
|
||||
"show_hide_ui_on_hover": "ਹਵਰ ਉੱਤੇ ਯੂਆਈ ਦਿਖਾਉ/ਛਿਪਾਉ",
|
||||
"always_on_top": "ਹਮੇਸ਼ਾ ਉੱਤੇ ਹੋਵੇ",
|
||||
"exit_mini_player": "ਮਿੰਨੀ ਪਲੇਅਰ ਤੋਂ ਬਾਹਰ ਨਿਕਲੋ",
|
||||
"download_location": "ਡਾਊਨਲੋਡ ਸਥਾਨ",
|
||||
"account": "ਖ਼ਾਤਾ",
|
||||
"login_with_spotify": "ਆਪਣੇ ਸਪੋਟਫਾਈ ਖ਼ਾਤੇ ਨਾਲ ਦਾਖ਼ਲ ਹੋਵੋ",
|
||||
"connect_with_spotify": "ਸਪੋਟਫਾਈ ਨਾਲ ਕਨੈਕਟ ਕਰੋ",
|
||||
"logout": "ਵਿਦਾਈ ਲਵੋ",
|
||||
"logout_of_this_account": "ਇਸ ਖ਼ਾਤੇ ਤੋਂ ਵਿਦਾਈ ਲਵੋ",
|
||||
"language_region": "ਭਾਸ਼ਾ ਅਤੇ ਖੇਤਰ",
|
||||
"language": "ਭਾਸ਼ਾ",
|
||||
"system_default": "ਸਿਸਟਮ ਡਿਫਾਲਟ",
|
||||
"market_place_region": "ਮਾਰਕਿਟਪਲੇਸ ਖੇਤਰ",
|
||||
"recommendation_country": "ਸੁਝਾਇਆ ਗਿਆ ਦੇਸ਼",
|
||||
"appearance": "ਦਿੱਖ",
|
||||
"layout_mode": "ਲੇਆਊਟ ਮੋਡ",
|
||||
"override_layout_settings": "ਓਵਰਰਾਈਡ ਰਿਸਪਾਂਸਿਵ ਲੇਆਊਟ ਮੋਡ ਸੈਟਿੰਗ",
|
||||
"adaptive": "ਅਨੁਕੂਲ",
|
||||
"compact": "ਕੰਪੈਕਟ",
|
||||
"extended": "ਵਿਸਤ੍ਰਿਤ",
|
||||
"theme": "ਥੀਮ",
|
||||
"dark": "ਗੂੜ੍ਹਾ",
|
||||
"light": "ਚਾਨਣ",
|
||||
"system": "ਸਿਸਟਮ",
|
||||
"accent_color": "ਅੱਖਰਸ਼ੈਲੀ ਦਾ ਰੰਗ",
|
||||
"sync_album_color": "ਐਲਬਮ ਦਾ ਰੰਗ ਸਿੰਕ ਕਰੋ",
|
||||
"sync_album_color_description": "ਐਲਬਮ ਕਲਾ ਦਾ ਪ੍ਰਾਇਮਰੀ ਰੰਗ ਇੱਕ ਲਹਿਜ਼ੇ ਦੇ ਰੰਗ ਵਜੋਂ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ",
|
||||
"playback": "ਪਲੇਬੈਕ",
|
||||
"audio_quality": "ਆਡੀਉ ਗੁਣਵੱਤਾ",
|
||||
"high": "ਉੱਚ",
|
||||
"low": "ਨਿਮਨ",
|
||||
"pre_download_play": "ਪਹਿਲਾਂ ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਚਲਾਉ",
|
||||
"pre_download_play_description": "ਸਟ੍ਰੀਮਿੰਗ ਆਡੀਓ ਦੀ ਬਜਾਏ ਬਾਈਟ ਡਾਊਨਲੋਡ ਕਰੋ ਅਤੇ ਚਲਾਓ (ਉੱਚ ਬੈਂਡਵਿਡਥ ਉਪਭੋਗਤਾਵਾਂ ਲਈ ਸਿਫ਼ਾਰਿਸ਼ ਕੀਤੀ ਗਈ ਹੈ)",
|
||||
"skip_non_music": "ਗੀਤ ਦੇ ਇਲਾਵਾ ਸੈਗਮੈਂਟਾਂ ਨੂੰ ਛੱਡੋ (ਸਪਾਂਸਰਬਲਾਕ)",
|
||||
"blacklist_description": "ਕਾਲੀਸੂਚੀ ਵਿੱਚ ਸ਼ਾਮਲ ਟ੍ਰੈਕ ਅਤੇ ਕਲਾਕਾਰ",
|
||||
"wait_for_download_to_finish": "ਵਰਤਮਾਨ ਡਾਊਨਲੋਡ ਸਮਾਪਤ ਹੋਣ ਤੱਕ ਇੰਤਜ਼ਾਰ ਕਰੋ",
|
||||
"desktop": "ਡੈਸਕਟਾਪ",
|
||||
"close_behavior": "ਬੰਦ ਕਰਨ ਦਾ ਵਿਵਹਾਰ",
|
||||
"close": "ਬੰਦ ਕਰੋ",
|
||||
"minimize_to_tray": "ਟ੍ਰੇ ਵਿੱਚ ਘੱਟ ਕਰੋ",
|
||||
"show_tray_icon": "ਸਿਸਟਮ ਟ੍ਰੇ ਆਈਕਨ ਦਿਖਾਉ",
|
||||
"about": "ਬਾਬਤ",
|
||||
"u_love_spotube": "ਅਸੀਂ ਜਾਣਦੇ ਹਾਂ ਕਿ ਤੁਸੀਂ Spotube ਨੂੰ ਪਿਆਰ ਕਰਦੇ ਹੋ",
|
||||
"check_for_updates": "ਅੱਪਡੇਟ ਲਈ ਜਾਂਚ ਕਰੋ",
|
||||
"about_spotube": "Spotube ਦੇ ਬਾਰੇ",
|
||||
"blacklist": "ਕਾਲੀਸੂਚੀ",
|
||||
"please_sponsor": "ਕਿਰਪਾ ਕਰਕੇ ਸਪਾਂਸਰ / ਦਾਨ ਕਰੋ",
|
||||
"spotube_description": "Spotube, ਇੱਕ ਹਲਕਾ, ਸਾਰੇ ਪਲੇਟਫਾਰਮਾਂ ਤੇ ਚੱਲਣ ਵਾਲਾ, ਮੁਫ਼ਤ ਸਪੋਟੀਫਾਈ ਕਲਾਇੰਟ",
|
||||
"version": "ਸੰਸਕਰਨ",
|
||||
"build_number": "ਬਿਲਡ ਨੰਬਰ",
|
||||
"founder": "ਸੰਸਥਾਪਕ",
|
||||
"repository": "ਭੰਡਾਰ",
|
||||
"bug_issues": "ਦਿੱਕਤਾਂ+ਮੁੱਦੇ",
|
||||
"made_with": "ਬੰਗਲਾਦੇਸ਼🇧🇩 ਵਿੱਚ ਦਿਲ ਨਾਲ ਬਣਾਇਆ ਗਿਆ",
|
||||
"kingkor_roy_tirtho": "ਕਿੰਗਕੋਰ ਰੌਏ ਤਿਰਥੋ",
|
||||
"copyright": "© 2021-{current_year} ਕਿੰਗਕੋਰ ਰੌਏ ਤਿਰਥੋ",
|
||||
"license": "ਲਸੰਸ",
|
||||
"add_spotify_credentials": "ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਸਪੋਟੀਫਾਈ ਕਰੇਡੈਂਸ਼ੀਅਲ ਜੋੜੋ",
|
||||
"credentials_will_not_be_shared_disclaimer": "ਚਿੰਤਾ ਨਾ ਕਰੋ, ਤੁਹਾਡੇ ਕਰੇਡੈਂਸ਼ੀਅਲ ਕਿਸੇ ਵੀ ਤਰ੍ਹਾਂ ਨਾਲ ਇਕੱਠੇ ਜਾਂ ਸਾਂਝੇ ਨਹੀਂ ਕੀਤੇ ਜਾਣਗੇ",
|
||||
"know_how_to_login": "ਇਸਨੂੰ ਕਿਵੇਂ ਕਰਨਾ ਹੈ, ਨਹੀਂ ਪਤਾ?",
|
||||
"follow_step_by_step_guide": "ਕਦਮ ਨਾਲ ਕਦਮ ਗਾਈਡ ਦੇ ਨਾਲ ਚੱਲੋ",
|
||||
"spotify_cookie": "ਸਪੋਟੀਫਾਈ {name} ਕੁਕੀ",
|
||||
"cookie_name_cookie": "{name} ਕੁਕੀ",
|
||||
"fill_in_all_fields": "ਕ੍ਰਿਪਾ ਸਾਰੇ ਫੀਲਡ ਭਰੋ",
|
||||
"submit": "ਸਪੁਰਦ",
|
||||
"exit": "ਬਾਹਰ ਨਿਕਲੋ",
|
||||
"previous": "ਪਿਛਲਾ",
|
||||
"next": "ਅਗਲਾ",
|
||||
"done": "ਹੋ ਗਿਆ",
|
||||
"step_1": "ਚਰਣ 1",
|
||||
"first_go_to": "ਪਹਿਲਾਂ, ਜਾਉ",
|
||||
"login_if_not_logged_in": "ਅਤੇ ਜੇਕਰ ਤੁਸੀਂ ਦਾਖ਼ਲ ਨਹੀਂ ਹੋਏ ਹੋ ਤਾਂ ਦਾਖ਼ਲ ਹੋਵੋ / ਸਾਈਨਅੱਪ ਕਰੋ",
|
||||
"step_2": "ਚਰਣ 2",
|
||||
"step_2_steps": "1. ਇੱਕ ਵਾਰ ਜਦੋਂ ਤੁਸੀਂ ਦਾਖ਼ਲ ਹੋ ਜਾਂਦੇ ਹੋ, ਤਾਂ F12 ਦਬਾਓ ਜਾਂ ਮਾਊਸ ਸੱਜਾ ਕਲਿੱਕ ਕਰੋ > ਜਾਂਚ ਕਰੋ ਤਾਂ ਕਿ ਬ੍ਰਾਊਜ਼ਰ DevTools ਖੁੱਲ੍ਹ ਜਾਵੇ।\n2. ਫਿਰ ਬ੍ਰਾਊਜ਼ਰ ਦੇ \"ਐਪਲੀਕੇਸ਼ਨ\" ਟੈਬ (Chrome, Edge, Brave ਆਦਿ) ਜਾਂ \"ਸਟੋਰੇਜ\" ਟੈਬ (Firefox, Palemoon ਆਦਿ) ਵਿੱਚ ਜਾਉ\n3. \"ਕੁਕੀਜ\" ਅਨੁਭਾਗ ਵਿੱਚ ਜਾਉ ਫਿਰ \"https: //accounts.spotify.com\" ਉਪ-ਅਨੁਭਾਗ ਵਿੱਚ ਜਾਉ",
|
||||
"step_3": "ਚਰਣ 3",
|
||||
"success_emoji": "ਸਫ਼ਲ🥳",
|
||||
"success_message": "ਤੁਸੀਂ ਹੁਣ ਆਪਣੇ Spotify ਖ਼ਾਤੇ ਨਾਲ ਸਫ਼ਲਤਾਪੂਰਵਕ ਦਾਖ਼ਲ ਹੋ ਗਏ ਹੋ। ਤੁਸੀਂ ਵਧੀਆ ਕੰਮ ਕੀਤਾ!",
|
||||
"step_4": "ਚਰਣ 4",
|
||||
"something_went_wrong": "ਕੁੱਝ ਗਲਤ ਹੋ ਗਿਆ ਹੈ",
|
||||
"piped_instance": "ਪਾਈਪਡ ਸਰਵਰ",
|
||||
"piped_description": "ਪਾਈਪ ਕੀਤੇ ਗਏ ਸਰਵਰ",
|
||||
"piped_warning": "ਗਾਣਿਆਂ ਨਾਲ ਮੇਲ ਕਰਨ ਲਈ ਵਰਤੇ ਜਾਂਦੇ ਹਨ, ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਉਹਨਾਂ ਵਿੱਚੋਂ ਕੁਝ ਦੇ ਨਾਲ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ ਇਸਲਈ ਆਪਣੇ ਜੋਖਮ 'ਤੇ ਵਰਤੋਂ",
|
||||
"generate_playlist": "ਪਲੇਸੂਚੀ ਬਣਾਉ",
|
||||
"track_exists": "ਟ੍ਰੈਕ {track} ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ",
|
||||
"replace_downloaded_tracks": "ਸਾਰੇ ਡਾਊਨਲੋਡ ਕੀਤੇ ਗਏ ਟ੍ਰੈਕ ਬਦਲੋ",
|
||||
"skip_download_tracks": "ਸਾਰੇ ਡਾਊਨਲੋਡ ਕੀਤੇ ਗਏ ਟ੍ਰੈਕ ਛੱਡੋ",
|
||||
"do_you_want_to_replace": "ਕੀ ਤੁਸੀਂ ਮੌਜੂਦਾ ਟ੍ਰੈਕ ਬਦਲਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"replace": "ਬਦਲੋ",
|
||||
"skip": "ਛੱਡੋ",
|
||||
"select_up_to_count_type": "{count} {type} ਤੱਕ ਚੁਣੋ",
|
||||
"select_genres": "ਸ਼ੈਲੀਆਂ ਚੁਣੋ",
|
||||
"add_genres": "ਸ਼ੈਲੀਆਂ ਜੋੜੋ",
|
||||
"country": "ਦੇਸ਼",
|
||||
"number_of_tracks_generate": "ਉਤਪੰਨ ਕਰਨ ਲਈ ਟ੍ਰੈਕਾਂ ਦੀ ਸੰਖਿਆ",
|
||||
"acousticness": "ਧੁਨਿਕਤਾ",
|
||||
"danceability": "ਨੱਚਣ ਵਾਲੇ",
|
||||
"energy": "ਊਰਜਾ",
|
||||
"instrumentalness": "ਅਲਾਪਿਕਤਾ",
|
||||
"liveness": "ਜੀਵੰਤਤਾ",
|
||||
"loudness": "ਸ਼ੋਰ",
|
||||
"speechiness": "ਬੋਲਚਾਲਤਾ",
|
||||
"valence": "ਮਨੋਦਯਤਾ",
|
||||
"popularity": "ਲੋਕਪ੍ਰਿਯਤਾ",
|
||||
"key": "ਕੁੰਜੀ",
|
||||
"duration": "ਅਵਧੀ (ਸੈਕਿੰਡ)",
|
||||
"tempo": "ਗਤੀ (BPM)",
|
||||
"mode": "ਮੋਡ",
|
||||
"time_signature": "ਸਮਾਂ ਛਾਪ",
|
||||
"short": "ਸੰਖੇਪ",
|
||||
"medium": "ਮੱਧਮ",
|
||||
"long": "ਲੰਬਾ",
|
||||
"min": "ਨਿਊਨਤਮ",
|
||||
"max": "ਅਧਿਕਤਮ",
|
||||
"target": "ਟੀਚਾ",
|
||||
"moderate": "ਮੱਧਮ",
|
||||
"deselect_all": "ਸਭ ਨੂੰ ਅਣਚੁਣਿਆ ਕਰੋ",
|
||||
"select_all": "ਸਭ ਨੂੰ ਚੁਣੋ",
|
||||
"are_you_sure": "ਕੁ ਤੁਹਾਨੂੰ ਯਕੀਨ ਹੈ?",
|
||||
"generating_playlist": "ਤੁਹਾਡੀ ਆਪਣੀ ਪਲੇਸੂਚੀ ਬਣਾਈ ਜਾ ਰਹੀ ਹੈ...",
|
||||
"selected_count_tracks": "{count} ਟ੍ਰੈਕ ਚੁਣੇ ਗਏ",
|
||||
"download_warning": "ਜੇਕਰ ਤੁਸੀਂ ਸਾਰੇ ਟਰੈਕਾਂ ਨੂੰ ਬਲਕ ਵਿੱਚ ਡਾਊਨਲੋਡ ਕਰਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੰਗੀਤ ਦੀ ਗੈਰ-ਕਾਨੂੰਨੀ ਨਕਲ ਕਰ ਰਹੇ ਹੋ ਅਤੇ ਸੰਗੀਤ ਦੇ ਰਚਨਾਤਮਕ ਸਮਾਜ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚਾ ਰਹੇ ਹੋ। ਮੈਨੂੰ ਉਮੀਦ ਹੈ ਕਿ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਜਾਣਦੇ ਹੋ। ਹਮੇਸ਼ਾ ਕਲਾਕਾਰ ਦੀ ਮਿਹਨਤ ਦਾ ਸਤਿਕਾਰ ਅਤੇ ਸਮਰਥਨ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
|
||||
"download_ip_ban_warning": "ਬਾਹਰੀ ਡਾਉਨਲੋਡ ਬੇਨਤੀਆਂ ਕਾਰਨ ਤੁਹਾਡਾ IP YouTube 'ਤੇ ਵੱਧ ਤੋਂ ਵੱਧ ਬਲੌਕ ਹੋ ਸਕਦਾ ਹੈ। IP ਬਲਾਕ ਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਸੀਂ ਘੱਟੋ-ਘੱਟ 2-3 ਮਹੀਨਿਆਂ ਲਈ ਉਸੇ IP ਡਿਵਾਈਸ ਤੋਂ YouTube ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕਰ ਸਕੋਗੇ (ਭਾਵੇਂ ਤੁਸੀਂ ਲੌਗਇਨ ਕੀਤਾ ਹੋਵੇ)। ਅਤੇ ਜੇਕਰ ਅਜਿਹਾ ਹੁੰਦਾ ਹੈ ਤਾਂ Spottube ਕੋਈ ਜ਼ਿੰਮੇਵਾਰੀ ਨਹੀਂ ਲੈਂਦਾ।",
|
||||
"by_clicking_accept_terms": "'ਸਵੀਕਾਰ' ਕਲਿੱਕ ਕਰਕੇ ਤੁਸੀਂ ਹੇਠਾਂ ਦਿੱਤੀਆਂ ਸ਼ਰਤਾਂ ਨਾਲ ਸਹਿਮਤ ਹੁੰਦੇ ਹੋ:",
|
||||
"download_agreement_1": "ਮੈਨੂੰ ਪਤਾ ਹੈ ਕਿ ਮੈਂ ਗ਼ੈਰ-ਕਾਨੂੰਨੀ ਸੰਗੀਤ ਨੂੰ ਨਕਲੀ ਬਣਾ ਰਿਹਾ ਹਾਂ। ਮੈਂ ਬੁਰਾ ਹਾਂ",
|
||||
"download_agreement_2": "ਮੈਂ ਜਿੱਥੇ ਵੀ ਹੋ ਸਕੇ ਕਲਾਕਾਰਾਂ ਦਾ ਸਮਰਥਨ ਕਰਾਂਗਾ ਅਤੇ ਮੈਂ ਅਜਿਹਾ ਇਸ ਲਈ ਕਰ ਰਿਹਾ ਹਾਂ ਕਿਉਂਕਿ ਮੇਰੇ ਕੋਲ ਉਨ੍ਹਾਂ ਦੀ ਕਲਾ ਖਰੀਦਣ ਲਈ ਪੈਸੇ ਨਹੀਂ ਹਨ।",
|
||||
"download_agreement_3": "ਮੈਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਪਤਾ ਹੈ ਕਿ ਮੇਰਾ ਆਈਪੀ ਯੂਟਿਊਬ 'ਤੇ ਬਲੌਕ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਅਤੇ ਮੈਂ ਕਿਸੇ ਵੀ ਦੁਰਘਟਨਾ ਲਈ ਸਪੋਟੀਫਾਈ ਜਾਂ ਇਸਦੇ ਮਾਲਕਾਂ/ਸਬੰਧੀਆਂ ਨੂੰ ਜ਼ਿੰਮੇਵਾਰ ਨਹੀਂ ਠਹਿਰਾਉਂਦਾ ਹਾਂ।",
|
||||
"decline": "ਇਨਕਾਰ ਕਰੋ",
|
||||
"accept": "ਸਵੀਕਾਰ ਕਰੋ",
|
||||
"details": "ਵਿਵਰਣ",
|
||||
"youtube": "ਯੂਟਿਊਬ",
|
||||
"channel": "ਚੈਨਲ",
|
||||
"likes": "ਪਸੰਦ",
|
||||
"dislikes": "ਨਾਪਸੰਦ",
|
||||
"views": "ਵਿਯੂ",
|
||||
"streamUrl": "ਸਟ੍ਰੀਮ ਯੂਆਰਐਲ",
|
||||
"stop": "ਰੋਕੋ",
|
||||
"sort_newest": "ਨਵੇਂ ਜੋੜੇ ਗਏ ਅਨੁਸਾਰ ਕ੍ਰਮਬੱਧ ਕਰੋ",
|
||||
"sort_oldest": "ਸਭਤੋਂ ਪੁਰਾਣੇ ਅਨੁਸਾਰ ਕ੍ਰਮਬੱਧ ਕਰੋ",
|
||||
"sleep_timer": "ਸਲੀਪ ਟਾਈਮਰ",
|
||||
"mins": "{minutes} ਮਿੰਟ",
|
||||
"hours": "{hours} ਘੰਟੇ",
|
||||
"hour": "{hours} ਘੰਟਾ",
|
||||
"custom_hours": "ਕਸਟਮ ਘੰਟੇ",
|
||||
"logs": "ਚਿੱਠੇ",
|
||||
"developers": "ਡਿਵੈਲਪਰ",
|
||||
"not_logged_in": "ਤੁਸੀਂ ਦਾਖ਼ਲ ਨਹੀਂ ਹੋ",
|
||||
"search_mode": "ਖੋਜ ਮੋਡ",
|
||||
"audio_source": "ਆਡੀਓ ਸਰੋਤ",
|
||||
"ok": "ਠੀਕ ਹੈ",
|
||||
"failed_to_encrypt": "ਇਨਕ੍ਰਿਪਟ ਕਰਨ ਵਿੱਚ ਅਸਫਲ",
|
||||
"encryption_failed_warning": "Spottube ਤੁਹਾਡੇ ਡੇਟਾ ਨੂੰ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਟੋਰ ਕਰਨ ਲਈ ਐਨਕ੍ਰਿਪਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਪਰ ਇਹ ਅਸਫਲ ਰਿਹਾ. ਇਸ ਲਈ, ਇਹ ਅਸੁਰੱਖਿਅਤ ਸਟੋਰੇਜ 'ਤੇ ਵਾਪਸ ਆ ਜਾਵੇਗਾ\nਜੇਕਰ ਤੁਸੀਂ ਲੀਨਕਸ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੇ ਹੋ, ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਗੁਪਤ-ਸੇਵਾਵਾਂ ਜਿਵੇਂ ਕਿ gnome-keyring, kde-wallet, Keepassxc ਆਦਿ ਇੰਸਟਾਲ ਹਨ।",
|
||||
"querying_info": "ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰ ਰਿਹਾ ਹੈ",
|
||||
"piped_api_down": "ਪਾਈਪਡ ਏਪੀਆਈ ਡਾਊਨ ਹੈ",
|
||||
"piped_down_error_instructions": "ਪਾਈਪਡ ਇੰਸਟੈਂਸ {pipedInstance} ਵਰਤਮਾਨ ਵਿੱਚ ਡਾਊਨ ਹੈ\n\nਇੰਸਟੈਂਸ ਬਦਲੋ ਜਾਂ 'ਏਪੀਆਈ ਪ੍ਰਕਾਰ' ਨੂੰ ਅਧਿਕਾਰਤ YouTube API ਵਿੱਚ ਬਦਲੋ\n\nਤਬਦੀਲੀਆਂ ਤੋਂ ਬਾਅਦ ਐਪ ਨੂੰ ਦੁਬਾਰਾ ਚਲਾਉਣਾ ਯਕੀਨੀ ਬਣਾਓ",
|
||||
"you_are_offline": "ਤੁਸੀਂ ਵਰਤਮਾਨ ਵਿੱਚ ਆਫ਼ਲਾਈਨ ਹੋ",
|
||||
"connection_restored": "ਤੁਹਾਡਾ ਇੰਟਰਨੈੱਟ ਕੁਨੈਕਸ਼ਨ ਬਹਾਲ ਹੋ ਗਿਆ ਹੈ",
|
||||
"use_system_title_bar": "ਸਿਸਟਮ ਸਿਰਲੇਖ ਪੱਟੀ ਵਰਤੋ",
|
||||
"update_playlist": "ਪਲੇਸੂਚੀ ਅੱਪਡੇਟ ਕਰੋ",
|
||||
"update": "ਅੱਪਡੇਟ ਕਰੋ",
|
||||
"crunching_results": "ਨਤੀਜਿਆਂ 'ਤੇ ਕਾਰਵਾਈ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ...",
|
||||
"search_to_get_results": "ਨਤੀਜੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਖੋਜੋ",
|
||||
"use_amoled_mode": "AMOLED ਮੋਡ ਵਰਤੋ",
|
||||
"pitch_dark_theme": "ਪਿੱਚ ਬਲੈਕ ਗੂੜ੍ਹੀ ਥੀਮ",
|
||||
"normalize_audio": "ਆਡੀਓ ਨੂੰ ਸਧਾਰਨ ਕਰੋ",
|
||||
"change_cover": "ਕਵਰ ਬਦਲੋ",
|
||||
"add_cover": "ਕਵਰ ਜੋੜੋ",
|
||||
"restore_defaults": "ਡਿਫਾਲਟ ਸੈਟਿੰਗਾਂ ਬਹਾਲ ਕਰੋ",
|
||||
"download_music_codec": "ਸੰਗੀਤ ਕੋਡੈਕ ਡਾਊਨਲੋਡ ਕਰੋ",
|
||||
"streaming_music_codec": "ਸਟ੍ਰੀਮਿੰਗ ਸੰਗੀਤ ਕੋਡੈਕ",
|
||||
"login_with_lastfm": "Last.fm ਨਾਲ ਦਾਖ਼ਲ ਹੋਵੋ",
|
||||
"connect": "ਕਨੈਕਟ ਕਰੋ",
|
||||
"disconnect_lastfm": "Last.fm ਤੋਂ ਡਿਸਕਨੈਕਟ ਕਰੋ",
|
||||
"disconnect": "ਡਿਸਕਨੈਕਟ ਕਰੋ",
|
||||
"username": "ਉਪਯੋਗਕਰਤਾ ਨਾਮ",
|
||||
"password": "ਪਾਸਵਰਡ",
|
||||
"login": "ਦਾਖ਼ਲ ਹੋਵੋ",
|
||||
"login_with_your_lastfm": "ਆਪਣੇ Last.fm ਖ਼ਾਤੇ ਨਾਲ਼ ਦਾਖ਼ਲ ਹੋਵੋ",
|
||||
"scrobble_to_lastfm": "Last.fm ਉੱਤੇ ਸਕਰੌਬਲ ਕਰੋ",
|
||||
"go_to_album": "ਐਲਬਮ ਤੇ ਜਾਓ",
|
||||
"discord_rich_presence": "ਡਿਸਕਾਰਡ ਰਿਚ ਪ੍ਰੈਜੈਂਸ",
|
||||
"browse_all": "ਸਾਰਿਆਂ ਨੂੰ ਬ੍ਰਾਊਜ਼ ਕਰੋ",
|
||||
"genres": "ਸ਼ੈਲੀਆਂ",
|
||||
"explore_genres": "ਸ਼ੈਲੀਆਂ ਫਰੋਲੋ",
|
||||
"step_3_steps": "\"sp_dc\" ਕੁਕੀ ਦਾ ਮੁੱਲ ਕਾਪੀ ਕਰੋ",
|
||||
"step_4_steps": "ਕਾਪੀ ਕੀਤੇ ਗਏ \"sp_dc\" ਮੁੱਲ ਨੂੰ ਪੈਸਟ ਕਰੋ",
|
||||
"friends": "ਦੋਸਤ",
|
||||
"no_lyrics_available": "ਮਾਫ਼ ਕਰਨਾ, ਇਸ ਟ੍ਰੈਕ ਦੇ ਬੋਲ ਨਹੀਂ ਮਿਲ ਸਕੋ",
|
||||
"sort_duration": "ਅਵਧੀ ਅਨੁਸਾਰ ਕ੍ਰਮਬੱਧ ਕਰੋ",
|
||||
"start_a_radio": "ਰੇਡੀਉ ਸ਼ੁਰੂ ਕਰੋ",
|
||||
"how_to_start_radio": "ਰੇਡੀਉ ਕਿਵੇਂ ਸ਼ੁਰੂ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"replace_queue_question": "ਕੀ ਤੁਸੀਂ ਮੌਜੂਦਾ ਕਤਾਰ ਨੂੰ ਬਦਲਣਾ ਚਾਹੁੰਦੇ ਹੋ ਜਾਂ ਜੋੜਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"endless_playback": "ਬੇਅੰਤ ਪਲੇਬੈਕ",
|
||||
"delete_playlist": "ਪਲੇਸੂਚੀ ਹਟਾਉ",
|
||||
"delete_playlist_confirmation": "ਕੀ ਤੁਸੀਂ ਸੱਚਮੁੱਚ ਇਸ ਪਲੇਸੂਚੀ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"local_tracks": "ਸਥਾਨਕ ਟ੍ਰੈਕ",
|
||||
"song_link": "ਗੀਤ ਦਾ ਲਿੰਕ",
|
||||
"skip_this_nonsense": "ਇਸਨੂੰ ਛੱਡੋ",
|
||||
"freedom_of_music": "“ਸੰਗੀਤ ਦੀ ਅਜ਼ਾਦੀ”",
|
||||
"freedom_of_music_palm": "“ਹੱਥ ਵਿੱਚ ਸੰਗੀਤ ਦੀ ਅਜ਼ਾਦੀ”",
|
||||
"get_started": "ਆਉ ਸ਼ੁਰੂ ਕਰੋ",
|
||||
"youtube_source_description": "ਸਿਫ਼ਾਰਸ਼ ਕੀਤਾ ਗਿਆ ਅਤੇ ਸਭਤੋਂ ਵਧੀਆ ਕੰਮ ਕਰਦਾ ਹੈ।",
|
||||
"piped_source_description": "ਮੁਫ਼ਤ ਮਹਿਸੂਸ ਕਰ ਰਹੇ ਹੋ? YouTube ਦੇ ਸਮਾਨ ਪਰ ਕਾਫੀ ਮੁਫ਼ਤ।",
|
||||
"jiosaavn_source_description": "ਦੱਖਣੀ ਏਸ਼ੀਆਈ ਖੇਤਰ ਲਈ ਸਭ ਤੋਂ ਵਧੀਆ।",
|
||||
"highest_quality": "ਸਭਤੋਂ ਵਧੀਆ ਗੁਣਵੱਤਾ: {quality}",
|
||||
"select_audio_source": "ਆਡੀਉ ਸਰੋਤ ਚੁਣੋ",
|
||||
"endless_playback_description": "ਕ੍ਰਮਬੱਧ ਕਤਾਰ ਦੇ ਅੰਤ ਵਿੱਚ ਆਪਣੇ ਆਪ ਨਵੇਂ ਗੀਤ ਸ਼ਾਮਲ ਕਰੋ",
|
||||
"choose_your_region": "ਆਪਣਾ ਖੇਤਰ ਚੁਣੋ",
|
||||
"choose_your_region_description": "ਇਹ Spotube ਨੂੰ ਤੁਹਾਡੇ ਟਿਕਾਣੇ ਲਈ ਸਹੀ ਸਮੱਗਰੀ ਦਿਖਾਉਣ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਕਰੇਗਾ।",
|
||||
"choose_your_language": "ਆਪਣੀ ਭਾਸ਼ਾ ਚੁਣੋ",
|
||||
"help_project_grow": "ਇਸ ਪਰਿਯੋਜਨਾ ਨੂੰ ਵਧਾਉਣ ਵਿੱਚ ਮਦਦ ਕਰੋ",
|
||||
"help_project_grow_description": "Spotube ਇੱਕ ਓਪਨ ਸੋਰਸ ਪ੍ਰੋਜੈਕਟ ਹੈ। ਤੁਸੀਂ ਯੋਗਦਾਨ ਦੇ ਕੇ, ਬੱਗਾਂ ਦੀ ਰਿਪੋਰਟ ਕਰਕੇ, ਜਾਂ ਨਵੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦਾ ਸੁਝਾਅ ਦੇ ਕੇ ਇਸ ਪ੍ਰੋਜੈਕਟ ਨੂੰ ਵਧਾ ਸਕਦੇ ਹੋ।",
|
||||
"contribute_on_github": "GitHub ਉੱਤੇ ਯੋਗਦਾਨ ਦਿਉ",
|
||||
"donate_on_open_collective": "ਓਪਨ ਕਲੈਕਟਿਵ ਉੱਤੇ ਯੋਗਦਾਨ ਦਿਉ",
|
||||
"browse_anonymously": "ਬਿਨ੍ਹਾ ਨਾਮ ਦੇ ਬ੍ਰਾਊਜ਼ ਕਰੋ",
|
||||
"enable_connect": "ਕਨੈਕਟ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ",
|
||||
"enable_connect_description": "ਹੋਰ ਡਿਵਾਈਸਾਂ ਤੋਂ Spottube ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ",
|
||||
"devices": "ਉਪਕਰਣ",
|
||||
"select": "ਚੁਣੋ",
|
||||
"connect_client_alert": "ਤੁਹਾਨੂੰ {client} ਦੁਆਰਾ ਕੰਟਰੋਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
|
||||
"this_device": "ਇਹ ਉਪਕਰਣ",
|
||||
"remote": "ਰਿਮੋਟ",
|
||||
"local_library": "ਸਥਾਨਕ ਲਾਇਬ੍ਰੇਰੀ",
|
||||
"add_library_location": "ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਜੋੜੋ",
|
||||
"remove_library_location": "ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚੋਂ ਹਟਾਉ",
|
||||
"local_tab": "ਸਥਾਨਕ",
|
||||
"stats": "ਅੰਕੜੇ",
|
||||
"and_n_more": "और {count} और",
|
||||
"recently_played": "ਹਾਲ ਹੀ ਵਿੱਚ ਚਲਾਏ ਗਏ",
|
||||
"browse_more": "ਹੋਰ ਬ੍ਰਾਉਜ਼ ਕਰੋ",
|
||||
"no_title": "ਕੋਈ ਸਿਰਲੇਖ ਨਹੀਂ",
|
||||
"not_playing": "ਚੱਲ ਨਹੀਂ ਰਿਹਾ",
|
||||
"epic_failure": "ਮਹਾਨ ਅਸਫ਼ਲਤਾ!",
|
||||
"added_num_tracks_to_queue": "{tracks_length} ਟ੍ਰੈਕ ਕਤਾਰ ਵਿੱਚ ਜੋੜੇ ਗਏ",
|
||||
"spotube_has_an_update": "Spotube ਲਈ ਇੱਕ ਅੱਪਡੇਟ ਹੈ",
|
||||
"download_now": "ਹੁਣੇ ਡਾਊਨਲੋਡ ਕਰੋ",
|
||||
"nightly_version": "Spotube Nightly {nightlyBuildNum} ਜਾਰੀ ਕੀਤਾ ਗਿਆ ਹੈ",
|
||||
"release_version": "Spotube v{version} ਜਾਰੀ ਕੀਤਾ ਗਿਆ ਹੈ",
|
||||
"read_the_latest": "ਨਵੀਨਤਮ ਪੜ੍ਹੋ",
|
||||
"release_notes": "ਰਿਲੀਜ਼ ਨੋਟਸ",
|
||||
"pick_color_scheme": "ਰੰਗ ਯੋਜਨਾ ਚੁਣੋ",
|
||||
"save": "ਸੰਭਾਲੋ",
|
||||
"choose_the_device": "ਉਪਕਰਣ ਚੁਣੋ:",
|
||||
"multiple_device_connected": "ਕਈ ਉਪਕਰਣ ਜੋੜੇ ਗਏ ਹਨ।\nਉਸ ਉਪਕਰਣ ਨੂੰ ਚੁਣੋ ਜਿਸ ਤੇ ਤੁਸੀਂ ਕਿਰਿਆ ਕਰਨੀ ਚਾਹੁੰਦੇ ਹੋ",
|
||||
"nothing_found": "ਕੁੱਝ ਨਹੀਂ ਮਿਲਿਆ",
|
||||
"the_box_is_empty": "ਡੱਬਾ ਖਾਲੀ ਹੈ",
|
||||
"top_artists": "ਚੋਟੀ ਦਾ ਕਲਾਕਾਰ",
|
||||
"top_albums": "ਚੋਟੀ ਦੀ ਐਲਬਮ",
|
||||
"this_week": "ਇਸ ਹਫ਼ਤੇ",
|
||||
"this_month": "ਇਸ ਮਹੀਨੇ",
|
||||
"last_6_months": "ਪਿਛਲੇ 6 ਮਹੀਨੇ",
|
||||
"this_year": "ਇਸ ਸਾਲ",
|
||||
"last_2_years": "ਪਿਛਲੇ 2 साल",
|
||||
"all_time": "ਪੂਰਾ ਸਮਾਂ",
|
||||
"powered_by_provider": "{providerName} ਦੁਆਰਾ ਸੰਚਾਲਿਤ",
|
||||
"email": "ਈਮੇਲ",
|
||||
"profile_followers": "ਫ਼ਾਲੋਅਰ",
|
||||
"birthday": "ਜਨਮਦਿਨ",
|
||||
"subscription": "ਗਾਹਕੀਆਂ",
|
||||
"not_born": "ਹਲੇ ਪੈਦਾ ਨਹੀਂ ਹੋਇਆ",
|
||||
"hacker": "ਹੈਕਰ",
|
||||
"profile": "ਪ੍ਰੋਫ਼ਾਈਲ",
|
||||
"no_name": "ਕੋਈ ਨਾਮ ਨਹੀਂ",
|
||||
"edit": "ਸੋਧੋ",
|
||||
"user_profile": "ਵਰਤੋਂਕਾਰ ਪ੍ਰੋਫ਼ਾਈਲ",
|
||||
"count_plays": "{count} ਪਲੇ",
|
||||
"streaming_fees_hypothetical": "ਸਟ੍ਰੀਮਿੰਗ ਫੀਸ (ਕਾਲਪਨਿਕ)",
|
||||
"count_mins": "{minutes} ਮਿੰਟ",
|
||||
"summary_minutes": "ਮਿੰਟ",
|
||||
"summary_listened_to_music": "ਸੁਣਿਆ ਗਿਆ ਸੰਗੀਤ",
|
||||
"summary_songs": "ਗੀਤ",
|
||||
"summary_streamed_overall": "ਕੁੱਲ ਸਟ੍ਰੀਮ",
|
||||
"summary_owed_to_artists": "ਕਲਾਕਾਰਾਂ ਨੂੰ ਦੇਣਦਾਰ\nਇਸ ਮਹੀਨੇ",
|
||||
"summary_artists": "ਕਲਾਕਾਰ",
|
||||
"summary_music_reached_you": "ਸੰਗੀਤ ਤੁਹਾਡੇ ਤੱਕ ਪਹੁੰਚ ਗਿਆ",
|
||||
"summary_full_albums": "ਪੂਰਾ ਐਲਬਮ",
|
||||
"summary_got_your_love": "ਤੁਹਾਡਾ ਪਿਆਰ ਮਿਲਿਆ",
|
||||
"summary_playlists": "ਪਲੇਸੂਚੀਆਂ",
|
||||
"summary_were_on_repeat": "ਦੁਹਰਾਇਆ ਗਿਆ",
|
||||
"total_money": "ਕੁੱਲ {money}",
|
||||
"minutes_listened": "ਮਿੰਟ ਸੁਣਿਆ",
|
||||
"streamed_songs": "ਗੀਤ ਸਟ੍ਰੀਮ ਕੀਤੇ",
|
||||
"count_streams": "{count} ਸਟ੍ਰੀਮ",
|
||||
"owned_by_you": "ਤੁਹਾਡੀ ਮਲਕੀਅਤ",
|
||||
"copied_shareurl_to_clipboard": "{shareUrl} ਕਲਿੱਪਬੋਰਡ ਉੱਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ",
|
||||
"spotify_hipotetical_calculation": "*ਇਸਦੀ ਗਣਨਾ Spotify ਪ੍ਰਤੀ ਸਟ੍ਰੀਮ\n$0.003 ਤੋਂ $0.005 ਦੇ ਭੁਗਤਾਨ ਦੇ ਆਧਾਰ 'ਤੇ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਇਹ ਇੱਕ ਕਾਲਪਨਿਕ\nਗਣਨਾ ਹੈ ਜੋ ਉਪਭੋਗਤਾ ਨੂੰ ਇਸ ਬਾਰੇ ਜਾਣਕਾਰੀ ਦਿੰਦਾ ਹੈ ਕਿ \nਜੇਕਰ ਉਹਨਾਂ ਨੇ Spotify 'ਤੇ ਗੀਤ ਸੁਣਿਆ ਤਾਂ ਉਹਨਾਂ ਨੇ ਕਿੰਨਾ ਭੁਗਤਾਨ ਕੀਤਾ ਹੋਵੇਗਾ।",
|
||||
"webview_not_found": "ਵੈੱਬਵਿਯੂ ਨਹੀਂ ਮਿਲਿਆ",
|
||||
"webview_not_found_description": "ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਕੋਈ ਵੈੱਬਵਿਯੂ ਰਨਟਾਈਮ ਇੰਸਟਾਲ ਨਹੀਂ ਹੈ।\nਜੈਕਰ ਇਹ ਇੰਸਟਾਲ ਹੈ ਤਾਂ ਪੱਕਾ ਕਰੋ ਕਿ ਇਹ ਵਾਤਾਵਰਨ ਰਸਤੇ ਵਿੱਚ ਹੈ\n\nਇੰਸਟਾਲ ਕਰਨ ਤੋਂ ਬਾਅਦ, ਐਪ ਦੁਬਾਰਾ ਚਲਾਉ",
|
||||
"unsupported_platform": "ਅਸਮਰਥਿਤ ਪਲੇਟਫਾਰਮ",
|
||||
"invidious_instance": "ਇਨਡਿਵੀਡਿਊਸ ਸਰਵਰ ਇੰਸਟੈਂਸ",
|
||||
"invidious_description": "ਟ੍ਰੈਕ ਮਿਲਾਨ ਲਈ ਇਨਡਿਵੀਡਿਊਸ ਸਰਵਰ ਇੰਸਟੈਂਸ",
|
||||
"invidious_warning": "ਕੁੱਝ ਇੰਸਟੈਂਸ ਸਹੀ ਕੰਮ ਨਹੀਂ ਕਰ ਰਹੇ ਹਨ। ਆਪਣੇ ਜੋਖਮ ਤੇ ਵਰਤੋ",
|
||||
"invidious_source_description": "ਪਾਈਪਡ ਦੇ ਸਮਾਨ, ਪਰ ਵੱਧ ਉਪਲਬਧਤਾ ਨਾਲ",
|
||||
"cache_music": "ਸੰਗੀਤ ਨੂੰ ਕੈਸ਼ ਕਰੋ",
|
||||
"open": "ਖੋਲ੍ਹੋ",
|
||||
"cache_folder": "ਕੈਸ਼ ਫੋਲਡਰ",
|
||||
"export": "ਨਿਰਯਾਤ ਕਰੋ",
|
||||
"clear_cache": "ਕੈਸ਼ ਸਾਫ਼ ਕਰੋ",
|
||||
"clear_cache_confirmation": "ਕੀ ਤੁਸੀਂ ਕੈਸ਼ ਸਾਫ਼ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
|
||||
"export_cache_files": "ਕੈਸ਼ ਫ਼ਾਈਲਾਂ ਨਿਰਯਾਤ ਕਰੋ",
|
||||
"found_n_files": "{count} ਫ਼ਾਈਲਾਂ ਮਿਲੀਆਂ",
|
||||
"export_cache_confirmation": "ਕੀ ਤੁਸੀਂ ਇਹਨਾਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਨਿਰਯਾਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ",
|
||||
"exported_n_out_of_m_files": "{files} ਵਿੱਚੋਂ {filesExported} ਫ਼ਾਈਲਾਂ ਨਿਰਯਾਤ ਕੀਤੀਆਂ ਗਈਆਂ"
|
||||
}
|
||||
@ -8,7 +8,7 @@ import 'package:spotube/collections/routes.gr.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/models/spotify_friends.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class FriendItem extends HookConsumerWidget {
|
||||
final SpotifyFriendActivity friend;
|
||||
@ -95,8 +95,9 @@ class FriendItem extends HookConsumerWidget {
|
||||
text: " ${friend.track.album.name}",
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
final album =
|
||||
await spotify.albums.get(friend.track.album.id);
|
||||
final album = await spotify.invoke(
|
||||
(api) => api.albums.get(friend.track.album.id),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.navigateTo(
|
||||
AlbumRoute(id: album.id!, album: album),
|
||||
|
||||
@ -19,7 +19,6 @@ import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
|
||||
class PlaylistCreateDialog extends HookConsumerWidget {
|
||||
/// Track ids to add to the playlist
|
||||
@ -260,7 +259,7 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
||||
class PlaylistCreateDialogButton extends HookConsumerWidget {
|
||||
const PlaylistCreateDialogButton({super.key});
|
||||
|
||||
showPlaylistDialog(BuildContext context, SpotifyApi spotify) {
|
||||
showPlaylistDialog(BuildContext context, SpotifyApiWrapper spotify) {
|
||||
showDialog(
|
||||
context: context,
|
||||
alignment: Alignment.center,
|
||||
|
||||
@ -22,7 +22,6 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/models/spotify/recommendation_seeds.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
@ -70,22 +69,24 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
leftSeedCount,
|
||||
context.l10n.artists,
|
||||
)),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.artist],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Artist>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
fetchSeeds: (textEditingValue) => spotify.invoke(
|
||||
(api) => api.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.artist],
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
artists.value.none((artist) => element.id == artist.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Artist>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
artists.value.none((artist) => element.id == artist.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ButtonTile(
|
||||
leading: Avatar(
|
||||
initials: "O",
|
||||
@ -146,22 +147,24 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
leftSeedCount,
|
||||
context.l10n.tracks,
|
||||
)),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.track],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Track>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
fetchSeeds: (textEditingValue) => spotify.invoke(
|
||||
(api) => api.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.track],
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
tracks.value.none((track) => element.id == track.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Track>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
tracks.value.none((track) => element.id == track.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ButtonTile(
|
||||
leading: Avatar(
|
||||
initials: option.name!.substring(0, 1),
|
||||
|
||||
@ -15,6 +15,7 @@ import 'package:spotube/components/dialogs/prompt_dialog.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:otp_util/otp_util.dart';
|
||||
// ignore: implementation_imports
|
||||
@ -197,6 +198,34 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response> getToken({
|
||||
required String totp,
|
||||
required int timestamp,
|
||||
String mode = "transport",
|
||||
String? spDc,
|
||||
}) async {
|
||||
assert(mode == "transport" || mode == "init");
|
||||
|
||||
final accessTokenUrl = Uri.parse(
|
||||
"https://open.spotify.com/get_access_token?reason=$mode&productType=web-player"
|
||||
"&totp=$totp&totpVer=5&ts=$timestamp",
|
||||
);
|
||||
|
||||
final res = await dio.getUri(
|
||||
accessTokenUrl,
|
||||
options: Options(
|
||||
headers: {
|
||||
"Cookie": spDc ?? "",
|
||||
"User-Agent": ServiceUtils.randomUserAgent(
|
||||
kIsDesktop ? UserAgentDevice.desktop : UserAgentDevice.mobile,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<AuthenticationTableCompanion> credentialsFromCookie(
|
||||
String cookie,
|
||||
) async {
|
||||
@ -207,24 +236,34 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
|
||||
?.trim();
|
||||
|
||||
final totp = await generateTotp();
|
||||
|
||||
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor();
|
||||
|
||||
final accessTokenUrl = Uri.parse(
|
||||
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player"
|
||||
"&totp=$totp&totpVer=5&ts=$timestamp",
|
||||
var res = await getToken(
|
||||
totp: totp,
|
||||
timestamp: timestamp,
|
||||
spDc: spDc,
|
||||
mode: "transport",
|
||||
);
|
||||
|
||||
final res = await dio.getUri(
|
||||
accessTokenUrl,
|
||||
options: Options(
|
||||
headers: {
|
||||
"Cookie": spDc ?? "",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
||||
},
|
||||
),
|
||||
);
|
||||
final body = res.data;
|
||||
if ((res.data["accessToken"]?.length ?? 0) != 374) {
|
||||
res = await getToken(
|
||||
totp: totp,
|
||||
timestamp: timestamp,
|
||||
spDc: spDc,
|
||||
mode: "init",
|
||||
);
|
||||
}
|
||||
|
||||
final body = res.data as Map<String, dynamic>;
|
||||
|
||||
if (body["accessToken"] == null) {
|
||||
AppLogger.reportError(
|
||||
"The access token is only ${body["accessToken"]?.length} characters long instead of 374\n"
|
||||
"Your authentication probably doesn't work",
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
|
||||
return AuthenticationTableCompanion.insert(
|
||||
id: const Value(0),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/services/custom_spotify_endpoints/spotify_endpoints.dart';
|
||||
|
||||
final customSpotifyEndpointProvider = Provider<CustomSpotifyEndpoints>((ref) {
|
||||
|
||||
@ -76,8 +76,6 @@ final localTracksProvider =
|
||||
final mime = lookupMimeType(e.path) ??
|
||||
(extension(e.path) == ".opus" ? "audio/opus" : null);
|
||||
|
||||
print("${basename(e.path)}: $mime");
|
||||
|
||||
return e is File && supportedAudioTypes.contains(mime);
|
||||
},
|
||||
).cast<File>(),
|
||||
|
||||
@ -22,11 +22,14 @@ class FavoriteAlbumState extends PaginatedState<AlbumSimple> {
|
||||
class FavoriteAlbumNotifier
|
||||
extends PaginatedAsyncNotifier<AlbumSimple, FavoriteAlbumState> {
|
||||
@override
|
||||
Future<List<AlbumSimple>> fetch(int offset, int limit) {
|
||||
return spotify.me
|
||||
.savedAlbums()
|
||||
.getPage(limit, offset)
|
||||
.then((value) => value.items?.toList() ?? []);
|
||||
Future<List<AlbumSimple>> fetch(int offset, int limit) async {
|
||||
return await spotify
|
||||
.invoke(
|
||||
(api) => api.me.savedAlbums().getPage(limit, offset),
|
||||
)
|
||||
.then(
|
||||
(value) => value.items?.toList() ?? <AlbumSimple>[],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -45,8 +48,10 @@ class FavoriteAlbumNotifier
|
||||
if (state.value == null) return;
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
await spotify.me.saveAlbums(ids);
|
||||
final albums = await spotify.albums.list(ids);
|
||||
await spotify.invoke((api) => api.me.saveAlbums(ids));
|
||||
final albums = await spotify.invoke(
|
||||
(api) => api.albums.list(ids),
|
||||
);
|
||||
|
||||
return state.value!.copyWith(
|
||||
items: [
|
||||
@ -65,7 +70,7 @@ class FavoriteAlbumNotifier
|
||||
if (state.value == null) return;
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
await spotify.me.removeAlbums(ids);
|
||||
await spotify.invoke((api) => api.me.removeAlbums(ids));
|
||||
|
||||
return state.value!.copyWith(
|
||||
items: state.value!.items
|
||||
|
||||
@ -3,8 +3,10 @@ part of '../spotify.dart';
|
||||
final albumsIsSavedProvider = FutureProvider.autoDispose.family<bool, String>(
|
||||
(ref, albumId) async {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
return spotify.me.containsSavedAlbums([albumId]).then(
|
||||
(value) => value[albumId] ?? false,
|
||||
return spotify.invoke(
|
||||
(api) => api.me.containsSavedAlbums([albumId]).then(
|
||||
(value) => value[albumId] ?? false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -32,9 +32,9 @@ class AlbumReleasesNotifier
|
||||
fetch(int offset, int limit) async {
|
||||
final market = ref.read(userPreferencesProvider).market;
|
||||
|
||||
final albums = await spotify.browse
|
||||
.newReleases(country: market)
|
||||
.getPage(limit, offset);
|
||||
final albums = await spotify.invoke(
|
||||
(api) => api.browse.newReleases(country: market).getPage(limit, offset),
|
||||
);
|
||||
|
||||
return albums.items?.map((album) => album.toAlbum()).toList() ?? [];
|
||||
}
|
||||
|
||||
@ -30,7 +30,9 @@ class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
|
||||
|
||||
@override
|
||||
fetch(arg, offset, limit) async {
|
||||
final tracks = await spotify.albums.tracks(arg.id!).getPage(limit, offset);
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.albums.tracks(arg.id!).getPage(limit, offset),
|
||||
);
|
||||
final items = tracks.items?.map((e) => e.asTrack(arg)).toList() ?? [];
|
||||
|
||||
return (
|
||||
|
||||
@ -31,9 +31,9 @@ class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
@override
|
||||
fetch(arg, offset, limit) async {
|
||||
final market = ref.read(userPreferencesProvider).market;
|
||||
final albums = await spotify.artists
|
||||
.albums(arg, country: market)
|
||||
.getPage(limit, offset);
|
||||
final albums = await spotify.invoke(
|
||||
(api) => api.artists.albums(arg, country: market).getPage(limit, offset),
|
||||
);
|
||||
|
||||
final items = albums.items?.toList() ?? [];
|
||||
|
||||
|
||||
@ -6,5 +6,5 @@ final artistProvider =
|
||||
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
|
||||
return spotify.artists.get(artistId);
|
||||
return spotify.invoke((api) => api.artists.get(artistId));
|
||||
});
|
||||
|
||||
@ -33,10 +33,12 @@ class FollowedArtistsNotifier
|
||||
|
||||
@override
|
||||
fetch(offset, limit) async {
|
||||
final artists = await spotify.me.following(FollowingType.artist).getPage(
|
||||
limit,
|
||||
offset ?? '',
|
||||
);
|
||||
final artists = await spotify.invoke(
|
||||
(api) => api.me.following(FollowingType.artist).getPage(
|
||||
limit,
|
||||
offset ?? '',
|
||||
),
|
||||
);
|
||||
|
||||
return (artists.items?.toList() ?? [], artists.after);
|
||||
}
|
||||
@ -55,7 +57,9 @@ class FollowedArtistsNotifier
|
||||
|
||||
Future<void> _followArtists(List<String> artistIds) async {
|
||||
try {
|
||||
final creds = await spotify.getCredentials();
|
||||
final creds = await spotify.invoke(
|
||||
(api) => api.getCredentials(),
|
||||
);
|
||||
|
||||
await dio.post(
|
||||
"https://api-partner.spotify.com/pathfinder/v1/query",
|
||||
@ -93,7 +97,9 @@ class FollowedArtistsNotifier
|
||||
await _followArtists(artistIds);
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
final artists = await spotify.artists.list(artistIds);
|
||||
final artists = await spotify.invoke(
|
||||
(api) => api.artists.list(artistIds),
|
||||
);
|
||||
|
||||
return state.value!.copyWith(
|
||||
items: [
|
||||
@ -110,7 +116,9 @@ class FollowedArtistsNotifier
|
||||
|
||||
Future<void> removeArtists(List<String> artistIds) async {
|
||||
if (state.value == null) return;
|
||||
await spotify.me.unfollow(FollowingType.artist, artistIds);
|
||||
await spotify.invoke(
|
||||
(api) => api.me.unfollow(FollowingType.artist, artistIds),
|
||||
);
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
final artists = state.value!.items.where((artist) {
|
||||
@ -136,7 +144,9 @@ final followedArtistsProvider =
|
||||
final allFollowedArtistsProvider = FutureProvider<List<Artist>>(
|
||||
(ref) async {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final artists = await spotify.me.following(FollowingType.artist).all();
|
||||
final artists = await spotify.invoke(
|
||||
(api) => api.me.following(FollowingType.artist).all(),
|
||||
);
|
||||
return artists.toList();
|
||||
},
|
||||
);
|
||||
|
||||
@ -3,8 +3,10 @@ part of '../spotify.dart';
|
||||
final artistIsFollowingProvider = FutureProvider.family(
|
||||
(ref, String artistId) async {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
return spotify.me.checkFollowing(FollowingType.artist, [artistId]).then(
|
||||
(value) => value[artistId] ?? false,
|
||||
return spotify.invoke(
|
||||
(api) => api.me.checkFollowing(FollowingType.artist, [artistId]).then(
|
||||
(value) => value[artistId] ?? false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -5,7 +5,9 @@ final relatedArtistsProvider = FutureProvider.autoDispose
|
||||
ref.cacheFor();
|
||||
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final artists = await spotify.artists.relatedArtists(artistId);
|
||||
final artists = await spotify.invoke(
|
||||
(api) => api.artists.relatedArtists(artistId),
|
||||
);
|
||||
|
||||
return artists.toList();
|
||||
});
|
||||
|
||||
@ -7,7 +7,9 @@ final artistTopTracksProvider =
|
||||
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final market = ref.watch(userPreferencesProvider.select((s) => s.market));
|
||||
final tracks = await spotify.artists.topTracks(artistId, market);
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.artists.topTracks(artistId, market),
|
||||
);
|
||||
|
||||
return tracks.toList();
|
||||
},
|
||||
|
||||
@ -5,14 +5,16 @@ final categoriesProvider = FutureProvider(
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final market = ref.watch(userPreferencesProvider.select((s) => s.market));
|
||||
final locale = ref.watch(userPreferencesProvider.select((s) => s.locale));
|
||||
final categories = await spotify.categories
|
||||
.list(
|
||||
country: market,
|
||||
locale: Intl.canonicalizedLocale(
|
||||
locale.toString(),
|
||||
),
|
||||
)
|
||||
.all();
|
||||
final categories = await spotify.invoke(
|
||||
(api) => api.categories
|
||||
.list(
|
||||
country: market,
|
||||
locale: Intl.canonicalizedLocale(
|
||||
locale.toString(),
|
||||
),
|
||||
)
|
||||
.all(),
|
||||
);
|
||||
|
||||
return categories.toList()..shuffle();
|
||||
},
|
||||
|
||||
@ -32,7 +32,7 @@ class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
fetch(arg, offset, limit) async {
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
final playlists = await Pages<PlaylistSimple?>(
|
||||
spotify,
|
||||
spotify.api,
|
||||
"v1/browse/categories/$arg/playlists?country=${preferences.market.name}&locale=${preferences.locale}",
|
||||
(json) => json == null ? null : PlaylistSimple.fromJson(json),
|
||||
'playlists',
|
||||
|
||||
@ -138,7 +138,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier<SubtitleSimple, Track?> {
|
||||
|
||||
SubtitleSimple? lyrics = cachedLyrics;
|
||||
|
||||
final token = await spotify.getCredentials();
|
||||
final token = await spotify.invoke((api) => api.getCredentials());
|
||||
|
||||
if ((lyrics == null || lyrics.lyrics.isEmpty) && auth != null) {
|
||||
lyrics = await getSpotifyLyrics(token.accessToken);
|
||||
|
||||
@ -30,9 +30,11 @@ class FavoritePlaylistsNotifier
|
||||
|
||||
@override
|
||||
fetch(int offset, int limit) async {
|
||||
final playlists = await spotify.playlists.me.getPage(
|
||||
limit,
|
||||
offset,
|
||||
final playlists = await spotify.invoke(
|
||||
(api) => api.playlists.me.getPage(
|
||||
limit,
|
||||
offset,
|
||||
),
|
||||
);
|
||||
|
||||
return playlists.items?.toList() ?? [];
|
||||
@ -67,7 +69,9 @@ class FavoritePlaylistsNotifier
|
||||
|
||||
Future<void> addFavorite(PlaylistSimple playlist) async {
|
||||
await update((state) async {
|
||||
await spotify.playlists.followPlaylist(playlist.id!);
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.followPlaylist(playlist.id!),
|
||||
);
|
||||
return state.copyWith(
|
||||
items: [...state.items, playlist],
|
||||
);
|
||||
@ -78,7 +82,9 @@ class FavoritePlaylistsNotifier
|
||||
|
||||
Future<void> removeFavorite(PlaylistSimple playlist) async {
|
||||
await update((state) async {
|
||||
await spotify.playlists.unfollowPlaylist(playlist.id!);
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.unfollowPlaylist(playlist.id!),
|
||||
);
|
||||
return state.copyWith(
|
||||
items: state.items.where((e) => e.id != playlist.id).toList(),
|
||||
);
|
||||
@ -92,9 +98,11 @@ class FavoritePlaylistsNotifier
|
||||
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
|
||||
await spotify.playlists.addTracks(
|
||||
trackIds.map((id) => 'spotify:track:$id').toList(),
|
||||
playlistId,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.addTracks(
|
||||
trackIds.map((id) => 'spotify:track:$id').toList(),
|
||||
playlistId,
|
||||
),
|
||||
);
|
||||
|
||||
ref.invalidate(playlistTracksProvider(playlistId));
|
||||
@ -105,9 +113,11 @@ class FavoritePlaylistsNotifier
|
||||
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
|
||||
await spotify.playlists.removeTracks(
|
||||
trackIds.map((id) => 'spotify:track:$id').toList(),
|
||||
playlistId,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.removeTracks(
|
||||
trackIds.map((id) => 'spotify:track:$id').toList(),
|
||||
playlistId,
|
||||
),
|
||||
);
|
||||
|
||||
ref.invalidate(playlistTracksProvider(playlistId));
|
||||
@ -128,8 +138,8 @@ final isFavoritePlaylistProvider = FutureProvider.family<bool, String>(
|
||||
return false;
|
||||
}
|
||||
|
||||
final follows =
|
||||
await spotify.playlists.followedByUsers(id, [me.value!.id!]);
|
||||
final follows = await spotify
|
||||
.invoke((api) => api.playlists.followedByUsers(id, [me.value!.id!]));
|
||||
|
||||
return follows[me.value!.id!] ?? false;
|
||||
},
|
||||
|
||||
@ -30,9 +30,8 @@ class FeaturedPlaylistsNotifier
|
||||
|
||||
@override
|
||||
fetch(int offset, int limit) async {
|
||||
final playlists = await spotify.playlists.featured.getPage(
|
||||
limit,
|
||||
offset,
|
||||
final playlists = await spotify.invoke(
|
||||
(api) => api.playlists.featured.getPage(limit, offset),
|
||||
);
|
||||
|
||||
return playlists.items?.toList() ?? [];
|
||||
|
||||
@ -8,32 +8,36 @@ final generatePlaylistProvider = FutureProvider.autoDispose
|
||||
userPreferencesProvider.select((s) => s.market),
|
||||
);
|
||||
|
||||
final recommendation = await spotify.recommendations
|
||||
.get(
|
||||
limit: input.limit,
|
||||
seedArtists: input.seedArtists?.toList(),
|
||||
seedGenres: input.seedGenres?.toList(),
|
||||
seedTracks: input.seedTracks?.toList(),
|
||||
market: market,
|
||||
max: (input.max?.toJson()?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
min: (input.min?.toJson()?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
target: (input.target?.toJson()
|
||||
?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
)
|
||||
.catchError((e, stackTrace) {
|
||||
AppLogger.reportError(e, stackTrace);
|
||||
return Recommendations();
|
||||
});
|
||||
final recommendation = await spotify.invoke(
|
||||
(api) => api.recommendations
|
||||
.get(
|
||||
limit: input.limit,
|
||||
seedArtists: input.seedArtists?.toList(),
|
||||
seedGenres: input.seedGenres?.toList(),
|
||||
seedTracks: input.seedTracks?.toList(),
|
||||
market: market,
|
||||
max: (input.max?.toJson()?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
min: (input.min?.toJson()?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
target: (input.target?.toJson()
|
||||
?..removeWhere((key, value) => value == null))
|
||||
?.cast<String, num>(),
|
||||
)
|
||||
.catchError((e, stackTrace) {
|
||||
AppLogger.reportError(e, stackTrace);
|
||||
return Recommendations();
|
||||
}),
|
||||
);
|
||||
|
||||
if (recommendation.tracks?.isEmpty ?? true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final tracks = await spotify.tracks
|
||||
.list(recommendation.tracks!.map((e) => e.id!).toList());
|
||||
final tracks = await spotify.invoke(
|
||||
(api) =>
|
||||
api.tracks.list(recommendation.tracks!.map((e) => e.id!).toList()),
|
||||
);
|
||||
|
||||
return tracks.toList();
|
||||
},
|
||||
|
||||
@ -4,7 +4,9 @@ class LikedTracksNotifier extends AsyncNotifier<List<Track>> {
|
||||
@override
|
||||
FutureOr<List<Track>> build() async {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final savedTracked = await spotify.tracks.me.saved.all();
|
||||
final savedTracked = await spotify.invoke(
|
||||
(api) => api.tracks.me.saved.all(),
|
||||
);
|
||||
|
||||
return savedTracked.map((e) => e.track!).toList();
|
||||
}
|
||||
@ -17,10 +19,14 @@ class LikedTracksNotifier extends AsyncNotifier<List<Track>> {
|
||||
final isLiked = tracks.map((e) => e.id).contains(track.id);
|
||||
|
||||
if (isLiked) {
|
||||
await spotify.tracks.me.removeOne(track.id!);
|
||||
await spotify.invoke(
|
||||
(api) => api.tracks.me.removeOne(track.id!),
|
||||
);
|
||||
return tracks.where((e) => e.id != track.id).toList();
|
||||
} else {
|
||||
await spotify.tracks.me.saveOne(track.id!);
|
||||
await spotify.invoke(
|
||||
(api) => api.tracks.me.saveOne(track.id!),
|
||||
);
|
||||
return [track, ...tracks];
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,7 +12,9 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
||||
@override
|
||||
FutureOr<Playlist> build(String arg) {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
return spotify.playlists.get(arg);
|
||||
return spotify.invoke(
|
||||
(api) => api.playlists.get(arg),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> create(PlaylistInput input, [ValueChanged? onError]) async {
|
||||
@ -26,18 +28,22 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
||||
|
||||
state = await AsyncValue.guard(() async {
|
||||
try {
|
||||
final playlist = await spotify.playlists.createPlaylist(
|
||||
me.value!.id!,
|
||||
input.playlistName,
|
||||
collaborative: input.collaborative,
|
||||
description: input.description,
|
||||
public: input.public,
|
||||
final playlist = await spotify.invoke(
|
||||
(api) => api.playlists.createPlaylist(
|
||||
me.value!.id!,
|
||||
input.playlistName,
|
||||
collaborative: input.collaborative,
|
||||
description: input.description,
|
||||
public: input.public,
|
||||
),
|
||||
);
|
||||
|
||||
if (input.base64Image != null) {
|
||||
await spotify.playlists.updatePlaylistImage(
|
||||
playlist.id!,
|
||||
input.base64Image!,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.updatePlaylistImage(
|
||||
playlist.id!,
|
||||
input.base64Image!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -58,21 +64,27 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
||||
|
||||
await update((state) async {
|
||||
try {
|
||||
await spotify.playlists.updatePlaylist(
|
||||
state.id!,
|
||||
input.playlistName,
|
||||
collaborative: input.collaborative,
|
||||
description: input.description,
|
||||
public: input.public,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.updatePlaylist(
|
||||
state.id!,
|
||||
input.playlistName,
|
||||
collaborative: input.collaborative,
|
||||
description: input.description,
|
||||
public: input.public,
|
||||
),
|
||||
);
|
||||
|
||||
if (input.base64Image != null) {
|
||||
await spotify.playlists.updatePlaylistImage(
|
||||
state.id!,
|
||||
input.base64Image!,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.updatePlaylistImage(
|
||||
state.id!,
|
||||
input.base64Image!,
|
||||
),
|
||||
);
|
||||
|
||||
final playlist = await spotify.playlists.get(state.id!);
|
||||
final playlist = await spotify.invoke(
|
||||
(api) => api.playlists.get(state.id!),
|
||||
);
|
||||
|
||||
ref.read(favoritePlaylistsProvider.notifier).updatePlaylist(playlist);
|
||||
return playlist;
|
||||
@ -105,9 +117,11 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
||||
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
|
||||
await spotify.playlists.addTracks(
|
||||
trackIds.map((id) => "spotify:track:$id").toList(),
|
||||
state.value!.id!,
|
||||
await spotify.invoke(
|
||||
(api) => api.playlists.addTracks(
|
||||
trackIds.map((id) => "spotify:track:$id").toList(),
|
||||
state.value!.id!,
|
||||
),
|
||||
);
|
||||
} catch (e, stack) {
|
||||
onError?.call(e);
|
||||
|
||||
@ -30,9 +30,9 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
|
||||
@override
|
||||
fetch(arg, offset, limit) async {
|
||||
final tracks = await spotify.playlists
|
||||
.getTracksByPlaylistId(arg)
|
||||
.getPage(limit, offset);
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.playlists.getTracksByPlaylistId(arg).getPage(limit, offset),
|
||||
);
|
||||
|
||||
/// Filter out tracks with null id because some personal playlists
|
||||
/// may contain local tracks that are not available in the Spotify catalog
|
||||
|
||||
@ -44,13 +44,15 @@ class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
|
||||
nextOffset: 0,
|
||||
);
|
||||
}
|
||||
final results = await spotify.search
|
||||
.get(
|
||||
ref.read(searchTermStateProvider),
|
||||
types: [arg],
|
||||
market: ref.read(userPreferencesProvider).market,
|
||||
)
|
||||
.getPage(limit, offset);
|
||||
final results = await spotify.invoke(
|
||||
(api) => api.search
|
||||
.get(
|
||||
ref.read(searchTermStateProvider),
|
||||
types: [arg],
|
||||
market: ref.read(userPreferencesProvider).market,
|
||||
)
|
||||
.getPage(limit, offset),
|
||||
);
|
||||
|
||||
final items = results.expand((e) => e.items ?? <Y>[]).toList().cast<Y>();
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:spotube/collections/assets.gen.dart';
|
||||
import 'package:spotube/collections/env.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
@ -25,10 +26,10 @@ import 'package:spotube/models/lyrics.dart';
|
||||
import 'package:spotube/models/spotify/recommendation_seeds.dart';
|
||||
import 'package:spotube/models/spotify_friends.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/dio/dio.dart';
|
||||
import 'package:spotube/services/wikipedia/wikipedia.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
import 'package:wikipedia_api/wikipedia_api.dart';
|
||||
|
||||
@ -76,3 +77,57 @@ part 'utils/provider/paginated.dart';
|
||||
part 'utils/provider/cursor.dart';
|
||||
part 'utils/provider/paginated_family.dart';
|
||||
part 'utils/provider/cursor_family.dart';
|
||||
|
||||
class SpotifyApiWrapper {
|
||||
final SpotifyApi api;
|
||||
|
||||
final Ref ref;
|
||||
SpotifyApiWrapper(
|
||||
this.ref,
|
||||
this.api,
|
||||
);
|
||||
|
||||
bool _isRefreshing = false;
|
||||
|
||||
FutureOr<T> invoke<T>(
|
||||
FutureOr<T> Function(SpotifyApi api) fn,
|
||||
) async {
|
||||
try {
|
||||
return await fn(api);
|
||||
} catch (e) {
|
||||
if (((e is AuthorizationException && e.error == 'invalid_token') ||
|
||||
e is ExpirationException) &&
|
||||
!_isRefreshing) {
|
||||
_isRefreshing = true;
|
||||
await ref.read(authenticationProvider.notifier).refreshCredentials();
|
||||
|
||||
_isRefreshing = false;
|
||||
return await fn(api);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final spotifyProvider = Provider<SpotifyApiWrapper>(
|
||||
(ref) {
|
||||
final authState = ref.watch(authenticationProvider);
|
||||
final anonCred = PrimitiveUtils.getRandomElement(Env.spotifySecrets);
|
||||
|
||||
final wrapper = SpotifyApiWrapper(
|
||||
ref,
|
||||
authState.asData?.value == null
|
||||
? SpotifyApi(
|
||||
SpotifyApiCredentials(
|
||||
anonCred["clientId"],
|
||||
anonCred["clientSecret"],
|
||||
),
|
||||
)
|
||||
: SpotifyApi.withAccessToken(
|
||||
authState.asData!.value!.accessToken.value,
|
||||
),
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
);
|
||||
|
||||
@ -6,5 +6,5 @@ final trackProvider =
|
||||
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
|
||||
return spotify.tracks.get(id);
|
||||
return spotify.invoke((api) => api.tracks.get(id));
|
||||
});
|
||||
|
||||
@ -2,5 +2,5 @@ part of '../spotify.dart';
|
||||
|
||||
final meProvider = FutureProvider<User>((ref) async {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
return spotify.me.get();
|
||||
return spotify.invoke((api) => api.me.get());
|
||||
});
|
||||
|
||||
@ -2,7 +2,7 @@ part of '../spotify.dart';
|
||||
|
||||
// ignore: invalid_use_of_internal_member
|
||||
mixin SpotifyMixin<T> on AsyncNotifierBase<T> {
|
||||
SpotifyApi get spotify => ref.read(spotifyProvider);
|
||||
SpotifyApiWrapper get spotify => ref.read(spotifyProvider);
|
||||
}
|
||||
|
||||
extension on AutoDisposeAsyncNotifierProviderRef {
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/env.dart';
|
||||
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||
final authState = ref.watch(authenticationProvider);
|
||||
final anonCred = PrimitiveUtils.getRandomElement(Env.spotifySecrets);
|
||||
|
||||
if (authState.asData?.value == null) {
|
||||
return SpotifyApi(
|
||||
SpotifyApiCredentials(
|
||||
anonCred["clientId"],
|
||||
anonCred["clientSecret"],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SpotifyApi.withAccessToken(authState.asData!.value!.accessToken.value);
|
||||
});
|
||||
@ -42,10 +42,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
|
||||
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
version: "6.4.0"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El
|
||||
|
||||
publish_to: "none"
|
||||
|
||||
version: 4.0.1+40
|
||||
version: 4.0.2+41
|
||||
|
||||
homepage: https://spotube.krtirtho.dev
|
||||
repository: https://github.com/KRTirtho/spotube
|
||||
@ -13,7 +13,7 @@ environment:
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
dependencies:
|
||||
app_links: ^6.3.2
|
||||
app_links: ^6.4.0
|
||||
args: ^2.5.0
|
||||
async: ^2.11.0
|
||||
audio_service: ^0.18.13
|
||||
|
||||
Loading…
Reference in New Issue
Block a user