Merge branch 'SAVE_SONG_LDE'

# Conflicts:
#	Sources/justMUSIC/lib/main.dart
#	Sources/justMUSIC/lib/screens/detail_post_screen.dart
#	Sources/justMUSIC/lib/view_model/MusicViewModel.dart
FIX_LIKES_LDE
Lucas Delanier 2 years ago
commit d88a92fcf3

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,
@ -45,7 +46,6 @@ class MyApp extends StatefulWidget {
static PostViewModel postViewModel = PostViewModel();
static AudioPlayer audioPlayer = AudioPlayer();
static CommentViewModel commentViewModel = CommentViewModel();
static const keyManager = 'customCacheKey';
const MyApp({super.key});

@ -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,14 @@ 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 +24,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));
}

@ -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,42 +172,94 @@ class _SearchSongScreenState extends State<SearchSongScreen> {
),
),
Flexible(
child: 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: PageView(
controller: controller,
physics: BouncingScrollPhysics(),
children: [
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,
),
));
}),
),
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();
}
},
),
)
],
))
],
),

@ -29,7 +29,7 @@ class AuthService {
"nbCapsules": 0,
"followers": [],
"token_notify": token,
"musics_likes": [],
"saved_musics": [],
"picture":
"https://firebasestorage.googleapis.com/v0/b/justmusic-435d5.appspot.com/o/justMusicDefaultImage.png?alt=media&token=020d0fcb-b7df-4d4d-b380-e99597293fcc"
};
@ -55,10 +55,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++;
@ -70,8 +68,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');
@ -98,8 +95,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;
}
}

@ -2,7 +2,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 +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}');
}
}
@ -49,15 +48,12 @@ 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: {
'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<String, dynamic> responseData = jsonDecode(response.body);
@ -65,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<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: {
'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<String, dynamic> responseData = jsonDecode(response.body);
@ -86,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<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: {
'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<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 +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<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,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<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: {
'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<String, dynamic> responseData = jsonDecode(response.body);
@ -167,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<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 +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<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 +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}');
}
}
@ -227,18 +201,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);
@ -254,26 +231,4 @@ class MusicViewModel {
rethrow;
}
}
Future<List<Tuple2<String, Music>>> getHistoryCapsulesMonthWhitIdUser(
String idUser, int month, int year) async {
try {
List<Tuple2<String, Music>> capsules = [];
var capsulesData = await _musicService.getHistoryCapsulesMonthWhitIdUser(
idUser, month, year);
var musics = await getMusicsWithIds(
capsulesData.map((capsule) => capsule.item2).toList());
for (var capsule in capsulesData) {
var music = musics.firstWhere((music) => music.id == capsule.item2);
capsules.add(Tuple2(capsule.item1, music));
}
return capsules;
} catch (e) {
print(e);
rethrow;
}
}
}

Loading…
Cancel
Save