Merge with 'SAVE_SONG_LDE'
continuous-integration/drone/push Build is passing Details

LIKES_POST_LDE-EKA
Emre KARTAL 2 years ago
commit 6d6da9f2e1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

@ -29,6 +29,7 @@ import 'package:timezone/data/latest.dart' as tz;
Future<void> main() async {
tz.initializeTimeZones();
Paint.enableDithering = true;
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,

@ -14,11 +14,11 @@ class Music {
List<Artist> _artists;
// Constructor
Music(this._id, this._title, this._cover, this._previewUrl, this._date,
this._duration, this._explicit, this._artists);
Music(
this._id, this._title, this._cover, this._previewUrl, this._date, this._duration, this._explicit, this._artists);
//Getters and setters
String? get id => _id;
String get id => _id;
String? get title => _title;

@ -6,12 +6,13 @@ class User {
String _pp;
String _token;
List<String> _followers;
List<String> _musics_likes;
int _capsules;
List<String> _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 +23,12 @@ class User {
_pseudo = value;
}
List<String> get musics_likes => _musics_likes;
set musics_likes(List<String> value) {
_musics_likes = value;
}
String get uniquePseudo => _uniquePseudo;
set uniquePseudo(String value) {

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

@ -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,10 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
});
}
bool isSaved() {
return MyApp.userViewModel.userCurrent.musics_likes.contains(widget.post.music.id);
}
@override
void dispose() {
MyApp.audioPlayer.release();
@ -89,26 +94,35 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
child: Container(
height: 400,
width: double.infinity,
child: PinchZoom(
resetDuration: const Duration(milliseconds: 400),
maxScale: 2.5,
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",
),
image: choice ? widget.post.selfie! : widget.post.music.cover!,
width: double.infinity,
fit: BoxFit.cover,
)),
),
),
Column(
children: [
Container(
IgnorePointer(
child: Container(
height: 200,
margin: EdgeInsets.only(top: 230),
width: double.infinity,
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, bgModal],
stops: [0, 0.8],
colors: [
Colors.transparent,
bgModal.withOpacity(0.5),
bgModal.withOpacity(0.75),
bgModal
],
stops: [0, 0.2, 0.4, 0.8],
),
),
child: Padding(
@ -240,6 +254,7 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
),
),
),
),
widget.post.description != null
? Align(
alignment: Alignment.bottomLeft,
@ -274,11 +289,14 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
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'),
Column(
children: [
GestureDetector(
onTap: () {
myFocusNode.requestFocus();
@ -286,44 +304,103 @@ class _DetailPostScreenState extends State<DetailPostScreen> {
child: SvgPicture.asset("assets/images/chat.svg",
semanticsLabel: 'Chat Logo'),
),
SvgPicture.asset("assets/images/add.svg",
semanticsLabel: 'Add playlist Logo'),
SvgPicture.asset("assets/images/save.svg", semanticsLabel: 'Save Logo'),
SvgPicture.asset("assets/images/report.svg", semanticsLabel: 'Report Logo'),
],
),
),
FutureBuilder<List<Comment>>(
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) {
builder:
(BuildContext context, AsyncSnapshot<List<Comment>> snapshot) {
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(),
return Text(snapshot.data!.length.toString(),
style: GoogleFonts.plusJakartaSans(
color: Colors.white,
fontWeight: FontWeight.w800,
));
} else {
return Container(
child: Center(
child: CupertinoActivityIndicator(),
),
children: [
);
}
},
),
)
],
),
SvgPicture.asset("assets/images/add.svg",
semanticsLabel: 'Add playlist 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>[
TextSpan(
text: snapshot.data!.length > 1
? " commentaires"
: " commentaire",
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>[
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'),
],
),
),
)
: Container(),
FutureBuilder<List<Comment>>(
future: MyApp.commentViewModel.getCommentsByPostId(widget.post.id),
builder: (BuildContext context, AsyncSnapshot<List<Comment>> snapshot) {
if (snapshot.hasData) {
return Column(
children: [
snapshot.data!.length > 0
? Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),

@ -91,7 +91,14 @@ class _FeedScreenState extends State<FeedScreen> with SingleTickerProviderStateM
builder: ((BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
child: DetailPostScreen(post: post));
child: SizedBox(
height: 760.h,
child: Scaffold(
primary: false,
extendBody: false,
backgroundColor: Colors.transparent,
body: DetailPostScreen(post: post)),
));
}),
);
}

@ -11,7 +11,6 @@ import '../components/editable_post_component.dart';
import '../components/post_button_component.dart';
import '../main.dart';
import '../model/Music.dart';
import '../model/Post.dart';
import '../values/constants.dart';
class PostScreen extends StatefulWidget {
@ -93,7 +92,7 @@ class _PostScreenState extends State<PostScreen> with SingleTickerProviderStateM
}
handleSubmit() async {
MyApp.postViewModel.addPost(description, (selectedMusic?.id)!, selectedImage, selectedCity);
MyApp.postViewModel.addPost(description, selectedMusic!.id, selectedImage, selectedCity);
quit();
}

@ -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<SearchSongScreen> {
final ScrollController _scrollController = ScrollController();
final TextEditingController _textEditingController = TextEditingController();
PageController controller = PageController();
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
void dispose() {
MyApp.audioPlayer.pause();
@ -124,7 +130,6 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
child: SizedBox(
height: 40,
child: TextField(
autofocus: true,
controller: _textEditingController,
keyboardAppearance: Brightness.dark,
onEditingComplete: resetFullScreen,
@ -132,11 +137,15 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
if (_textEditingController.text.isEmpty) {
fetchTrendingMusic();
} else {
setState(() {
filteredData = [];
});
filteredData = await MyApp.musicViewModel.getMusicsWithNameOrArtistName(value);
setState(() {
filteredData = filteredData;
});
}
controller.animateTo(0, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
cursorColor: Colors.white,
keyboardType: TextInputType.text,
@ -163,7 +172,11 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
),
),
Flexible(
child: ScrollConfiguration(
child: PageView(
controller: controller,
physics: BouncingScrollPhysics(),
children: [
ScrollConfiguration(
behavior: ScrollBehavior().copyWith(scrollbars: true),
child: ListView.builder(
physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
@ -199,6 +212,54 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
),
));
}),
),
ScrollConfiguration(
behavior: ScrollBehavior().copyWith(scrollbars: true),
child: FutureBuilder(
future: _fetchSavedSong(),
builder: (BuildContext context, AsyncSnapshot<List<Music>> 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();
}
},
),
)
],
))
],
),

