Skip to content

Commit

Permalink
Merge pull request #1 from ChunhThanhDe/main
Browse files Browse the repository at this point in the history
Add Music Page, User Profile and Favorite Songs
  • Loading branch information
ChunhThanhDe authored Nov 29, 2024
2 parents 6f1179c + e7c86b2 commit ba87932
Show file tree
Hide file tree
Showing 35 changed files with 611 additions and 61 deletions.
53 changes: 34 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ A beautiful and interactive **Spotify Clone** built using Flutter. Show some ❤

###### Contact for work, email: [email protected]

<img src="assets/icons/spotify_logo.png" height="120px" alt="spotify logo"/>
<br>

<img src="assets/images/spotify_logo.png" alt="spotify logo"/>

<br>

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

## 🎵 Overview

**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.
**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

> Note: This project uses mock data and is not affiliated with or integrated with Spotify’s official API.
## 🖥️ Screens and Features

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

- **Music Streaming**: Stream your favorite songs seamlessly.
- **Beautiful UI**: A modern and sleek design similar to the original Spotify app.
- **Playlist Management**: Create and manage your playlists with ease.
- **Search Functionality**: Search for your favorite songs, artists, and albums.
- **Cross-Platform**: Available on Android, iOS, and web.
## 🎨 Figma Design

