diff --git a/Sources/justMUSIC/lib/model/User.dart b/Sources/justMUSIC/lib/model/User.dart index d88a05e..0cf474a 100644 --- a/Sources/justMUSIC/lib/model/User.dart +++ b/Sources/justMUSIC/lib/model/User.dart @@ -6,12 +6,14 @@ class User { String _pp; String _token; List _followers; + List _musics_likes; + int _capsules; List _followed; // Constructor - User(this._id, this._pseudo, this._uniquePseudo, this._mail, this._pp, - this._token, this._followers, this._capsules, this._followed); + User(this._id, this._pseudo, this._uniquePseudo, this._mail, this._pp, this._token, this._followers, + this._musics_likes, this._capsules, this._followed); //Getters and setters String get id => _id; @@ -22,6 +24,12 @@ class User { _pseudo = value; } + List get musics_likes => _musics_likes; + + set musics_likes(List value) { + _musics_likes = value; + } + String get uniquePseudo => _uniquePseudo; set uniquePseudo(String value) { diff --git a/Sources/justMUSIC/lib/model/mapper/UserMapper.dart b/Sources/justMUSIC/lib/model/mapper/UserMapper.dart index 7d0c5ab..e21ff5c 100644 --- a/Sources/justMUSIC/lib/model/mapper/UserMapper.dart +++ b/Sources/justMUSIC/lib/model/mapper/UserMapper.dart @@ -12,6 +12,7 @@ class UserMapper { data?["picture"], data?["token_notify"], List.from(data?["followers"] as List), + List.from(data?["musics_likes"] as List), data?["nbCapsules"] ?? 0, List.from(data?["followed"] as List)); } diff --git a/Sources/justMUSIC/lib/screens/detail_post_screen.dart b/Sources/justMUSIC/lib/screens/detail_post_screen.dart index fd9372d..f481f12 100644 --- a/Sources/justMUSIC/lib/screens/detail_post_screen.dart +++ b/Sources/justMUSIC/lib/screens/detail_post_screen.dart @@ -45,6 +45,10 @@ class _DetailPostScreenState extends State { }); } + bool isSaved() { + return MyApp.userViewModel.userCurrent.musics_likes.contains(widget.post.music.id); + } + @override void dispose() { MyApp.audioPlayer.release(); @@ -285,17 +289,45 @@ class _DetailPostScreenState extends State { child: Column( children: [ Padding( - padding: EdgeInsets.symmetric(vertical: 20), + padding: EdgeInsets.only(top: 30, bottom: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, children: [ SvgPicture.asset("assets/images/heart.svg", semanticsLabel: 'Like Logo'), - GestureDetector( - onTap: () { - myFocusNode.requestFocus(); - }, - child: SvgPicture.asset("assets/images/chat.svg", - semanticsLabel: 'Chat Logo'), + Column( + children: [ + GestureDetector( + onTap: () { + myFocusNode.requestFocus(); + }, + child: SvgPicture.asset("assets/images/chat.svg", + semanticsLabel: 'Chat Logo'), + ), + Container( + padding: EdgeInsets.only(top: 8), + height: 30, + child: FutureBuilder>( + future: MyApp.commentViewModel.getCommentsByPostId(widget.post.id), + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + return Text(snapshot.data!.length.toString(), + style: GoogleFonts.plusJakartaSans( + color: Colors.white, + fontWeight: FontWeight.w800, + )); + } else { + return Container( + child: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), + ) + ], ), SvgPicture.asset("assets/images/add.svg", semanticsLabel: 'Add playlist Logo'), @@ -306,20 +338,23 @@ class _DetailPostScreenState extends State { !bool ? ScaffoldMessenger.of(context).showSnackBar( SnackBar( - shape: RoundedRectangleBorder( - borderRadius: - new BorderRadius.all(new Radius.circular(300.0)), - ), - behavior: SnackBarBehavior.floating, - content: Text( - "${widget.post.music.title} ajouté à votre collection", - style: GoogleFonts.plusJakartaSans( - color: Colors.white, - fontWeight: FontWeight.w400, - fontSize: 15), + content: RichText( textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, + text: TextSpan( + style: GoogleFonts.plusJakartaSans( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 15, + ), + children: [ + TextSpan( + text: "${widget.post.music.title}", + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: " ajouté à votre collection"), + ], + ), ), backgroundColor: primaryColor, closeIconColor: Colors.white, @@ -327,28 +362,35 @@ class _DetailPostScreenState extends State { ) : ScaffoldMessenger.of(context).showSnackBar( SnackBar( - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: - new BorderRadius.all(new Radius.circular(300.0)), - ), - content: Text( - "${widget.post.music.title} retiré de votre collection", + content: RichText( textAlign: TextAlign.center, - style: GoogleFonts.plusJakartaSans( - color: Colors.white, - fontWeight: FontWeight.w400, - fontSize: 15), maxLines: 1, overflow: TextOverflow.ellipsis, + text: TextSpan( + style: GoogleFonts.plusJakartaSans( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 15, + ), + children: [ + TextSpan( + text: "${widget.post.music.title}", + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: " retiré de votre collection"), + ], + ), ), backgroundColor: Colors.red, closeIconColor: Colors.white, ), ); + setState(() {}); }, - child: SvgPicture.asset("assets/images/save.svg", - semanticsLabel: 'Save Logo')), + child: SvgPicture.asset( + "assets/images/save.svg", + semanticsLabel: 'Save Logo', + color: isSaved() ? primaryColor : Colors.white, + )), SvgPicture.asset("assets/images/report.svg", semanticsLabel: 'Report Logo'), ], ), @@ -359,31 +401,6 @@ class _DetailPostScreenState extends State { if (snapshot.hasData) { return Column( children: [ - snapshot.data!.length > 0 - ? Padding( - padding: const EdgeInsets.all(15.0), - child: RichText( - text: TextSpan( - text: snapshot.data!.length.toString(), - style: GoogleFonts.plusJakartaSans( - color: Colors.white, - fontWeight: FontWeight.w800, - ), - children: [ - TextSpan( - text: snapshot.data!.length > 1 - ? " commentaires" - : " commentaire", - style: GoogleFonts.plusJakartaSans( - color: Colors.white, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - ) - : Container(), snapshot.data!.length > 0 ? Padding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), diff --git a/Sources/justMUSIC/lib/screens/search_song_screen.dart b/Sources/justMUSIC/lib/screens/search_song_screen.dart index 5972629..05353df 100644 --- a/Sources/justMUSIC/lib/screens/search_song_screen.dart +++ b/Sources/justMUSIC/lib/screens/search_song_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:ui'; import 'package:flutter/Material.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:justmusic/model/Music.dart'; @@ -21,6 +22,7 @@ class SearchSongScreen extends StatefulWidget { class _SearchSongScreenState extends State { final ScrollController _scrollController = ScrollController(); final TextEditingController _textEditingController = TextEditingController(); + PageController controller = PageController(); int? playingIndex; @@ -81,6 +83,10 @@ class _SearchSongScreenState extends State { } } + Future> _fetchSavedSong() async { + return await MyApp.musicViewModel.getFavoriteMusicsByUserId(MyApp.userViewModel.userCurrent.id); + } + @override void dispose() { MyApp.audioPlayer.pause(); @@ -139,6 +145,7 @@ class _SearchSongScreenState extends State { filteredData = filteredData; }); } + controller.animateTo(0, duration: Duration(milliseconds: 200), curve: Curves.easeIn); }, cursorColor: Colors.white, keyboardType: TextInputType.text, @@ -166,6 +173,7 @@ class _SearchSongScreenState extends State { ), Flexible( child: PageView( + controller: controller, physics: BouncingScrollPhysics(), children: [ ScrollConfiguration( @@ -207,40 +215,49 @@ class _SearchSongScreenState extends State { ), ScrollConfiguration( behavior: ScrollBehavior().copyWith(scrollbars: true), - child: ListView.builder( - physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast), - controller: _scrollController, - itemCount: filteredData.length, - itemBuilder: (context, index) { - if (playingIndex == index) { - return InkWell( - onTap: () { - widget.callback(filteredData[index]); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: MusicListComponent( - music: filteredData[index], - playing: true, - callback: playMusic, - index: index, - ), - )); - } - return InkWell( - onTap: () { - widget.callback(filteredData[index]); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: MusicListComponent( - music: filteredData[index], - playing: false, - callback: playMusic, - index: index, - ), - )); - }), + child: FutureBuilder( + future: _fetchSavedSong(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + return ListView.builder( + physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast), + controller: _scrollController, + itemCount: snapshot.data?.length, + itemBuilder: (context, index) { + if (playingIndex == index) { + return InkWell( + onTap: () { + widget.callback(filteredData[index]); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: MusicListComponent( + music: (snapshot.data?[index])!, + playing: true, + callback: playMusic, + index: index, + ), + )); + } + return InkWell( + onTap: () { + widget.callback((snapshot.data?[index])!); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: MusicListComponent( + music: (snapshot.data?[index])!, + playing: false, + callback: playMusic, + index: index, + ), + )); + }); + } else { + return CupertinoActivityIndicator(); + } + }, + ), ) ], )) diff --git a/Sources/justMUSIC/lib/services/MusicService.dart b/Sources/justMUSIC/lib/services/MusicService.dart index 46c4ffa..1eebc12 100644 --- a/Sources/justMUSIC/lib/services/MusicService.dart +++ b/Sources/justMUSIC/lib/services/MusicService.dart @@ -17,14 +17,17 @@ class MusicService { var userRef = await FirebaseFirestore.instance.collection("users").doc(MyApp.userViewModel.userCurrent.id); var response = await userRef.get(); - List musicFavorite = List.from(response.get("saved_musics")); + List musicFavorite = List.from(response.get("musics_likes")); + if (!musicFavorite.contains(id)) { musicFavorite.add(id); - await userRef.update({"saved_musics": musicFavorite}); + await userRef.update({"musics_likes": musicFavorite}); + MyApp.userViewModel.userCurrent.musics_likes.add(id); return false; } else { musicFavorite.remove(id); - await userRef.update({"saved_musics": musicFavorite}); + await userRef.update({"musics_likes": musicFavorite}); + MyApp.userViewModel.userCurrent.musics_likes.remove(id); return true; } } diff --git a/Sources/justMUSIC/lib/view_model/MusicViewModel.dart b/Sources/justMUSIC/lib/view_model/MusicViewModel.dart index c84c691..78c0f8b 100644 --- a/Sources/justMUSIC/lib/view_model/MusicViewModel.dart +++ b/Sources/justMUSIC/lib/view_model/MusicViewModel.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:justmusic/view_model/TokenSpotify.dart'; import 'package:http/http.dart' as http; +import '../main.dart'; import '../model/Artist.dart'; import '../model/Music.dart'; import '../services/MusicService.dart'; @@ -27,8 +28,7 @@ class MusicViewModel { return _getMusicFromResponse(responseData); } else { - throw Exception( - 'Error retrieving music information : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error retrieving music information : ${response.statusCode} ${response.reasonPhrase}'); } } @@ -48,15 +48,12 @@ class MusicViewModel { artists); } - Future> getMusicsWithName(String name, - {int limit = 20, int offset = 0, String market = "FR"}) async { + Future> getMusicsWithName(String name, {int limit = 20, int offset = 0, String market = "FR"}) async { var accessToken = await _token.getAccessToken(); - var response = await http.get( - Uri.parse( - '$API_URL/search?q=track%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), - headers: { - 'Authorization': 'Bearer $accessToken', - }); + var response = await http + .get(Uri.parse('$API_URL/search?q=track%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), headers: { + 'Authorization': 'Bearer $accessToken', + }); if (response.statusCode == 200) { Map responseData = jsonDecode(response.body); @@ -64,20 +61,17 @@ class MusicViewModel { return _getMusicFromResponse(track); })); } else { - throw Exception( - 'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); } } Future> getMusicsWithArtistName(String name, {int limit = 20, int offset = 0, String market = "FR"}) async { var accessToken = await _token.getAccessToken(); - var response = await http.get( - Uri.parse( - '$API_URL/search?q=artist%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), - headers: { - 'Authorization': 'Bearer $accessToken', - }); + var response = await http + .get(Uri.parse('$API_URL/search?q=artist%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), headers: { + 'Authorization': 'Bearer $accessToken', + }); if (response.statusCode == 200) { Map responseData = jsonDecode(response.body); @@ -85,24 +79,19 @@ class MusicViewModel { return _getMusicFromResponse(track); })); } else { - throw Exception( - 'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); } } Future getArtistWithName(String name, {String market = "FR"}) async { var accessToken = await _token.getAccessToken(); - var response = await http.get( - Uri.parse( - '$API_URL/search?q=artist%3A$name&type=artist&market=$market'), - headers: { - 'Authorization': 'Bearer $accessToken', - }); + var response = await http.get(Uri.parse('$API_URL/search?q=artist%3A$name&type=artist&market=$market'), headers: { + 'Authorization': 'Bearer $accessToken', + }); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); - List artists = - List.from(responseData['artists']['items'].map((artist) { + List artists = List.from(responseData['artists']['items'].map((artist) { String image = ''; if (!artist['images'].isEmpty) { image = artist['images'][0]['url']; @@ -118,25 +107,21 @@ class MusicViewModel { throw Exception('Artist not found : ${name}'); } else { - throw Exception( - 'Error retrieving artist information : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error retrieving artist information : ${response.statusCode} ${response.reasonPhrase}'); } } - Future> getArtistsWithName(String name, - {int limit = 20, int offset = 0, String market = "FR"}) async { + Future> getArtistsWithName(String name, {int limit = 20, int offset = 0, String market = "FR"}) async { var accessToken = await _token.getAccessToken(); var response = await http.get( - Uri.parse( - '$API_URL/search?q=artist%3A$name&type=artist&market=$market&limit=$limit&offset=$offset'), + Uri.parse('$API_URL/search?q=artist%3A$name&type=artist&market=$market&limit=$limit&offset=$offset'), headers: { 'Authorization': 'Bearer $accessToken', }); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); - List artists = - List.from(responseData['artists']['items'].map((artist) { + List artists = List.from(responseData['artists']['items'].map((artist) { String image = ''; if (!artist['images'].isEmpty) { image = artist['images'][0]['url']; @@ -146,19 +131,15 @@ class MusicViewModel { return artists; } else { - throw Exception( - 'Error while retrieving artist : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving artist : ${response.statusCode} ${response.reasonPhrase}'); } } - Future> getTopMusicsWithArtistId(String id, - {String market = "FR"}) async { + Future> getTopMusicsWithArtistId(String id, {String market = "FR"}) async { var accessToken = await _token.getAccessToken(); - var response = await http.get( - Uri.parse('$API_URL/artists/$id/top-tracks?market=$market'), - headers: { - 'Authorization': 'Bearer $accessToken', - }); + var response = await http.get(Uri.parse('$API_URL/artists/$id/top-tracks?market=$market'), headers: { + 'Authorization': 'Bearer $accessToken', + }); if (response.statusCode == 200) { Map responseData = jsonDecode(response.body); @@ -166,16 +147,13 @@ class MusicViewModel { return _getMusicFromResponse(track); })); } else { - throw Exception( - 'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); } } - Future> getMusicsWithPlaylistId(String id, - {String market = "FR"}) async { + Future> getMusicsWithPlaylistId(String id, {String market = "FR"}) async { var accessToken = await _token.getAccessToken(); - var response = await http - .get(Uri.parse('$API_URL/playlists/$id?market=$market'), headers: { + var response = await http.get(Uri.parse('$API_URL/playlists/$id?market=$market'), headers: { 'Authorization': 'Bearer $accessToken', }); @@ -191,13 +169,11 @@ class MusicViewModel { return musics; } else { - throw Exception( - 'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); } } - Future> getMusicsWithIds(List ids, - {String market = "FR"}) async { + Future> getMusicsWithIds(List ids, {String market = "FR"}) async { var accessToken = await _token.getAccessToken(); String url = API_URL + '/tracks?market=$market&ids='; @@ -215,8 +191,7 @@ class MusicViewModel { return _getMusicFromResponse(track); })); } else { - throw Exception( - 'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); + throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}'); } } @@ -226,18 +201,21 @@ class MusicViewModel { List musics = []; Artist artist = await getArtistWithName(name, market: market); musics.addAll(await getTopMusicsWithArtistId(artist.id)); - musics.addAll(await getMusicsWithName(name, - limit: limit, offset: offset, market: market)); + musics.addAll(await getMusicsWithName(name, limit: limit, offset: offset, market: market)); return musics; } catch (e) { - return await getMusicsWithName(name, - limit: limit, offset: offset, market: market); + return await getMusicsWithName(name, limit: limit, offset: offset, market: market); } } Future> getFavoriteMusicsByUserId(String id) async { try { - var idMusics = await _musicService.getFavoriteMusicsByUserId(id); + List idMusics = []; + if (id == MyApp.userViewModel.userCurrent.id) { + idMusics = MyApp.userViewModel.userCurrent.musics_likes; + } else { + idMusics = await _musicService.getFavoriteMusicsByUserId(id); + } return await getMusicsWithIds(idMusics); } catch (e) { print(e);