saved song
continuous-integration/drone/push Build is passing Details

LIKES_POST_LDE-EKA
Lucas Delanier 2 years ago
parent 4036f02391
commit 5d254431db

@ -6,12 +6,14 @@ class User {
String _pp; String _pp;
String _token; String _token;
List<String> _followers; List<String> _followers;
List<String> _musics_likes;
int _capsules; int _capsules;
List<String> _followed; List<String> _followed;
// Constructor // Constructor
User(this._id, this._pseudo, this._uniquePseudo, this._mail, this._pp, User(this._id, this._pseudo, this._uniquePseudo, this._mail, this._pp, this._token, this._followers,
this._token, this._followers, this._capsules, this._followed); this._musics_likes, this._capsules, this._followed);
//Getters and setters //Getters and setters
String get id => _id; String get id => _id;
@ -22,6 +24,12 @@ class User {
_pseudo = value; _pseudo = value;
} }
List<String> get musics_likes => _musics_likes;
set musics_likes(List<String> value) {
_musics_likes = value;
}
String get uniquePseudo => _uniquePseudo; String get uniquePseudo => _uniquePseudo;
set uniquePseudo(String value) { set uniquePseudo(String value) {

@ -12,6 +12,7 @@ class UserMapper {
data?["picture"], data?["picture"],
data?["token_notify"], data?["token_notify"],
List<String>.from(data?["followers"] as List), List<String>.from(data?["followers"] as List),
List<String>.from(data?["musics_likes"] as List),
data?["nbCapsules"] ?? 0, data?["nbCapsules"] ?? 0,
List<String>.from(data?["followed"] as List)); List<String>.from(data?["followed"] as List));
} }