- [Light Mode Design](https://www.figma.com/community/file/1166665330965959412/spotify-redesign-free-ui-kit-light)
- [Dark Mode Design](https://www.figma.com/community/file/1172466818809176172/spotify-redesign-free-ui-kit-dark-mode)

## 🎮 Demo

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

<table>
<tr>
<td><img src="https://github.com/Flutter-Journey/Spotify-With-Flutter/blob/master/media/screenshot1.jpg" height="300px"></td>
<td><img src="https://github.com/Flutter-Journey/Spotify-With-Flutter/blob/master/media/screenshot2.jpg" height="300px"></td>
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/gif/spotify_light.gif"></td>
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/gif/spotify_dark.gif"></td>
</tr>
</table>

## 🏏 Screen

<table>
<tr>
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/login_screen.png"></td>
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/choose_mode_screen.png"></td>
<td><img src="https://raw.githubusercontent.com/Flutter-Journey/Spotify-With-Flutter/refs/heads/main/media/image/home_screen.png"></td>
</tr>
</table>

## 🛠️ Technologies Used

- **Flutter**: Framework for building natively compiled applications.
- **Dart**: The programming language used for Flutter development.
- **Spotify API**: Integration with Spotify's API for fetching music data.
- **Animations**: Smooth animations for enhancing the user experience.

## 📚 References

- [Official Flutter Documentation](https://flutter.dev/docs)
- [Dart Documentation](https://dart.dev/guides)
- [Spotify API Documentation](https://developer.spotify.com/documentation/web-api/)
- **Firebase**: Backend as a Service for authentication, storage, and database.
- **BLoC (Business Logic Component)**: For state management.
- **Clean Architecture**: For scalable and maintainable code.

## 🌟 Conclusion

Expand Down
8 changes: 6 additions & 2 deletions lib/common/widgets/appbar/app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ class BasicAppBar extends StatelessWidget implements PreferredSizeWidget {
final Widget title;
final Widget action;
final bool hiveBack;
final Color backgroundColor;

const BasicAppBar({
super.key,
this.hiveBack = false,
this.title = const Text(''),
this.action = const SizedBox(),
this.backgroundColor = Colors.transparent,
});

@override
Widget build(BuildContext context) {
return AppBar(
title: title,
centerTitle: true,
backgroundColor: Colors.transparent,
backgroundColor: backgroundColor,
elevation: 0,
actions: [
action,
Expand All @@ -34,7 +36,9 @@ class BasicAppBar extends StatelessWidget implements PreferredSizeWidget {
height: 50,
width: 50,
decoration: BoxDecoration(
color: context.isDarkMode ? AppColors.white.withOpacity(0.03) : AppColors.dark.withOpacity(0.04),
color: context.isDarkMode
? AppColors.white.withOpacity(0.03)
: AppColors.dark.withOpacity(0.04),
shape: BoxShape.circle,
),
child: Icon(
Expand Down
14 changes: 12 additions & 2 deletions lib/common/widgets/favorite_button/favorite_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import 'package:spotify_with_flutter/domain/entities/songs/songs.dart';
class FavoriteButton extends StatelessWidget {
final SongEntity songEntity;
final double sizeIcons;
final Function? function;

const FavoriteButton({
super.key,
required this.songEntity,
this.sizeIcons = 25,
this.function,
});

@override
Expand All @@ -27,9 +29,15 @@ class FavoriteButton extends StatelessWidget {
await context.read<FavoriteButtonCubit>().favoriteButtonUpdated(
songEntity.songId,
);

if (function != null) {
function!();
}
},
icon: Icon(
songEntity.isFavorite ? Icons.favorite : Icons.favorite_outline_outlined,
songEntity.isFavorite
? Icons.favorite
: Icons.favorite_outline_outlined,
size: sizeIcons,
color: AppColors.darkGrey,
),
Expand All @@ -44,7 +52,9 @@ class FavoriteButton extends StatelessWidget {
);
},
icon: Icon(
state.isFavorite ? Icons.favorite : Icons.favorite_outline_outlined,
state.isFavorite
? Icons.favorite
: Icons.favorite_outline_outlined,
size: sizeIcons,
color: AppColors.darkGrey,
),
Expand Down
1 change: 1 addition & 0 deletions lib/core/configs/theme/app_color.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class AppColors {
static const darkBackground = Color(0xff0D0C0C);
static const grey = Color(0xffBEBEBE);
static const darkGrey = Color(0xff616161);
static const metalDark = Color(0xff2C2B2B);

static const greyTitle = Color(0xffDADADA);
static const white = Color(0xffF6F6F6);
Expand Down
8 changes: 6 additions & 2 deletions lib/core/constants/app_urls.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class AppUrls {
static const coverFireStorage = 'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/covers%2F';
static const songFireStorage = 'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/songs%2F';
static const coverFireStorage =
'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/covers%2F';
static const songFireStorage =
'https://firebasestorage.googleapis.com/v0/b/spotify-flutter-4aa82.appspot.com/o/songs%2F';
static const temp = 'Son-Tung-';
static const mediaAlt = 'alt=media';
static const defaultAvatar =
'https://cdn-icons-png.flaticon.com/512/10542/10542486.png';
}
29 changes: 29 additions & 0 deletions lib/data/models/auth/user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:spotify_with_flutter/domain/entities/auth/user.dart';

class UserModel {
String? email;
String? fullName;
String? imageURL;

UserModel({
this.email,
this.fullName,
this.imageURL,
});

UserModel.fromJson(Map<String, dynamic> data) {
email = data['email'];
fullName = data['name'];
imageURL = data['imageURL'];
}
}

extension UserModelX on UserModel {
UserEntity toEntity() {
return UserEntity(
email: email!,
fullName: fullName!,
imageURL: imageURL!,
);
}
}
5 changes: 5 additions & 0 deletions lib/data/repository/auth/auth_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ class AuthRepositoryImpl extends AuthRepository {
Future<Either> signup(CreateUserReq createUserReq) async {
return await sl<AuthFirebaseService>().signup(createUserReq);
}

@override
Future<Either> getUser() async {
return await sl<AuthFirebaseService>().getUser();
}
}
5 changes: 5 additions & 0 deletions lib/data/repository/song/song_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ class SongRepositoryImpl extends SongsRepository {
Future<bool> isFavoriteSong(String songId) async {
return await sl<SongFirebaseService>().isFavoriteSong(songId);
}

@override
Future<Either> getUserFavoriteSongs() async {
return await sl<SongFirebaseService>().getUserFavoriteSong();
}
}
34 changes: 34 additions & 0 deletions lib/data/sources/auth/auth_firebase_service.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dartz/dartz.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:spotify_with_flutter/core/constants/app_urls.dart';
import 'package:spotify_with_flutter/data/models/auth/create_user_req.dart';
import 'package:spotify_with_flutter/data/models/auth/signin_user_req.dart';
import 'package:spotify_with_flutter/data/models/auth/user.dart';
import 'package:spotify_with_flutter/domain/entities/auth/user.dart';

abstract class AuthFirebaseService {
Future<Either> signup(CreateUserReq createUserReq);
Future<Either> signin(SigninUserReq signinUserReq);
Future<Either> getUser();
}

class AuthFirebaseServiceImpl extends AuthFirebaseService {
Expand Down Expand Up @@ -60,4 +65,33 @@ class AuthFirebaseServiceImpl extends AuthFirebaseService {
return left(message);
}
}

@override
Future<Either> getUser() async {
try {
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;

var user = await firebaseFirestore
.collection('Users')
.doc(
firebaseAuth.currentUser?.uid,
)
.get();

UserModel userModel = UserModel.fromJson(user.data()!);

userModel.imageURL =
firebaseAuth.currentUser?.photoURL ?? AppUrls.defaultAvatar;

UserEntity userEntity = userModel.toEntity();

return Right(userEntity);
} catch (e) {
if (kDebugMode) {
print("error: $e");
}
return const Left('An error occurred');
}
}
}
45 changes: 42 additions & 3 deletions lib/data/sources/song/song_firebase_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ abstract class SongFirebaseService {
Future<Either> getPlayList();
Future<Either> addOrRemoveFavoriteSong(String songId);
Future<bool> isFavoriteSong(String songId);
Future<Either> getUserFavoriteSong();
}

class SongFirebaseServiceImpl extends SongFirebaseService {
Expand Down Expand Up @@ -93,7 +94,7 @@ class SongFirebaseServiceImpl extends SongFirebaseService {

late bool isFavorite;

var user = await firebaseAuth.currentUser;
var user = firebaseAuth.currentUser;

String uID = user!.uid;

Expand All @@ -111,7 +112,11 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
await favoriteSongs.docs.first.reference.delete();
isFavorite = false;
} else {
await firebaseFirestore.collection('Users').doc(uID).collection('Favorites').add(
await firebaseFirestore
.collection('Users')
.doc(uID)
.collection('Favorites')
.add(
{
'songId': songId,
'addedDate': Timestamp.now(),
Expand All @@ -133,7 +138,7 @@ class SongFirebaseServiceImpl extends SongFirebaseService {

final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;

var user = await firebaseAuth.currentUser;
var user = firebaseAuth.currentUser;

String uID = user!.uid;

Expand All @@ -156,4 +161,38 @@ class SongFirebaseServiceImpl extends SongFirebaseService {
return false;
}
}

@override
Future<Either> getUserFavoriteSong() async {
try {
List<SongEntity> favoriteSong = [];
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;

final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;

var user = firebaseAuth.currentUser;

String uID = user!.uid;

QuerySnapshot favoriteSnapshot = await firebaseFirestore
.collection('Users')
.doc(uID)
.collection('Favorites')
.get();

for (var element in favoriteSnapshot.docs) {
String songId = element['songId'];
var song =
await firebaseFirestore.collection('Songs').doc(songId).get();
SongModel songModel = SongModel.fromJson(song.data()!);
songModel.isFavorite = true;
songModel.songId = songId;
favoriteSong.add(songModel.toEntity());
}

return Right(favoriteSong);
} catch (e) {
return const Left('An error occurred');
}
}
}
4 changes: 2 additions & 2 deletions lib/domain/entities/auth/user.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
class UserEntity {
String? userId;
String? imageURL;
String? fullName;
String? email;

UserEntity({
this.userId,
this.imageURL,
this.fullName,
this.email,
});
Expand Down
3 changes: 3 additions & 0 deletions lib/domain/repository/auth/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ import 'package:spotify_with_flutter/data/models/auth/signin_user_req.dart';

abstract class AuthRepository {
Future<Either> signup(CreateUserReq createUserReq);

Future<Either> signin(SigninUserReq signinUserReq);

Future<Either> getUser();
}
1 change: 1 addition & 0 deletions lib/domain/repository/song/song.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ abstract class SongsRepository {
Future<Either> getPlayList();
Future<Either> addOrRemoveFavoriteSong(String songId);
Future<bool> isFavoriteSong(String songId);
Future<Either> getUserFavoriteSongs();
}
11 changes: 11 additions & 0 deletions lib/domain/usecase/auth/get_user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:dartz/dartz.dart';
import 'package:spotify_with_flutter/core/usecase/usecase.dart';
import 'package:spotify_with_flutter/domain/repository/auth/auth.dart';
import 'package:spotify_with_flutter/service_locator.dart';

class GetUserUseCase implements UseCase<Either, dynamic> {
@override
Future<Either> call({params}) async {
return await sl<AuthRepository>().getUser();
}
}
Loading

0 comments on commit ba87932

Please sign in to comment.