Skip to content

Commit

Permalink
Search functionality added.
Browse files Browse the repository at this point in the history
Queue page added.
Updated README.md.
Bug fixes and improvements.
  • Loading branch information
ShokhrukhbekYuldoshev committed Apr 8, 2024
1 parent 97f5bc8 commit 8f22bf3
Show file tree
Hide file tree
Showing 27 changed files with 736 additions and 535 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"albumart"
]
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Meloplay is a local music player app that plays music from your device built wit
## 📱 Platforms

- Android
- iOS
- iOS (not tested)

## ✨ Features

Expand All @@ -15,9 +15,9 @@ Meloplay is a local music player app that plays music from your device built wit
- [x] Lock screen controls
- [x] Play, pause, skip, previous, seek
- [x] Shuffle and repeat
- [ ] Search for music, playlists, artists, albums, genres
- [x] Search for music, artists, albums, genres
- [ ] Playlists (Read, create, rename, delete, add songs, remove songs)
- [ ] Queue
- [ ] Queue (List, add songs, remove songs, change order)
- [x] Favorites (Add songs, remove songs)
- [x] Recently played
- [ ] Most played
Expand Down
Binary file added assets/images/earphones.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/heart.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:meloplay/src/bloc/search/search_bloc.dart';
import 'package:permission_handler/permission_handler.dart';

import 'package:meloplay/src/app.dart';
Expand Down Expand Up @@ -61,6 +62,9 @@ Future<void> main() async {
BlocProvider(
create: (context) => sl<RecentsBloc>(),
),
BlocProvider(
create: (context) => sl<SearchBloc>(),
),
],
child: const MyApp(),
),
Expand Down
17 changes: 17 additions & 0 deletions lib/src/bloc/search/search_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:bloc/bloc.dart';
import 'package:meloplay/src/data/models/search_result.dart';
import 'package:meloplay/src/data/repositories/search_repository.dart';
import 'package:meta/meta.dart';

part 'search_event.dart';
part 'search_state.dart';

class SearchBloc extends Bloc<SearchEvent, SearchState> {
SearchBloc({required SearchRepository repository}) : super(SearchInitial()) {
on<SearchQueryChanged>((event, emit) async {
emit(SearchLoading());
final result = await repository.search(event.query);
emit(SearchLoaded(searchResult: result));
});
}
}
10 changes: 10 additions & 0 deletions lib/src/bloc/search/search_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of 'search_bloc.dart';

@immutable
sealed class SearchEvent {}

class SearchQueryChanged extends SearchEvent {
final String query;

SearchQueryChanged(this.query);
}
18 changes: 18 additions & 0 deletions lib/src/bloc/search/search_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
part of 'search_bloc.dart';

@immutable
sealed class SearchState {}

final class SearchInitial extends SearchState {}

final class SearchLoading extends SearchState {}

final class SearchLoaded extends SearchState {
final SearchResultModel searchResult;
SearchLoaded({required this.searchResult});
}