@ -88,10 +88,8 @@ class AuthService {
Future<String> generateUniqueId(String pseudo) async {
String uniqueId = '$pseudo#0001';
int suffix = 1;
final CollectionReference usersCollection =
FirebaseFirestore.instance.collection("users");
final QuerySnapshot querySnapshot =
await usersCollection.where('pseudo', isEqualTo: pseudo).get();
final CollectionReference usersCollection = FirebaseFirestore.instance.collection("users");
final QuerySnapshot querySnapshot = await usersCollection.where('pseudo', isEqualTo: pseudo).get();
querySnapshot.docs.forEach((snapshot) {
suffix++;
@ -103,8 +101,7 @@ class AuthService {
login(String email, String password) async {
try {
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw ('Mail incorrect');
@ -137,8 +134,7 @@ class AuthService {
.doc(currentUser?.uid)
.delete()
.then((value) => print("Firestore deleted user"))
.catchError(
(error) => print("Error deleting user from Firestore: $error"));
.catchError((error) => print("Error deleting user from Firestore: $error"));
await currentUser?.delete();
await FirebaseAuth.instance.signOut();

@ -5,45 +5,30 @@ import '../main.dart';
class MusicService {
Future<dynamic> getFavoriteMusicsByUserId(String id) async {
var response =
await FirebaseFirestore.instance.collection("users").doc(id).get();
var response = await FirebaseFirestore.instance.collection("users").doc(id).get();
if (response.exists) {
var musicFavorite = response.get("musics_likes");
var musicFavorite = response.get("saved_musics");
return List.from(musicFavorite);
} else {
return [];
}
}
deleteFavoriteMusic(String id) async {
var userRef = await FirebaseFirestore.instance
.collection("users")
.doc(MyApp.userViewModel.userCurrent.id);
var response = await userRef.get();
List<dynamic> musicFavorite = List.from(response.get("musics_likes"));
if (!musicFavorite.contains(id)) {
musicFavorite.remove(id);
await userRef.update({"musics_likes": musicFavorite});
} else {
print("Delete error: The music is not in the user's favorite music list");
}
}
Future<bool> addOrDeleteFavoriteMusic(String id) async {
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();
List<dynamic> musicFavorite = List.from(response.get("musics_likes"));
List<String> musicFavorite = List.from(response.get("musics_likes"));
if (!musicFavorite.contains(id)) {
musicFavorite.add(id);
await userRef.update({"musics_likes": musicFavorite});
MyApp.userViewModel.userCurrent.musics_likes.add(id);
return false;
} else {
musicFavorite.remove(id);
await userRef.update({"musics_likes": musicFavorite});
MyApp.userViewModel.userCurrent.musics_likes.remove(id);
return true;
}
}

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:justmusic/view_model/TokenSpotify.dart';
import 'package:http/http.dart' as http;
import 'package:tuple/tuple.dart';
import '../main.dart';
import '../model/Artist.dart';
import '../model/Music.dart';
import '../services/MusicService.dart';
@ -28,8 +29,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}');
}
}
@ -49,13 +49,10 @@ class MusicViewModel {
artists);
}
Future<List<Music>> getMusicsWithName(String name,
{int limit = 20, int offset = 0, String market = "FR"}) async {
Future<List<Music>> 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: {
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',
});
@ -65,18 +62,15 @@ 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<List<Music>> 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: {
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',
});
@ -86,24 +80,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<Artist> 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: {
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<Artist> artists =
List<Artist>.from(responseData['artists']['items'].map((artist) {
List<Artist> artists = List<Artist>.from(responseData['artists']['items'].map((artist) {
String image = '';
if (!artist['images'].isEmpty) {
image = artist['images'][0]['url'];
@ -119,25 +108,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<List<Artist>> getArtistsWithName(String name,
{int limit = 20, int offset = 0, String market = "FR"}) async {
Future<List<Artist>> 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<Artist> artists =
List<Artist>.from(responseData['artists']['items'].map((artist) {
List<Artist> artists = List<Artist>.from(responseData['artists']['items'].map((artist) {
String image = '';
if (!artist['images'].isEmpty) {
image = artist['images'][0]['url'];
@ -147,17 +132,13 @@ 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<List<Music>> getTopMusicsWithArtistId(String id,
{String market = "FR"}) async {
Future<List<Music>> 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: {
var response = await http.get(Uri.parse('$API_URL/artists/$id/top-tracks?market=$market'), headers: {
'Authorization': 'Bearer $accessToken',
});
@ -167,16 +148,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<List<Music>> getMusicsWithPlaylistId(String id,
{String market = "FR"}) async {
Future<List<Music>> 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',
});
@ -192,13 +170,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<List<Music>> getMusicsWithIds(List<String> ids,
{String market = "FR"}) async {
Future<List<Music>> getMusicsWithIds(List<String> ids, {String market = "FR"}) async {
var accessToken = await _token.getAccessToken();
String url = API_URL + '/tracks?market=$market&ids=';
@ -216,8 +192,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}');
}
}
@ -227,18 +202,21 @@ class MusicViewModel {
List<Music> 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<List<Music>> getFavoriteMusicsByUserId(String id) async {
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);
} catch (e) {
print(e);

Loading…
Cancel
Save