Skip to content

Commit ba87932

Browse files
authored
Merge pull request #1 from ChunhThanhDe/main
Add Music Page, User Profile and Favorite Songs
2 parents 6f1179c + e7c86b2 commit ba87932

35 files changed

+611
-61
lines changed

README.md

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ A beautiful and interactive **Spotify Clone** built using Flutter. Show some ❤
44

55
###### Contact for work, email: chunhthanhde.dev@gmail.com
66

7-
<img src="assets/icons/spotify_logo.png" height="120px" alt="spotify logo"/>
7+
<br>
8+
9+
<img src="assets/images/spotify_logo.png" alt="spotify logo"/>
10+
11+
<br>
812

913
![GitHub stars](https://img.shields.io/github/stars/Flutter-Journey/Spotify-With-Flutter?style=social)
1014
![GitHub forks](https://img.shields.io/github/forks/Flutter-Journey/Spotify-With-Flutter?style=social)
@@ -18,39 +22,50 @@ A beautiful and interactive **Spotify Clone** built using Flutter. Show some ❤
1822

1923
## 🎵 Overview
2024

21-
**Spotify With Flutter** is a music streaming app inspired by Spotify, featuring a smooth user interface and core functionalities like song playback, playlists, and browsing music. Developed using Flutter, it is available on Android, iOS, and web platforms.
25+
**Spotify With Flutter** is a demo music streaming app inspired by Spotify, featuring a smooth user interface and basic functionalities like firebase login, play music, and favorites song screen. Developed using Flutter, it is available on Android, iOS
26+
27+
> Note: This project uses mock data and is not affiliated with or integrated with Spotify’s official API.
28+
29+
## 🖥️ Screens and Features
2230

23-
## 🌟 Features
31+
- **Home Screen**: Displays playlists and recommended songs.
32+
- **Playlist Screen**: View and manage your playlists.
33+
- **Song Player Screen**: Play songs with full controls.
34+
- **Profile Screen**: View user information and favorite tracks.
2435

25-
- **Music Streaming**: Stream your favorite songs seamlessly.
26-
- **Beautiful UI**: A modern and sleek design similar to the original Spotify app.
27-
- **Playlist Management**: Create and manage your playlists with ease.
28-
- **Search Functionality**: Search for your favorite songs, artists, and albums.
29-
- **Cross-Platform**: Available on Android, iOS, and web.
36+
## 🎨 Figma Design
37+
38+
- [Light Mode Design](https://www.figma.com/community/file/1166665330965959412/spotify-redesign-free-ui-kit-light)
39+
- [Dark Mode Design](https://www.figma.com/community/file/1172466818809176172/spotify-redesign-free-ui-kit-dark-mode)
3040

3141
## 🎮 Demo
3242

33-
Check out how the app looks in action! You can view a demo [here](https://www.youtube.com/watch?v=demo_link).
43+
Check out how the app looks in action! You can view a demo [here](https://www.youtube.com/shorts/aEHOczCZQ00).
3444

3545
<table>
3646
<tr>
37-
<td><img src="https://github.com/Flutter-Journey/Spotify-With-Flutter/blob/master/media/screenshot1.jpg" height="300px"></td>
38-
<td><img src="https://github.com/Flutter-Journey/Spotify-With-Flutter/blob/master/media/screenshot2.jpg" height="300px"></td>
47+
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/gif/spotify_light.gif"></td>
48+
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/gif/spotify_dark.gif"></td>
49+
</tr>
50+
</table>
51+
52+
## 🏏 Screen
53+
54+
<table>
55+
<tr>
56+
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/login_screen.png"></td>
57+
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/choose_mode_screen.png"></td>
58+
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/home_screen.png"></td>
3959
</tr>
4060
</table>
4161

4262
## 🛠️ Technologies Used
4363

4464
- **Flutter**: Framework for building natively compiled applications.
4565
- **Dart**: The programming language used for Flutter development.
46-
- **Spotify API**: Integration with Spotify's API for fetching music data.
47-
- **Animations**: Smooth animations for enhancing the user experience.
48-
49-
## 📚 References
50-
51-
- [Official Flutter Documentation](https://flutter.dev/docs)
52-
- [Dart Documentation](https://dart.dev/guides)
53-
- [Spotify API Documentation](https://developer.spotify.com/documentation/web-api/)
66+
- **Firebase**: Backend as a Service for authentication, storage, and database.
67+
- **BLoC (Business Logic Component)**: For state management.
68+
- **Clean Architecture**: For scalable and maintainable code.
5469

5570
## 🌟 Conclusion
5671

lib/common/widgets/appbar/app_bar.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@ class BasicAppBar extends StatelessWidget implements PreferredSizeWidget {
66
final Widget title;
77
final Widget action;
88
final bool hiveBack;
9+
final Color backgroundColor;
910

1011
const BasicAppBar({
1112
super.key,
1213
this.hiveBack = false,
1314
this.title = const Text(''),
1415
this.action = const SizedBox(),
16+
this.backgroundColor = Colors.transparent,
1517
});
1618

1719
@override
1820
Widget build(BuildContext context) {
1921
return AppBar(
2022
title: title,
2123
centerTitle: true,
22-
backgroundColor: Colors.transparent,
24+
backgroundColor: backgroundColor,
2325
elevation: 0,
2426
actions: [
2527
action,
@@ -34,7 +36,9 @@ class BasicAppBar extends StatelessWidget implements PreferredSizeWidget {
3436
height: 50,
3537
width: 50,
3638
decoration: BoxDecoration(
37-
color: context.isDarkMode ? AppColors.white.withOpacity(0.03) : AppColors.dark.withOpacity(0.04),
39+
color: context.isDarkMode
40+
? AppColors.white.withOpacity(0.03)
41+
: AppColors.dark.withOpacity(0.04),
3842
shape: BoxShape.circle,
3943
),
4044
child: Icon(

lib/common/widgets/favorite_button/favorite_button.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import 'package:spotify_with_flutter/domain/entities/songs/songs.dart';
88
class FavoriteButton extends StatelessWidget {
99
final SongEntity songEntity;
1010
final double sizeIcons;
11+
final Function? function;
1112

1213
const FavoriteButton({
1314
super.key,
1415
required this.songEntity,
1516
this.sizeIcons = 25,
17+
this.function,
1618
});
1719

1820
@override
@@ -27,9 +29,15 @@ class FavoriteButton extends StatelessWidget {
2729
await context.read<FavoriteButtonCubit>().favoriteButtonUpdated(
2830
songEntity.songId,
2931
);
32+
33+
if (function != null) {
34+
function!();
35+
}
3036
},
3137
icon: Icon(
32-
songEntity.isFavorite ? Icons.favorite : Icons.favorite_outline_outlined,
38+
songEntity.isFavorite
39+
? Icons.favorite
40+
: Icons.favorite_outline_outlined,
3341
size: sizeIcons,
3442
color: AppColors.darkGrey,
3543
),
@@ -44,7 +52,9 @@ class FavoriteButton extends StatelessWidget {
4452
);
4553
},
4654
icon: Icon(
47-
state.isFavorite ? Icons.favorite : Icons.favorite_outline_outlined,
55+
state.isFavorite
56+
? Icons.favorite
57+
: Icons.favorite_outline_outlined,
4858
size: sizeIcons,
4959
color: AppColors.darkGrey,
5060
),

lib/core/configs/theme/app_color.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class AppColors {
66
static const darkBackground = Color(0xff0D0C0C);
77
static const grey = Color(0xffBEBEBE);
88
static const darkGrey = Color(0xff616161);
9+
static const metalDark = Color(0xff2C2B2B);
910

1011
static const greyTitle = Color(0xffDADADA);
1112
static const white = Color(0xffF6F6F6);

lib/core/constants/app_urls.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
class AppUrls {
2-
static const coverFireStorage = 'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/covers%2F';
3-
static const songFireStorage = 'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/songs%2F';
2+
static const coverFireStorage =
3+
'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/covers%2F';
4+
static const songFireStorage =
5+
'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/songs%2F';
46
static const temp = 'Son-Tung-';
57
static const mediaAlt = 'alt=media';
8+
static const defaultAvatar =
9+
'https://cdn-icons-png.flaticon.com/512/10542/10542486.png';
610
}

lib/data/models/auth/user.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'package:spotify_with_flutter/domain/entities/auth/user.dart';
2+
3+
class UserModel {
4+
String? email;
5+
String? fullName;
6+
String? imageURL;
7+
8+
UserModel({
9+
this.email,
10+
this.fullName,
11+
this.imageURL,
12+
});
13+
14+
UserModel.fromJson(Map<String, dynamic> data) {
15+
email = data['email'];
16+
fullName = data['name'];
17+
imageURL = data['imageURL'];
18+
}
19+
}
20+
21+
extension UserModelX on UserModel {
22+
UserEntity toEntity() {
23+
return UserEntity(
24+
email: email!,
25+
fullName: fullName!,
26+
imageURL: imageURL!,
27+
);
28+
}
29+
}

lib/data/repository/auth/auth_repository_impl.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ class AuthRepositoryImpl extends AuthRepository {
1515
Future<Either> signup(CreateUserReq createUserReq) async {
1616
return await sl<AuthFirebaseService>().signup(createUserReq);
1717
}
18+
19+
@override
20+
Future<Either> getUser() async {
21+
return await sl<AuthFirebaseService>().getUser();
22+
}
1823
}

lib/data/repository/song/song_repository_impl.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ class SongRepositoryImpl extends SongsRepository {
2323
Future<bool> isFavoriteSong(String songId) async {
2424
return await sl<SongFirebaseService>().isFavoriteSong(songId);
2525
}
26+
27+
@override
28+
Future<Either> getUserFavoriteSongs() async {
29+
return await sl<SongFirebaseService>().getUserFavoriteSong();
30+
}
2631
}

lib/data/sources/auth/auth_firebase_service.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import 'package:cloud_firestore/cloud_firestore.dart';
22
import 'package:dartz/dartz.dart';
33
import 'package:firebase_auth/firebase_auth.dart';
4+
import 'package:flutter/foundation.dart';
5+
import 'package:spotify_with_flutter/core/constants/app_urls.dart';
46
import 'package:spotify_with_flutter/data/models/auth/create_user_req.dart';
57
import 'package:spotify_with_flutter/data/models/auth/signin_user_req.dart';
8+
import 'package:spotify_with_flutter/data/models/auth/user.dart';
9+
import 'package:spotify_with_flutter/domain/entities/auth/user.dart';
610

711
abstract class AuthFirebaseService {
812
Future<Either> signup(CreateUserReq createUserReq);
913
Future<Either> signin(SigninUserReq signinUserReq);
14+
Future<Either> getUser();
1015
}
1116

1217
class AuthFirebaseServiceImpl extends AuthFirebaseService {
@@ -60,4 +65,33 @@ class AuthFirebaseServiceImpl extends AuthFirebaseService {
6065
return left(message);
6166
}
6267
}
68+
69+
@override
70+
Future<Either> getUser() async {
71+
try {
72+
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
73+
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
74+
75+
var user = await firebaseFirestore
76+
.collection('Users')
77+
.doc(
78+
firebaseAuth.currentUser?.uid,
79+
)
80+
.get();
81+
82+
UserModel userModel = UserModel.fromJson(user.data()!);
83+
84+
userModel.imageURL =
85+
firebaseAuth.currentUser?.photoURL ?? AppUrls.defaultAvatar;
86+
87+
UserEntity userEntity = userModel.toEntity();
88+
89+
return Right(userEntity);
90+
} catch (e) {
91+
if (kDebugMode) {
92+
print("error: $e");
93+
}
94+
return const Left('An error occurred');
95+
}
96+
}
6397
}

lib/data/sources/song/song_firebase_service.dart

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ abstract class SongFirebaseService {
1212
Future<Either> getPlayList();
1313
Future<Either> addOrRemoveFavoriteSong(String songId);
1414
Future<bool> isFavoriteSong(String songId);
15+
Future<Either> getUserFavoriteSong();
1516
}
1617

1718
class SongFirebaseServiceImpl extends SongFirebaseService {
@@ -93,7 +94,7 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
9394

9495
late bool isFavorite;
9596

96-
var user = await firebaseAuth.currentUser;
97+
var user = firebaseAuth.currentUser;
9798

9899
String uID = user!.uid;
99100

@@ -111,7 +112,11 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
111112
await favoriteSongs.docs.first.reference.delete();
112113
isFavorite = false;
113114
} else {
114-
await firebaseFirestore.collection('Users').doc(uID).collection('Favorites').add(
115+
await firebaseFirestore
116+
.collection('Users')
117+
.doc(uID)
118+
.collection('Favorites')
119+
.add(
115120
{
116121
'songId': songId,
117122
'addedDate': Timestamp.now(),
@@ -133,7 +138,7 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
133138

134139
final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
135140

136-
var user = await firebaseAuth.currentUser;
141+
var user = firebaseAuth.currentUser;
137142

138143
String uID = user!.uid;
139144

@@ -156,4 +161,38 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
156161
return false;
157162
}
158163
}
164+
165+
@override
166+
Future<Either> getUserFavoriteSong() async {
167+
try {
168+
List<SongEntity> favoriteSong = [];
169+
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
170+
171+
final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
172+
173+
var user = firebaseAuth.currentUser;
174+
175+
String uID = user!.uid;
176+
177+
QuerySnapshot favoriteSnapshot = await firebaseFirestore
178+
.collection('Users')
179+
.doc(uID)
180+
.collection('Favorites')
181+
.get();
182+
183+
for (var element in favoriteSnapshot.docs) {
184+
String songId = element['songId'];
185+
var song =
186+
await firebaseFirestore.collection('Songs').doc(songId).get();
187+
SongModel songModel = SongModel.fromJson(song.data()!);
188+
songModel.isFavorite = true;
189+
songModel.songId = songId;
190+
favoriteSong.add(songModel.toEntity());
191+
}
192+
193+
return Right(favoriteSong);
194+
} catch (e) {
195+
return const Left('An error occurred');
196+
}
197+
}
159198
}

0 commit comments

Comments
 (0)