final class SearchError extends SearchState {
final String message;
SearchError({required this.message});
}
4 changes: 4 additions & 0 deletions lib/src/core/di/service_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import 'package:meloplay/src/bloc/favorites/favorites_bloc.dart';
import 'package:meloplay/src/bloc/home/home_bloc.dart';
import 'package:meloplay/src/bloc/player/player_bloc.dart';
import 'package:meloplay/src/bloc/recents/recents_bloc.dart';
import 'package:meloplay/src/bloc/search/search_bloc.dart';
import 'package:meloplay/src/bloc/song/song_bloc.dart';
import 'package:meloplay/src/bloc/theme/theme_bloc.dart';
import 'package:meloplay/src/data/repositories/favorites_repository.dart';
import 'package:meloplay/src/data/repositories/home_repository.dart';
import 'package:meloplay/src/data/repositories/player_repository.dart';
import 'package:meloplay/src/data/repositories/recents_repository.dart';
import 'package:meloplay/src/data/repositories/search_repository.dart';
import 'package:meloplay/src/data/repositories/song_repository.dart';
import 'package:meloplay/src/data/repositories/theme_repository.dart';
import 'package:on_audio_query/on_audio_query.dart';
Expand All @@ -23,6 +25,7 @@ void init() {
sl.registerFactory(() => SongBloc(repository: sl()));
sl.registerFactory(() => FavoritesBloc(repository: sl()));
sl.registerFactory(() => RecentsBloc(repository: sl()));
sl.registerFactory(() => SearchBloc(repository: sl()));

// Repository
sl.registerLazySingleton(() => ThemeRepository());
Expand All @@ -33,6 +36,7 @@ void init() {
sl.registerLazySingleton(() => SongRepository());
sl.registerLazySingleton(() => FavoritesRepository());
sl.registerLazySingleton(() => RecentsRepository());
sl.registerLazySingleton(() => SearchRepository());

// Third Party
sl.registerLazySingleton(() => OnAudioQuery());
Expand Down
9 changes: 9 additions & 0 deletions lib/src/core/extensions/list_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension ListExtension<T> on List<T> {
void swap(int first, int second) {
if (first < length && second < length) {
var temp = this[first];
this[first] = this[second];
this[second] = temp;
}
}
}
19 changes: 13 additions & 6 deletions lib/src/core/router/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:meloplay/src/presentation/pages/playlist_details_page.dart';
import 'package:meloplay/src/presentation/pages/queue_page.dart';
import 'package:meloplay/src/presentation/pages/search_page.dart';
import 'package:on_audio_query/on_audio_query.dart';

import 'package:meloplay/src/presentation/pages/about_page.dart';
Expand All @@ -9,7 +11,6 @@ import 'package:meloplay/src/presentation/pages/favorites_page.dart';
import 'package:meloplay/src/presentation/pages/genre_page.dart';
import 'package:meloplay/src/presentation/pages/home/home_page.dart';
import 'package:meloplay/src/presentation/pages/player_page.dart';
import 'package:meloplay/src/presentation/pages/playlists_page.dart';
import 'package:meloplay/src/presentation/pages/recents_page.dart';
import 'package:meloplay/src/presentation/pages/themes_page.dart';
import 'package:meloplay/src/presentation/pages/splash_page.dart';
Expand All @@ -18,7 +19,6 @@ class AppRouter {
static const String splashRoute = '/';
static const String homeRoute = '/home';
static const String favoritesRoute = '/favorites';
static const String playlistsRoute = '/playlists';
static const String recentsRoute = '/recents';
static const String playerRoute = '/player';
static const String artistRoute = '/artist';
Expand All @@ -27,6 +27,8 @@ class AppRouter {
static const String aboutRoute = '/about';
static const String settingsRoute = '/settings';
static const String playlistDetailsRoute = '/playlist';
static const String queueRoute = '/queue';
static const String searchRoute = '/search';

static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
Expand All @@ -42,10 +44,7 @@ class AppRouter {
return MaterialPageRoute<dynamic>(
builder: (_) => const FavoritesPage(),
);
case playlistsRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const PlaylistsPage(),
);

case recentsRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const RecentsPage(),
Expand Down Expand Up @@ -86,6 +85,14 @@ class AppRouter {
playlist: settings.arguments as PlaylistModel,
),
);
case queueRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const QueuePage(),
);
case searchRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const SearchPage(),
);
default:
return MaterialPageRoute<dynamic>(
builder: (_) => Scaffold(
Expand Down
28 changes: 14 additions & 14 deletions lib/src/core/theme/themes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:hive/hive.dart';
import 'package:meloplay/src/data/services/hive_box.dart';

class Themes {
static final List<CustomTheme> _themes = [
static final List<ThemeColor> _themes = [
PurpleTheme(),
BlueTheme(),
GreenTheme(),
Expand All @@ -30,7 +30,7 @@ class Themes {
static get themes => _themes;
static List<String> get themeNames => _themeNames;

static CustomTheme getThemeFromKey(String key) {
static ThemeColor getThemeFromKey(String key) {
switch (key) {
case 'Purple':
return _themes[0];
Expand Down Expand Up @@ -66,21 +66,21 @@ class Themes {
return themeName ?? 'Purple';
}

static CustomTheme getTheme() {
static ThemeColor getTheme() {
final Box<dynamic> box = Hive.box(HiveBox.boxName);
final String? themeName = box.get(HiveBox.themeKey) as String?;
return getThemeFromKey(themeName ?? 'Purple');
}
}

abstract class CustomTheme {
abstract class ThemeColor {
final String themeName;
final Color primaryColor;
final Color secondaryColor;
final ColorScheme colorScheme;
final LinearGradient linearGradient;

const CustomTheme({
const ThemeColor({
required this.themeName,
required this.primaryColor,
required this.secondaryColor,
Expand All @@ -89,7 +89,7 @@ abstract class CustomTheme {
});
}

class PurpleTheme extends CustomTheme {
class PurpleTheme extends ThemeColor {
PurpleTheme()
: super(
themeName: 'Purple',
Expand All @@ -110,7 +110,7 @@ class PurpleTheme extends CustomTheme {
);
}

class BlueTheme extends CustomTheme {
class BlueTheme extends ThemeColor {
BlueTheme()
: super(
themeName: 'Blue',
Expand All @@ -131,7 +131,7 @@ class BlueTheme extends CustomTheme {
);
}

class GreenTheme extends CustomTheme {
class GreenTheme extends ThemeColor {
GreenTheme()
: super(
themeName: 'Green',
Expand All @@ -152,7 +152,7 @@ class GreenTheme extends CustomTheme {
);
}

class OrangeTheme extends CustomTheme {
class OrangeTheme extends ThemeColor {
OrangeTheme()
: super(
themeName: 'Orange',
Expand All @@ -173,7 +173,7 @@ class OrangeTheme extends CustomTheme {
);
}

class YellowTheme extends CustomTheme {
class YellowTheme extends ThemeColor {
YellowTheme()
: super(
themeName: 'Yellow',
Expand All @@ -194,7 +194,7 @@ class YellowTheme extends CustomTheme {
);
}

class TealTheme extends CustomTheme {
class TealTheme extends ThemeColor {
TealTheme()
: super(
themeName: 'Teal',
Expand All @@ -215,7 +215,7 @@ class TealTheme extends CustomTheme {
);
}

class RedTheme extends CustomTheme {
class RedTheme extends ThemeColor {
RedTheme()
: super(
themeName: 'Red',
Expand All @@ -236,7 +236,7 @@ class RedTheme extends CustomTheme {
);
}

class BlackTheme extends CustomTheme {
class BlackTheme extends ThemeColor {
BlackTheme()
: super(
themeName: 'Black',
Expand All @@ -257,7 +257,7 @@ class BlackTheme extends CustomTheme {
);
}

class WhiteTheme extends CustomTheme {
class WhiteTheme extends ThemeColor {
WhiteTheme()
: super(
themeName: 'White',
Expand Down
15 changes: 15 additions & 0 deletions lib/src/data/models/search_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:on_audio_query/on_audio_query.dart';

class SearchResultModel {
final List<SongModel> songs;
final List<ArtistModel> artists;
final List<AlbumModel> albums;
final List<GenreModel> genres;

SearchResultModel({
required this.songs,
required this.artists,
required this.albums,
required this.genres,
});
}
15 changes: 13 additions & 2 deletions lib/src/data/repositories/player_repository.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:on_audio_query/on_audio_query.dart';
Expand All @@ -23,6 +25,7 @@ abstract class JustAudioPlayer {
Stream<bool> get playing;
Stream<int?> get currentIndex;
Stream<SequenceState?> get sequenceState;
List<SongModel> get playlist;
Stream<ProcessingState> get processingStateStream;
Future<void> dispose();
Future<void> setVolume(double volume);
Expand Down Expand Up @@ -64,15 +67,20 @@ class JustAudioPlayerImpl implements JustAudioPlayer {
List<AudioSource> sources = [];

for (var song in playlist) {
var artUri = 'content://media/external/audio/albumart/';

if (song.albumId != null) {
artUri += song.albumId.toString();
}

sources.add(
AudioSource.uri(
Uri.parse(song.uri!),
tag: MediaItem(
id: song.id.toString(),
title: song.title,
album: song.album,
// TODO: fix
// artUri: Uri.parse(song.uri!),
artUri: Platform.isAndroid ? Uri.parse(artUri) : null,
artist: song.artist,
duration: Duration(milliseconds: song.duration!),
genre: song.genre,
Expand Down Expand Up @@ -193,4 +201,7 @@ class JustAudioPlayerImpl implements JustAudioPlayer {
Future<void> setShuffleModeEnabled(bool enabled) async {
await _player.setShuffleModeEnabled(enabled);
}

@override
List<SongModel> get playlist => currentPlaylist;
}
Loading

0 comments on commit 8f22bf3

Please # to comment.