@ -45,6 +45,10 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
}); });
} }
bool isSaved() {
return MyApp.userViewModel.userCurrent.musics_likes.contains(widget.post.music.id);
}
@override @override
void dispose() { void dispose() {
MyApp.audioPlayer.release(); MyApp.audioPlayer.release();
@ -285,17 +289,45 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 20), padding: EdgeInsets.only(top: 30, bottom: 20),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SvgPicture.asset("assets/images/heart.svg", semanticsLabel: 'Like Logo'), SvgPicture.asset("assets/images/heart.svg", semanticsLabel: 'Like Logo'),
GestureDetector( Column(
onTap: () { children: [
myFocusNode.requestFocus(); GestureDetector(
}, onTap: () {
child: SvgPicture.asset("assets/images/chat.svg", myFocusNode.requestFocus();
semanticsLabel: 'Chat Logo'), },
child: SvgPicture.asset("assets/images/chat.svg",
semanticsLabel: 'Chat Logo'),
),
Container(
padding: EdgeInsets.only(top: 8),
height: 30,
child: FutureBuilder<List<Comment>>(
future: MyApp.commentViewModel.getCommentsByPostId(widget.post.id),
builder:
(BuildContext context, AsyncSnapshot<List<Comment>> 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", SvgPicture.asset("assets/images/add.svg",
semanticsLabel: 'Add playlist Logo'), semanticsLabel: 'Add playlist Logo'),
@ -306,20 +338,23 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
!bool !bool
? ScaffoldMessenger.of(context).showSnackBar( ? ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
shape: RoundedRectangleBorder( content: RichText(
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),
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
text: TextSpan(
style: GoogleFonts.plusJakartaSans(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 15,
),
children: <TextSpan>[
TextSpan(
text: "${widget.post.music.title}",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: " ajouté à votre collection"),
],
),
), ),
backgroundColor: primaryColor, backgroundColor: primaryColor,
closeIconColor: Colors.white, closeIconColor: Colors.white,
@ -327,28 +362,35 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
) )
: ScaffoldMessenger.of(context).showSnackBar( : ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
behavior: SnackBarBehavior.floating, content: RichText(
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.all(new Radius.circular(300.0)),
),
content: Text(
"${widget.post.music.title} retiré de votre collection",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.plusJakartaSans(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 15),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
text: TextSpan(
style: GoogleFonts.plusJakartaSans(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 15,
),
children: <TextSpan>[
TextSpan(
text: "${widget.post.music.title}",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: " retiré de votre collection"),
],
),
), ),
backgroundColor: Colors.red, backgroundColor: Colors.red,
closeIconColor: Colors.white, closeIconColor: Colors.white,
), ),
); );
setState(() {});
}, },
child: SvgPicture.asset("assets/images/save.svg", child: SvgPicture.asset(
semanticsLabel: 'Save Logo')), "assets/images/save.svg",
semanticsLabel: 'Save Logo',
color: isSaved() ? primaryColor : Colors.white,
)),
SvgPicture.asset("assets/images/report.svg", semanticsLabel: 'Report Logo'), SvgPicture.asset("assets/images/report.svg", semanticsLabel: 'Report Logo'),
], ],
), ),
@ -359,31 +401,6 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
if (snapshot.hasData) { if (snapshot.hasData) {
return Column( return Column(
children: [ 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 snapshot.data!.length > 0
? Padding( ? Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/Material.dart'; import 'package:flutter/Material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:justmusic/model/Music.dart'; import 'package:justmusic/model/Music.dart';
@ -21,6 +22,7 @@ class SearchSongScreen extends StatefulWidget {
class _SearchSongScreenState extends State<SearchSongScreen> { class _SearchSongScreenState extends State<SearchSongScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final TextEditingController _textEditingController = TextEditingController(); final TextEditingController _textEditingController = TextEditingController();
PageController controller = PageController();
int? playingIndex; int? playingIndex;
@ -81,6 +83,10 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
} }
} }
Future<List<Music>> _fetchSavedSong() async {
return await MyApp.musicViewModel.getFavoriteMusicsByUserId(MyApp.userViewModel.userCurrent.id);
}
@override @override
void dispose() { void dispose() {
MyApp.audioPlayer.pause(); MyApp.audioPlayer.pause();
@ -139,6 +145,7 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
filteredData = filteredData; filteredData = filteredData;
}); });
} }
controller.animateTo(0, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
}, },
cursorColor: Colors.white, cursorColor: Colors.white,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
@ -166,6 +173,7 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
), ),
Flexible( Flexible(
child: PageView( child: PageView(
controller: controller,
physics: BouncingScrollPhysics(), physics: BouncingScrollPhysics(),
children: [ children: [
ScrollConfiguration( ScrollConfiguration(
@ -207,40 +215,49 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
), ),
ScrollConfiguration( ScrollConfiguration(
behavior: ScrollBehavior().copyWith(scrollbars: true), behavior: ScrollBehavior().copyWith(scrollbars: true),
child: ListView.builder( child: FutureBuilder(
physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast), future: _fetchSavedSong(),
controller: _scrollController, builder: (BuildContext context, AsyncSnapshot<List<Music>> snapshot) {
itemCount: filteredData.length, if (snapshot.hasData) {
itemBuilder: (context, index) { return ListView.builder(
if (playingIndex == index) { physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
return InkWell( controller: _scrollController,
onTap: () { itemCount: snapshot.data?.length,
widget.callback(filteredData[index]); itemBuilder: (context, index) {
}, if (playingIndex == index) {
child: Padding( return InkWell(
padding: const EdgeInsets.symmetric(horizontal: 20), onTap: () {
child: MusicListComponent( widget.callback(filteredData[index]);
music: filteredData[index], },
playing: true, child: Padding(
callback: playMusic, padding: const EdgeInsets.symmetric(horizontal: 20),
index: index, child: MusicListComponent(
), music: (snapshot.data?[index])!,
)); playing: true,
} callback: playMusic,
return InkWell( index: index,
onTap: () { ),
widget.callback(filteredData[index]); ));
}, }
child: Padding( return InkWell(
padding: const EdgeInsets.symmetric(horizontal: 20), onTap: () {
child: MusicListComponent( widget.callback((snapshot.data?[index])!);
music: filteredData[index], },
playing: false, child: Padding(
callback: playMusic, padding: const EdgeInsets.symmetric(horizontal: 20),
index: index, child: MusicListComponent(
), music: (snapshot.data?[index])!,
)); playing: false,
}), callback: playMusic,
index: index,
),
));
});
} else {
return CupertinoActivityIndicator();
}
},
),
) )
], ],
)) ))

