diff --git a/README.md b/README.md
index 4cfb7966..768c5c4c 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,9 @@ Following are the features that currently spotube offers:
- Small size & less data hungry
- No spotify or youtube ads since it uses all public & free APIs (But it's recommended to support the creators by watching/liking/subscribing to the artists youtube channel or add as favourite track in spotify. Mostly buying spotify premium is the best way to support their valuable creations)
- Lyrics
-- Downloadable track (WIP)
+- Downloadable track
+
+
# Installation
@@ -98,9 +100,9 @@ Also, you need a [genius](https://genius.com) account for **lyrics** & a API Cli
# TODO:
-- [ ] Compile, Debug & Build for **MacOS**
+- [x] Compile, Debug & Build for **MacOS**
- [x] Add support for show Lyric of currently playing track
-- [ ] Track download
+- [x] Track download
- [ ] Support for playing/streaming podcasts/shows
- [ ] Artist, User & Album pages
diff --git a/lib/components/Artist/ArtistCard.dart b/lib/components/Artist/ArtistCard.dart
index e07e7277..0f5099e8 100644
--- a/lib/components/Artist/ArtistCard.dart
+++ b/lib/components/Artist/ArtistCard.dart
@@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart';
+import 'package:spotube/components/Artist/ArtistProfile.dart';
class ArtistCard extends StatelessWidget {
final Artist artist;
@@ -9,7 +10,13 @@ class ArtistCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
- onTap: () {},
+ onTap: () {
+ Navigator.of(context).push(MaterialPageRoute(
+ builder: (context) {
+ return ArtistProfile(artist.id!);
+ },
+ ));
+ },
borderRadius: BorderRadius.circular(10),
child: Ink(
width: 200,
@@ -35,7 +42,7 @@ class ArtistCard extends StatelessWidget {
.images?.isNotEmpty ??
false)
? artist.images!.first.url!
- : "https://avatars.dicebear.com/api/open-peeps/${artist.id}.svg?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6"),
+ : "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6"),
),
Text(
artist.name!,
diff --git a/lib/components/Artist/ArtistProfile.dart b/lib/components/Artist/ArtistProfile.dart
new file mode 100644
index 00000000..da55a726
--- /dev/null
+++ b/lib/components/Artist/ArtistProfile.dart
@@ -0,0 +1,124 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+import 'package:spotify/spotify.dart';
+import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
+import 'package:spotube/helpers/readable-number.dart';
+import 'package:spotube/provider/SpotifyDI.dart';
+
+class ArtistProfile extends StatefulWidget {
+ final String artistId;
+ const ArtistProfile(this.artistId, {Key? key}) : super(key: key);
+
+ @override
+ _ArtistProfileState createState() => _ArtistProfileState();
+}
+
+class _ArtistProfileState extends State {
+ @override
+ Widget build(BuildContext context) {
+ SpotifyApi spotify = context.watch().spotifyApi;
+ return Scaffold(
+ body: FutureBuilder(
+ future: spotify.artists.get(widget.artistId),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const Center(child: CircularProgressIndicator.adaptive());
+ }
+ return Column(
+ children: [
+ const PageWindowTitleBar(
+ leading: BackButton(),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(20),
+ child: Row(
+ children: [
+ const SizedBox(width: 50),
+ CircleAvatar(
+ maxRadius: 250,
+ minRadius: 100,
+ backgroundImage: CachedNetworkImageProvider(
+ snapshot.data!.images!.first.url!,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(20),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 10, vertical: 5),
+ decoration: BoxDecoration(
+ color: Colors.blue,
+ borderRadius: BorderRadius.circular(50)),
+ child: Text(snapshot.data!.type!.toUpperCase(),
+ style: Theme.of(context)
+ .textTheme
+ .headline6
+ ?.copyWith(color: Colors.white)),
+ ),
+ Text(
+ snapshot.data!.name!,
+ style: Theme.of(context).textTheme.headline2,
+ ),
+ Text(
+ "${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers",
+ style: Theme.of(context).textTheme.headline5,
+ ),
+ const SizedBox(height: 20),
+ Row(
+ children: [
+ // TODO: Implement check if user follows this artist
+ // LIMITATION: spotify-dart lib
+ FutureBuilder(
+ future: Future.value(true),
+ builder: (context, snapshot) {
+ return OutlinedButton(
+ onPressed: () async {
+ // TODO: make `follow/unfollow` artists button work
+ // LIMITATION: spotify-dart lib
+ },
+ child: Text(snapshot.data == true
+ ? "Following"
+ : "Follow"),
+ );
+ }),
+ IconButton(
+ icon: const Icon(Icons.share_rounded),
+ onPressed: () {
+ Clipboard.setData(
+ ClipboardData(
+ text: snapshot
+ .data?.externalUrls?.spotify),
+ ).then((val) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ width: 300,
+ behavior: SnackBarBehavior.floating,
+ content: Text(
+ "Artist URL copied to clipboard",
+ textAlign: TextAlign.center,
+ ),
+ ),
+ );
+ });
+ },
+ )
+ ],
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ )
+ ],
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/components/Player/Player.dart b/lib/components/Player/Player.dart
index 78571eb8..cbe86799 100644
--- a/lib/components/Player/Player.dart
+++ b/lib/components/Player/Player.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
@@ -48,30 +47,6 @@ class _PlayerState extends State with WidgetsBindingObserver {
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
_playOrPause,
),
- // causaes crash in Windows and macOS for aquiring global hotkey of
- // keyboard media buttons
- if (!Platform.isWindows && !Platform.isMacOS) ...[
- GlobalKeyActions(
- HotKey(KeyCode.mediaPlayPause),
- _playOrPause,
- ),
- GlobalKeyActions(HotKey(KeyCode.mediaTrackNext), (key) async {
- _movePlaylistPositionBy(1);
- }),
- GlobalKeyActions(HotKey(KeyCode.mediaTrackPrevious), (key) async {
- _movePlaylistPositionBy(-1);
- }),
- GlobalKeyActions(HotKey(KeyCode.mediaStop), (key) async {
- Playback playback = context.read();
- setState(() {
- _isPlaying = false;
- _currentTrackId = null;
- _duration = null;
- _shuffled = false;
- });
- playback.reset();
- })
- ]
];
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance?.addPostFrameCallback(_init);
diff --git a/lib/helpers/readable-number.dart b/lib/helpers/readable-number.dart
new file mode 100644
index 00000000..05a169b7
--- /dev/null
+++ b/lib/helpers/readable-number.dart
@@ -0,0 +1,13 @@
+String toReadableNumber(double num) {
+ if (num > 999 && num < 99999) {
+ return "${(num / 1000).toStringAsFixed(1)}K";
+ } else if (num > 99999 && num < 999999) {
+ return "${(num / 1000).toStringAsFixed(0)}K";
+ } else if (num > 999999 && num < 999999999) {
+ return "${(num / 1000000).toStringAsFixed(1)}M";
+ } else if (num > 999999999) {
+ return "${(num / 1000000000).toStringAsFixed(1)}B";
+ } else {
+ return num.toString();
+ }
+}