Skip to content

Commit

Permalink
Merge branch 'dev' into feat/spotify-friends
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jan 22, 2024
2 parents df8ee98 + 682e88e commit 74c47b3
Show file tree
Hide file tree
Showing 45 changed files with 420 additions and 180 deletions.
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
},
{
"name": "spotube (mobile)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"args": [
"--flavor",
"dev"
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">

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

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://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>
Expand All @@ -26,7 +26,7 @@ Btw it's not another Electron app😉
## 🌃 Features

- 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹
- ⬇️ Downloadable tracks
- ⬇️ Freely downloadable tracks
- 🖥️ 📱 Cross-platform support
- 🪶 Small size & less data usage
- 🕵️ Anonymous/guest login
Expand All @@ -40,13 +40,13 @@ Btw it's not another Electron app😉

### ❌ 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)

## 📜 ⬇️ Installation guide

New releases usually appear after 3-4 months.<br />
This handy table lists all methods you can use to install Spotube:
New versions usually release every 3-4 months.<br />
This handy table lists all the methods you can use to install Spotube:

<table>
<tr>
Expand Down
Binary file added assets/jiosaavn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/liked-tracks.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions lib/collections/assets.gen.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/collections/spotube_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,5 @@ abstract class SpotubeIcons {
static const normalize = FeatherIcons.barChart2;
static const wikipedia = SimpleIcons.wikipedia;
static const discord = SimpleIcons.discord;
static const youtube = SimpleIcons.youtube;
}
15 changes: 2 additions & 13 deletions lib/components/desktop_login/login_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class TokenLoginForm extends HookConsumerWidget {
final authenticationNotifier =
ref.watch(AuthenticationNotifier.provider.notifier);
final directCodeController = useTextEditingController();
final keyCodeController = useTextEditingController();
final mounted = useIsMounted();

final isLoading = useState(false);
Expand All @@ -37,23 +36,13 @@ class TokenLoginForm extends HookConsumerWidget {
keyboardType: TextInputType.visiblePassword,
),
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(
onPressed: isLoading.value
? null
: () async {
try {
isLoading.value = true;
if (keyCodeController.text.isEmpty ||
directCodeController.text.isEmpty) {
if (directCodeController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.fill_in_all_fields),
Expand All @@ -63,7 +52,7 @@ class TokenLoginForm extends HookConsumerWidget {
return;
}
final cookieHeader =
"sp_dc=${directCodeController.text.trim()}; sp_key=${keyCodeController.text.trim()}";
"sp_dc=${directCodeController.text.trim()}";

authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie(
Expand Down
25 changes: 15 additions & 10 deletions lib/components/home/sections/new_releases.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ class HomeNewReleasesSection extends HookConsumerWidget {
userArtistsQuery.data?.map((s) => s.id!).toList() ?? const [];

final albums = useMemoized(
() => newReleases.pages
.whereType<Page<AlbumSimple>>()
.expand((page) => page.items ?? const <AlbumSimple>[])
.where((album) {
return album.artists
?.any((artist) => userArtists.contains(artist.id!)) ==
true;
})
.map((album) => TypeConversionUtils.simpleAlbum_X_Album(album))
.toList(),
() {
final allReleases = newReleases.pages
.whereType<Page<AlbumSimple>>()
.expand((page) => page.items ?? const <AlbumSimple>[])
.map((album) => TypeConversionUtils.simpleAlbum_X_Album(album));

final userArtistReleases = allReleases.where((album) {
return album.artists
?.any((artist) => userArtists.contains(artist.id!)) ==
true;
}).toList();

if (userArtistReleases.isEmpty) return allReleases.toList();
return userArtistReleases;
},
[newReleases.pages],
);

Expand Down
30 changes: 15 additions & 15 deletions lib/components/library/user_playlists.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@ class UserPlaylists extends HookConsumerWidget {
);

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

final playlists = useMemoized(
() {
Expand Down
54 changes: 48 additions & 6 deletions lib/components/player/sibling_tracks_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart' hide Offset;
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart';

import 'package:spotube/components/shared/image/universal_image.dart';
Expand All @@ -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/video_info.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/utils/service_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 {
final bool floating;
const SiblingTracksSheet({
Expand Down Expand Up @@ -64,17 +83,34 @@ class SiblingTracksSheet extends HookConsumerWidget {
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(
results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async {
final searchResults = await Future.wait([
...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);
return siblingType.info;
}),
);
]);
final activeSourceInfo =
(playlist.activeTrack! as SourcedTrack).sourceInfo;
return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id)
..insert(
0,
activeSourceInfo,
);
}, [
searchTerm,
searchMode.value,
playlist.activeTrack,
]);

final siblings = useMemoized(
Expand Down Expand Up @@ -104,6 +140,7 @@ class SiblingTracksSheet extends HookConsumerWidget {

final itemBuilder = useCallback(
(SourceInfo sourceInfo) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType];
return ListTile(
title: Text(sourceInfo.title),
leading: Padding(
Expand All @@ -118,7 +155,12 @@ class SiblingTracksSheet extends HookConsumerWidget {
borderRadius: BorderRadius.circular(5),
),
trailing: Text(sourceInfo.duration.toHumanReadableString()),
subtitle: Text(sourceInfo.artist),
subtitle: Row(
children: [
if (icon != null) icon,
Text(" • ${sourceInfo.artist}"),
],
),
enabled: playlist.isFetching != true,
selected: playlist.isFetching != true &&
sourceInfo.id ==
Expand All @@ -137,7 +179,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
[playlist.isFetching, playlist.activeTrack, siblings],
);

var mediaQuery = MediaQuery.of(context);
final mediaQuery = MediaQuery.of(context);
return SafeArea(
child: ClipRRect(
borderRadius: borderRadius,
Expand Down
2 changes: 1 addition & 1 deletion lib/components/root/sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class Sidebar extends HookConsumerWidget {
margin: EdgeInsets.only(
bottom: 10,
left: 0,
top: kIsMacOS ? 35 : 5,
top: kIsMacOS ? 0 : 5,
),
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
onNotification: (notification) => true,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
dragDevices: PointerDeviceKind.values.toSet(),
),
child: items.isEmpty
? ListView.builder(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:ui';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand Down Expand Up @@ -62,7 +61,7 @@ class TrackViewFlexHeader extends HookConsumerWidget {
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
image: DecorationImage(
image: CachedNetworkImageProvider(props.image),
image: UniversalImage.imageProvider(props.image),
fit: BoxFit.cover,
),
),
Expand Down
2 changes: 1 addition & 1 deletion lib/hooks/configurators/use_init_sys_tray.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void useInitSysTray(WidgetRef ref) {
}
final enabled = !playlist.isFetching;
systemTray.value = await DesktopTools.createSystemTrayMenu(
title: DesktopTools.platform.isLinux ? "" : "Spotube",
title: DesktopTools.platform.isWindows ? "Spotube" : "",
iconPath: "assets/spotube-logo.png",
windowsIconPath: "assets/spotube-logo.ico",
items: [
Expand Down
2 changes: 0 additions & 2 deletions lib/l10n/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,9 @@
"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",
"piped_description": "مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة",
Expand Down
2 changes: 0 additions & 2 deletions lib/l10n/app_bn.arb
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,9 @@
"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_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 সার্ভার এড্রেস",
"piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার",
Expand Down
2 changes: 0 additions & 2 deletions lib/l10n/app_ca.arb
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,9 @@
"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_3": "Pas 3",
"step_3_steps": "Copiï els valors de les Cookies \"sp_dc\" i \"sp_key\" (o sp_gaid)",
"success_emoji": "Èxit! 🥳",
"success_message": "Ara has iniciat sessió amb èxit al teu compte de Spotify. Bona feina!",
"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",
"piped_instance": "Instància del servidor Piped",
"piped_description": "La instància del servidor Piped a utilitzar per la coincidència de cançons",
Expand Down
2 changes: 0 additions & 2 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,9 @@
"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_3": "Schritt 3",
"step_3_steps": "Kopiere die Werte der Cookies \"sp_dc\" und \"sp_key\" (oder sp_gaid)",
"success_emoji": "Erfolg🥳",
"success_message": "Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!",
"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",
"piped_instance": "Piped-Serverinstanz",
"piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll",
Expand Down
Loading

0 comments on commit 74c47b3

Please sign in to comment.