@ -17,14 +17,17 @@ class MusicService {
var userRef = await FirebaseFirestore.instance.collection("users").doc(MyApp.userViewModel.userCurrent.id); var userRef = await FirebaseFirestore.instance.collection("users").doc(MyApp.userViewModel.userCurrent.id);
var response = await userRef.get(); var response = await userRef.get();
List<dynamic> musicFavorite = List.from(response.get("saved_musics")); List<String> musicFavorite = List.from(response.get("musics_likes"));
if (!musicFavorite.contains(id)) { if (!musicFavorite.contains(id)) {
musicFavorite.add(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; return false;
} else { } else {
musicFavorite.remove(id); musicFavorite.remove(id);
await userRef.update({"saved_musics": musicFavorite}); await userRef.update({"musics_likes": musicFavorite});
MyApp.userViewModel.userCurrent.musics_likes.remove(id);
return true; return true;
} }
} }

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:justmusic/view_model/TokenSpotify.dart'; import 'package:justmusic/view_model/TokenSpotify.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../main.dart';
import '../model/Artist.dart'; import '../model/Artist.dart';
import '../model/Music.dart'; import '../model/Music.dart';
import '../services/MusicService.dart'; import '../services/MusicService.dart';
@ -27,8 +28,7 @@ class MusicViewModel {
return _getMusicFromResponse(responseData); return _getMusicFromResponse(responseData);
} else { } else {
throw Exception( throw Exception('Error retrieving music information : ${response.statusCode} ${response.reasonPhrase}');
'Error retrieving music information : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
@ -48,15 +48,12 @@ class MusicViewModel {
artists); artists);
} }
Future<List<Music>> getMusicsWithName(String name, Future<List<Music>> getMusicsWithName(String name, {int limit = 20, int offset = 0, String market = "FR"}) async {
{int limit = 20, int offset = 0, String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http.get( var response = await http
Uri.parse( .get(Uri.parse('$API_URL/search?q=track%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), headers: {
'$API_URL/search?q=track%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), 'Authorization': 'Bearer $accessToken',
headers: { });
'Authorization': 'Bearer $accessToken',
});
if (response.statusCode == 200) { if (response.statusCode == 200) {
Map<String, dynamic> responseData = jsonDecode(response.body); Map<String, dynamic> responseData = jsonDecode(response.body);
@ -64,20 +61,17 @@ class MusicViewModel {
return _getMusicFromResponse(track); return _getMusicFromResponse(track);
})); }));
} else { } else {
throw Exception( throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<List<Music>> getMusicsWithArtistName(String name, Future<List<Music>> getMusicsWithArtistName(String name,
{int limit = 20, int offset = 0, String market = "FR"}) async { {int limit = 20, int offset = 0, String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http.get( var response = await http
Uri.parse( .get(Uri.parse('$API_URL/search?q=artist%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), headers: {
'$API_URL/search?q=artist%3A$name&type=track&market=fr&limit=$limit&offset=$offset'), 'Authorization': 'Bearer $accessToken',
headers: { });
'Authorization': 'Bearer $accessToken',
});
if (response.statusCode == 200) { if (response.statusCode == 200) {
Map<String, dynamic> responseData = jsonDecode(response.body); Map<String, dynamic> responseData = jsonDecode(response.body);
@ -85,24 +79,19 @@ class MusicViewModel {
return _getMusicFromResponse(track); return _getMusicFromResponse(track);
})); }));
} else { } else {
throw Exception( throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<Artist> getArtistWithName(String name, {String market = "FR"}) async { Future<Artist> getArtistWithName(String name, {String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http.get( var response = await http.get(Uri.parse('$API_URL/search?q=artist%3A$name&type=artist&market=$market'), headers: {
Uri.parse( 'Authorization': 'Bearer $accessToken',
'$API_URL/search?q=artist%3A$name&type=artist&market=$market'), });
headers: {
'Authorization': 'Bearer $accessToken',
});
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseData = jsonDecode(response.body); final responseData = jsonDecode(response.body);
List<Artist> artists = List<Artist> artists = List<Artist>.from(responseData['artists']['items'].map((artist) {
List<Artist>.from(responseData['artists']['items'].map((artist) {
String image = ''; String image = '';
if (!artist['images'].isEmpty) { if (!artist['images'].isEmpty) {
image = artist['images'][0]['url']; image = artist['images'][0]['url'];
@ -118,25 +107,21 @@ class MusicViewModel {
throw Exception('Artist not found : ${name}'); throw Exception('Artist not found : ${name}');
} else { } else {
throw Exception( throw Exception('Error retrieving artist information : ${response.statusCode} ${response.reasonPhrase}');
'Error retrieving artist information : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<List<Artist>> getArtistsWithName(String name, Future<List<Artist>> getArtistsWithName(String name, {int limit = 20, int offset = 0, String market = "FR"}) async {
{int limit = 20, int offset = 0, String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http.get( var response = await http.get(
Uri.parse( Uri.parse('$API_URL/search?q=artist%3A$name&type=artist&market=$market&limit=$limit&offset=$offset'),
'$API_URL/search?q=artist%3A$name&type=artist&market=$market&limit=$limit&offset=$offset'),
headers: { headers: {
'Authorization': 'Bearer $accessToken', 'Authorization': 'Bearer $accessToken',
}); });
if (response.statusCode == 200) { if (response.statusCode == 200) {
final responseData = jsonDecode(response.body); final responseData = jsonDecode(response.body);
List<Artist> artists = List<Artist> artists = List<Artist>.from(responseData['artists']['items'].map((artist) {
List<Artist>.from(responseData['artists']['items'].map((artist) {
String image = ''; String image = '';
if (!artist['images'].isEmpty) { if (!artist['images'].isEmpty) {
image = artist['images'][0]['url']; image = artist['images'][0]['url'];
@ -146,19 +131,15 @@ class MusicViewModel {
return artists; return artists;
} else { } else {
throw Exception( throw Exception('Error while retrieving artist : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving artist : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<List<Music>> getTopMusicsWithArtistId(String id, Future<List<Music>> getTopMusicsWithArtistId(String id, {String market = "FR"}) async {
{String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http.get( var response = await http.get(Uri.parse('$API_URL/artists/$id/top-tracks?market=$market'), headers: {
Uri.parse('$API_URL/artists/$id/top-tracks?market=$market'), 'Authorization': 'Bearer $accessToken',
headers: { });
'Authorization': 'Bearer $accessToken',
});
if (response.statusCode == 200) { if (response.statusCode == 200) {
Map<String, dynamic> responseData = jsonDecode(response.body); Map<String, dynamic> responseData = jsonDecode(response.body);
@ -166,16 +147,13 @@ class MusicViewModel {
return _getMusicFromResponse(track); return _getMusicFromResponse(track);
})); }));
} else { } else {
throw Exception( throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<List<Music>> getMusicsWithPlaylistId(String id, Future<List<Music>> getMusicsWithPlaylistId(String id, {String market = "FR"}) async {
{String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
var response = await http var response = await http.get(Uri.parse('$API_URL/playlists/$id?market=$market'), headers: {
.get(Uri.parse('$API_URL/playlists/$id?market=$market'), headers: {
'Authorization': 'Bearer $accessToken', 'Authorization': 'Bearer $accessToken',
}); });
@ -191,13 +169,11 @@ class MusicViewModel {
return musics; return musics;
} else { } else {
throw Exception( throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
Future<List<Music>> getMusicsWithIds(List<String> ids, Future<List<Music>> getMusicsWithIds(List<String> ids, {String market = "FR"}) async {
{String market = "FR"}) async {
var accessToken = await _token.getAccessToken(); var accessToken = await _token.getAccessToken();
String url = API_URL + '/tracks?market=$market&ids='; String url = API_URL + '/tracks?market=$market&ids=';
@ -215,8 +191,7 @@ class MusicViewModel {
return _getMusicFromResponse(track); return _getMusicFromResponse(track);
})); }));
} else { } else {
throw Exception( throw Exception('Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
'Error while retrieving music : ${response.statusCode} ${response.reasonPhrase}');
} }
} }
@ -226,18 +201,21 @@ class MusicViewModel {
List<Music> musics = []; List<Music> musics = [];
Artist artist = await getArtistWithName(name, market: market); Artist artist = await getArtistWithName(name, market: market);
musics.addAll(await getTopMusicsWithArtistId(artist.id)); musics.addAll(await getTopMusicsWithArtistId(artist.id));
musics.addAll(await getMusicsWithName(name, musics.addAll(await getMusicsWithName(name, limit: limit, offset: offset, market: market));
limit: limit, offset: offset, market: market));
return musics; return musics;
} catch (e) { } catch (e) {
return await getMusicsWithName(name, return await getMusicsWithName(name, limit: limit, offset: offset, market: market);
limit: limit, offset: offset, market: market);
} }
} }
Future<List<Music>> getFavoriteMusicsByUserId(String id) async { Future<List<Music>> getFavoriteMusicsByUserId(String id) async {
try { try {
var idMusics = await _musicService.getFavoriteMusicsByUserId(id); List<String> idMusics = [];
if (id == MyApp.userViewModel.userCurrent.id) {
idMusics = MyApp.userViewModel.userCurrent.musics_likes;
} else {
idMusics = await _musicService.getFavoriteMusicsByUserId(id);
}
return await getMusicsWithIds(idMusics); return await getMusicsWithIds(idMusics);
} catch (e) { } catch (e) {
print(e); print(e);

Loading…
Cancel
Save