From c27b497c4b0b5e5344114e00e71accbc46076ec0 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 30 Mar 2022 20:38:14 +0600 Subject: [PATCH] Follow/Unfollow artist/playlist support Save/Remove album support Add/Remove track from favorite support Easier way to create secrets locally Updated contribution details according newest changes --- CONTRIBUTION.md | 34 +++++++++++---- README.md | 34 +-------------- bin/create-secrets.dart | 44 ++++++++------------ lib/components/Album/AlbumCard.dart | 3 -- lib/components/Album/AlbumView.dart | 38 +++++++++++++---- lib/components/Artist/ArtistProfile.dart | 47 ++++++++++++++++----- lib/components/Home/Home.dart | 2 + lib/components/Library/UserAlbums.dart | 37 +++++++++++++++++ lib/components/Library/UserLibrary.dart | 3 +- lib/components/Player/Player.dart | 2 +- lib/components/Player/PlayerActions.dart | 32 +++++++++------ lib/components/Player/PlayerView.dart | 2 +- lib/components/Playlist/PlaylistView.dart | 50 ++++++++++++++++++----- lib/components/Shared/HeartButton.dart | 22 ++++++++++ lib/helpers/simple-album-to-album.dart | 2 +- lib/hooks/useForceUpdate.dart | 6 +++ pubspec.lock | 17 +++----- pubspec.yaml | 4 +- 18 files changed, 253 insertions(+), 126 deletions(-) create mode 100644 lib/components/Library/UserAlbums.dart create mode 100644 lib/components/Shared/HeartButton.dart create mode 100644 lib/hooks/useForceUpdate.dart diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 09f13865..c42dfdef 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -114,11 +114,31 @@ Do the following: - Install Development dependencies in linux - `libwebkit2gtk-4.0-dev`, `libkeybinder-3.0-0` & `libkeybinder-3.0-0-dev` (for Debian/Ubuntu) - `webkit2gtk` & `libkeybinder3` (for Arch/Manjaro) -- Clone the Repo +- Clone the Repo & Run `flutter pub get` in the Terminal +- Create a `secrets.json` in root of the project. The structure should be similar to the following example: + ```jsoc name="secrets.json" + { + "LYRICS_SECRET": [ + "Bo3LQEMcL2xUAJ6yCfQowV6f8K78s9J9FLa67AsyWmvhkP9LWikkgcEyFrzvs7jsR", + "HiLHxLj8uv2VhBZfq9BQ9HVrWQk5Jc8aneMZX8RV4KjTmC387K692xrbNK35c8Qe4", + ], + "SPOTIFY_SECRET": [ + { + "clientId": "9ed19daf-c7a2-4c28-91ac-2c5283ad86cf", + "clientSecret": "236d5822-820e-457e-b18c-10e258c9386b" + }, + { + "clientId": "b4769027-e048-4485-8f0b-b8a336f2cd97", + "clientSecret": "41df6ea4-eba2-4d42-b7be-6f727555fccc" + }, + ] + } + ``` + > You can add more clientId/clientSecret/genius-access-token if you want. The credentials used in the example are dummy (fake). You've to use your own secrets +- Finally run these following commands in the root of the project to start the Spotube Locally + ```bash + $ dart create-secrets.dart --local + $ flutter run -d )> + ``` -```bash -$ flutter pub get -$ flutter run -d -``` - -Do debugging/testing/build etc then submit to us with PR against the development branch (master) & we'll review your code. **DO NOT TOUCH `build` branch** as it is only for CI & releases \ No newline at end of file +Do debugging/testing/build etc then submit to us with PR against the development branch (master) & we'll review your code \ No newline at end of file diff --git a/README.md b/README.md index 6fdf2223..3978acbe 100644 --- a/README.md +++ b/README.md @@ -154,38 +154,8 @@ Download the [Mac OS Disk Image (.dmg) file](https://github.com/KRTirtho/spotube # Building from source -- Download the latest Flutter SDK (>=2.15.1) & enable desktop support -- Install Development dependencies in linux - - `libwebkit2gtk-4.0-dev`, `libkeybinder-3.0-0` & `libkeybinder-3.0-0-dev` (for Debian/Ubuntu) - - `webkit2gtk` & `libkeybinder3` (for Arch/Manjaro) -- Clone the Repo & run - ```bash - $ flutter pub get - ``` -- Create a `.env` file containing 2 secrets `LYRICS_SECRET` & `SPOTIFY_SECRET`. These secrets should be a **base64 encoded JSON string** - - Structure of `LYRICS_SECRET` json string: - ```jsonc - [ - "", - // and so on... - ] - ``` - - Structure of `SPOTIFY_SECRET` json string: - ```jsonc - [ - {"clientId": "", "clientSecret": ""}, - // and so on .... - ] - ``` - > You can base64 encode the JSON [here](https://www.base64encode.org/) -- Run following in the terminal to generate secrets for your fork - ```bash - $ dart bin/create-secrets.dart --loadEnv - ``` -Finally, to start the app run: -```bash -$ flutter run -d -``` +You can find the details [here](CONTRIBUTION.md#your-first-code-contribution) + # Things that don't work - Shows & Podcasts aren't supported as it'd require premium anyway diff --git a/bin/create-secrets.dart b/bin/create-secrets.dart index f464c2ad..feb0d7fe 100644 --- a/bin/create-secrets.dart +++ b/bin/create-secrets.dart @@ -2,40 +2,32 @@ import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; -import 'package:dotenv/dotenv.dart'; void main(List args) async { - String lyricSecret; - String spotifySecret; + List val; + List val2; + final cwd = Directory.current.path; + final binSafe = cwd.endsWith("/bin") ? ".." : ""; if (args.isEmpty) { - throw ArgumentError("Expected 2 arguments but passed none"); + throw ArgumentError("Expected 1-2 arguments but passed none"); } - if (args.contains("--loadEnv")) { - load(); - if (env["SPOTIFY_SECRET"] == null || env["LYRICS_SECRET"] == null) { - throw Exception( - "'LYRICS_SECRET' or 'SPOTIFY_SECRET' environmental variable aren't set correctly "); - } - lyricSecret = env["LYRICS_SECRET"]!; - spotifySecret = env["SPOTIFY_SECRET"]!; + if (args.contains("--local")) { + final secretFilePath = path.join(cwd, binSafe, "secrets.json"); + final file = File(secretFilePath); + if (!file.existsSync()) throw Exception("secrets.json file not found"); + final data = jsonDecode(await file.readAsString()); + val = List.castFrom(data["LYRICS_SECRET"]); + val2 = List.castFrom(data["SPOTIFY_SECRET"]); } else { - lyricSecret = args.first; - spotifySecret = args.last; + final decodedLyricSecret = utf8.decode(base64Decode(args.first)); + final decodedSpotifySecrete = utf8.decode(base64Decode(args.last)); + val = List.castFrom(jsonDecode(decodedLyricSecret)); + val2 = List.castFrom(jsonDecode(decodedSpotifySecrete)); } - final decodedLyricSecret = utf8.decode(base64Decode(lyricSecret)); - final decodedSpotifySecrete = utf8.decode(base64Decode(spotifySecret)); - final val = jsonDecode(decodedLyricSecret); - final val2 = jsonDecode(decodedSpotifySecrete); - if (val is! List || (val2 is! List && (val2 as List).first is! Map)) { - throw Exception( - "'LYRICS_SECRET' and 'SPOTIFY_SECRET' Environmental Variable isn't configured properly"); - } - - await File(path.join( - Directory.current.path, "lib/models/generated_secrets.dart")) + await File(path.join(cwd, binSafe, "lib/models/generated_secrets.dart")) .writeAsString( - "final List lyricsSecrets = $decodedLyricSecret;\nfinal List> spotifySecrets = $decodedSpotifySecrete;", + "final List lyricsSecrets = ${jsonEncode(val)};\nfinal List> spotifySecrets = ${jsonEncode(val2)};", ); } diff --git a/lib/components/Album/AlbumCard.dart b/lib/components/Album/AlbumCard.dart index 81163b1b..287c3738 100644 --- a/lib/components/Album/AlbumCard.dart +++ b/lib/components/Album/AlbumCard.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Shared/PlaybuttonCard.dart'; -import 'package:spotube/components/Shared/SpotubePageRoute.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/simple-track-to-track.dart'; diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart index 2054602d..920437d2 100644 --- a/lib/components/Album/AlbumView.dart +++ b/lib/components/Album/AlbumView.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Shared/HeartButton.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/simple-track-to-track.dart'; +import 'package:spotube/hooks/useForceUpdate.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; -class AlbumView extends ConsumerWidget { +class AlbumView extends HookConsumerWidget { final AlbumSimple album; const AlbumView(this.album, {Key? key}) : super(key: key); @@ -36,8 +39,12 @@ class AlbumView extends ConsumerWidget { Widget build(BuildContext context, ref) { Playback playback = ref.watch(playbackProvider); - var isPlaylistPlaying = playback.currentPlaylist?.id == album.id; - SpotifyApi spotify = ref.watch(spotifyProvider); + final isPlaylistPlaying = playback.currentPlaylist?.id == album.id; + final SpotifyApi spotify = ref.watch(spotifyProvider); + final Auth auth = ref.watch(authProvider); + + final update = useForceUpdate(); + return SafeArea( child: Scaffold( body: FutureBuilder>( @@ -55,10 +62,25 @@ class AlbumView extends ConsumerWidget { // nav back const BackButton(), // heart playlist - IconButton( - icon: const Icon(Icons.favorite_outline_rounded), - onPressed: () {}, - ), + if (auth.isLoggedIn) + FutureBuilder>( + future: spotify.me.isSavedAlbums([album.id!]), + builder: (context, snapshot) { + final isSaved = snapshot.data?.first == true; + return HeartButton( + isLiked: isSaved, + onPressed: () { + (isSaved + ? spotify.me.removeAlbums( + [album.id!], + ) + : spotify.me.saveAlbums( + [album.id!], + )) + .then((_) => update()); + }, + ); + }), // play playlist IconButton( icon: Icon( diff --git a/lib/components/Artist/ArtistProfile.dart b/lib/components/Artist/ArtistProfile.dart index f5f47f65..61681c14 100644 --- a/lib/components/Artist/ArtistProfile.dart +++ b/lib/components/Artist/ArtistProfile.dart @@ -14,12 +14,15 @@ import 'package:spotube/helpers/readable-number.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; +import 'package:spotube/hooks/useForceUpdate.dart'; +import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; class ArtistProfile extends HookConsumerWidget { final String artistId; - const ArtistProfile(this.artistId, {Key? key}) : super(key: key); + final logger = createLogger(ArtistProfile); + ArtistProfile(this.artistId, {Key? key}) : super(key: key); @override Widget build(BuildContext context, ref) { @@ -44,6 +47,7 @@ class ArtistProfile extends HookConsumerWidget { ); final breakpoint = useBreakpoints(); + final update = useForceUpdate(); return SafeArea( child: Scaffold( @@ -106,19 +110,42 @@ class ArtistProfile extends HookConsumerWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - // TODO: Implement check if user follows this artist - // LIMITATION: spotify-dart lib - FutureBuilder( - future: Future.value(true), + FutureBuilder>( + future: spotify.me.isFollowing( + FollowingType.artist, + [artistId], + ), builder: (context, snapshot) { + final isFollowing = + snapshot.data?.first == true; return OutlinedButton( onPressed: () async { - // TODO: make `follow/unfollow` artists button work - // LIMITATION: spotify-dart lib + try { + isFollowing + ? await spotify.me.unfollow( + FollowingType.artist, + [artistId], + ) + : await spotify.me.follow( + FollowingType.artist, + [artistId], + ); + } catch (e, stack) { + logger.e( + "FollowButton.onPressed", + e, + stack, + ); + } finally { + update(); + } }, - child: Text(snapshot.data == true - ? "Following" - : "Follow"), + child: snapshot.hasData + ? Text(isFollowing + ? "Following" + : "Follow") + : const CircularProgressIndicator + .adaptive(), ); }), IconButton( diff --git a/lib/components/Home/Home.dart b/lib/components/Home/Home.dart index 97101a6e..d3bc8c80 100644 --- a/lib/components/Home/Home.dart +++ b/lib/components/Home/Home.dart @@ -29,6 +29,8 @@ import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; List spotifyScopes = [ + "playlist-modify-public", + "playlist-modify-private", "user-library-read", "user-library-modify", "user-read-private", diff --git a/lib/components/Library/UserAlbums.dart b/lib/components/Library/UserAlbums.dart new file mode 100644 index 00000000..c945293d --- /dev/null +++ b/lib/components/Library/UserAlbums.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart' hide Image; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Album/AlbumCard.dart'; +import 'package:spotube/helpers/simple-album-to-album.dart'; +import 'package:spotube/provider/SpotifyDI.dart'; + +class UserAlbums extends ConsumerWidget { + const UserAlbums({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + SpotifyApi spotifyApi = ref.watch(spotifyProvider); + + return FutureBuilder>( + future: spotifyApi.me.savedAlbums().all(), + builder: (context, snapshot) { + if (!snapshot.hasData && snapshot.data == null) { + return const Center(child: CircularProgressIndicator.adaptive()); + } + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 20, // gap between adjacent chips + runSpacing: 20, // gap between lines + alignment: WrapAlignment.center, + children: snapshot.data! + .map((album) => AlbumCard(simpleAlbumToAlbum(album))) + .toList(), + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/Library/UserLibrary.dart b/lib/components/Library/UserLibrary.dart index ae13f1b6..28083adc 100644 --- a/lib/components/Library/UserLibrary.dart +++ b/lib/components/Library/UserLibrary.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' hide Image; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotube/components/Library/UserAlbums.dart'; import 'package:spotube/components/Library/UserArtists.dart'; import 'package:spotube/components/Library/UserPlaylists.dart'; import 'package:spotube/components/Shared/AnonymousFallback.dart'; @@ -29,7 +30,7 @@ class UserLibrary extends ConsumerWidget { ? const TabBarView(children: [ UserPlaylists(), UserArtists(), - Icon(Icons.ac_unit_outlined), + UserAlbums(), ]) : const AnonymousFallback(), ), diff --git a/lib/components/Player/Player.dart b/lib/components/Player/Player.dart index 074d8cc3..53f4bee7 100644 --- a/lib/components/Player/Player.dart +++ b/lib/components/Player/Player.dart @@ -162,7 +162,7 @@ class Player extends HookConsumerWidget { }, ), ), - const PlayerActions() + PlayerActions() ], ), ) diff --git a/lib/components/Player/PlayerActions.dart b/lib/components/Player/PlayerActions.dart index 6cc985cb..28d64319 100644 --- a/lib/components/Player/PlayerActions.dart +++ b/lib/components/Player/PlayerActions.dart @@ -2,22 +2,27 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Shared/DownloadTrackButton.dart'; +import 'package:spotube/components/Shared/HeartButton.dart'; +import 'package:spotube/hooks/useForceUpdate.dart'; +import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; class PlayerActions extends HookConsumerWidget { final MainAxisAlignment mainAxisAlignment; - const PlayerActions({ + PlayerActions({ this.mainAxisAlignment = MainAxisAlignment.center, Key? key, }) : super(key: key); + final logger = createLogger(PlayerActions); @override Widget build(BuildContext context, ref) { final SpotifyApi spotifyApi = ref.watch(spotifyProvider); final Playback playback = ref.watch(playbackProvider); final Auth auth = ref.watch(authProvider); + final update = useForceUpdate(); return Row( mainAxisAlignment: mainAxisAlignment, children: [ @@ -32,17 +37,20 @@ class PlayerActions extends HookConsumerWidget { initialData: false, builder: (context, snapshot) { bool isLiked = snapshot.data ?? false; - return IconButton( - icon: Icon( - !isLiked - ? Icons.favorite_outline_rounded - : Icons.favorite_rounded, - color: isLiked ? Colors.green : null, - ), - onPressed: () { - if (!isLiked && playback.currentTrack?.id != null) { - spotifyApi.tracks.me - .saveOne(playback.currentTrack!.id!); + return HeartButton( + isLiked: isLiked, + onPressed: () async { + try { + if (playback.currentTrack?.id == null) return; + isLiked + ? await spotifyApi.tracks.me + .removeOne(playback.currentTrack!.id!) + : await spotifyApi.tracks.me + .saveOne(playback.currentTrack!.id!); + } catch (e, stack) { + logger.e("FavoriteButton.onPressed", e, stack); + } finally { + update(); } }); }), diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index 64285112..b910f329 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -95,7 +95,7 @@ class PlayerView extends HookConsumerWidget { ); }), const Spacer(), - const PlayerActions( + PlayerActions( mainAxisAlignment: MainAxisAlignment.spaceBetween, ), PlayerControls(iconColor: paletteColor.bodyTextColor), diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index a6908d60..bb08e3c1 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -1,16 +1,20 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/components/Shared/HeartButton.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; +import 'package:spotube/hooks/useForceUpdate.dart'; +import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/provider/SpotifyDI.dart'; -class PlaylistView extends ConsumerWidget { +class PlaylistView extends HookConsumerWidget { + final logger = createLogger(PlaylistView); final PlaylistSimple playlist; - const PlaylistView(this.playlist, {Key? key}) : super(key: key); + PlaylistView(this.playlist, {Key? key}) : super(key: key); playPlaylist(Playback playback, List tracks, {Track? currentTrack}) async { @@ -37,15 +41,17 @@ class PlaylistView extends ConsumerWidget { Widget build(BuildContext context, ref) { Playback playback = ref.watch(playbackProvider); final Auth auth = ref.watch(authProvider); - SpotifyApi spotifyApi = ref.watch(spotifyProvider); - var isPlaylistPlaying = playback.currentPlaylist?.id != null && + SpotifyApi spotify = ref.watch(spotifyProvider); + final isPlaylistPlaying = playback.currentPlaylist?.id != null && playback.currentPlaylist?.id == playlist.id; + final update = useForceUpdate(); + return SafeArea( child: Scaffold( body: FutureBuilder>( future: playlist.id != "user-liked-tracks" - ? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all() - : spotifyApi.tracks.me.saved + ? spotify.playlists.getTracksByPlaylistId(playlist.id).all() + : spotify.tracks.me.saved .all() .then((tracks) => tracks.map((e) => e.track!)), builder: (context, snapshot) { @@ -59,10 +65,32 @@ class PlaylistView extends ConsumerWidget { const BackButton(), // heart playlist if (auth.isLoggedIn) - IconButton( - icon: const Icon(Icons.favorite_outline_rounded), - onPressed: () {}, - ), + FutureBuilder>( + future: spotify.me.get().then( + (me) => spotify.playlists + .followedBy(playlist.id!, [me.id!]), + ), + builder: (context, snapshot) { + final isFollowing = + snapshot.data?.first ?? false; + return HeartButton( + isLiked: isFollowing, + onPressed: () async { + try { + isFollowing + ? spotify.playlists + .unfollowPlaylist(playlist.id!) + : spotify.playlists + .followPlaylist(playlist.id!); + } catch (e, stack) { + logger.e( + "FollowButton.onPressed", e, stack); + } finally { + update(); + } + }, + ); + }), // play playlist IconButton( icon: Icon( diff --git a/lib/components/Shared/HeartButton.dart b/lib/components/Shared/HeartButton.dart new file mode 100644 index 00000000..d9da43b1 --- /dev/null +++ b/lib/components/Shared/HeartButton.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class HeartButton extends StatelessWidget { + final bool isLiked; + final void Function() onPressed; + const HeartButton({ + required this.isLiked, + required this.onPressed, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return IconButton( + icon: Icon( + !isLiked ? Icons.favorite_outline_rounded : Icons.favorite_rounded, + color: isLiked ? Colors.green : null, + ), + onPressed: onPressed, + ); + } +} diff --git a/lib/helpers/simple-album-to-album.dart b/lib/helpers/simple-album-to-album.dart index fb05a84e..19e8f8a6 100644 --- a/lib/helpers/simple-album-to-album.dart +++ b/lib/helpers/simple-album-to-album.dart @@ -1,6 +1,6 @@ import 'package:spotify/spotify.dart'; -simpleAlbumToAlbum(AlbumSimple albumSimple) { +Album simpleAlbumToAlbum(AlbumSimple albumSimple) { Album album = Album(); album.albumType = albumSimple.albumType; album.artists = albumSimple.artists; diff --git a/lib/hooks/useForceUpdate.dart b/lib/hooks/useForceUpdate.dart new file mode 100644 index 00000000..74151a65 --- /dev/null +++ b/lib/hooks/useForceUpdate.dart @@ -0,0 +1,6 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; + +void Function() useForceUpdate() { + final state = useState(null); + return () => state.notifyListeners(); +} diff --git a/pubspec.lock b/pubspec.lock index 1c7467e7..0ed0e552 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,13 +169,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" - dotenv: - dependency: "direct dev" - description: - name: dotenv - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" fake_async: dependency: transitive description: @@ -668,10 +661,12 @@ packages: spotify: dependency: "direct main" description: - name: spotify - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.0" + path: "." + ref: HEAD + resolved-ref: e36eb5884de44d39b310d0878779a697048061bd + url: "https://github.com/KRTirtho/spotify-dart.git" + source: git + version: "0.7.0" sqflite: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b16ebdf1..f0472360 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,8 @@ dependencies: html: ^0.15.0 http: ^0.13.4 shared_preferences: ^2.0.11 - spotify: ^0.6.0 + spotify: + git: https://github.com/KRTirtho/spotify-dart.git url_launcher: ^6.0.17 youtube_explode_dart: ^1.10.8 infinite_scroll_pagination: ^3.1.0 @@ -70,7 +71,6 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^1.0.0 flutter_launcher_icons: ^0.9.2 - dotenv: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec