Skip to content

Commit

Permalink
Update song search screen
Browse files Browse the repository at this point in the history
  • Loading branch information
up2code committed Apr 17, 2024
1 parent 6f87488 commit a449f75
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 18 deletions.
2 changes: 2 additions & 0 deletions lib/src/common_widgets/search_appbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class SearchAppBar extends StatefulWidget implements PreferredSizeWidget {
static const iconClearKey = Key('icon-clear');
static const iconSearchKey = Key('icon-search');
static const textTitleKey = Key('text-title');
static const searchInputKey = Key('search-input');

const SearchAppBar(
{super.key,
Expand Down Expand Up @@ -58,6 +59,7 @@ class _SearchAppBarState extends State<SearchAppBar> {
return AppBar(
title: (showTextInput)
? TextField(
key: SearchAppBar.searchInputKey,
controller: _textEditingController,
onChanged: (value) {
if (widget.onChanged != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class HomeScreen extends StatelessWidget {
ShortcutMenuButton(
title: 'Songs',
iconData: Icons.music_note,
onPressed: () {},
onPressed: () {
context.goSongSearchScreen();
},
),
ShortcutMenuButton(
title: 'Artists',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:vocadb_app/src/features/settings/data/user_settings_repository.dart';
import 'package:vocadb_app/src/features/songs/domain/songs_list_params.dart';

class SongsListParamsState extends StateNotifier<SongsListParams> {
SongsListParamsState({String lang = 'Default'})
: super(SongsListParams(lang: lang));
void updateSongTypes(String? value) =>
state = state.copyWith(songTypes: value);
void updateQuery(String value) => state = state.copyWith(query: value);
void updateSort(String value) => state = state.copyWith(sort: value);
void clearQuery() => state = state.copyWith(query: null);
}

final songsListParamsStateProvider = StateNotifierProvider.autoDispose<
SongsListParamsState, SongsListParams>((ref) {
final preferredLang = ref.watch(userSettingsRepositoryProvider
.select((value) => value.currentPreferredLang));
return SongsListParamsState(lang: preferredLang);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:vocadb_app/src/common_widgets/async_value_widget.dart';
import 'package:vocadb_app/src/common_widgets/search_appbar.dart';
import 'package:vocadb_app/src/features/songs/data/song_repository.dart';
import 'package:vocadb_app/src/features/songs/domain/song.dart';
import 'package:vocadb_app/src/features/songs/presentation/songs_list/songs_list_view.dart';
import 'package:vocadb_app/src/features/songs/presentation/songs_list_screen/songs_list_params_state.dart';

class SongsListScreen extends ConsumerWidget {
const SongsListScreen({super.key, this.onSelectSong});

static const filterKey = Key('icon-filter-key');

final Function(Song)? onSelectSong;

@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: SearchAppBar(
titleText: 'Songs',
actions: [
IconButton(
key: filterKey,
icon: const Icon(Icons.tune),
onPressed: () => {}
),
],
onSubmitted: (value) {
ref.read(songsListParamsStateProvider.notifier).updateQuery(value);
},
onCleared: () {
ref.read(songsListParamsStateProvider.notifier).clearQuery();
},
),
body: Consumer(builder: ((context, ref, child) {
final value = ref.watch(songsListProvider);
return AsyncValueWidget(
value: value,
data: (data) {
return SongListView(
songs: data,
onSelect: (song) => {}
);
});
})),
);
}
}

// State
final songsListProvider = FutureProvider.autoDispose<List<Song>>((ref) {
final params = ref.watch(songsListParamsStateProvider);
final songRepository = ref.watch(songRepositoryProvider);
return songRepository.fetchSongsList(params: params);
});
5 changes: 5 additions & 0 deletions lib/src/routing/app_route_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ extension AppRouteContext on BuildContext {

}

Future<void> goSongSearchScreen() async {
goNamed(AppRoute.songsList.name);

}

Future<void> goArtistsListFilterScreen() async {
goNamed(AppRoute.artistsListFilter.name);

Expand Down
45 changes: 28 additions & 17 deletions lib/src/routing/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:vocadb_app/src/features/albums/domain/album.dart';
import 'package:vocadb_app/src/features/albums/presentation/album_detail_screen/album_detail_screen.dart';
import 'package:vocadb_app/src/features/songs/presentation/songs_list_screen/songs_list_screen.dart';
import 'package:vocadb_app/src/features/users/presentation/user_albums_screen/user_albums_filter_screen.dart';
import 'package:vocadb_app/src/features/users/presentation/user_albums_screen/user_albums_screen.dart';
import 'package:vocadb_app/src/features/artists/presentation/artist_detail_screen/artist_detail_screen.dart';
Expand Down Expand Up @@ -35,6 +36,7 @@ enum AppRoute {
songDetail,
account,
signIn,
songsList,
albumDetail,
artistDetail,
artistsList,
Expand Down Expand Up @@ -84,22 +86,31 @@ final goRouterProvider = Provider.autoDispose<GoRouter>(
builder: (context, state) => const MainScreen(),
routes: [
GoRoute(
path: 'S/:id',
name: AppRoute.songDetail.name,
path: 'S',
name: AppRoute.songsList.name,
builder: (context, state) {
final songId = state.pathParameters['id']!;
return SongDetailScreen(song: Song(id: int.parse(songId)));
return SongsListScreen(onSelectSong: (song) {
context.goSongDetail(song);
});
},
routes: [
GoRoute(
path: ':id',
name: AppRoute.songDetail.name,
builder: (context, state) {
final songId = state.pathParameters['id']!;
return SongDetailScreen(song: Song(id: int.parse(songId)));
},
),
],
),
GoRoute(
path: 'Ar',
name: AppRoute.artistsList.name,
builder: (context, state) {
return ArtistsListScreen(
onSelectArtist: (artist) {
context.goArtistDetail(artist);
}
);
return ArtistsListScreen(onSelectArtist: (artist) {
context.goArtistDetail(artist);
});
},
routes: [
GoRoute(
Expand All @@ -111,14 +122,14 @@ final goRouterProvider = Provider.autoDispose<GoRouter>(
child: const ArtistsFilterScreen(),
),
),
GoRoute(
path: ':id',
name: AppRoute.artistDetail.name,
builder: (context, state) {
final artistId = state.pathParameters['id']!;
return ArtistDetailScreen(artistId: artistId);
},
),
GoRoute(
path: ':id',
name: AppRoute.artistDetail.name,
builder: (context, state) {
final artistId = state.pathParameters['id']!;
return ArtistDetailScreen(artistId: artistId);
},
),
],
),
GoRoute(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vocadb_app/src/common_widgets/search_appbar.dart';
import 'package:vocadb_app/src/features/settings/data/user_settings_repository.dart';
import 'package:vocadb_app/src/features/songs/data/song_repository.dart';
import 'package:vocadb_app/src/features/songs/presentation/song_tile/song_tile.dart';
import 'package:vocadb_app/src/features/songs/presentation/songs_list_screen/songs_list_screen.dart';

class SongsListScreenRobot {
final WidgetTester tester;

SongsListScreenRobot(this.tester);

Future<void> pumpSongsListScreen(
{SongRepository? songRepository}) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
if (songRepository != null)
songRepositoryProvider.overrideWithValue(songRepository),
userSettingsRepositoryProvider
.overrideWithValue(UserSettingsRepository())
],
child: const MaterialApp(
home: SongsListScreen(),
),
),
);

await tester.pump();
await tester.pump();
await tester.pump();
}

Future<void> expectSongsDisplayCountAtLeast(int count) async {
final finder = find.byType(SongTile);
expect(finder, findsAtLeastNWidgets(count));
}

Future<void> expectSongsDisplayCount(int count) async {
final finder = find.byType(SongTile);
expect(finder, findsNWidgets(count));
}

Future<void> tapSearchIcon() async {
final finder = find.byKey(SearchAppBar.iconSearchKey);
expect(finder, findsOneWidget);
await tester.tap(finder);
await tester.pump();
}

Future<void> typingSearchText(String text) async {
final finder = find.byKey(SearchAppBar.searchInputKey);
expect(finder, findsOneWidget);
await tester.enterText(finder, text);
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pump();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:vocadb_app/src/features/songs/data/constants/fake_songs_list.dart';
import 'package:vocadb_app/src/features/songs/domain/songs_list_params.dart';

import '../../../../mocks.dart';
import 'songs_list_screen_robot.dart';

void main() {
testWidgets('song list screen test', (tester) async {
registerFallbackValue(FakeSongsListParams());

final r = SongsListScreenRobot(tester);
final songRepository = MockSongRepository();

when(() => songRepository.fetchSongsList(
params: any(named: 'params', that: isNotNull),
)).thenAnswer((_) => Future.value(kFakeSongsList));

await r.pumpSongsListScreen(songRepository: songRepository);

await r.expectSongsDisplayCountAtLeast(3);

expect(
verify(() =>
songRepository.fetchSongsList(params: captureAny(named: 'params')))
.captured,
[
const SongsListParams(),
]);
});

}
3 changes: 3 additions & 0 deletions test/src/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:vocadb_app/src/features/entries/data/entry_repository.dart';
import 'package:vocadb_app/src/features/releaseEvents/data/release_event_repository.dart';
import 'package:vocadb_app/src/features/settings/data/user_settings_repository.dart';
import 'package:vocadb_app/src/features/songs/data/song_repository.dart';
import 'package:vocadb_app/src/features/songs/domain/songs_list_params.dart';
import 'package:vocadb_app/src/features/tags/data/tag_repository.dart';
import 'package:vocadb_app/src/features/users/data/user_repository.dart';
import 'package:vocadb_app/src/features/users/domain/rated_songs_list_params.dart';
Expand All @@ -36,6 +37,8 @@ class RatedSongsListParamsFake extends Fake implements RatedSongsListParams {}

class FakeArtistsListParams extends Fake implements ArtistsListParams {}

class FakeSongsListParams extends Fake implements SongsListParams {}

class MockUserSettingsRepository extends Mock
implements UserSettingsRepository {}

Expand Down

0 comments on commit a449f75

Please sign in to comment.