Skip to content

Commit

Permalink
Merge pull request #6 from priyanshuverma-dev/feat-search
Browse files Browse the repository at this point in the history
[Feat] search function
  • Loading branch information
PriyanshuPz authored Jun 5, 2024
2 parents 20eccd5 + 5ae2bfd commit 04367b2
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 43 deletions.
Binary file added assets/app_icon.ico
Binary file not shown.
9 changes: 6 additions & 3 deletions lib/apis/song_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final songAPIProvider = Provider((ref) {
abstract class ISongAPI {
FutureEither<List<SongModel>> fetchInitData();
FutureEither<List<SongModel>> fetchSearchData({required String query});

FutureEither<List<SongModel>> fetchSongRecommedationData(
{required String id});
}
Expand Down Expand Up @@ -52,14 +53,16 @@ class SongAPI extends ISongAPI {
@override
FutureEither<List<SongModel>> fetchSearchData({required String query}) async {
try {
final uri =
Uri.https(Constants.serverUrl, 'api/search/songs', {"query": query});
final uri = Uri.https(Constants.serverUrl, 'api/search/songs', {
"query": query,
"limit": "24",
});
final res = await http.get(uri);
if (res.statusCode != 200) throw Error();

Map<String, dynamic> jsonMap = jsonDecode(res.body);
// Extract the list of songs from the Map
List<dynamic> songsObj = jsonMap['data']['songs']['results'];
List<dynamic> songsObj = jsonMap['data']['results'];
List<SongModel> songs =
songsObj.map((song) => SongModel.fromMap(song)).toList();
return right(songs);
Expand Down
2 changes: 2 additions & 0 deletions lib/core/constants.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class Constants {
static String serverUrl = "saavn.dev";
static String appName = "Saavn Desktop";
static String appIconPath = "assets/app_icon.ico";
}

class SharedPrefs {
Expand Down
8 changes: 8 additions & 0 deletions lib/frame/commons.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_riverpod/flutter_riverpod.dart';
import 'package:saavn/functions/explore/views/explore_view.dart';
import 'package:saavn/functions/player/views/playlist_view.dart';
import 'package:saavn/functions/search/views/search_view.dart';
import 'package:saavn/functions/settings/views/settings_view.dart';

final appScreenConfigProvider =
Expand All @@ -27,6 +28,13 @@ enum Screens {
name: "Settings",
view: SettingsView(),
),
),

search(
AppScreen(
name: "Search Songs",
view: SearchView(),
),
);

final AppScreen screen;
Expand Down
29 changes: 29 additions & 0 deletions lib/frame/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ class _HomeFrameState extends ConsumerState<HomeFrame> {
.goto(screen: Screens.settings);
}

void onPressSearch() {
if (ref.watch(appScreenConfigProvider) == Screens.search) {
return ref
.watch(appScreenConfigProvider.notifier)
.goto(screen: Screens.explore);
}
return ref
.watch(appScreenConfigProvider.notifier)
.goto(screen: Screens.search);
}

@override
Widget build(BuildContext context) {
final screen = ref.watch(appScreenConfigProvider).screen;
Expand All @@ -32,6 +43,24 @@ class _HomeFrameState extends ConsumerState<HomeFrame> {
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: onPressSearch,
icon: const Stack(
children: [
Icon(Icons.search),
Positioned(
// draw a red marble
top: 0.0,
right: 0.0,
child: Icon(Icons.brightness_1,
size: 8.0, color: Colors.redAccent),
)
],
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
Expand Down
38 changes: 38 additions & 0 deletions lib/functions/search/controllers/search_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:saavn/apis/song_api.dart';
import 'package:saavn/models/song_model.dart';

final searchControllerProvider =
StateNotifierProvider<SearchController, bool>((ref) {
return SearchController(
songAPI: ref.watch(songAPIProvider),
);
});

final searchDataProvider = FutureProvider<List<SongModel>>((ref) async {
return ref.watch(searchControllerProvider.notifier).searchData;
});

class SearchController extends StateNotifier<bool> {
final SongAPI _songAPI;

SearchController({required SongAPI songAPI})
: _songAPI = songAPI,
super(false);

// ADD SEARCH METHODS

List<SongModel> searchData = [];

Future<void> searchSong({required String query}) async {
state = true;
final fetchedsongs = await _songAPI.fetchSearchData(query: query);

fetchedsongs.fold(
(l) => throw Error.throwWithStackTrace(l.message, l.stackTrace),
(r) => searchData = r,
);

state = false;
}
}
67 changes: 67 additions & 0 deletions lib/functions/search/views/search_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:saavn/core/core.dart';
import 'package:saavn/functions/player/controllers/player_controller.dart';
import 'package:saavn/functions/search/controllers/search_controller.dart';
import 'package:saavn/functions/search/widgets/searchbar.dart';
import 'package:saavn/functions/search/widgets/song_tile.dart';

class SearchView extends ConsumerStatefulWidget {
const SearchView({super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() => _SearchViewState();
}

class _SearchViewState extends ConsumerState<SearchView> {
final TextEditingController _searchController = TextEditingController();

void search(String q) async {
await ref.watch(searchControllerProvider.notifier).searchSong(query: q);
ref.invalidate(searchDataProvider);
}

@override
Widget build(BuildContext context) {
return Column(
children: [
BaseSearchBar(
controller: _searchController,
onPressed: () => search(_searchController.text),
onSubmit: (value) => search(value),
),
Expanded(
child: (ref.watch(searchDataProvider).when(
skipLoadingOnRefresh: false,
data: (songs) {
if (songs.isEmpty) {
return const Center(
child: Text('Search results will be displayed here!'),
);
}

return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
for (final song in songs)
SearchSongTile(
song: song,
onTap: () => ref
.read(playerControllerProvider.notifier)
.setSong(song: song),
),
const Center(
child: Text(
'Only 24 results because this feature is in test phase.'),
)
],
);
},
error: (error, st) => ErrorPage(error: error.toString()),
loading: () => const Loader(),
)),
),
],
);
}
}
42 changes: 42 additions & 0 deletions lib/functions/search/widgets/searchbar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';

class BaseSearchBar extends StatelessWidget {
final TextEditingController controller;
final void Function(String)? onSubmit;
final void Function()? onPressed;

const BaseSearchBar({
super.key,
required this.controller,
required this.onSubmit,
required this.onPressed,
});

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: Theme.of(context).primaryColorLight.withOpacity(.2),
),
borderRadius: BorderRadius.circular(25),
),
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 7),
child: TextField(
decoration: InputDecoration(
hintText: 'Search',
border: InputBorder.none,
contentPadding: const EdgeInsets.all(10),
suffixIcon: IconButton(
onPressed: onPressed,
icon: const Icon(Icons.search),
),
),
controller: controller,
onSubmitted: onSubmit,
),
);
}
}
22 changes: 22 additions & 0 deletions lib/functions/search/widgets/song_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:saavn/models/song_model.dart';

class SearchSongTile extends StatelessWidget {
final SongModel song;
final VoidCallback onTap;
const SearchSongTile({super.key, required this.song, required this.onTap});

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(song.name),
subtitle: Text("${song.label} - ${song.year}"),
leading: CircleAvatar(
radius: 25,
backgroundColor: Theme.of(context).primaryColorDark,
foregroundImage: NetworkImage(song.image[0].url),
),
onTap: onTap,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class SettingsController extends StateNotifier<bool> {
Future<SongQualityType> getSongQuality() async {
final prefs = await SharedPreferences.getInstance();
final val = prefs.getString(SharedPrefs.songQuality);
print("SETTED: $val");
if (val == null) return SongQualityType.high;
var quality = SongQualityType.values
.where((element) => element.type == val)
Expand Down
25 changes: 25 additions & 0 deletions lib/initialization.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:system_theme/system_theme.dart';
import 'package:window_manager/window_manager.dart';

Future<void> initialiseAppFunctions() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemTheme.accentColor.load();
await initWindowManager();
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
}

Future<void> initWindowManager() async {
await windowManager.ensureInitialized();

WindowOptions windowOptions = const WindowOptions(
center: true,
title: "Saavn Music Desktop",
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
37 changes: 6 additions & 31 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:saavn/frame/home.dart';
import 'package:system_theme/system_theme.dart';
import 'package:window_manager/window_manager.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
final session = await AudioSession.instance;

await session.configure(const AudioSessionConfiguration.music());
await SystemTheme.accentColor.load();
await windowManager.ensureInitialized();

await Window.initialize();
await Window.setEffect(
effect: WindowEffect.aero,
color: Colors.black45,
dark: true,
);
import 'package:saavn/frame/home.dart';
import 'package:saavn/initialization.dart';

WindowOptions windowOptions = const WindowOptions(
center: true,
title: "Saavn Music Desktop",
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
void main() async {
await initialiseAppFunctions();
runApp(
const ProviderScope(
child: MyApp(),
Expand All @@ -49,11 +28,7 @@ class MyApp extends StatelessWidget {
useMaterial3: true,
colorSchemeSeed: SystemTheme.accentColor.accent,
),
darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
scaffoldBackgroundColor: Colors.transparent,
iconTheme: IconThemeData(
color: Theme.of(context).primaryColorLight,
)),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark,
home: const HomeFrame(),
);
Expand Down
12 changes: 6 additions & 6 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -268,26 +268,26 @@ packages:
dependency: "direct main"
description:
name: just_audio
sha256: "5abfab1d199e01ab5beffa61b3e782350df5dad036cb8c83b79fa45fc656614e"
sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b
url: "https://pub.dev"
source: hosted
version: "0.9.38"
version: "0.9.37"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790"
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
url: "https://pub.dev"
source: hosted
version: "4.3.0"
version: "4.2.2"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "0edb481ad4aa1ff38f8c40f1a3576013c3420bf6669b686fe661627d49bc606c"
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
url: "https://pub.dev"
source: hosted
version: "0.4.11"
version: "0.4.9"
just_audio_windows:
dependency: "direct main"
description:
Expand Down
Loading

0 comments on commit 04367b2

Please sign in to comment.