diff --git a/lib/src/authorization_scope.dart b/lib/src/authorization_scope.dart index 62f5d43c..63813251 100644 --- a/lib/src/authorization_scope.dart +++ b/lib/src/authorization_scope.dart @@ -54,7 +54,7 @@ class FollowAuthorizationScope extends _Scope { /// Read access to the list of artists and other users that the user follows. /// /// Endpoints that require the `user-follow-read` scope: - /// * [Me.isFollowing] + /// * [Me.containsFollowing] /// * [Me.following] String get read => 'user-follow-read'; @@ -145,7 +145,7 @@ class LibraryAuthorizationScope extends _Scope { /// * [Me.containsSavedEpisodes] /// * [Me.savedAlbums] /// * [Me.containsSavedAlbums] - /// * [TracksMe.contains] + /// * [TracksMe.containsTracks] /// * [TracksMe.containsOne] /// * [TracksMe.saved] String get read => 'user-library-read'; @@ -194,7 +194,7 @@ class PlaylistAuthorizationScope extends _Scope { /// Read access to user's private playlists. /// /// Endpoints that require the `playlist-read-private` scope: - /// * [Playlists.followedBy] + /// * [Playlists.followedByUsers] /// * [Playlists.me] /// * [Users.playlist] String get readPrivate => 'playlist-read-private'; diff --git a/lib/src/endpoints/endpoint_paging.dart b/lib/src/endpoints/endpoint_paging.dart index cd1f53a2..de94eb3a 100644 --- a/lib/src/endpoints/endpoint_paging.dart +++ b/lib/src/endpoints/endpoint_paging.dart @@ -110,19 +110,19 @@ abstract class NextStrategy { /// Strategy to get the next set of elements from an offset mixin OffsetStrategy implements NextStrategy { @override - Future first([int limit = defaultLimit]) => _getPage(limit, 0); + Future first([int limit = defaultLimit]) => getPage(limit); @override Future _getPage(int limit, dynamic next) => getPage(limit, next as int); /// Abstract method that is used to do the api call and json serializing - Future getPage(int limit, int offset); + Future getPage(int limit, [int offset = 0]); } /// Strategy to get the next set of elements from a cursor mixin CursorStrategy implements NextStrategy { @override - Future first([int limit = defaultLimit]) => _getPage(limit, ''); + Future first([int limit = defaultLimit]) => getPage(limit); @override Future _getPage(int limit, dynamic next) => getPage(limit, next as String); @@ -229,7 +229,7 @@ class Pages extends SinglePages> with OffsetStrategy> { } @override - Future> getPage(int limit, int offset) async { + Future> getPage(int limit, [int offset = 0]) async { var pathDelimiter = _path.contains('?') ? '&' : '?'; var newPath = '$_path${pathDelimiter}limit=$limit&offset=$offset'; @@ -293,7 +293,7 @@ class BundledPages extends _Pages with OffsetStrategy>> { : super(api, path, pageKey, pageContainerParser); @override - Future>> getPage(int limit, int offset) async { + Future>> getPage(int limit, [int offset = 0]) async { var pathDelimiter = _path.contains('?') ? '&' : '?'; var path = '$_path${pathDelimiter}limit=$limit&offset=$offset'; diff --git a/lib/src/endpoints/me.dart b/lib/src/endpoints/me.dart index 344540de..05d3a165 100644 --- a/lib/src/endpoints/me.dart +++ b/lib/src/endpoints/me.dart @@ -25,9 +25,15 @@ class Me extends _MeEndpointBase { return User.fromJson(map); } - /// Endpoint /v1/me/following only supports "artist" type at the moment. - /// needs 'user-follow-read' scope - CursorPages following(FollowingType type, [String after = '']) { + /// Endpoint `/v1/me/following` only supports [FollowingType.artist] + /// at the moment. + /// + /// Needs `user-follow-read` scope + CursorPages following(FollowingType type) { + assert( + type == FollowingType.artist, + 'Only [FollowingType.artist] supported for now. Check the spotify documentation: ' + 'https://developer.spotify.com/documentation/web-api/reference/get-followed'); // since 'artists' is the container, there is no // containerParse necessary. Adding json to make the // CursorPages-Object happy. @@ -35,14 +41,29 @@ class Me extends _MeEndpointBase { (json) => Artist.fromJson(json), 'artists', (json) => json); } - /// Check if current user follow the provided artists. The output [bool] - /// list is in the same order as the provided artist-id list - Future> isFollowing(FollowingType type, List ids) async { + /// Check to see if the current user is following one or more artists or + /// other Spotify users. The output [bool] list + /// is in the same order as the provided artist-id list + @Deprecated('Use [spotify.me.checkFollowing(type, ids)] instead') + Future> isFollowing(FollowingType type, List ids) async => + (await checkFollowing(type, ids)).values.toList(); + + /// Check if current user follow the provided [FollowingType.artist]s or + /// [FollowingType.user]s. + /// + /// Returns the list of [ids] mapped with the response whether it has been + /// followed or not + Future> checkFollowing( + FollowingType type, List ids) async { assert(ids.isNotEmpty, 'No user/artist id was provided'); - final jsonString = await _api._get( - '$_path/following/contains?type=${type._key}&ids=${ids.join(",")}'); + + final jsonString = await _api._get('$_path/following/contains?' + + _buildQuery({ + 'type': type._key, + 'ids': ids.join(','), + })); final list = List.castFrom(json.decode(jsonString)); - return list; + return Map.fromIterables(ids, list); } /// Follow provided users/artists\ @@ -207,10 +228,8 @@ class Me extends _MeEndpointBase { /// Returns the current user's saved episodes. Requires the `user-library-read` /// scope. - Pages savedEpisodes() { - return _getPages( - '$_path/episodes', (json) => EpisodeFull.fromJson(json['episode'])); - } + Pages savedEpisodes() => _getPages( + '$_path/episodes', (json) => EpisodeFull.fromJson(json['episode'])); /// Saves episodes for the current user. Requires the `user-library-modify` /// scope. diff --git a/lib/src/endpoints/playlists.dart b/lib/src/endpoints/playlists.dart index 35f55ea8..d509b3ea 100644 --- a/lib/src/endpoints/playlists.dart +++ b/lib/src/endpoints/playlists.dart @@ -292,13 +292,25 @@ class Playlists extends EndpointPaging { /// [playlistId] - the playlist ID /// [userIds] - the ids of the users /// The output List of boolean maps to the order of provided userIds list + @Deprecated('Use [followedByUsers(playListId, userIds)] instead') Future> followedBy(String playlistId, List userIds) async { + return (await followedByUsers(playlistId, userIds)).values.toList(); + } + + /// check if a playlist is followed by provided users + /// [playlistId] - the playlist ID + /// [userIds] - the ids of the users + /// + /// Returns the list of [userIds] mapped with whether they are following or + /// not + Future> followedByUsers( + String playlistId, List userIds) async { assert(userIds.isNotEmpty, 'No user id was provided for checking'); final jsonString = await _api._get( 'v1/playlists/$playlistId/followers/contains?ids=${userIds.join(",")}', ); final list = List.castFrom(json.decode(jsonString)); - return list; + return Map.fromIterables(userIds, list); } /// Returns the cover images of [playlistId] diff --git a/lib/src/endpoints/tracks.dart b/lib/src/endpoints/tracks.dart index f292dcf2..355fc6ac 100644 --- a/lib/src/endpoints/tracks.dart +++ b/lib/src/endpoints/tracks.dart @@ -58,22 +58,25 @@ class TracksMe extends EndpointPaging { } Future containsOne(String id) async { - final list = await contains([id]); - return list.first; + final list = await containsTracks([id]); + return list[id] ?? false; } + @Deprecated('Use [containsTracks(ids)] instead') Future> contains(List ids) async { + return (await containsTracks(ids)).values.toList(); + } + + Future> containsTracks(List ids) async { assert(ids.isNotEmpty, 'No track ids were provided'); final limit = ids.length < 50 ? ids.length : 50; final idsParam = ids.sublist(0, limit).join(','); final jsonString = await _api._get('$_path/contains?ids=$idsParam'); final list = List.castFrom(json.decode(jsonString)); - return list; + return Map.fromIterables(ids, list); } - Future saveOne(String id) { - return save([id]); - } + Future saveOne(String id) => save([id]); Future save(List ids) async { assert(ids.isNotEmpty, 'No track ids were provided'); diff --git a/test/data/v1/me/tracks/contains.json b/test/data/v1/me/tracks/contains.json new file mode 100644 index 00000000..4610cf9a --- /dev/null +++ b/test/data/v1/me/tracks/contains.json @@ -0,0 +1,5 @@ +[ + true, + false, + true +] \ No newline at end of file diff --git a/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/followers/contains.json b/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/followers/contains.json new file mode 100644 index 00000000..4610cf9a --- /dev/null +++ b/test/data/v1/playlists/1XIAxOGAEK2h4ravpNTmYF/followers/contains.json @@ -0,0 +1,5 @@ +[ + true, + false, + true +] \ No newline at end of file diff --git a/test/spotify_test.dart b/test/spotify_test.dart index a3619346..54e68407 100644 --- a/test/spotify_test.dart +++ b/test/spotify_test.dart @@ -186,6 +186,16 @@ Future main() async { expect(firstImage.height, 300); expect(firstImage.width, 300); }); + + test('followedByUsers', () async { + var result = await spotify.playlists + .followedByUsers('1XIAxOGAEK2h4ravpNTmYF', ['1', '2', '3']); + + expect(result.length, 3); + expect(result['1'], isTrue); + expect(result['2'], isFalse); + expect(result['3'], isTrue); + }); }); group('Shows', () { @@ -309,16 +319,16 @@ Future main() async { expect(first.after, '0aV6DOiouImYTqrR5YlIqx'); }); - test('isFollowing', () async { - final result = await spotify.me.isFollowing(FollowingType.artist, [ + test('checkFollowing', () async { + final result = await spotify.me.checkFollowing(FollowingType.artist, [ '2CIMQHirSU0MQqyYHq0eOx', '57dN52uHvrHOxijzpIgu3E', '1vCWHaC5f2uS3yhpwWbIA6' ]); expect(result.isNotEmpty, isTrue); - expect(result.first, isTrue); - expect(result[1], isFalse); - expect(result.last, isTrue); + expect(result['2CIMQHirSU0MQqyYHq0eOx'], isTrue); + expect(result['57dN52uHvrHOxijzpIgu3E'], isFalse); + expect(result['1vCWHaC5f2uS3yhpwWbIA6'], isTrue); }); group('me/top', () { @@ -454,6 +464,17 @@ Future main() async { }); }); + group('Tracks', () { + test('containsTracks', () async { + var result = await spotify.tracks.me.containsTracks(['1', '2', '3']); + + expect(result.length, 3); + expect(result['1'], isTrue); + expect(result['2'], isFalse); + expect(result['3'], isTrue); + }); + }); + group('Auth', () { test('getCredentials', () async { var result = await spotify.getCredentials();