diff --git a/lib/src/features/home/presentation/home_screen/home_screen.dart b/lib/src/features/home/presentation/home_screen/home_screen.dart index 3683b9d0..69a61769 100644 --- a/lib/src/features/home/presentation/home_screen/home_screen.dart +++ b/lib/src/features/home/presentation/home_screen/home_screen.dart @@ -60,7 +60,9 @@ class HomeScreen extends StatelessWidget { ShortcutMenuButton( title: 'Events', iconData: Icons.event, - onPressed: () {}, + onPressed: () { + context.goReleaseEventList(); + }, ) ], ), diff --git a/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_params_state.dart b/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_params_state.dart new file mode 100644 index 00000000..a52a9bc0 --- /dev/null +++ b/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_params_state.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:vocadb_app/src/features/releaseEvents/domain/release_events_list_params.dart'; +import 'package:vocadb_app/src/features/settings/data/user_settings_repository.dart'; + +class ReleaseEventsListParamsState extends StateNotifier { + ReleaseEventsListParamsState({String lang = 'Default'}) + : super(ReleaseEventsListParams(lang: lang)); + 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 releaseEventsListParamsStateProvider = StateNotifierProvider.autoDispose< + ReleaseEventsListParamsState, ReleaseEventsListParams>((ref) { + final preferredLang = ref.watch(userSettingsRepositoryProvider + .select((value) => value.currentPreferredLang)); + return ReleaseEventsListParamsState(lang: preferredLang); +}); diff --git a/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen.dart b/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen.dart new file mode 100644 index 00000000..67d12ed0 --- /dev/null +++ b/lib/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen.dart @@ -0,0 +1,55 @@ + +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/artists/data/artist_repository.dart'; +import 'package:vocadb_app/src/features/artists/domain/artist.dart'; +import 'package:vocadb_app/src/features/artists/presentation/artists_list/artist_list_view.dart'; +import 'package:vocadb_app/src/features/artists/presentation/artists_list_screen/artists_list_params_state.dart'; +import 'package:vocadb_app/src/features/releaseEvents/data/release_event_repository.dart'; +import 'package:vocadb_app/src/features/releaseEvents/domain/release_event.dart'; +import 'package:vocadb_app/src/features/releaseEvents/presentation/release_events_list/release_events_list_view.dart'; +import 'package:vocadb_app/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_params_state.dart'; +import 'package:vocadb_app/src/routing/app_route_context.dart'; + +class ReleaseEventsListScreen extends ConsumerWidget { + const ReleaseEventsListScreen({super.key, this.onSelectEvent}); + + static const filterKey = Key('icon-filter-key'); + + final Function(ReleaseEvent)? onSelectEvent; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: SearchAppBar( + titleText: 'Events', + onSubmitted: (value) { + ref.read(releaseEventsListParamsStateProvider.notifier).updateQuery(value); + }, + onCleared: () { + ref.read(releaseEventsListParamsStateProvider.notifier).clearQuery(); + }, + ), + body: Consumer(builder: ((context, ref, child) { + final value = ref.watch(releaseEventsListProvider); + return AsyncValueWidget( + value: value, + data: (data) { + return ReleaseEventsListView( + events: data, + onSelect: (event) => onSelectEvent?.call(event), + ); + }); + })), + ); + } +} + +// State +final releaseEventsListProvider = FutureProvider.autoDispose>((ref) { + final params = ref.watch(releaseEventsListParamsStateProvider); + final releaseEventRepository = ref.watch(releaseEventRepositoryProvider); + return releaseEventRepository.fetchReleaseEventsList(params: params); +}); diff --git a/lib/src/routing/app_route_context.dart b/lib/src/routing/app_route_context.dart index e12b78ac..ad963742 100644 --- a/lib/src/routing/app_route_context.dart +++ b/lib/src/routing/app_route_context.dart @@ -29,6 +29,10 @@ extension AppRouteContext on BuildContext { goNamed(AppRoute.releaseEventDetail.name, pathParameters: { 'id': releaseEvent.id.toString() }); } + Future goReleaseEventList() async { + goNamed(AppRoute.releaseEventList.name); + } + Future goArtistSearchScreen() async { goNamed(AppRoute.artistsList.name); diff --git a/lib/src/routing/app_router.dart b/lib/src/routing/app_router.dart index d7a81bef..04d8e4b8 100644 --- a/lib/src/routing/app_router.dart +++ b/lib/src/routing/app_router.dart @@ -5,6 +5,7 @@ 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/albums/presentation/albums_list/albums_list_filter_screen.dart'; import 'package:vocadb_app/src/features/albums/presentation/albums_list/albums_list_screen.dart'; +import 'package:vocadb_app/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen.dart'; import 'package:vocadb_app/src/features/songs/presentation/songs_list_screen/songs_list_filter_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'; @@ -48,6 +49,7 @@ enum AppRoute { artistsList, artistsListFilter, tagDetail, + releaseEventList, releaseEventDetail, entriesSearch, entriesFilter, @@ -191,6 +193,15 @@ final goRouterProvider = Provider.autoDispose( ), //// Release events + GoRoute( + path: 'E', + name: AppRoute.releaseEventList.name, + builder: (context, state) { + return ReleaseEventsListScreen(onSelectEvent: (event) { + context.goReleaseEventDetail(event); + }); + }, + routes: [ GoRoute( path: 'E/:id', name: AppRoute.releaseEventDetail.name, @@ -199,6 +210,10 @@ final goRouterProvider = Provider.autoDispose( return ReleaseEventDetailScreen(releaseEventId: releaseEventId); }, ), + ], + ), + + //// Personal GoRoute( diff --git a/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_robot.dart b/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_robot.dart new file mode 100644 index 00000000..6c34bf4d --- /dev/null +++ b/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_robot.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:vocadb_app/src/features/releaseEvents/data/release_event_repository.dart'; +import 'package:vocadb_app/src/features/releaseEvents/presentation/release_event_tile/release_event_tile.dart'; +import 'package:vocadb_app/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen.dart'; +import 'package:vocadb_app/src/features/settings/data/user_settings_repository.dart'; + +class ReleaseEventsListScreenRobot { + final WidgetTester tester; + + ReleaseEventsListScreenRobot(this.tester); + + Future pumpReleaseEventsListScreen( + {ReleaseEventRepository? releaseEventRepository}) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + if (releaseEventRepository != null) + releaseEventRepositoryProvider.overrideWithValue(releaseEventRepository), + userSettingsRepositoryProvider + .overrideWithValue(UserSettingsRepository()) + ], + child: const MaterialApp( + home: ReleaseEventsListScreen(), + ), + ), + ); + + await tester.pump(); + await tester.pump(); + await tester.pump(); + } + + Future expectReleaseEventsDisplayCountAtLeast(int count) async { + final finder = find.byType(ReleaseEventTile); + expect(finder, findsAtLeastNWidgets(count)); + } +} diff --git a/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_test.dart b/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_test.dart new file mode 100644 index 00000000..1fb9909a --- /dev/null +++ b/test/src/features/releaseEvents/presentation/release_events_list_screen/release_events_list_screen_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:vocadb_app/src/features/releaseEvents/data/constants/fake_release_events_list.dart'; +import 'package:vocadb_app/src/features/releaseEvents/domain/release_events_list_params.dart'; + +import '../../../../mocks.dart'; +import 'release_events_list_screen_robot.dart'; + +void main() { + testWidgets('Release events list screen ...', (tester) async { + registerFallbackValue(FakeReleaseEventsListParams()); + + final r = ReleaseEventsListScreenRobot(tester); + final releaseEventRepository = MockReleaseEventRepository(); + + when(() => releaseEventRepository.fetchReleaseEventsList( + params: any(named: 'params', that: isNotNull), + )).thenAnswer((_) => Future.value(kFakeReleaseEventsList)); + + await r.pumpReleaseEventsListScreen(releaseEventRepository: releaseEventRepository); + + await r.expectReleaseEventsDisplayCountAtLeast(3); + + expect( + verify(() => + releaseEventRepository.fetchReleaseEventsList(params: captureAny(named: 'params'))) + .captured, + [ + const ReleaseEventsListParams(), + ]); + }); +} diff --git a/test/src/mocks.dart b/test/src/mocks.dart index 5a194c9b..1cedd6b2 100644 --- a/test/src/mocks.dart +++ b/test/src/mocks.dart @@ -11,6 +11,7 @@ import 'package:vocadb_app/src/features/artists/domain/artists_list_params.dart' import 'package:vocadb_app/src/features/authentication/data/auth_repository.dart'; 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/releaseEvents/domain/release_events_list_params.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'; @@ -38,6 +39,8 @@ class RatedSongsListParamsFake extends Fake implements RatedSongsListParams {} class FakeArtistsListParams extends Fake implements ArtistsListParams {} +class FakeReleaseEventsListParams extends Fake implements ReleaseEventsListParams {} + class FakeAlbumsListParams extends Fake implements AlbumsListParams {} class FakeSongsListParams extends Fake implements SongsListParams {}