Merge branch 'dev' into dependabot/pub/dev/go_router-13.0.1

This commit is contained in:
Kingkor Roy Tirtho 2024-01-24 10:08:50 +06:00 committed by GitHub
commit 3bf264f67f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 1674 additions and 292 deletions

View File

@ -71,3 +71,10 @@ body:
description: Anything else you'd like to include? description: Anything else you'd like to include?
validations: validations:
required: false required: false
- type: checkboxes
attributes:
label: Self grab
description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions!
options:
- label: I'm ready to work on this issue!
required: false

View File

@ -35,4 +35,11 @@ body:
label: Additional information label: Additional information
description: Anything else you'd like to include? description: Anything else you'd like to include?
validations: validations:
required: false required: false
- type: checkboxes
attributes:
label: Self grab
description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions!
options:
- label: I'm ready to work on this issue!
required: false

View File

@ -33,7 +33,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2.10.0 - uses: subosito/flutter-action@v2.12.0
with: with:
cache: true cache: true
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -89,7 +89,7 @@ jobs:
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error if-no-files-found: error
name: Spotube-Release-Binaries name: Spotube-Release-Binaries
@ -107,7 +107,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2.10.0 - uses: subosito/flutter-action@v2.12.0
with: with:
cache: true cache: true
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -180,7 +180,7 @@ jobs:
mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-x86_64.rpm mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-x86_64.rpm
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error if-no-files-found: error
name: Spotube-Release-Binaries name: Spotube-Release-Binaries
@ -200,7 +200,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2.10.0 - uses: subosito/flutter-action@v2.12.0
with: with:
cache: true cache: true
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -257,7 +257,7 @@ jobs:
mv build/app/outputs/bundle/${{ inputs.channel }}Release/app-${{ inputs.channel }}-release.aab build/Spotube-playstore-all-arch.aab mv build/app/outputs/bundle/${{ inputs.channel }}Release/app-${{ inputs.channel }}-release.aab build/Spotube-playstore-all-arch.aab
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error if-no-files-found: error
name: Spotube-Release-Binaries name: Spotube-Release-Binaries
@ -276,7 +276,7 @@ jobs:
runs-on: macos-12 runs-on: macos-12
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: subosito/flutter-action@v2.10.0 - uses: subosito/flutter-action@v2.12.0
with: with:
cache: true cache: true
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -363,7 +363,7 @@ jobs:
ln -sf ./build/ios/iphoneos Payload ln -sf ./build/ios/iphoneos Payload
zip -r9 Spotube-iOS.ipa Payload/${{ inputs.channel }}.app zip -r9 Spotube-iOS.ipa Payload/${{ inputs.channel }}.app
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error if-no-files-found: error
name: Spotube-Release-Binaries name: Spotube-Release-Binaries
@ -386,7 +386,7 @@ jobs:
- macos - macos
- iOS - iOS
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v4
with: with:
name: Spotube-Release-Binaries name: Spotube-Release-Binaries
path: ./Spotube-Release-Binaries path: ./Spotube-Release-Binaries
@ -401,7 +401,7 @@ jobs:
sha256sum Spotube-Release-Binaries/* >> RELEASE.sha256sum sha256sum Spotube-Release-Binaries/* >> RELEASE.sha256sum
sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
if-no-files-found: error if-no-files-found: error
name: Spotube-Release-Binaries name: Spotube-Release-Binaries

6
.vscode/launch.json vendored
View File

@ -6,6 +6,12 @@
"type": "dart", "type": "dart",
"request": "launch", "request": "launch",
"program": "lib/main.dart", "program": "lib/main.dart",
},
{
"name": "spotube (mobile)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"args": [ "args": [
"--flavor", "--flavor",
"dev" "dev"

View File

@ -2,10 +2,10 @@
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo"> <img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
An open source, cross-platform Spotify client compatible across multiple platforms<br /> An open source, cross-platform Spotify client compatible across multiple platforms<br />
utilizing Spotify's data API and YouTube (or Piped.video or JioSaavn) as an audio source,<br /> utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source,<br />
eliminating the need for Spotify Premium eliminating the need for Spotify Premium
Btw it's not another Electron app😉 Btw it's not just another Electron app 😉
<a href="https://spotube.netlify.app"><img alt="Visit the website" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/website_vector.svg"></a> <a href="https://spotube.netlify.app"><img alt="Visit the website" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/website_vector.svg"></a>
<a href="https://discord.gg/uJ94vxB6vg"><img alt="Discord Server" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/discord-plural_vector.svg"></a> <a href="https://discord.gg/uJ94vxB6vg"><img alt="Discord Server" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/discord-plural_vector.svg"></a>
@ -26,7 +26,7 @@ Btw it's not another Electron app😉
## 🌃 Features ## 🌃 Features
- 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹ - 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹
- ⬇️ Downloadable tracks - ⬇️ Freely downloadable tracks
- 🖥️ 📱 Cross-platform support - 🖥️ 📱 Cross-platform support
- 🪶 Small size & less data usage - 🪶 Small size & less data usage
- 🕵️ Anonymous/guest login - 🕵️ Anonymous/guest login
@ -36,17 +36,17 @@ Btw it's not another Electron app😉
- 📖 Open source/libre software - 📖 Open source/libre software
- 🔉 Playback control is done locally, not on the server - 🔉 Playback control is done locally, not on the server
**¹** It is still **recommended** to support the creators by watching/liking/subscribing to the artists' YouTube channels or liking their tracks on Spotify (or purchasing a Spotify Premium subscription too). **¹** It is still **recommended** to support creators by engaging with their YouTube channels/Spotify tracks (or preferably by buying their merch/concert tickets/physical media).
### ❌ Unsupported features ### ❌ Unsupported features
- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts can <ins>**never be supported**</ins> because the audio tracks are _only_ available on Spotify and accessing them would require Spotify Premium. - 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> available on Spotify and accessing them would require Spotify Premium.
- 🎧 **Spotify Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8) - 🎧 **Spotify Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8)
## 📜 ⬇️ Installation guide ## 📜 ⬇️ Installation guide
New releases usually appear after 3-4 months.<br /> New versions usually release every 3-4 months.<br />
This handy table lists all methods you can use to install Spotube: This handy table lists all the methods you can use to install Spotube:
<table> <table>
<tr> <tr>
@ -304,4 +304,4 @@ If you are concerned, you can [read the reason of choosing this license](https:/
1. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games. 1. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games.
</details> </details>
<div align="center"><h4>© Copyright Spotube 2023</h4></div> <div align="center"><h4>© Copyright Spotube 2024</h4></div>

BIN
assets/jiosaavn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/liked-tracks.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -34,6 +34,9 @@ class Assets {
AssetGenImage('assets/bengali-patterns-bg.jpg'); AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
static const AssetGenImage likedTracks =
AssetGenImage('assets/liked-tracks.jpg');
static const AssetGenImage placeholder = static const AssetGenImage placeholder =
AssetGenImage('assets/placeholder.png'); AssetGenImage('assets/placeholder.png');
static const AssetGenImage spotubeHeroBanner = static const AssetGenImage spotubeHeroBanner =
@ -74,6 +77,8 @@ class Assets {
bengaliPatternsBg, bengaliPatternsBg,
branding, branding,
emptyBox, emptyBox,
jiosaavn,
likedTracks,
placeholder, placeholder,
spotubeHeroBanner, spotubeHeroBanner,
spotubeLogoForeground, spotubeLogoForeground,

View File

@ -1,5 +1,6 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/track.dart'; import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/spotify_friends.dart';
abstract class FakeData { abstract class FakeData {
static final Image image = Image() static final Image image = Image()
@ -164,4 +165,35 @@ abstract class FakeData {
..icons = [image] ..icons = [image]
..id = "1" ..id = "1"
..name = "category"; ..name = "category";
static final friends = SpotifyFriends(
friends: [
for (var i = 0; i < 3; i++)
SpotifyFriendActivity(
user: const SpotifyFriend(
name: "name",
imageUrl: "imageUrl",
uri: "uri",
),
track: SpotifyActivityTrack(
name: "name",
artist: const SpotifyActivityArtist(
name: "name",
uri: "uri",
),
album: const SpotifyActivityAlbum(
name: "name",
uri: "uri",
),
context: SpotifyActivityContext(
name: "name",
index: i,
uri: "uri",
),
imageUrl: "imageUrl",
uri: "uri",
),
),
],
);
} }

View File

@ -452,10 +452,10 @@ abstract class LanguageLocals {
// name: "North Ndebele", // name: "North Ndebele",
// nativeName: "isiNdebele", // nativeName: "isiNdebele",
// ), // ),
// "ne": const ISOLanguageName( "ne": const ISOLanguageName(
// name: "Nepali", name: "Nepali",
// nativeName: "नेपाली", nativeName: "नेपाली",
// ), ),
// "ng": const ISOLanguageName( // "ng": const ISOLanguageName(
// name: "Ndonga", // name: "Ndonga",
// nativeName: "Owambo", // nativeName: "Owambo",

View File

@ -17,6 +17,7 @@ import 'package:spotube/pages/search/search.dart';
import 'package:spotube/pages/settings/blacklist.dart'; import 'package:spotube/pages/settings/blacklist.dart';
import 'package:spotube/pages/settings/about.dart'; import 'package:spotube/pages/settings/about.dart';
import 'package:spotube/pages/settings/logs.dart'; import 'package:spotube/pages/settings/logs.dart';
import 'package:spotube/pages/track/track.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/components/shared/spotube_page_route.dart'; import 'package:spotube/components/shared/spotube_page_route.dart';
import 'package:spotube/pages/artist/artist.dart'; import 'package:spotube/pages/artist/artist.dart';
@ -144,6 +145,15 @@ final router = GoRouter(
); );
}, },
), ),
GoRoute(
path: "/track/:id",
pageBuilder: (context, state) {
final id = state.pathParameters["id"]!;
return SpotubePage(
child: TrackPage(trackId: id),
);
},
),
], ],
), ),
GoRoute( GoRoute(

View File

@ -41,6 +41,7 @@ abstract class SpotubeIcons {
static const clock = FeatherIcons.clock; static const clock = FeatherIcons.clock;
static const lyrics = Icons.lyrics_rounded; static const lyrics = Icons.lyrics_rounded;
static const lyricsOff = Icons.lyrics_outlined; static const lyricsOff = Icons.lyrics_outlined;
static const noLyrics = Icons.music_off_outlined;
static const logout = FeatherIcons.logOut; static const logout = FeatherIcons.logOut;
static const login = FeatherIcons.logIn; static const login = FeatherIcons.logIn;
static const dashboard = FeatherIcons.grid; static const dashboard = FeatherIcons.grid;
@ -109,4 +110,5 @@ abstract class SpotubeIcons {
static const normalize = FeatherIcons.barChart2; static const normalize = FeatherIcons.barChart2;
static const wikipedia = SimpleIcons.wikipedia; static const wikipedia = SimpleIcons.wikipedia;
static const discord = SimpleIcons.discord; static const discord = SimpleIcons.discord;
static const youtube = SimpleIcons.youtube;
} }

View File

@ -17,7 +17,6 @@ class TokenLoginForm extends HookConsumerWidget {
final authenticationNotifier = final authenticationNotifier =
ref.watch(AuthenticationNotifier.provider.notifier); ref.watch(AuthenticationNotifier.provider.notifier);
final directCodeController = useTextEditingController(); final directCodeController = useTextEditingController();
final keyCodeController = useTextEditingController();
final mounted = useIsMounted(); final mounted = useIsMounted();
final isLoading = useState(false); final isLoading = useState(false);
@ -37,23 +36,13 @@ class TokenLoginForm extends HookConsumerWidget {
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
TextField(
controller: keyCodeController,
decoration: InputDecoration(
hintText: context.l10n.spotify_cookie("\"sp_key (or sp_gaid)\""),
labelText: context.l10n.cookie_name_cookie("sp_key (or sp_gaid)"),
),
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(height: 20),
FilledButton( FilledButton(
onPressed: isLoading.value onPressed: isLoading.value
? null ? null
: () async { : () async {
try { try {
isLoading.value = true; isLoading.value = true;
if (keyCodeController.text.isEmpty || if (directCodeController.text.isEmpty) {
directCodeController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(context.l10n.fill_in_all_fields), content: Text(context.l10n.fill_in_all_fields),
@ -63,7 +52,7 @@ class TokenLoginForm extends HookConsumerWidget {
return; return;
} }
final cookieHeader = final cookieHeader =
"sp_dc=${directCodeController.text.trim()}; sp_key=${keyCodeController.text.trim()}"; "sp_dc=${directCodeController.text.trim()}";
authenticationNotifier.setCredentials( authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie( await AuthenticationCredentials.fromCookie(

View File

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/components/home/sections/friends/friend_item.dart';
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/services/queries/queries.dart';
class HomePageFriendsSection extends HookConsumerWidget {
const HomePageFriendsSection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final friendsQuery = useQueries.user.friendActivity(ref);
final friends = friendsQuery.data?.friends ?? FakeData.friends.friends;
final groupCount = useBreakpointValue(
sm: 3,
xs: 2,
md: 4,
lg: 5,
xl: 6,
xxl: 7,
);
final friendGroup = friends.fold<List<List<SpotifyFriendActivity>>>(
[],
(previousValue, element) {
if (previousValue.isEmpty) {
return [
[element]
];
}
final lastGroup = previousValue.last;
if (lastGroup.length < groupCount) {
return [
...previousValue.sublist(0, previousValue.length - 1),
[...lastGroup, element]
];
}
return [
...previousValue,
[element]
];
},
);
if (!friendsQuery.isLoading &&
(!friendsQuery.hasData || friendsQuery.data!.friends.isEmpty)) {
return const SliverToBoxAdapter(
child: SizedBox.shrink(),
);
}
return Skeletonizer.sliver(
enabled: friendsQuery.isLoading,
child: SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Friends',
style: Theme.of(context).textTheme.titleMedium,
),
),
),
SliverToBoxAdapter(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final group in friendGroup)
Row(
children: [
for (final friend in group)
Padding(
padding: const EdgeInsets.all(8.0),
child: FriendItem(friend: friend),
),
],
),
],
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,136 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/provider/spotify_provider.dart';
class FriendItem extends HookConsumerWidget {
final SpotifyFriendActivity friend;
const FriendItem({
Key? key,
required this.friend,
}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final ThemeData(
textTheme: textTheme,
colorScheme: colorScheme,
) = Theme.of(context);
final queryClient = useQueryClient();
final spotify = ref.watch(spotifyProvider);
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(15),
),
constraints: const BoxConstraints(
minWidth: 300,
),
height: 80,
child: Row(
children: [
CircleAvatar(
backgroundImage: UniversalImage.imageProvider(
friend.user.imageUrl,
),
),
const Gap(8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
friend.user.name,
style: textTheme.bodyLarge,
),
RichText(
text: TextSpan(
style: textTheme.bodySmall,
children: [
TextSpan(
text: friend.track.name,
recognizer: TapGestureRecognizer()
..onTap = () {
context.push("/track/${friend.track.id}");
},
),
const TextSpan(text: ""),
const WidgetSpan(
child: Icon(
SpotubeIcons.artist,
size: 12,
),
),
TextSpan(
text: " ${friend.track.artist.name}",
recognizer: TapGestureRecognizer()
..onTap = () {
context.push(
"/artist/${friend.track.artist.id}",
);
},
),
const TextSpan(text: "\n"),
TextSpan(
text: friend.track.context.name,
recognizer: TapGestureRecognizer()
..onTap = () async {
context.push(
"/${friend.track.context.path}",
extra: !friend.track.context.path
.startsWith("album")
? null
: await queryClient.fetchQuery<Album, dynamic>(
"album/${friend.track.album.id}",
() => spotify.albums.get(
friend.track.album.id,
),
),
);
},
),
const TextSpan(text: ""),
const WidgetSpan(
child: Icon(
SpotubeIcons.album,
size: 12,
),
),
TextSpan(
text: " ${friend.track.album.name}",
recognizer: TapGestureRecognizer()
..onTap = () async {
final album =
await queryClient.fetchQuery<Album, dynamic>(
"album/${friend.track.album.id}",
() => spotify.albums.get(
friend.track.album.id,
),
);
if (context.mounted) {
context.push(
"/album/${friend.track.album.id}",
extra: album,
);
}
},
),
],
),
),
],
),
],
),
);
}
}

View File

@ -21,16 +21,21 @@ class HomeNewReleasesSection extends HookConsumerWidget {
userArtistsQuery.data?.map((s) => s.id!).toList() ?? const []; userArtistsQuery.data?.map((s) => s.id!).toList() ?? const [];
final albums = useMemoized( final albums = useMemoized(
() => newReleases.pages () {
.whereType<Page<AlbumSimple>>() final allReleases = newReleases.pages
.expand((page) => page.items ?? const <AlbumSimple>[]) .whereType<Page<AlbumSimple>>()
.where((album) { .expand((page) => page.items ?? const <AlbumSimple>[])
return album.artists .map((album) => TypeConversionUtils.simpleAlbum_X_Album(album));
?.any((artist) => userArtists.contains(artist.id!)) ==
true; final userArtistReleases = allReleases.where((album) {
}) return album.artists
.map((album) => TypeConversionUtils.simpleAlbum_X_Album(album)) ?.any((artist) => userArtists.contains(artist.id!)) ==
.toList(), true;
}).toList();
if (userArtistReleases.isEmpty) return allReleases.toList();
return userArtistReleases;
},
[newReleases.pages], [newReleases.pages],
); );

View File

@ -37,21 +37,21 @@ class UserPlaylists extends HookConsumerWidget {
); );
final likedTracksPlaylist = useMemoized( final likedTracksPlaylist = useMemoized(
() => PlaylistSimple() () => PlaylistSimple()
..name = context.l10n.liked_tracks ..name = context.l10n.liked_tracks
..description = context.l10n.liked_tracks_description ..description = context.l10n.liked_tracks_description
..type = "playlist" ..type = "playlist"
..collaborative = false ..collaborative = false
..public = false ..public = false
..id = "user-liked-tracks" ..id = "user-liked-tracks"
..images = [ ..images = [
Image() Image()
..height = 300 ..height = 300
..width = 300 ..width = 300
..url = ..url = "assets/liked-tracks.jpg"
"https://t.scdn.co/images/3099b3803ad9496896c43f22fe9be8c4.png" ],
], [context.l10n],
[context.l10n]); );
final playlists = useMemoized( final playlists = useMemoized(
() { () {

View File

@ -4,6 +4,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/components/shared/links/link_text.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
@ -44,10 +45,12 @@ class PlayerTrackDetails extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text( LinkText(
playback.activeTrack?.name ?? "", playback.activeTrack?.name ?? "",
"/track/${playback.activeTrack?.id}",
push: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium!.copyWith(
color: color, color: color,
), ),
), ),
@ -66,8 +69,10 @@ class PlayerTrackDetails extends HookConsumerWidget {
flex: 1, flex: 1,
child: Column( child: Column(
children: [ children: [
Text( LinkText(
playback.activeTrack?.name ?? "", playback.activeTrack?.name ?? "",
"/track/${playback.activeTrack?.id}",
push: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.bold, color: color), style: TextStyle(fontWeight: FontWeight.bold, color: color),
), ),

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart' hide Offset; import 'package:spotify/spotify.dart' hide Offset;
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
@ -19,10 +20,28 @@ import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart';
import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart';
import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:spotube/services/sourced_track/sources/jiosaavn.dart';
import 'package:spotube/services/sourced_track/sources/piped.dart';
import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
final sourceInfoToIconMap = {
YoutubeSourceInfo: const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)),
JioSaavnSourceInfo: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(90),
image: DecorationImage(
image: Assets.jiosaavn.provider(),
fit: BoxFit.cover,
),
),
),
PipedSourceInfo: const Icon(SpotubeIcons.piped),
};
class SiblingTracksSheet extends HookConsumerWidget { class SiblingTracksSheet extends HookConsumerWidget {
final bool floating; final bool floating;
const SiblingTracksSheet({ const SiblingTracksSheet({
@ -64,17 +83,34 @@ class SiblingTracksSheet extends HookConsumerWidget {
return <SourceInfo>[]; return <SourceInfo>[];
} }
final results = await youtubeClient.search.search(searchTerm.trim()); final resultsYt = await youtubeClient.search.search(searchTerm.trim());
final resultsJioSaavn =
await jiosaavnClient.search.songs(searchTerm.trim());
return await Future.wait( final searchResults = await Future.wait([
results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async { ...resultsJioSaavn.results.mapIndexed((i, song) async {
final siblingType = JioSaavnSourcedTrack.toSiblingType(song);
return siblingType.info;
}),
...resultsYt
.map(YoutubeVideoInfo.fromVideo)
.mapIndexed((i, video) async {
final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video); final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video);
return siblingType.info; return siblingType.info;
}), }),
); ]);
final activeSourceInfo =
(playlist.activeTrack! as SourcedTrack).sourceInfo;
return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id)
..insert(
0,
activeSourceInfo,
);
}, [ }, [
searchTerm, searchTerm,
searchMode.value, searchMode.value,
playlist.activeTrack,
]); ]);
final siblings = useMemoized( final siblings = useMemoized(
@ -104,6 +140,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
final itemBuilder = useCallback( final itemBuilder = useCallback(
(SourceInfo sourceInfo) { (SourceInfo sourceInfo) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType];
return ListTile( return ListTile(
title: Text(sourceInfo.title), title: Text(sourceInfo.title),
leading: Padding( leading: Padding(
@ -118,7 +155,12 @@ class SiblingTracksSheet extends HookConsumerWidget {
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
), ),
trailing: Text(sourceInfo.duration.toHumanReadableString()), trailing: Text(sourceInfo.duration.toHumanReadableString()),
subtitle: Text(sourceInfo.artist), subtitle: Row(
children: [
if (icon != null) icon,
Text("${sourceInfo.artist}"),
],
),
enabled: playlist.isFetching != true, enabled: playlist.isFetching != true,
selected: playlist.isFetching != true && selected: playlist.isFetching != true &&
sourceInfo.id == sourceInfo.id ==
@ -137,7 +179,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
[playlist.isFetching, playlist.activeTrack, siblings], [playlist.isFetching, playlist.activeTrack, siblings],
); );
var mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
return SafeArea( return SafeArea(
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius, borderRadius: borderRadius,

View File

@ -159,7 +159,7 @@ class Sidebar extends HookConsumerWidget {
margin: EdgeInsets.only( margin: EdgeInsets.only(
bottom: 10, bottom: 10,
left: 0, left: 0,
top: kIsMacOS ? 35 : 5, top: kIsMacOS ? 0 : 5,
), ),
padding: const EdgeInsets.symmetric(horizontal: 6), padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -55,48 +55,49 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
), ),
SizedBox( SizedBox(
height: height, height: height,
child: ScrollConfiguration( child: NotificationListener(
behavior: ScrollConfiguration.of(context).copyWith( // disable multiple scrollbar to use this
dragDevices: { onNotification: (notification) => true,
PointerDeviceKind.touch, child: ScrollConfiguration(
PointerDeviceKind.mouse, behavior: ScrollConfiguration.of(context).copyWith(
}, dragDevices: PointerDeviceKind.values.toSet(),
), ),
child: items.isEmpty child: items.isEmpty
? ListView.builder( ? ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: 5, itemCount: 5,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AlbumCard(FakeData.albumSimple); return AlbumCard(FakeData.albumSimple);
}, },
) )
: InfiniteList( : InfiniteList(
scrollController: scrollController, scrollController: scrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: items.length, itemCount: items.length,
onFetchData: onFetchMore, onFetchData: onFetchMore,
loadingBuilder: (context) => Skeletonizer( loadingBuilder: (context) => Skeletonizer(
enabled: true, enabled: true,
child: AlbumCard(FakeData.albumSimple), child: AlbumCard(FakeData.albumSimple),
),
isLoading: isLoadingNextPage,
hasReachedMax: !hasNextPage,
itemBuilder: (context, index) {
final item = items[index];
return switch (item.runtimeType) {
PlaylistSimple =>
PlaylistCard(item as PlaylistSimple),
Album => AlbumCard(item as Album),
Artist => Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12.0),
child: ArtistCard(item as Artist),
), ),
_ => const SizedBox.shrink(), isLoading: isLoadingNextPage,
}; hasReachedMax: !hasNextPage,
}), itemBuilder: (context, index) {
final item = items[index];
return switch (item.runtimeType) {
PlaylistSimple =>
PlaylistCard(item as PlaylistSimple),
Album => AlbumCard(item as Album),
Artist => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),
child: ArtistCard(item as Artist),
),
_ => const SizedBox.shrink(),
};
}),
),
), ),
), ),
], ],

View File

@ -8,6 +8,7 @@ class LinkText<T> extends StatelessWidget {
final TextAlign? textAlign; final TextAlign? textAlign;
final TextOverflow? overflow; final TextOverflow? overflow;
final String route; final String route;
final int? maxLines;
final T? extra; final T? extra;
final bool push; final bool push;
@ -19,6 +20,7 @@ class LinkText<T> extends StatelessWidget {
this.extra, this.extra,
this.overflow, this.overflow,
this.style = const TextStyle(), this.style = const TextStyle(),
this.maxLines,
this.push = false, this.push = false,
}) : super(key: key); }) : super(key: key);
@ -37,6 +39,7 @@ class LinkText<T> extends StatelessWidget {
overflow: overflow, overflow: overflow,
style: style, style: style,
textAlign: textAlign, textAlign: textAlign,
maxLines: maxLines,
); );
} }
} }

View File

@ -2,14 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:titlebar_buttons/titlebar_buttons.dart'; import 'package:titlebar_buttons/titlebar_buttons.dart';
import 'dart:math'; import 'dart:math';
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform, exit; import 'dart:io' show Platform;
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:local_notifier/local_notifier.dart';
class PageWindowTitleBar extends StatefulHookConsumerWidget class PageWindowTitleBar extends StatefulHookConsumerWidget
implements PreferredSizeWidget { implements PreferredSizeWidget {

View File

@ -43,12 +43,14 @@ class TrackOptions extends HookConsumerWidget {
final bool userPlaylist; final bool userPlaylist;
final String? playlistId; final String? playlistId;
final ObjectRef<ValueChanged<RelativeRect>?>? showMenuCbRef; final ObjectRef<ValueChanged<RelativeRect>?>? showMenuCbRef;
final Widget? icon;
const TrackOptions({ const TrackOptions({
Key? key, Key? key,
required this.track, required this.track,
this.showMenuCbRef, this.showMenuCbRef,
this.userPlaylist = false, this.userPlaylist = false,
this.playlistId, this.playlistId,
this.icon,
}) : super(key: key); }) : super(key: key);
void actionShare(BuildContext context, Track track) { void actionShare(BuildContext context, Track track) {
@ -207,7 +209,7 @@ class TrackOptions extends HookConsumerWidget {
break; break;
} }
}, },
icon: const Icon(SpotubeIcons.moreHorizontal), icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
headings: [ headings: [
ListTile( ListTile(
dense: true, dense: true,

View File

@ -193,8 +193,10 @@ class TrackTile extends HookConsumerWidget {
children: [ children: [
Expanded( Expanded(
flex: 6, flex: 6,
child: Text( child: LinkText(
track.name!, track.name!,
"/track/${track.id}",
push: true,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@ -1,6 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:cached_network_image/cached_network_image.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:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -62,7 +61,7 @@ class TrackViewFlexHeader extends HookConsumerWidget {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: CachedNetworkImageProvider(props.image), image: UniversalImage.imageProvider(props.image),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),

View File

@ -48,6 +48,11 @@ void useDeepLinking(WidgetRef ref) {
), ),
); );
break; break;
case "track":
router.push(
"/track/${url.pathSegments.last}",
);
break;
default: default:
break; break;
} }
@ -80,6 +85,9 @@ void useDeepLinking(WidgetRef ref) {
case "spotify:artist": case "spotify:artist":
await router.push("/artist/$endSegment"); await router.push("/artist/$endSegment");
break; break;
case "spotify:track":
await router.push("/track/$endSegment");
break;
case "spotify:playlist": case "spotify:playlist":
await router.push( await router.push(
"/playlist/$endSegment", "/playlist/$endSegment",

View File

@ -25,7 +25,7 @@ void useInitSysTray(WidgetRef ref) {
} }
final enabled = !playlist.isFetching; final enabled = !playlist.isFetching;
systemTray.value = await DesktopTools.createSystemTrayMenu( systemTray.value = await DesktopTools.createSystemTrayMenu(
title: DesktopTools.platform.isLinux ? "" : "Spotube", title: DesktopTools.platform.isWindows ? "Spotube" : "",
iconPath: "assets/spotube-logo.png", iconPath: "assets/spotube-logo.png",
windowsIconPath: "assets/spotube-logo.ico", windowsIconPath: "assets/spotube-logo.ico",
items: [ items: [

View File

@ -177,11 +177,9 @@
"step_2": "الخطوة 2", "step_2": "الخطوة 2",
"step_2_steps": "1. بمجرد تسجيل الدخول، اضغط على F12 أو انقر بزر الماوس الأيمن > فحص لفتح أدوات تطوير المتصفح.\n2. ثم انتقل إلى علامة التبويب \"التطبيقات\" (Chrome وEdge وBrave وما إلى ذلك.) أو علامة التبويب \"التخزين\" (Firefox وPalemoon وما إلى ذلك..)\n3. انتقل إلى قسم \"ملفات تعريف الارتباط\" ثم القسم الفرعي \"https://accounts.spotify.com\"", "step_2_steps": "1. بمجرد تسجيل الدخول، اضغط على F12 أو انقر بزر الماوس الأيمن > فحص لفتح أدوات تطوير المتصفح.\n2. ثم انتقل إلى علامة التبويب \"التطبيقات\" (Chrome وEdge وBrave وما إلى ذلك.) أو علامة التبويب \"التخزين\" (Firefox وPalemoon وما إلى ذلك..)\n3. انتقل إلى قسم \"ملفات تعريف الارتباط\" ثم القسم الفرعي \"https://accounts.spotify.com\"",
"step_3": "الخطوة 3", "step_3": "الخطوة 3",
"step_3_steps": "انسخ قيم \"sp_dc\" و \"sp_key\" (أو sp_gaid) الكويز",
"success_emoji": "نجاح 🥳", "success_emoji": "نجاح 🥳",
"success_message": "لقد قمت الآن بتسجيل الدخول بنجاح باستخدام حساب Spotify الخاص بك. عمل جيد يا صديقي!", "success_message": "لقد قمت الآن بتسجيل الدخول بنجاح باستخدام حساب Spotify الخاص بك. عمل جيد يا صديقي!",
"step_4": "الخطوة 4", "step_4": "الخطوة 4",
"step_4_steps": "قم بلصق قيم \"sp_dc\" و \"sp_key\" (أو sp_gaid) المنسوخة في الحقول المعنية",
"something_went_wrong": "هناك خطأ ما", "something_went_wrong": "هناك خطأ ما",
"piped_instance": "مثيل خادم Piped", "piped_instance": "مثيل خادم Piped",
"piped_description": "مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة", "piped_description": "مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة",

View File

@ -175,11 +175,9 @@
"step_2": "ধাপ 2", "step_2": "ধাপ 2",
"step_2_steps": "১. একবার আপনি লগ ইন করলে, ব্রাউজার ডেভটুল খুলতে F12 বা মাউসের রাইট ক্লিক > \"Inspect to open Browser DevTools\" টিপুন।\n২. তারপর \"Application\" ট্যাবে যান (Chrome, Edge, Brave etc..) অথবা \"Storage\" Tab (Firefox, Palemoon etc..)\n৩. \"Cookies \" বিভাগে যান তারপর \"https://accounts.spotify.com\" উপবিভাগে যান", "step_2_steps": "১. একবার আপনি লগ ইন করলে, ব্রাউজার ডেভটুল খুলতে F12 বা মাউসের রাইট ক্লিক > \"Inspect to open Browser DevTools\" টিপুন।\n২. তারপর \"Application\" ট্যাবে যান (Chrome, Edge, Brave etc..) অথবা \"Storage\" Tab (Firefox, Palemoon etc..)\n৩. \"Cookies \" বিভাগে যান তারপর \"https://accounts.spotify.com\" উপবিভাগে যান",
"step_3": "ধাপ 3", "step_3": "ধাপ 3",
"step_3_steps": "\"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) কুকিজের মান কপি করুন",
"success_emoji": "আমরা সফল🥳", "success_emoji": "আমরা সফল🥳",
"success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে", "success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে",
"step_4": "ধাপ 4", "step_4": "ধাপ 4",
"step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন",
"something_went_wrong": "কিছু ভুল হয়েছে", "something_went_wrong": "কিছু ভুল হয়েছে",
"piped_instance": "Piped সার্ভার এড্রেস", "piped_instance": "Piped সার্ভার এড্রেস",
"piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার", "piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার",

View File

@ -175,11 +175,9 @@
"step_2": "Pas 2", "step_2": "Pas 2",
"step_2_steps": "1. Una vegada que hagi iniciat sessió, premi F12 o faci clic dret amb el ratolí > Inspeccionar per obrir les eines de desenvolulpador del navegador.\n2. Després vagi a la pestanya \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Vagi a la secció \"Cookies\" i després a la subsecció \"https://accounts.spotify.com\"", "step_2_steps": "1. Una vegada que hagi iniciat sessió, premi F12 o faci clic dret amb el ratolí > Inspeccionar per obrir les eines de desenvolulpador del navegador.\n2. Després vagi a la pestanya \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Vagi a la secció \"Cookies\" i després a la subsecció \"https://accounts.spotify.com\"",
"step_3": "Pas 3", "step_3": "Pas 3",
"step_3_steps": "Copiï els valors de les Cookies \"sp_dc\" i \"sp_key\" (o sp_gaid)",
"success_emoji": "Èxit! 🥳", "success_emoji": "Èxit! 🥳",
"success_message": "Ara has iniciat sessió amb èxit al teu compte de Spotify. Bona feina!", "success_message": "Ara has iniciat sessió amb èxit al teu compte de Spotify. Bona feina!",
"step_4": "Pas 4", "step_4": "Pas 4",
"step_4_steps": "Enganxi els valors coppiats de \"sp_dc\" i \"sp_key\" (o sp_gaid) en els camps respectius",
"something_went_wrong": "Quelcom ha sortit malament", "something_went_wrong": "Quelcom ha sortit malament",
"piped_instance": "Instància del servidor Piped", "piped_instance": "Instància del servidor Piped",
"piped_description": "La instància del servidor Piped a utilitzar per la coincidència de cançons", "piped_description": "La instància del servidor Piped a utilitzar per la coincidència de cançons",

View File

@ -175,11 +175,9 @@
"step_2": "Schritt 2", "step_2": "Schritt 2",
"step_2_steps": "1. Wenn du angemeldet bist, drücke F12 oder klicke mit der rechten Maustaste > Inspektion, um die Browser-Entwicklertools zu öffnen.\n2. Gehe dann zum \"Anwendungs\"-Tab (Chrome, Edge, Brave usw.) oder zum \"Storage\"-Tab (Firefox, Palemoon usw.)\n3. Gehe zum Abschnitt \"Cookies\" und dann zum Unterabschnitt \"https://accounts.spotify.com\"", "step_2_steps": "1. Wenn du angemeldet bist, drücke F12 oder klicke mit der rechten Maustaste > Inspektion, um die Browser-Entwicklertools zu öffnen.\n2. Gehe dann zum \"Anwendungs\"-Tab (Chrome, Edge, Brave usw.) oder zum \"Storage\"-Tab (Firefox, Palemoon usw.)\n3. Gehe zum Abschnitt \"Cookies\" und dann zum Unterabschnitt \"https://accounts.spotify.com\"",
"step_3": "Schritt 3", "step_3": "Schritt 3",
"step_3_steps": "Kopiere die Werte der Cookies \"sp_dc\" und \"sp_key\" (oder sp_gaid)",
"success_emoji": "Erfolg🥳", "success_emoji": "Erfolg🥳",
"success_message": "Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!", "success_message": "Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!",
"step_4": "Schritt 4", "step_4": "Schritt 4",
"step_4_steps": "Füge die kopierten Werte von \"sp_dc\" und \"sp_key\" (oder sp_gaid) in die entsprechenden Felder ein",
"something_went_wrong": "Etwas ist schiefgelaufen", "something_went_wrong": "Etwas ist schiefgelaufen",
"piped_instance": "Piped-Serverinstanz", "piped_instance": "Piped-Serverinstanz",
"piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll", "piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll",

View File

@ -177,11 +177,11 @@
"step_2": "Step 2", "step_2": "Step 2",
"step_2_steps": "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection", "step_2_steps": "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection",
"step_3": "Step 3", "step_3": "Step 3",
"step_3_steps": "Copy the values of \"sp_dc\" and \"sp_key\" (or sp_gaid) Cookies", "step_3_steps": "Copy the value of \"sp_dc\" Cookie",
"success_emoji": "Success🥳", "success_emoji": "Success🥳",
"success_message": "Now you're successfully Logged In with your Spotify account. Good Job, mate!", "success_message": "Now you've 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\" (or sp_gaid) values in the respective fields", "step_4_steps": "Paste the copied \"sp_dc\" value",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"piped_instance": "Piped Server Instance", "piped_instance": "Piped Server Instance",
"piped_description": "The Piped server instance to use for track matching", "piped_description": "The Piped server instance to use for track matching",
@ -284,5 +284,7 @@
"discord_rich_presence": "Discord Rich Presence", "discord_rich_presence": "Discord Rich Presence",
"browse_all": "Browse All", "browse_all": "Browse All",
"genres": "Genres", "genres": "Genres",
"explore_genres": "Explore Genres" "explore_genres": "Explore Genres",
"friends": "Friends",
"no_lyrics_available": "Sorry, unable find lyrics for this track"
} }

View File

@ -175,11 +175,9 @@
"step_2": "Paso 2", "step_2": "Paso 2",
"step_2_steps": "1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"", "step_2_steps": "1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"",
"step_3": "Paso 3", "step_3": "Paso 3",
"step_3_steps": "Copia los valores de las Cookies \"sp_dc\" y \"sp_key\" (o sp_gaid)",
"success_emoji": "¡Éxito! 🥳", "success_emoji": "¡Éxito! 🥳",
"success_message": "Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!", "success_message": "Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!",
"step_4": "Paso 4", "step_4": "Paso 4",
"step_4_steps": "Pega los valores copiados de \"sp_dc\" y \"sp_key\" (o sp_gaid) en los campos respectivos",
"something_went_wrong": "Algo salió mal", "something_went_wrong": "Algo salió mal",
"piped_instance": "Instancia del servidor Piped", "piped_instance": "Instancia del servidor Piped",
"piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas", "piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas",

View File

@ -177,11 +177,9 @@
"step_2": "گام 2", "step_2": "گام 2",
"step_2_steps": "1. پس از ورود به سیستم، F12 یا کلیک راست ماوس > Inspect را فشار دهید تا ابزارهای توسعه مرورگر باز شود..\n2. سپس به تب \"Application\" (Chrome, Edge, Brave etc..) یا \"Storage\" Tab (Firefox, Palemoon etc..)\n3. به قسمت \"Cookies\" و به پخش \"https://accounts.spotify.com\" بروید", "step_2_steps": "1. پس از ورود به سیستم، F12 یا کلیک راست ماوس > Inspect را فشار دهید تا ابزارهای توسعه مرورگر باز شود..\n2. سپس به تب \"Application\" (Chrome, Edge, Brave etc..) یا \"Storage\" Tab (Firefox, Palemoon etc..)\n3. به قسمت \"Cookies\" و به پخش \"https://accounts.spotify.com\" بروید",
"step_3": "گام 3", "step_3": "گام 3",
"step_3_steps": "کپی کردن مقادیر \"sp_dc\" و \"sp_key\" (یا sp_gaid) کوکی",
"success_emoji": "موفقیت🥳", "success_emoji": "موفقیت🥳",
"success_message": "اکنون با موفقیت با حساب اسپوتیفای خود وارد شده اید", "success_message": "اکنون با موفقیت با حساب اسپوتیفای خود وارد شده اید",
"step_4": "مرحله 4", "step_4": "مرحله 4",
"step_4_steps": "مقدار کپی شده را \"sp_dc\" and \"sp_key\" (یا sp_gaid) در فیلد مربوط پر کنید",
"something_went_wrong": "اشتباهی رخ داده", "something_went_wrong": "اشتباهی رخ داده",
"piped_instance": "مشکل در ارتباط با سرور", "piped_instance": "مشکل در ارتباط با سرور",
"piped_description": "مشکل در ارتباط با سرور در دریافت آهنگ ها", "piped_description": "مشکل در ارتباط با سرور در دریافت آهنگ ها",

View File

@ -175,11 +175,9 @@
"step_2": "Étape 2", "step_2": "Étape 2",
"step_2_steps": "1. Une fois connecté, appuyez sur F12 ou clic droit de la souris > Inspecter pour ouvrir les outils de développement du navigateur.\n2. Ensuite, allez dans l'onglet \"Application\" (Chrome, Edge, Brave, etc.) ou l'onglet \"Stockage\" (Firefox, Palemoon, etc.)\n3. Allez dans la section \"Cookies\", puis dans la sous-section \"https://accounts.spotify.com\"", "step_2_steps": "1. Une fois connecté, appuyez sur F12 ou clic droit de la souris > Inspecter pour ouvrir les outils de développement du navigateur.\n2. Ensuite, allez dans l'onglet \"Application\" (Chrome, Edge, Brave, etc.) ou l'onglet \"Stockage\" (Firefox, Palemoon, etc.)\n3. Allez dans la section \"Cookies\", puis dans la sous-section \"https://accounts.spotify.com\"",
"step_3": "Étape 3", "step_3": "Étape 3",
"step_3_steps": "Copiez les valeurs des cookies \"sp_dc\" et \"sp_key\" (ou sp_gaid)",
"success_emoji": "Succès🥳", "success_emoji": "Succès🥳",
"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\" (ou sp_gaid) 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_instance": "Instance pipée",
"piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes", "piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes",

View File

@ -175,11 +175,9 @@
"step_2": "2 चरण", "step_2": "2 चरण",
"step_2_steps": "1. जब आप लॉगिन हो जाएँ, तो F12 दबाएं या माउस राइट क्लिक> निरीक्षण करें ताकि ब्राउज़र डेवटूल्स खुलें।\n2. फिर ब्राउज़र के \"एप्लिकेशन\" टैब (Chrome, Edge, Brave आदि) या \"स्टोरेज\" टैब (Firefox, Palemoon आदि) में जाएं\n3. \"कुकीज़\" अनुभाग में जाएं फिर \"https: //accounts.spotify.com\" उप-अनुभाग में जाएं", "step_2_steps": "1. जब आप लॉगिन हो जाएँ, तो F12 दबाएं या माउस राइट क्लिक> निरीक्षण करें ताकि ब्राउज़र डेवटूल्स खुलें।\n2. फिर ब्राउज़र के \"एप्लिकेशन\" टैब (Chrome, Edge, Brave आदि) या \"स्टोरेज\" टैब (Firefox, Palemoon आदि) में जाएं\n3. \"कुकीज़\" अनुभाग में जाएं फिर \"https: //accounts.spotify.com\" उप-अनुभाग में जाएं",
"step_3": "स्टेप 3", "step_3": "स्टेप 3",
"step_3_steps": "\"sp_dc\" और \"sp_key\" (या sp_gaid) कुकीज़ के मान कॉपी करें",
"success_emoji": "सफलता🥳", "success_emoji": "सफलता🥳",
"success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!", "success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!",
"step_4": "स्टेप 4", "step_4": "स्टेप 4",
"step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" (या sp_gaid) मानों को संबंधित फील्ड में पेस्ट करें",
"something_went_wrong": "कुछ गलत हो गया", "something_went_wrong": "कुछ गलत हो गया",
"piped_instance": "पाइप्ड सर्वर", "piped_instance": "पाइप्ड सर्वर",
"piped_description": "पाइप किए गए सर्वर", "piped_description": "पाइप किए गए सर्वर",

View File

@ -177,11 +177,9 @@
"step_2": "Passo 2", "step_2": "Passo 2",
"step_2_steps": "1. Quando sei acceduto premi F12 o premi il tasto destro del Mouse > Ispeziona per aprire gli strumenti di sviluppo del browser.\n2. Vai quindi nel tab \"Applicazione\" (Chrome, Edge, Brave etc..) o tab \"Archiviazione\" (Firefox, Palemoon etc..)\n3. Vai nella sezione \"Cookies\" quindi nella sezione \"https://accounts.spotify.com\"", "step_2_steps": "1. Quando sei acceduto premi F12 o premi il tasto destro del Mouse > Ispeziona per aprire gli strumenti di sviluppo del browser.\n2. Vai quindi nel tab \"Applicazione\" (Chrome, Edge, Brave etc..) o tab \"Archiviazione\" (Firefox, Palemoon etc..)\n3. Vai nella sezione \"Cookies\" quindi nella sezione \"https://accounts.spotify.com\"",
"step_3": "Passo 3", "step_3": "Passo 3",
"step_3_steps": "Copia il valore dei cookie \"sp_dc\" e \"sp_key\" (o sp_gaid)",
"success_emoji": "Successo🥳", "success_emoji": "Successo🥳",
"success_message": "Ora hai correttamente effettuato il login al tuo account Spotify. Bel lavoro, amico!", "success_message": "Ora hai correttamente effettuato il login al tuo account Spotify. Bel lavoro, amico!",
"step_4": "Passo 4", "step_4": "Passo 4",
"step_4_steps": "Incolla i valori copiati di \"sp_dc\" e \"sp_key\" (o sp_gaid) nei campi rispettivi",
"something_went_wrong": "Qualcosa è andato storto", "something_went_wrong": "Qualcosa è andato storto",
"piped_instance": "Istanza Server Piped", "piped_instance": "Istanza Server Piped",
"piped_description": "L'istanza server Piped da usare per il match della tracccia", "piped_description": "L'istanza server Piped da usare per il match della tracccia",

View File

@ -175,11 +175,9 @@
"step_2": "ステップ 2", "step_2": "ステップ 2",
"step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます", "step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます",
"step_3": "ステップ 3", "step_3": "ステップ 3",
"step_3_steps": "sp_dc と sp_key (または or sp_gaid) の値 (Value) をコピーします",
"success_emoji": "成功🥳", "success_emoji": "成功🥳",
"success_message": "アカウントへのログインに成功しました。よくできました!", "success_message": "アカウントへのログインに成功しました。よくできました!",
"step_4": "ステップ 4", "step_4": "ステップ 4",
"step_4_steps": "コピーした sp_dc と sp_key (または or sp_gaid) の値をそれぞれの入力欄に貼り付けます",
"something_went_wrong": "何か誤りがあります", "something_went_wrong": "何か誤りがあります",
"piped_instance": "Piped サーバーのインスタンス", "piped_instance": "Piped サーバーのインスタンス",
"piped_description": "曲の一致に使う Piped サーバーのインスタンス", "piped_description": "曲の一致に使う Piped サーバーのインスタンス",

288
lib/l10n/app_ne.arb Normal file
View File

@ -0,0 +1,288 @@
{
"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": "प्लेलिस्ट बनाउनुहोस्",
"update_playlist": "प्लेलिस्ट अपडेट गर्नुहोस्",
"create": "बनाउनुहोस्",
"cancel": "रद्द गर्नुहोस्",
"update": "अपडेट गर्नुहोस्",
"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": "कलाकार URL क्लिपबोर्डमा प्रतिलिपि गरिएको छ",
"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": "हवर गरेपछि UI देखाउनुहोस्/लुकाउनुहोस्",
"always_on_top": "सधैं टपमा राख्नुहोस्",
"exit_mini_player": "मिनि प्लेयर बाट बाहिर निस्कनुहोस्",
"download_location": "डाउनलोड स्थान",
"account": "खाता",
"login_with_spotify": "तपाईंको Spotify खातासँग लगइन गर्नुहोस्",
"connect_with_spotify": "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, एक हल्का, समृद्ध, स्वतन्त्र Spotify क्लाइयन",
"version": "संस्करण",
"build_number": "निर्माण नम्बर",
"founder": "संस्थापक",
"repository": "पुनरावलोकन स्थल",
"bug_issues": "त्रुटि + समस्याहरू",
"made_with": "❤️ 2021-2024 बाट बनाइएको",
"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": "Spotify {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 थिच्नुहोस् वा माउस राइट क्लिक गर्नुहोस् > इन्स्पेक्ट गर्नुहोस् भने ब्राउजर डेभटुलहरू खुलाउनका लागि।\n2. तपाईंको \"एप्लिकेसन\" ट्याबमा जानुहोस् (Chrome, Edge, Brave इत्यादि) वा \"स्टोरेज\" ट्याबमा जानुहोस् (Firefox, Palemoon इत्यादि)\n3. तपाईंको इन्सेक्ट गरेको ब्राउजर डेभटुलहरूमा \"कुकीहरू\" खण्डमा जानुहोस् अनि \"https://accounts.spotify.com\" उपकोणमा जानुहोस्",
"step_3": "कदम 3",
"step_3_steps": "\"sp_dc\" र \"sp_key\" (वा sp_gaid) कुकीहरूको मानहरू प्रतिलिपि गर्नुहोस्",
"success_emoji": "सफलता 🥳",
"success_message": "हाम्रो सानो भाइ, अब तपाईं सफलतापूर्वक आफ्नो Spotify खातामा लगइन गरेका छौं। राम्रो काम गरेको!",
"step_4": "कदम 4",
"step_4_steps": "प्रतिलिपि गरेको \"sp_dc\" र \"sp_key\" (वा sp_gaid) मानहरूलाई आफ्नो ठाउँमा पेस्ट गर्नुहोस्",
"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": "गति (बीपीएम)",
"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": "बितिएका डाउनलोड अनुरोधहरूका कारण तपाईंको आइपीले YouTube मा ब्लक हुन सक्छ। आइपी ब्लक भनेको कम्तीमा 2-3 महिनासम्म तपाईं त्यस आइपी यन्त्रबाट YouTube प्रयोग गर्न सक्नुहुन्छ। र यदि यो हुँदैछ भने स्पट्यूबले यसलाई कसैले गरेको बारेमा कुनै दायित्व लिन्छैन।",
"by_clicking_accept_terms": "'स्वीकृत' गरेर तपाईं निम्नलिखित निर्वाचन गर्दैछिन्:",
"download_agreement_1": "म मन्ने छु कि म साङ्गीत चोरी गरिरहेको छु। म बुरो हुँ",
"download_agreement_2": "म कहिल्यै कहिल्यै तिनीहरूलाई समर्थन गर्नेछु र म यो तिनीहरूको कला किन्ने पैसा छैन भने मा मात्र यो गरेको छु",
"download_agreement_3": "म पूरा रूपमा जान्छु कि मेरो आइपी YouTube मा ब्लक हुन सक्छ र म मन्छेहरूले मेरो चासोबाट भएको कुनै दुर्घटनामा स्पट्यूब वा तिनीहरूको मालिकहरू/सहयोगीहरूलाई दायित्वी ठान्छुँभन्ने पूर्ण जानकारी छैन",
"decline": "अस्वीकृत",
"accept": "स्वीकृत",
"details": "विवरण",
"youtube": "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": "स्पट्यूबले तपाईंको डेटा सुरक्षित रूपमा स्टोर गर्नका लागि एन्क्रिप्ट गर्न खोजेको छ। तर यसले गरेको छैन। यसले असुरक्षित स्टोरेजमा फल्लब्याक गर्दछ\nयदि तपाईंले लिनक्स प्रयोग गरिरहेका छन् भने कृपया सुनिश्चित गर्नुहोस् कि तपाईंले कुनै सीक्रेट-सर्भिस (गोनोम-किरिङ, केडीइ-वालेट, किपासेक्ससि इत्यादि) इन्स्टल गरेका छौं",
"querying_info": "जानकारी हेर्दै...",
"piped_api_down": "पाइपड एपीआई डाउन छ",
"piped_down_error_instructions": "पाइपड इन्स्ट्यान्स {pipedInstance} हाल डाउन छ\n\nजीसनै इन्स्ट्यान्स परिवर्तन गर्नुहोस् वा 'एपीआई प्रकार' लाइ YouTube आफिसियल एपीआईमा परिवर्तन गर्नुहोस्\n\nपरिवर्तनपछि एप्लिकेसन पुन: सुरु गर्नुहोस्",
"you_are_offline": "तपाईं वर्तमान अफलाइन हुनुहुन्छ",
"connection_restored": "तपाईंको इन्टरनेट कनेक्सन पुन: स्थापित भएको छ",
"use_system_title_bar": "सिस्टम शीर्षक पट्टी प्रयोग गर्नुहोस्",
"crunching_results": "परिणामहरू कपालबाट पीस्दै...",
"search_to_get_results": "परिणामहरू प्राप्त गर्नका लागि खोज्नुहोस्",
"use_amoled_mode": "कृष्ण ब्ल्याक गाढा थिम प्रयोग गर्नुहोस्",
"pitch_dark_theme": "एमोलेड मोड",
"normalize_audio": "अडियो सामान्य गर्नुहोस्",
"change_cover": "कवर परिवर्तन गर्नुहोस्",
"add_cover": "कवर थप्नुहोस्",
"restore_defaults": "पूर्वनिर्धारितहरू पुनः स्थापित गर्नुहोस्",
"download_music_codec": "साङ्गीत कोडेक डाउनलोड गर्नुहोस्",
"streaming_music_codec": "स्ट्रिमिङ साङ्गीत कोडेक",
"login_with_lastfm": "लास्ट.एफ.एम सँग लगइन गर्नुहोस्",
"connect": "जडान गर्नुहोस्",
"disconnect_lastfm": "लास्ट.एफ.एम डिसकनेक्ट गर्नुहोस्",
"disconnect": "डिसकनेक्ट",
"username": "प्रयोगकर्ता नाम",
"password": "पासवर्ड",
"login": "लगइन",
"login_with_your_lastfm": "तपाईंको लास्ट.एफ.एम खातामा लगइन गर्नुहोस्",
"scrobble_to_lastfm": "लास्ट.एफ.एम मा स्क्रबल गर्नुहोस्",
"go_to_album": "आल्बममा जानुहोस्",
"discord_rich_presence": "डिस्कर्ड धनी उपस्थिति",
"browse_all": "सबै हेर्नुहोस्",
"genres": "शैलीहरू",
"explore_genres": "शैलीहरू अन्वेषण गर्नुहोस्"
}

View File

@ -177,11 +177,9 @@
"step_2": "Stap 2", "step_2": "Stap 2",
"step_2_steps": "1. Zodra je bent aangemeld, druk je op F12 of klik je met de rechtermuisknop > Inspect om de Browser devtools te openen.\n2. Ga vervolgens naar het tabblad \"Toepassing\" (Chrome, Edge, Brave enz..) of naar het tabblad \"Opslag\" (Firefox, Palemoon enz..).\n3. Ga naar de sectie \"Cookies\" en vervolgens naar de subsectie \"https://accounts.spotify.com\".", "step_2_steps": "1. Zodra je bent aangemeld, druk je op F12 of klik je met de rechtermuisknop > Inspect om de Browser devtools te openen.\n2. Ga vervolgens naar het tabblad \"Toepassing\" (Chrome, Edge, Brave enz..) of naar het tabblad \"Opslag\" (Firefox, Palemoon enz..).\n3. Ga naar de sectie \"Cookies\" en vervolgens naar de subsectie \"https://accounts.spotify.com\".",
"step_3": "Stap 3", "step_3": "Stap 3",
"step_3_steps": "Kopieer de waarden van \"sp_dc\" en \"sp_key\" (of sp_gaid) Cookies",
"success_emoji": "Succes🥳", "success_emoji": "Succes🥳",
"success_message": "Je bent nu succesvol ingelogd met je Spotify account. Goed gedaan, maat!", "success_message": "Je bent nu succesvol ingelogd met je Spotify account. Goed gedaan, maat!",
"step_4": "Stap 4", "step_4": "Stap 4",
"step_4_steps": "Plak de gekopieerde \"sp_dc\" en \"sp_key\" (of sp_gaid) waarden in de respectievelijke velden",
"something_went_wrong": "Er ging iets mis", "something_went_wrong": "Er ging iets mis",
"piped_instance": "Piped-serverinstantie", "piped_instance": "Piped-serverinstantie",
"piped_description": "De Piped-serverinstantie die moet worden gebruikt voor het matchen van sporen", "piped_description": "De Piped-serverinstantie die moet worden gebruikt voor het matchen van sporen",

View File

@ -175,11 +175,9 @@
"step_2": "Krok 2", "step_2": "Krok 2",
"step_2_steps": "1. Jeśli jesteś zalogowany, naciśnij klawisz F12 lub Kliknij prawym przyciskiem myszy > Zbadaj, aby odtworzyć narzędzia developerskie.\n2. Następnie przejdź do zakładki \"Application\" (Chrome, Edge, Brave etc..) lub zakładki \"Storage\" (Firefox, Palemoon etc..)\n3. Przejdź do sekcji \"Cookies\" a następnie do pod-sekcji \"https://accounts.spotify.com\"", "step_2_steps": "1. Jeśli jesteś zalogowany, naciśnij klawisz F12 lub Kliknij prawym przyciskiem myszy > Zbadaj, aby odtworzyć narzędzia developerskie.\n2. Następnie przejdź do zakładki \"Application\" (Chrome, Edge, Brave etc..) lub zakładki \"Storage\" (Firefox, Palemoon etc..)\n3. Przejdź do sekcji \"Cookies\" a następnie do pod-sekcji \"https://accounts.spotify.com\"",
"step_3": "Krok 3", "step_3": "Krok 3",
"step_3_steps": "Skopiuj wartości \"sp_dc\" i \"sp_key\" (lub sp_gaid) Ciasteczek",
"success_emoji": "Sukces!🥳", "success_emoji": "Sukces!🥳",
"success_message": "Udało ci się zalogować! Dobra robota, stary!", "success_message": "Udało ci się zalogować! Dobra robota, stary!",
"step_4": "Krok 4", "step_4": "Krok 4",
"step_4_steps": "Wklej wartości \"sp_dc\" i \"sp_key\" (lub sp_gaid) do odpowiednich pul.",
"something_went_wrong": "Coś poszło nie tak 🙁", "something_went_wrong": "Coś poszło nie tak 🙁",
"piped_instance": "Instancja serwera Piped", "piped_instance": "Instancja serwera Piped",
"piped_description": "Instancja serwera Piped używana jest do dopasowania utworów.", "piped_description": "Instancja serwera Piped używana jest do dopasowania utworów.",

View File

@ -175,11 +175,9 @@
"step_2": "Passo 2", "step_2": "Passo 2",
"step_2_steps": "1. Uma vez logado, pressione F12 ou clique com o botão direito do mouse > Inspecionar para abrir as ferramentas de desenvolvimento do navegador.\n2. Em seguida, vá para a guia \"Aplicativo\" (Chrome, Edge, Brave, etc.) ou \"Armazenamento\" (Firefox, Palemoon, etc.)\n3. Acesse a seção \"Cookies\" e depois a subseção \"https://accounts.spotify.com\"", "step_2_steps": "1. Uma vez logado, pressione F12 ou clique com o botão direito do mouse > Inspecionar para abrir as ferramentas de desenvolvimento do navegador.\n2. Em seguida, vá para a guia \"Aplicativo\" (Chrome, Edge, Brave, etc.) ou \"Armazenamento\" (Firefox, Palemoon, etc.)\n3. Acesse a seção \"Cookies\" e depois a subseção \"https://accounts.spotify.com\"",
"step_3": "Passo 3", "step_3": "Passo 3",
"step_3_steps": "Copie os valores dos Cookies \"sp_dc\" e \"sp_key\" (ou sp_gaid)",
"success_emoji": "Sucesso🥳", "success_emoji": "Sucesso🥳",
"success_message": "Agora você está logado com sucesso em sua conta do Spotify. Bom trabalho!", "success_message": "Agora você está logado com sucesso em sua conta do Spotify. Bom trabalho!",
"step_4": "Passo 4", "step_4": "Passo 4",
"step_4_steps": "Cole os valores copiados \"sp_dc\" e \"sp_key\" (ou sp_gaid) nos campos correspondentes",
"something_went_wrong": "Algo deu errado", "something_went_wrong": "Algo deu errado",
"piped_instance": "Instância do Servidor Piped", "piped_instance": "Instância do Servidor Piped",
"piped_description": "A instância do servidor Piped a ser usada para correspondência de faixas", "piped_description": "A instância do servidor Piped a ser usada para correspondência de faixas",

View File

@ -175,11 +175,9 @@
"step_2": "Шаг 2", "step_2": "Шаг 2",
"step_2_steps": "1. После входа в систему нажмите F12 или щелкните правой кнопкой мыши > «Проверить», чтобы открыть инструменты разработчика браузера.\n2. Затем перейдите на вкладку \"Application\" (Chrome, Edge, Brave и т.д..) or \"Storage\" (Firefox, Palemoon и т.д..)\n3. Перейдите в раздел \"Cookies\", а затем в подраздел \"https://accounts.spotify.com\"", "step_2_steps": "1. После входа в систему нажмите F12 или щелкните правой кнопкой мыши > «Проверить», чтобы открыть инструменты разработчика браузера.\n2. Затем перейдите на вкладку \"Application\" (Chrome, Edge, Brave и т.д..) or \"Storage\" (Firefox, Palemoon и т.д..)\n3. Перейдите в раздел \"Cookies\", а затем в подраздел \"https://accounts.spotify.com\"",
"step_3": "Шаг 3", "step_3": "Шаг 3",
"step_3_steps": "Скопируйте значения \"sp_dc\" и \"sp_key\" (или sp_gaid) Cookies",
"success_emoji": "Успешно 🥳", "success_emoji": "Успешно 🥳",
"success_message": "Теперь вы успешно вошли в свою учетную запись Spotify. Отличная работа, приятель!", "success_message": "Теперь вы успешно вошли в свою учетную запись Spotify. Отличная работа, приятель!",
"step_4": "Шаг 4", "step_4": "Шаг 4",
"step_4_steps": "Вставьте скопированные \"sp_dc\" и \"sp_key\" (или sp_gaid) значения в соответствующие поля",
"something_went_wrong": "Что-то пошло не так", "something_went_wrong": "Что-то пошло не так",
"piped_instance": "Экземпляр сервера Piped", "piped_instance": "Экземпляр сервера Piped",
"piped_description": "Серверный экземпляр Piped для сопоставления треков", "piped_description": "Серверный экземпляр Piped для сопоставления треков",

View File

@ -177,11 +177,9 @@
"step_2": "2. Adım", "step_2": "2. Adım",
"step_2_steps": "1. Giriş yaptıktan sonra, Tarayıcı devtools.\n2'yi açmak için F12'ye basın veya Fare Sağ Tıklaması > İncele'ye basın. Ardından \"Uygulama\" Sekmesine (Chrome, Edge, Brave vb.) veya \"Depolama\" Sekmesine (Firefox, Palemoon vb.) gidin\n3. \"Çerezler\" bölümüne ve ardından \"https://accounts.spotify.com\" alt bölümüne gidin", "step_2_steps": "1. Giriş yaptıktan sonra, Tarayıcı devtools.\n2'yi açmak için F12'ye basın veya Fare Sağ Tıklaması > İncele'ye basın. Ardından \"Uygulama\" Sekmesine (Chrome, Edge, Brave vb.) veya \"Depolama\" Sekmesine (Firefox, Palemoon vb.) gidin\n3. \"Çerezler\" bölümüne ve ardından \"https://accounts.spotify.com\" alt bölümüne gidin",
"step_3": "3. Adım", "step_3": "3. Adım",
"step_3_steps": "\"sp_dc\" ve \"sp_key\" (veya sp_gaid) Çerezlerinin değerlerini kopyalayın",
"success_emoji": "Başarılı🥳", "success_emoji": "Başarılı🥳",
"success_message": "Şimdi Spotify hesabınızla başarılı bir şekilde oturum açtınız. İyi iş, dostum!", "success_message": "Şimdi Spotify hesabınızla başarılı bir şekilde oturum açtınız. İyi iş, dostum!",
"step_4": "4. Adım", "step_4": "4. Adım",
"step_4_steps": "Kopyalanan \"sp_dc\" ve \"sp_key\" (veya sp_gaid) değerlerini ilgili alanlara yapıştırın",
"something_went_wrong": "Bir şeyler ters gitti", "something_went_wrong": "Bir şeyler ters gitti",
"piped_instance": "Piped Sunucu Örneği", "piped_instance": "Piped Sunucu Örneği",
"piped_description": "Parça eşleştirme için kullanılacak Piped sunucu örneği", "piped_description": "Parça eşleştirme için kullanılacak Piped sunucu örneği",

View File

@ -177,11 +177,9 @@
"step_2": "Крок 2", "step_2": "Крок 2",
"step_2_steps": "1. Після входу натисніть F12 або клацніть правою кнопкою миші > Інспектувати, щоб відкрити інструменти розробки браузера.\n2. Потім перейдіть на вкладку 'Програма' (Chrome, Edge, Brave тощо) або вкладку 'Сховище' (Firefox, Palemoon тощо).\n3. Перейдіть до розділу 'Кукі-файли', а потім до підрозділу 'https://accounts.spotify.com'", "step_2_steps": "1. Після входу натисніть F12 або клацніть правою кнопкою миші > Інспектувати, щоб відкрити інструменти розробки браузера.\n2. Потім перейдіть на вкладку 'Програма' (Chrome, Edge, Brave тощо) або вкладку 'Сховище' (Firefox, Palemoon тощо).\n3. Перейдіть до розділу 'Кукі-файли', а потім до підрозділу 'https://accounts.spotify.com'",
"step_3": "Крок 3", "step_3": "Крок 3",
"step_3_steps": "Скопіюйте значення кукі-файлів 'sp_dc' та 'sp_key' (або sp_gaid)",
"success_emoji": "Успіх🥳", "success_emoji": "Успіх🥳",
"success_message": "Тепер ви успішно ввійшли у свій обліковий запис Spotify. Гарна робота, друже!", "success_message": "Тепер ви успішно ввійшли у свій обліковий запис Spotify. Гарна робота, друже!",
"step_4": "Крок 4", "step_4": "Крок 4",
"step_4_steps": "Вставте скопійовані значення 'sp_dc' та 'sp_key' (або sp_gaid) у відповідні поля",
"something_went_wrong": "Щось пішло не так", "something_went_wrong": "Щось пішло не так",
"piped_instance": "Примірник сервера Piped", "piped_instance": "Примірник сервера Piped",
"piped_description": "Примірник сервера Piped, який використовуватиметься для зіставлення треків", "piped_description": "Примірник сервера Piped, який використовуватиметься для зіставлення треків",

View File

@ -175,11 +175,9 @@
"step_2": "步骤 2", "step_2": "步骤 2",
"step_2_steps": "1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具DevTools\n2. 然后选择 \"应用Application\" 标签页Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储Storage\" 标签页 Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目", "step_2_steps": "1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具DevTools\n2. 然后选择 \"应用Application\" 标签页Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储Storage\" 标签页 Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目",
"step_3": "步骤 3", "step_3": "步骤 3",
"step_3_steps": "复制名称为 \"sp_dc\" 和 \"sp_key\" (或 sp_gaid) 的值Cookie Value",
"success_emoji": "成功🥳", "success_emoji": "成功🥳",
"success_message": "你已经成功使用 Spotify 登录。干得漂亮!", "success_message": "你已经成功使用 Spotify 登录。干得漂亮!",
"step_4": "步骤 4", "step_4": "步骤 4",
"step_4_steps": "将 \"sp_dc\" 与 \"sp_key\" (或 sp_gaid) 的值分别复制后粘贴到对应的区域",
"something_went_wrong": "某些地方出现了问题", "something_went_wrong": "某些地方出现了问题",
"piped_instance": "管道服务器实例", "piped_instance": "管道服务器实例",
"piped_description": "管道服务器实例用于匹配歌曲", "piped_description": "管道服务器实例用于匹配歌曲",

View File

@ -21,6 +21,7 @@ class L10n {
const Locale('es', 'ES'), const Locale('es', 'ES'),
const Locale("fa", "IR"), const Locale("fa", "IR"),
const Locale('fr', 'FR'), const Locale('fr', 'FR'),
const Locale('ne', 'NP'),
const Locale('hi', 'IN'), const Locale('hi', 'IN'),
const Locale('it', 'IT'), const Locale('it', 'IT'),
const Locale('ja', 'JP'), const Locale('ja', 'JP'),

View File

@ -228,7 +228,9 @@ class SpotubeState extends ConsumerState<Spotube> {
builder: (context, child) { builder: (context, child) {
return DevicePreview.appBuilder( return DevicePreview.appBuilder(
context, context,
DragToResizeArea(child: child!), DesktopTools.platform.isDesktop
? DragToResizeArea(child: child!)
: child,
); );
}, },
themeMode: themeMode, themeMode: themeMode,

View File

@ -0,0 +1,111 @@
import 'package:json_annotation/json_annotation.dart';
part 'spotify_friends.g.dart';
@JsonSerializable(createToJson: false)
class SpotifyFriend {
final String uri;
final String name;
final String imageUrl;
const SpotifyFriend({
required this.uri,
required this.name,
required this.imageUrl,
});
factory SpotifyFriend.fromJson(Map<String, dynamic> json) =>
_$SpotifyFriendFromJson(json);
String get id => uri.split(":").last;
}
@JsonSerializable(createToJson: false)
class SpotifyActivityArtist {
final String uri;
final String name;
const SpotifyActivityArtist({required this.uri, required this.name});
factory SpotifyActivityArtist.fromJson(Map<String, dynamic> json) =>
_$SpotifyActivityArtistFromJson(json);
String get id => uri.split(":").last;
}
@JsonSerializable(createToJson: false)
class SpotifyActivityAlbum {
final String uri;
final String name;
const SpotifyActivityAlbum({required this.uri, required this.name});
factory SpotifyActivityAlbum.fromJson(Map<String, dynamic> json) =>
_$SpotifyActivityAlbumFromJson(json);
String get id => uri.split(":").last;
}
@JsonSerializable(createToJson: false)
class SpotifyActivityContext {
final String uri;
final String name;
final num index;
const SpotifyActivityContext({
required this.uri,
required this.name,
required this.index,
});
factory SpotifyActivityContext.fromJson(Map<String, dynamic> json) =>
_$SpotifyActivityContextFromJson(json);
String get id => uri.split(":").last;
String get path => uri.split(":").skip(1).join("/");
}
@JsonSerializable(createToJson: false)
class SpotifyActivityTrack {
final String uri;
final String name;
final String imageUrl;
final SpotifyActivityArtist artist;
final SpotifyActivityAlbum album;
final SpotifyActivityContext context;
const SpotifyActivityTrack({
required this.uri,
required this.name,
required this.imageUrl,
required this.artist,
required this.album,
required this.context,
});
factory SpotifyActivityTrack.fromJson(Map<String, dynamic> json) =>
_$SpotifyActivityTrackFromJson(json);
String get id => uri.split(":").last;
}
@JsonSerializable(createToJson: false)
class SpotifyFriendActivity {
SpotifyFriend user;
SpotifyActivityTrack track;
SpotifyFriendActivity({required this.user, required this.track});
factory SpotifyFriendActivity.fromJson(Map<String, dynamic> json) =>
_$SpotifyFriendActivityFromJson(json);
}
@JsonSerializable(createToJson: false)
class SpotifyFriends {
List<SpotifyFriendActivity> friends;
SpotifyFriends({required this.friends});
factory SpotifyFriends.fromJson(Map<String, dynamic> json) =>
_$SpotifyFriendsFromJson(json);
}

View File

@ -0,0 +1,65 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'spotify_friends.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SpotifyFriend _$SpotifyFriendFromJson(Map<String, dynamic> json) =>
SpotifyFriend(
uri: json['uri'] as String,
name: json['name'] as String,
imageUrl: json['imageUrl'] as String,
);
SpotifyActivityArtist _$SpotifyActivityArtistFromJson(
Map<String, dynamic> json) =>
SpotifyActivityArtist(
uri: json['uri'] as String,
name: json['name'] as String,
);
SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson(
Map<String, dynamic> json) =>
SpotifyActivityAlbum(
uri: json['uri'] as String,
name: json['name'] as String,
);
SpotifyActivityContext _$SpotifyActivityContextFromJson(
Map<String, dynamic> json) =>
SpotifyActivityContext(
uri: json['uri'] as String,
name: json['name'] as String,
index: json['index'] as num,
);
SpotifyActivityTrack _$SpotifyActivityTrackFromJson(
Map<String, dynamic> json) =>
SpotifyActivityTrack(
uri: json['uri'] as String,
name: json['name'] as String,
imageUrl: json['imageUrl'] as String,
artist: SpotifyActivityArtist.fromJson(
json['artist'] as Map<String, dynamic>),
album:
SpotifyActivityAlbum.fromJson(json['album'] as Map<String, dynamic>),
context: SpotifyActivityContext.fromJson(
json['context'] as Map<String, dynamic>),
);
SpotifyFriendActivity _$SpotifyFriendActivityFromJson(
Map<String, dynamic> json) =>
SpotifyFriendActivity(
user: SpotifyFriend.fromJson(json['user'] as Map<String, dynamic>),
track:
SpotifyActivityTrack.fromJson(json['track'] as Map<String, dynamic>),
);
SpotifyFriends _$SpotifyFriendsFromJson(Map<String, dynamic> json) =>
SpotifyFriends(
friends: (json['friends'] as List<dynamic>)
.map((e) => SpotifyFriendActivity.fromJson(e as Map<String, dynamic>))
.toList(),
);

View File

@ -35,7 +35,7 @@ class ArtistPage extends HookConsumerWidget {
), ),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
body: Builder(builder: (context) { body: Builder(builder: (context) {
if (artistQuery.hasError) { if (artistQuery.hasError && artistQuery.data == null) {
return Center(child: Text(artistQuery.error.toString())); return Center(child: Text(artistQuery.error.toString()));
} }
return Skeletonizer( return Skeletonizer(

View File

@ -3,6 +3,7 @@ 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:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/components/home/sections/featured.dart'; import 'package:spotube/components/home/sections/featured.dart';
import 'package:spotube/components/home/sections/friends.dart';
import 'package:spotube/components/home/sections/genres.dart'; import 'package:spotube/components/home/sections/genres.dart';
import 'package:spotube/components/home/sections/made_for_user.dart'; import 'package:spotube/components/home/sections/made_for_user.dart';
import 'package:spotube/components/home/sections/new_releases.dart'; import 'package:spotube/components/home/sections/new_releases.dart';
@ -31,6 +32,7 @@ class HomePage extends HookConsumerWidget {
HomeNewReleasesSection(), HomeNewReleasesSection(),
], ],
), ),
const HomePageFriendsSection(),
const SliverSafeArea(sliver: HomeMadeForUserSection()), const SliverSafeArea(sliver: HomeMadeForUserSection()),
], ],
), ),

View File

@ -1,12 +1,15 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/lyrics/zoom_controls.dart'; import 'package:spotube/components/lyrics/zoom_controls.dart';
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart'; import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
@ -72,10 +75,22 @@ class PlainLyrics extends HookConsumerWidget {
if (lyricsQuery.isLoading || lyricsQuery.isRefreshing) { if (lyricsQuery.isLoading || lyricsQuery.isRefreshing) {
return const ShimmerLyrics(); return const ShimmerLyrics();
} else if (lyricsQuery.hasError) { } else if (lyricsQuery.hasError) {
return Text( return Container(
"Sorry, no Lyrics were found for `${playlist.activeTrack?.name}` :'(\n${lyricsQuery.error.toString()}", alignment: Alignment.center,
style: textTheme.bodyLarge?.copyWith( padding: const EdgeInsets.all(16),
color: palette.bodyTextColor, child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.no_lyrics_available,
style: textTheme.bodyLarge?.copyWith(
color: palette.bodyTextColor,
),
textAlign: TextAlign.center,
),
const Gap(26),
const Icon(SpotubeIcons.noLyrics, size: 60),
],
), ),
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
import 'package:spotify/spotify.dart' hide Offset; import 'package:spotify/spotify.dart' hide Offset;
@ -7,6 +8,7 @@ import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/lyrics/zoom_controls.dart'; import 'package:spotube/components/lyrics/zoom_controls.dart';
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart'; import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart';
import 'package:spotube/components/lyrics/use_synced_lyrics.dart'; import 'package:spotube/components/lyrics/use_synced_lyrics.dart';
import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:scroll_to_index/scroll_to_index.dart';
@ -188,12 +190,19 @@ class SyncedLyrics extends HookConsumerWidget {
child: ShimmerLyrics(), child: ShimmerLyrics(),
) )
else if (playlist.activeTrack != null && else if (playlist.activeTrack != null &&
(timedLyricsQuery.hasError)) (timedLyricsQuery.hasError)) ...[
Text( Container(
"Sorry, no Lyrics were found for `${playlist.activeTrack?.name}` :'(\n${timedLyricsQuery.error.toString()}", alignment: Alignment.center,
style: bodyTextTheme, padding: const EdgeInsets.all(16),
) child: Text(
else if (isUnSyncLyric == true) context.l10n.no_lyrics_available,
style: bodyTextTheme,
textAlign: TextAlign.center,
),
),
const Gap(26),
const Icon(SpotubeIcons.noLyrics, size: 60),
] else if (isUnSyncLyric == true)
Expanded( Expanded(
child: Center( child: Center(
child: RichText( child: RichText(

View File

@ -55,12 +55,7 @@ class WebViewLogin extends HookConsumerWidget {
final cookies = final cookies =
await CookieManager.instance().getCookies(url: action); await CookieManager.instance().getCookies(url: action);
final cookieHeader = final cookieHeader =
cookies.fold<String>("", (previousValue, element) { "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}";
if (element.name == "sp_dc" || element.name == "sp_key") {
return "$previousValue; ${element.name}=${element.value}";
}
return previousValue;
});
authenticationNotifier.setCredentials( authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie(cookieHeader), await AuthenticationCredentials.fromCookie(cookieHeader),

View File

@ -4,7 +4,6 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart';
import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
class LikedPlaylistPage extends HookConsumerWidget { class LikedPlaylistPage extends HookConsumerWidget {
final PlaylistSimple playlist; final PlaylistSimple playlist;
@ -20,10 +19,7 @@ class LikedPlaylistPage extends HookConsumerWidget {
return InheritedTrackView( return InheritedTrackView(
collectionId: playlist.id!, collectionId: playlist.id!,
image: TypeConversionUtils.image_X_UrlString( image: "assets/liked-tracks.jpg",
playlist.images,
placeholder: ImagePlaceholder.collection,
),
pagination: PaginationProps( pagination: PaginationProps(
hasNextPage: false, hasNextPage: false,
isLoading: false, isLoading: false,

227
lib/pages/track/track.dart Normal file
View File

@ -0,0 +1,227 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/heart_button.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/components/shared/links/link_text.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/components/shared/track_tile/track_options.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:spotube/extensions/constrains.dart';
class TrackPage extends HookConsumerWidget {
final String trackId;
const TrackPage({
Key? key,
required this.trackId,
}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final isActive = playlist.activeTrack?.id == trackId;
final trackQuery = useQueries.tracks.track(ref, trackId);
final track = trackQuery.data ?? FakeData.track;
void onPlay() async {
if (isActive) {
audioPlayer.pause();
} else {
await playlistNotifier.load([track], autoPlay: true);
}
}
return Scaffold(
appBar: const PageWindowTitleBar(
automaticallyImplyLeading: true,
backgroundColor: Colors.transparent,
),
extendBodyBehindAppBar: true,
body: Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: UniversalImage.imageProvider(
TypeConversionUtils.image_X_UrlString(
track.album!.images,
placeholder: ImagePlaceholder.albumArt,
),
),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
colorScheme.surface.withOpacity(0.5),
BlendMode.srcOver,
),
alignment: Alignment.topCenter,
),
),
),
),
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Skeletonizer(
enabled: trackQuery.isLoading,
child: Container(
alignment: Alignment.topCenter,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.surface,
Colors.transparent,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.2, 1],
),
),
child: SafeArea(
child: Wrap(
spacing: 20,
runSpacing: 20,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
runAlignment: WrapAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: UniversalImage(
path: TypeConversionUtils.image_X_UrlString(
track.album!.images,
placeholder: ImagePlaceholder.albumArt,
),
height: 200,
width: 200,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: mediaQuery.smAndDown
? CrossAxisAlignment.center
: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
track.name!,
style: textTheme.titleLarge,
),
const Gap(10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.album),
const Gap(5),
Flexible(
child: LinkText(
track.album!.name!,
'/album/${track.album!.id}',
push: true,
extra: track.album,
),
),
],
),
const Gap(10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.artist),
const Gap(5),
TypeConversionUtils
.artists_X_ClickableArtists(
track.artists!,
),
],
),
const Gap(10),
ConstrainedBox(
constraints:
const BoxConstraints(maxWidth: 350),
child: Row(
mainAxisSize: mediaQuery.smAndDown
? MainAxisSize.max
: MainAxisSize.min,
children: [
const Gap(5),
if (!isActive &&
!playlist.tracks.contains(track))
OutlinedButton.icon(
icon: const Icon(SpotubeIcons.queueAdd),
label: Text(context.l10n.queue),
onPressed: () {
playlistNotifier.addTrack(track);
},
),
const Gap(5),
if (!isActive &&
!playlist.tracks.contains(track))
IconButton.outlined(
icon:
const Icon(SpotubeIcons.lightning),
tooltip: context.l10n.play_next,
onPressed: () {
playlistNotifier
.addTracksAtFirst([track]);
},
),
const Gap(5),
IconButton.filled(
tooltip: isActive
? context.l10n.pause_playback
: context.l10n.play,
icon: Icon(
isActive
? SpotubeIcons.pause
: SpotubeIcons.play,
color: colorScheme.onPrimary,
),
onPressed: onPlay,
),
const Gap(5),
if (mediaQuery.smAndDown)
const Spacer()
else
const Gap(20),
TrackHeartButton(track: track),
TrackOptions(
track: track,
userPlaylist: false,
),
const Gap(5),
],
),
),
],
),
),
],
),
),
),
),
),
),
],
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/spotify_friends.dart';
class CustomSpotifyEndpoints { class CustomSpotifyEndpoints {
static const _baseUrl = 'https://api.spotify.com/v1'; static const _baseUrl = 'https://api.spotify.com/v1';
@ -162,4 +163,71 @@ class CustomSpotifyEndpoints {
result["tracks"].map((track) => Track.fromJson(track)).toList(), result["tracks"].map((track) => Track.fromJson(track)).toList(),
); );
} }
Future<SpotifyFriends> getFriendActivity() async {
final res = await _client.get(
Uri.parse("https://guc-spclient.spotify.com/presence-view/v1/buddylist"),
headers: {
"content-type": "application/json",
"authorization": "Bearer $accessToken",
"accept": "application/json",
},
);
return SpotifyFriends.fromJson(jsonDecode(res.body));
}
Future<Artist> artist({required String id}) async {
final pathQuery = "$_baseUrl/artists/$id";
final res = await _client.get(
Uri.parse(pathQuery),
headers: {
"content-type": "application/json",
if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken",
"accept": "application/json",
},
);
final data = jsonDecode(res.body);
return Artist.fromJson(_purifyArtistResponse(data));
}
Future<List<Artist>> relatedArtists({required String id}) async {
final pathQuery = "$_baseUrl/artists/$id/related-artists";
final res = await _client.get(
Uri.parse(pathQuery),
headers: {
"content-type": "application/json",
if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken",
"accept": "application/json",
},
);
final data = jsonDecode(res.body);
return List.castFrom<dynamic, Artist>(
data["artists"]
.map((artist) => Artist.fromJson(_purifyArtistResponse(artist)))
.toList(),
);
}
Map<String, dynamic> _purifyArtistResponse(Map<String, dynamic> data) {
if (data["popularity"] != null) {
data["popularity"] = data["popularity"].toInt();
}
if (data["followers"]?["total"] != null) {
data["followers"]["total"] = data["followers"]["total"].toInt();
}
if (data["images"] != null) {
data["images"] = data["images"].map((e) {
e["height"] = e["height"].toInt();
e["width"] = e["width"].toInt();
return e;
}).toList();
}
return data;
}
} }

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
final logger = getLogger("ChunkedDownload"); final logger = getLogger("ChunkedDownload");

View File

@ -6,6 +6,8 @@ import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:spotube/services/download_manager/chunked_download.dart'; import 'package:spotube/services/download_manager/chunked_download.dart';
import 'package:spotube/services/download_manager/download_request.dart'; import 'package:spotube/services/download_manager/download_request.dart';
@ -77,7 +79,18 @@ class DownloadManager {
logger.d("[DownloadManager] $url"); logger.d("[DownloadManager] $url");
final file = File(savePath.toString()); final file = File(savePath.toString());
partialFilePath = savePath + partialExtension;
final tmpDirPath = await Directory(
path.join(
(await getTemporaryDirectory()).path,
"spotube-downloads",
),
).create(recursive: true);
partialFilePath = path.join(
tmpDirPath.path,
path.basename(savePath) + partialExtension,
);
partialFile = File(partialFilePath); partialFile = File(partialFilePath);
final fileExist = await file.exists(); final fileExist = await file.exists();
@ -111,7 +124,9 @@ class DownloadManager {
await ioSink.addStream(partialChunkFile.openRead()); await ioSink.addStream(partialChunkFile.openRead());
await partialChunkFile.delete(); await partialChunkFile.delete();
await ioSink.close(); await ioSink.close();
await partialFile.rename(savePath);
await partialFile.copy(savePath);
await partialFile.delete();
setStatus(task, DownloadStatus.completed); setStatus(task, DownloadStatus.completed);
} }
@ -125,7 +140,8 @@ class DownloadManager {
); );
if (response.statusCode == HttpStatus.ok) { if (response.statusCode == HttpStatus.ok) {
await partialFile.rename(savePath); await partialFile.copy(savePath);
await partialFile.delete();
setStatus(task, DownloadStatus.completed); setStatus(task, DownloadStatus.completed);
} }
} }

View File

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart'; import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
import 'package:spotube/hooks/spotify/use_spotify_query.dart'; import 'package:spotube/hooks/spotify/use_spotify_query.dart';
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/wikipedia/wikipedia.dart'; import 'package:spotube/services/wikipedia/wikipedia.dart';
import 'package:wikipedia_api/wikipedia_api.dart'; import 'package:wikipedia_api/wikipedia_api.dart';
@ -15,9 +16,10 @@ class ArtistQueries {
WidgetRef ref, WidgetRef ref,
String artist, String artist,
) { ) {
final customSpotify = ref.watch(customSpotifyEndpointProvider);
return useSpotifyQuery<Artist, dynamic>( return useSpotifyQuery<Artist, dynamic>(
"artist-profile/$artist", "artist-profile/$artist",
(spotify) => spotify.artists.get(artist), (spotify) => customSpotify.artist(id: artist),
ref: ref, ref: ref,
); );
} }
@ -125,10 +127,11 @@ class ArtistQueries {
WidgetRef ref, WidgetRef ref,
String artist, String artist,
) { ) {
final customSpotify = ref.watch(customSpotifyEndpointProvider);
return useSpotifyQuery<Iterable<Artist>, dynamic>( return useSpotifyQuery<Iterable<Artist>, dynamic>(
"artist-related-artist-query/$artist", "artist-related-artist-query/$artist",
(spotify) { (spotify) {
return spotify.artists.relatedArtists(artist); return customSpotify.relatedArtists(id: artist);
}, },
ref: ref, ref: ref,
); );

View File

@ -63,8 +63,8 @@ class LyricsQueries {
/// Special thanks to [raptag](https://github.com/raptag) for discovering this /// Special thanks to [raptag](https://github.com/raptag) for discovering this
/// jem /// jem
Query<SubtitleSimple, dynamic> spotifySynced(WidgetRef ref, Track? track) { Query<SubtitleSimple, Exception> spotifySynced(WidgetRef ref, Track? track) {
return useSpotifyQuery<SubtitleSimple, dynamic>( return useSpotifyQuery<SubtitleSimple, Exception>(
"spotify-synced-lyrics/${track?.id}}", "spotify-synced-lyrics/${track?.id}}",
(spotify) async { (spotify) async {
if (track == null) { if (track == null) {

View File

@ -4,6 +4,7 @@ import 'package:spotube/services/queries/category.dart';
import 'package:spotube/services/queries/lyrics.dart'; import 'package:spotube/services/queries/lyrics.dart';
import 'package:spotube/services/queries/playlist.dart'; import 'package:spotube/services/queries/playlist.dart';
import 'package:spotube/services/queries/search.dart'; import 'package:spotube/services/queries/search.dart';
import 'package:spotube/services/queries/tracks.dart';
import 'package:spotube/services/queries/user.dart'; import 'package:spotube/services/queries/user.dart';
import 'package:spotube/services/queries/views.dart'; import 'package:spotube/services/queries/views.dart';
@ -17,6 +18,7 @@ class Queries {
final search = const SearchQueries(); final search = const SearchQueries();
final user = const UserQueries(); final user = const UserQueries();
final views = const ViewsQueries(); final views = const ViewsQueries();
final tracks = const TracksQueries();
} }
const useQueries = Queries._(); const useQueries = Queries._();

View File

@ -0,0 +1,16 @@
import 'package:fl_query/fl_query.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
class TracksQueries {
const TracksQueries();
Query<Track, dynamic> track(WidgetRef ref, String id) {
return useSpotifyQuery(
"track/$id",
(spotify) => spotify.tracks.get(id),
ref: ref,
);
}
}

View File

@ -3,7 +3,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/spotify/use_spotify_query.dart'; import 'package:spotube/hooks/spotify/use_spotify_query.dart';
import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
class UserQueries { class UserQueries {
@ -37,4 +39,15 @@ class UserQueries {
ref: ref, ref: ref,
); );
} }
Query<SpotifyFriends, dynamic> friendActivity(WidgetRef ref) {
final customSpotify = ref.read(customSpotifyEndpointProvider);
return useSpotifyQuery<SpotifyFriends, dynamic>(
"friend-activity",
(spotify) {
return customSpotify.getFriendActivity();
},
ref: ref,
);
}
} }

View File

@ -15,4 +15,4 @@ enum SourceQualities {
low, low,
} }
typedef SiblingType = ({SourceInfo info, SourceMap? source}); typedef SiblingType<T extends SourceInfo> = ({T info, SourceMap? source});

View File

@ -12,6 +12,19 @@ import 'package:spotube/extensions/string.dart';
final jiosaavnClient = JioSaavnClient(); final jiosaavnClient = JioSaavnClient();
class JioSaavnSourceInfo extends SourceInfo {
JioSaavnSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class JioSaavnSourcedTrack extends SourcedTrack { class JioSaavnSourcedTrack extends SourcedTrack {
JioSaavnSourcedTrack({ JioSaavnSourcedTrack({
required super.ref, required super.ref,
@ -70,7 +83,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
static SiblingType toSiblingType(SongResponse result) { static SiblingType toSiblingType(SongResponse result) {
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: JioSaavnSourceInfo(
artist: [ artist: [
result.primaryArtists, result.primaryArtists,
if (result.featuredArtists.isNotEmpty) ", ", if (result.featuredArtists.isNotEmpty) ", ",
@ -155,12 +168,16 @@ class JioSaavnSourcedTrack extends SourcedTrack {
@override @override
Future<JioSaavnSourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<JioSaavnSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);

View File

@ -22,6 +22,19 @@ final pipedProvider = Provider<PipedClient>(
}, },
); );
class PipedSourceInfo extends SourceInfo {
PipedSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class PipedSourcedTrack extends SourcedTrack { class PipedSourcedTrack extends SourcedTrack {
PipedSourcedTrack({ PipedSourcedTrack({
required super.ref, required super.ref,
@ -71,7 +84,7 @@ class PipedSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: [], siblings: [],
source: toSourceMap(manifest), source: toSourceMap(manifest),
sourceInfo: SourceInfo( sourceInfo: PipedSourceInfo(
id: manifest.id, id: manifest.id,
artist: manifest.uploader, artist: manifest.uploader,
artistUrl: manifest.uploaderUrl, artistUrl: manifest.uploaderUrl,
@ -122,7 +135,7 @@ class PipedSourcedTrack extends SourcedTrack {
} }
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: PipedSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/${item.channelId}", artistUrl: "https://www.youtube.com/${item.channelId}",
@ -233,12 +246,16 @@ class PipedSourcedTrack extends SourcedTrack {
@override @override
Future<SourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<SourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);

View File

@ -17,6 +17,19 @@ final officialMusicRegex = RegExp(
caseSensitive: false, caseSensitive: false,
); );
class YoutubeSourceInfo extends SourceInfo {
YoutubeSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class YoutubeSourcedTrack extends SourcedTrack { class YoutubeSourcedTrack extends SourcedTrack {
YoutubeSourcedTrack({ YoutubeSourcedTrack({
required super.source, required super.source,
@ -64,7 +77,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: [], siblings: [],
source: toSourceMap(manifest), source: toSourceMap(manifest),
sourceInfo: SourceInfo( sourceInfo: YoutubeSourceInfo(
id: item.id.value, id: item.id.value,
artist: item.author, artist: item.author,
artistUrl: "https://www.youtube.com/channel/${item.channelId}", artistUrl: "https://www.youtube.com/channel/${item.channelId}",
@ -117,7 +130,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
} }
final SiblingType sibling = ( final SiblingType sibling = (
info: SourceInfo( info: YoutubeSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/channel/${item.channelId}", artistUrl: "https://www.youtube.com/channel/${item.channelId}",
@ -217,12 +230,16 @@ class YoutubeSourcedTrack extends SourcedTrack {
@override @override
Future<YoutubeSourcedTrack?> swapWithSibling(SourceInfo sibling) async { Future<YoutubeSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id || if (sibling.id == sourceInfo.id) {
siblings.none((s) => s.id == sibling.id)) {
return null; return null;
} }
final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); // a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo); ..insert(0, sourceInfo);

View File

@ -1,4 +1,4 @@
platform :osx, '10.13' platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,101 +1,146 @@
PODS: PODS:
- app_links (1.0.0):
- FlutterMacOS
- audio_service (0.14.1): - audio_service (0.14.1):
- FlutterMacOS - FlutterMacOS
- audio_session (0.0.1): - audio_session (0.0.1):
- FlutterMacOS - FlutterMacOS
- audioplayers_darwin (0.0.1): - device_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- bitsdojo_window_macos (0.0.1): - file_selector_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- connectivity_plus_macos (0.0.1): - flutter_secure_storage_macos (6.1.1):
- FlutterMacOS - FlutterMacOS
- ReachabilitySwift
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- FMDB (2.7.5): - FMDB (2.7.5):
- FMDB/standard (= 2.7.5) - FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5) - FMDB/standard (2.7.5)
- macos_ui (0.1.0): - local_notifier (0.1.0):
- FlutterMacOS - FlutterMacOS
- metadata_god (0.0.1): - media_kit_libs_macos_audio (1.0.4):
- FlutterMacOS - FlutterMacOS
- package_info_plus_macos (0.0.1): - media_kit_native_event_loop (1.0.0):
- FlutterMacOS - FlutterMacOS
- path_provider_macos (0.0.1): - metadata_god (0.0.1)
- package_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- ReachabilitySwift (5.0.0) - path_provider_foundation (0.0.1):
- shared_preferences_macos (0.0.1): - Flutter
- FlutterMacOS
- screen_retriever (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS - FlutterMacOS
- sqflite (0.0.2): - sqflite (0.0.2):
- FlutterMacOS - FlutterMacOS
- FMDB (>= 2.7.5) - FMDB (>= 2.7.5)
- system_theme (0.0.1):
- FlutterMacOS
- system_tray (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
- window_size (0.0.2):
- FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`) - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`)
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
- audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- connectivity_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`)
- media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`)
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
- metadata_god (from `Flutter/ephemeral/.symlinks/plugins/metadata_god/macos`) - metadata_god (from `Flutter/ephemeral/.symlinks/plugins/metadata_god/macos`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`)
- system_tray (from `Flutter/ephemeral/.symlinks/plugins/system_tray/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- FMDB - FMDB
- ReachabilitySwift
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links:
:path: Flutter/ephemeral/.symlinks/plugins/app_links/macos
audio_service: audio_service:
:path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos :path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos
audio_session: audio_session:
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
audioplayers_darwin: device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
bitsdojo_window_macos: file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
connectivity_plus_macos: flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
macos_ui: local_notifier:
:path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos :path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos
media_kit_libs_macos_audio:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos
media_kit_native_event_loop:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
metadata_god: metadata_god:
:path: Flutter/ephemeral/.symlinks/plugins/metadata_god/macos :path: Flutter/ephemeral/.symlinks/plugins/metadata_god/macos
package_info_plus_macos: package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_macos: path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
shared_preferences_macos: screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite: sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
system_theme:
:path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos
system_tray:
:path: Flutter/ephemeral/.symlinks/plugins/system_tray/macos
url_launcher_macos: url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
window_size:
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
SPEC CHECKSUMS: SPEC CHECKSUMS:
app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9 audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72 audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00 file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
macos_ui: 125c911559d646194386d84c017ad6819122e2db local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
metadata_god: 55a71136c95eb75ec28142f6fbfc2bcff6f881b1 media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
system_tray: e53c972838c69589ff2e77d6d3abfd71332f9e5d
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
PODFILE CHECKSUM: a884f6dd3f7494f3892ee6c81feea3a3abbf9153 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.11.3 COCOAPODS: 1.14.3

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 51; objectVersion = 54;
objects = { objects = {
/* Begin PBXAggregateTarget section */ /* Begin PBXAggregateTarget section */
@ -208,7 +208,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
33CC10EC2044A3C60003C045 = { 33CC10EC2044A3C60003C045 = {
@ -261,6 +261,7 @@
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = { 3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -409,7 +410,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@ -431,7 +432,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@ -489,7 +490,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@ -536,7 +537,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@ -558,7 +559,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -579,7 +580,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.14;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1300" LastUpgradeVersion = "1430"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -543,10 +543,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.4" version: "7.0.0"
file_picker: file_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1014,10 +1014,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_fonts name: google_fonts
sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.0" version: "6.1.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -1255,6 +1255,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.2" version: "0.4.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "04be76c4a4bb50f14904e64749237e541e7c7bcf7ec0b196907322ab5d2fc739"
url: "https://pub.dev"
source: hosted
version: "9.0.16"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff
url: "https://pub.dev"
source: hosted
version: "1.0.5"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -1307,10 +1323,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1379,10 +1395,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.0"
metadata_god: metadata_god:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1595,10 +1611,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.3"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1635,10 +1651,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: process name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" sha256: "266ca5be5820feefc777793d0a583acfc8c40834893c87c00c6c09e2cf58ea42"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "5.0.1"
provider: provider:
dependency: transitive dependency: transitive
description: description:
@ -1756,10 +1772,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -1780,10 +1796,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1804,10 +1820,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -1836,10 +1852,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sidebarx name: sidebarx
sha256: "26a8392ceddb659c8f2c688beba6c04bcbf520b4d5decb143c5fd7253653081f" sha256: "7042d64844b8e64ca5c17e70d89b49df35b54a26c015b90000da9741eab70bc0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.16.3"
simple_icons: simple_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -2209,18 +2225,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: visibility_detector name: visibility_detector
sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3" version: "0.4.0+2"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.10.0" version: "13.0.0"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -2233,10 +2249,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 sha256: edc8a9573dd8c5a83a183dae1af2b6fd4131377404706ca4e5420474784906fa
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.4.0"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -2249,10 +2265,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webdriver name: webdriver
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
wikipedia_api: wikipedia_api:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -55,8 +55,8 @@ dependencies:
flutter_svg: ^1.1.6 flutter_svg: ^1.1.6
form_validator: ^2.1.1 form_validator: ^2.1.1
fuzzywuzzy: ^1.1.6 fuzzywuzzy: ^1.1.6
google_fonts: ^5.1.0
go_router: ^13.0.1 go_router: ^13.0.1
google_fonts: ^6.1.0
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
hooks_riverpod: ^2.4.3 hooks_riverpod: ^2.4.3
@ -83,8 +83,8 @@ dependencies:
url: https://github.com/KRTirtho/scrobblenaut.git url: https://github.com/KRTirtho/scrobblenaut.git
ref: dart-3-support ref: dart-3-support
scroll_to_index: ^3.0.1 scroll_to_index: ^3.0.1
shared_preferences: ^2.0.11 sidebarx: ^0.16.3
sidebarx: ^0.15.0 shared_preferences: ^2.2.2
skeleton_text: ^3.0.1 skeleton_text: ^3.0.1
smtc_windows: ^0.1.1 smtc_windows: ^0.1.1
spotify: ^0.12.0 spotify: ^0.12.0
@ -94,7 +94,7 @@ dependencies:
url_launcher: ^6.1.7 url_launcher: ^6.1.7
uuid: ^3.0.7 uuid: ^3.0.7
version: ^3.0.2 version: ^3.0.2
visibility_detector: ^0.3.3 visibility_detector: ^0.4.0+2
window_manager: ^0.3.1 window_manager: ^0.3.1
window_size: window_size:
git: git:

View File

@ -1 +1,120 @@
{} {
"ar": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"bn": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"ca": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"de": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"es": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"fa": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"fr": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"hi": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"it": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"ja": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"nl": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"pl": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"pt": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"ru": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"tr": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"uk": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
],
"zh": [
"step_3_steps",
"step_4_steps",
"friends",
"no_lyrics_available"
]
}