diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 7f7c6f9..70bceed 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -114,6 +114,27 @@ + + + + + + + + + + + + + + + + + + @@ -352,6 +373,20 @@ + + + + + + + + + + + + @@ -506,6 +541,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -667,6 +744,13 @@ + + + + + + @@ -758,6 +842,20 @@ + + + + + + + + + + + + @@ -786,6 +884,20 @@ + + + + + + + + + + + + @@ -945,6 +1057,9 @@ + + + @@ -978,6 +1093,8 @@ + + @@ -998,6 +1115,12 @@ + + + + + + @@ -1021,6 +1144,7 @@ + @@ -1034,9 +1158,13 @@ + + + + diff --git a/Sources/justMUSIC/lib/screens/detail_post_screen.dart b/Sources/justMUSIC/lib/screens/detail_post_screen.dart index 47b6f8a..397983e 100644 --- a/Sources/justMUSIC/lib/screens/detail_post_screen.dart +++ b/Sources/justMUSIC/lib/screens/detail_post_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:pinch_zoom/pinch_zoom.dart'; import 'package:text_scroll/text_scroll.dart'; import 'package:zoom_tap_animation/zoom_tap_animation.dart'; import '../components/button_play_component.dart'; @@ -44,6 +45,14 @@ class _DetailPostScreenState extends State { }); } + bool isSaved() { + return MyApp.userViewModel.userCurrent.musics_likes.contains(widget.post.music.id); + } + + bool isLiked() { + return widget.post.likes.contains(MyApp.userViewModel.userCurrent.id); + } + @override void dispose() { MyApp.audioPlayer.release(); @@ -89,154 +98,164 @@ class _DetailPostScreenState extends State { child: Container( height: 400, width: double.infinity, - child: FadeInImage.assetNetwork( - fit: BoxFit.cover, - image: choice ? widget.post.selfie! : widget.post.music.cover!, - fadeInDuration: const Duration(milliseconds: 100), - placeholder: "assets/images/loadingPlaceholder.gif", - ), + child: PinchZoom( + resetDuration: const Duration(milliseconds: 400), + maxScale: 2.5, + child: FadeInImage.assetNetwork( + placeholder: "assets/images/loadingPlaceholder.gif", + image: choice ? widget.post.selfie! : widget.post.music.cover!, + width: double.infinity, + fit: BoxFit.cover, + )), ), ), Column( children: [ - Container( - height: 200, - margin: EdgeInsets.only(top: 230), - width: double.infinity, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.transparent, bgModal], - stops: [0, 0.8], + IgnorePointer( + child: Container( + height: 200, + margin: EdgeInsets.only(top: 230), + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + bgModal.withOpacity(0.5), + bgModal.withOpacity(0.75), + bgModal + ], + stops: [0, 0.2, 0.4, 0.8], + ), ), - ), - child: Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 10), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: choice - ? Padding( - padding: const EdgeInsets.all(4), - child: ClipOval( - child: SizedBox.fromSize( - // Image radius - child: ProfilPictureComponent(user: widget.post.user), + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: choice + ? Padding( + padding: const EdgeInsets.all(4), + child: ClipOval( + child: SizedBox.fromSize( + // Image radius + child: ProfilPictureComponent(user: widget.post.user), + ), ), + ) + : widget.post.music.previewUrl != null + ? ButtonPlayComponent(music: widget.post.music) + : Container(), + ), + Flexible( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: ScrollConfiguration( + behavior: ScrollBehavior().copyWith(scrollbars: false), + child: TextScroll( + choice ? widget.post.user.pseudo : widget.post.music.title!, + style: GoogleFonts.plusJakartaSans( + height: 1, + color: Colors.white, + fontWeight: FontWeight.w800, + fontSize: 22, + ), + mode: TextScrollMode.endless, + pauseBetween: Duration(milliseconds: 500), + velocity: Velocity(pixelsPerSecond: Offset(20, 0)), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20.0), + child: choice + ? DateTime(today.year, today.month, today.day) + .isAtSameMomentAs( + DateTime( + widget.post.date.year, + widget.post.date.month, + widget.post.date.day, + ), + ) + ? Text( + "Aujourd'hui, ${widget.post.date.hour}:${widget.post.date.minute}", + style: GoogleFonts.plusJakartaSans( + height: 1, + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ) + : Text( + "hier, ${widget.post.date.hour}:${widget.post.date.minute}", + style: GoogleFonts.plusJakartaSans( + height: 1, + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ) + : Text( + widget.post.music.date.toString(), + style: GoogleFonts.plusJakartaSans( + height: 1, + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + ), + ], ), - ) - : widget.post.music.previewUrl != null - ? ButtonPlayComponent(music: widget.post.music) - : Container(), - ), - Flexible( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Expanded( - child: ScrollConfiguration( + ), + choice + ? widget.post.location.item2 != null + ? Text( + "${widget.post.location.item1}, ${widget.post.location.item2}", + style: GoogleFonts.plusJakartaSans( + color: Colors.white.withOpacity(0.5), + fontWeight: FontWeight.w400, + fontSize: 15, + ), + ) + : Text( + "", + style: GoogleFonts.plusJakartaSans( + color: Colors.white.withOpacity(0.4), + fontWeight: FontWeight.w300, + fontSize: 13, + ), + ) + : ScrollConfiguration( behavior: ScrollBehavior().copyWith(scrollbars: false), child: TextScroll( - choice ? widget.post.user.pseudo : widget.post.music.title!, + widget.post.music.artists.first.name!, style: GoogleFonts.plusJakartaSans( height: 1, color: Colors.white, - fontWeight: FontWeight.w800, - fontSize: 22, + fontWeight: FontWeight.w500, + fontSize: 17, ), mode: TextScrollMode.endless, pauseBetween: Duration(milliseconds: 500), velocity: Velocity(pixelsPerSecond: Offset(20, 0)), ), ), - ), - Padding( - padding: const EdgeInsets.only(left: 20.0), - child: choice - ? DateTime(today.year, today.month, today.day) - .isAtSameMomentAs( - DateTime( - widget.post.date.year, - widget.post.date.month, - widget.post.date.day, - ), - ) - ? Text( - "Aujourd'hui, ${widget.post.date.hour}:${widget.post.date.minute}", - style: GoogleFonts.plusJakartaSans( - height: 1, - color: Colors.white, - fontWeight: FontWeight.w900, - fontSize: 18, - ), - ) - : Text( - "hier, ${widget.post.date.hour}:${widget.post.date.minute}", - style: GoogleFonts.plusJakartaSans( - height: 1, - color: Colors.white, - fontWeight: FontWeight.w900, - fontSize: 18, - ), - ) - : Text( - widget.post.music.date.toString(), - style: GoogleFonts.plusJakartaSans( - height: 1, - color: Colors.white, - fontWeight: FontWeight.w900, - fontSize: 18, - ), - ), - ), - ], - ), - ), - choice - ? widget.post.location.item2 != null - ? Text( - "${widget.post.location.item1}, ${widget.post.location.item2}", - style: GoogleFonts.plusJakartaSans( - color: Colors.white.withOpacity(0.5), - fontWeight: FontWeight.w400, - fontSize: 15, - ), - ) - : Text( - "", - style: GoogleFonts.plusJakartaSans( - color: Colors.white.withOpacity(0.4), - fontWeight: FontWeight.w300, - fontSize: 13, - ), - ) - : ScrollConfiguration( - behavior: ScrollBehavior().copyWith(scrollbars: false), - child: TextScroll( - widget.post.music.artists.first.name!, - style: GoogleFonts.plusJakartaSans( - height: 1, - color: Colors.white, - fontWeight: FontWeight.w500, - fontSize: 17, - ), - mode: TextScrollMode.endless, - pauseBetween: Duration(milliseconds: 500), - velocity: Velocity(pixelsPerSecond: Offset(20, 0)), - ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), @@ -274,21 +293,172 @@ 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: () async { + var bool = await MyApp.postViewModel + .addOrDeleteFavoritePost(widget.post.id); + if (!bool) { + widget.post.likes.add(MyApp.userViewModel.userCurrent.id); + } else { + widget.post.likes.remove(MyApp.userViewModel.userCurrent.id); + } + !bool + ? ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Vous avez liké cette capsule", + style: TextStyle(fontWeight: FontWeight.bold)), + backgroundColor: primaryColor, + closeIconColor: Colors.white, + ), + ) + : ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: SnackBar( + content: Text("Vous avez supprimé votre like", + style: TextStyle(fontWeight: FontWeight.bold)), + backgroundColor: primaryColor, + closeIconColor: Colors.white, + ), + backgroundColor: Colors.red, + closeIconColor: Colors.white, + ), + ); + setState(() {}); + }, + child: SvgPicture.asset( + "assets/images/heart.svg", + semanticsLabel: 'Like Logo', + color: isLiked() ? primaryColor : Colors.white, + ), + ), + Container( + padding: EdgeInsets.only(top: 8), + height: 30, + child: FutureBuilder>( + future: MyApp.postViewModel.getLikesByPostId(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(), + ), + ); + } + }, + ), + ) + ], + ), + 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'), - SvgPicture.asset("assets/images/save.svg", semanticsLabel: 'Save Logo'), + GestureDetector( + onTap: () async { + var bool = await MyApp.musicViewModel + .addOrDeleteFavoriteMusic(widget.post.music.id); + !bool + ? ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + 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, + ), + ) + : ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + 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: " retiré de votre collection"), + ], + ), + ), + backgroundColor: Colors.red, + closeIconColor: Colors.white, + ), + ); + setState(() {}); + }, + child: SvgPicture.asset( + "assets/images/save.svg", + semanticsLabel: 'Save Logo', + color: isSaved() ? primaryColor : Colors.white, + )), SvgPicture.asset("assets/images/report.svg", semanticsLabel: 'Report Logo'), ], ), @@ -299,31 +469,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/services/PostService.dart b/Sources/justMUSIC/lib/services/PostService.dart index a7d9742..82bd2d0 100644 --- a/Sources/justMUSIC/lib/services/PostService.dart +++ b/Sources/justMUSIC/lib/services/PostService.dart @@ -7,8 +7,7 @@ import 'package:firebase_storage/firebase_storage.dart'; import '../main.dart'; class PostService { - createPost(String? description, String idMusic, File? image, - Tuple2? location) async { + createPost(String? description, String idMusic, File? image, Tuple2? location) async { var id = MyApp.userViewModel.userCurrent.id; final post = { "user_id": id, @@ -42,14 +41,11 @@ class PostService { deletePost() {} - Future>>> getPopularPosts( - {int limit = 10, int offset = 0}) async { + Future>>> getPopularPosts({int limit = 10, int offset = 0}) async { DateTime twentyFourHoursAgo = DateTime.now().subtract(Duration(hours: 24)); - Timestamp twentyFourHoursAgoTimestamp = - Timestamp.fromDate(twentyFourHoursAgo); + Timestamp twentyFourHoursAgoTimestamp = Timestamp.fromDate(twentyFourHoursAgo); - QuerySnapshot> response = await FirebaseFirestore - .instance + QuerySnapshot> response = await FirebaseFirestore.instance .collection("posts") .where("date", isGreaterThan: twentyFourHoursAgoTimestamp) .limit(limit) @@ -67,8 +63,7 @@ class PostService { return Timestamp.fromDate(twentyFourHoursAgo); } - Future>>> getPostsFriends( - {int limit = 10, int offset = 0}) async { + Future>>> getPostsFriends({int limit = 10, int offset = 0}) async { var timestamp = _getTwentyFourHoursAgoTimestamp(); var response = await FirebaseFirestore.instance .collection("posts") @@ -84,17 +79,12 @@ class PostService { Future getAvailable(String idUser) async { DateTime today = DateTime.now(); - QuerySnapshot> response = await FirebaseFirestore - .instance - .collection("posts") - .where("user_id", isEqualTo: idUser) - .get(); + QuerySnapshot> response = + await FirebaseFirestore.instance.collection("posts").where("user_id", isEqualTo: idUser).get(); bool isTodayAvailable = response.docs.any((doc) { DateTime date = doc["date"].toDate(); // Assuming the field name is "date" - return date.day == today.day && - date.month == today.month && - date.year == today.year; + return date.day == today.day && date.month == today.month && date.year == today.year; }); return !isTodayAvailable; @@ -105,15 +95,11 @@ class PostService { DateTime sevenDaysAgo = DateTime.now().subtract(Duration(days: 6)); - QuerySnapshot> response = await FirebaseFirestore - .instance - .collection("posts") - .where("user_id", isEqualTo: id) - .get(); + QuerySnapshot> response = + await FirebaseFirestore.instance.collection("posts").where("user_id", isEqualTo: id).get(); - List?> postList = response.docs - .map((DocumentSnapshot> doc) => doc.data()) - .toList(); + List?> postList = + response.docs.map((DocumentSnapshot> doc) => doc.data()).toList(); for (int i = 0; i < 7; i++) { DateTime date = sevenDaysAgo.add(Duration(days: i)); @@ -128,4 +114,32 @@ class PostService { return recapList; } + + Future> getLikesByPostId(String id) async { + var response = await FirebaseFirestore.instance.collection("posts").doc(id).get(); + if (response.exists) { + var musicFavorite = response.get("likes"); + return List.from(musicFavorite); + } else { + return []; + } + } + + Future addOrDeleteFavoritePost(String id) async { + final idUser = MyApp.userViewModel.userCurrent.id; + var postRef = await FirebaseFirestore.instance.collection("posts").doc(id); + var response = await postRef.get(); + + List likes = List.from(response.get("likes")); + + if (!likes.contains(idUser)) { + likes.add(idUser); + await postRef.update({"likes": likes}); + return false; + } else { + likes.remove(idUser); + await postRef.update({"likes": likes}); + return true; + } + } } diff --git a/Sources/justMUSIC/lib/view_model/PostViewModel.dart b/Sources/justMUSIC/lib/view_model/PostViewModel.dart index c70fbbb..f7498ce 100644 --- a/Sources/justMUSIC/lib/view_model/PostViewModel.dart +++ b/Sources/justMUSIC/lib/view_model/PostViewModel.dart @@ -22,8 +22,7 @@ class PostViewModel { List get bestPosts => _bestPosts; // Methods - addPost(String? description, String idMusic, File? image, - Tuple2? location) async { + addPost(String? description, String idMusic, File? image, Tuple2? location) async { await _postService.createPost(description, idMusic, image, location); } @@ -91,8 +90,25 @@ class PostViewModel { Future getAvailable() async { try { - return await _postService - .getAvailable(MyApp.userViewModel.userCurrent.id); + return await _postService.getAvailable(MyApp.userViewModel.userCurrent.id); + } catch (e) { + print(e); + rethrow; + } + } + + Future> getLikesByPostId(String id) async { + try { + return await _postService.getLikesByPostId(id); + } catch (e) { + print(e); + rethrow; + } + } + + Future addOrDeleteFavoritePost(String id) async { + try { + return await _postService.addOrDeleteFavoritePost(id); } catch (e) { print(e); rethrow;