diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index d7cbbcf..b0f625f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -20,6 +20,7 @@ jobs: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: flutter_channel: stable + coverage_excludes: "**/*.g.dart" spell-check: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 diff --git a/lib/album/view/album_page.dart b/lib/album/view/album_page.dart index 9ce1462..b8711c2 100644 --- a/lib/album/view/album_page.dart +++ b/lib/album/view/album_page.dart @@ -6,8 +6,6 @@ import 'package:swiftify_repository/swiftify_repository.dart'; class AlbumPage extends StatelessWidget { const AlbumPage({super.key}); - static const routeName = '/album'; - @override Widget build(BuildContext context) { return BlocProvider( diff --git a/lib/album/view/album_view.dart b/lib/album/view/album_view.dart index 1d04a34..fc84183 100644 --- a/lib/album/view/album_view.dart +++ b/lib/album/view/album_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:swiftify/album/bloc/album_bloc.dart'; +import 'package:swiftify/album/album.dart'; +import 'package:swiftify/app/app_router/router.dart'; class AlbumView extends StatelessWidget { const AlbumView({super.key}); @@ -10,22 +11,26 @@ class AlbumView extends StatelessWidget { final isLoading = context.select((AlbumBloc bloc) => bloc.state.isLoading); final isSuccess = context.select((AlbumBloc bloc) => bloc.state.isSuccess); - return isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : isSuccess - ? const Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AlbumsHeader(), - Expanded( - child: AlbumsContent(), - ), - ], - ) - : const Text('Failed to fetch albums'); + if (isLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + if (isSuccess) { + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AlbumsHeader(), + Expanded( + child: AlbumsContent(), + ), + ], + ); + } else { + return const Text('Failed to fetch albums'); + } + } } } @@ -79,10 +84,18 @@ class AlbumsContent extends StatelessWidget { itemCount: albums.length, itemBuilder: (context, index) { final album = albums[index]; - return AlbumItem( - title: album.title, - releaseDate: album.releaseDate, - coverAlbum: album.coverAlbum, + return GestureDetector( + onTap: () => SongsPageRoute( + albumId: album.albumId, + albumTitle: album.title, + coverAlbum: album.coverAlbum, + albumReleaseDate: album.releaseDate, + ).push(context), + child: AlbumItem( + title: album.title, + releaseDate: album.releaseDate, + coverAlbum: album.coverAlbum, + ), ); }, ); diff --git a/lib/app/app_router/app_router.dart b/lib/app/app_router/app_router.dart index bcbfd18..1e5621c 100644 --- a/lib/app/app_router/app_router.dart +++ b/lib/app/app_router/app_router.dart @@ -1,39 +1,17 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:swiftify/album/album.dart'; -import 'package:swiftify/app/app_router/routes/dashboard_routes.dart'; -import 'package:swiftify/app/app_router/routes/theme_route.dart'; +import 'package:swiftify/app/app_router/routes/routes.dart'; export 'go_router_observer.dart'; -final rootNavigatorKey = GlobalKey(); - -class AppRoute extends GoRoute { - AppRoute({ - required super.path, - super.name, - super.builder, - super.pageBuilder, - super.parentNavigatorKey, - super.routes = const [], - }); - - @override - GoRouterRedirect get redirect => (context, state) { - return null; - }; -} - class AppRouter { AppRouter({ - String? initialLocation = AlbumPage.routeName, List navigatorObservers = const [], GoRouter? goRouter, }) { _goRouter = goRouter ?? _routes( - initialLocation, navigatorObservers, ); } @@ -42,18 +20,14 @@ class AppRouter { GoRouter get routes => _goRouter; GoRouter _routes( - String? initialLocation, List navigatorObservers, ) { return GoRouter( - initialLocation: initialLocation, + initialLocation: const AlbumPageRoute().location, observers: navigatorObservers, debugLogDiagnostics: kDebugMode, navigatorKey: rootNavigatorKey, - routes: [ - dashBoardRoutes, - themeRoute, - ], + routes: $appRoutes, ); } } diff --git a/lib/app/app_router/modal_pop_up_page.dart b/lib/app/app_router/modal_pop_up_page.dart deleted file mode 100644 index 247545d..0000000 --- a/lib/app/app_router/modal_pop_up_page.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/cupertino.dart'; - -class ModalPopupPage extends Page { - const ModalPopupPage({ - required this.builder, - this.anchorPoint, - this.barrierDismissible = true, - this.barrierLabel = 'Dismiss', - this.barrierColor, - this.semanticsDismissible = true, - this.filter, - super.key, - }); - final Offset? anchorPoint; - final Color? barrierColor; - final bool barrierDismissible; - final String barrierLabel; - final bool semanticsDismissible; - final WidgetBuilder builder; - final ImageFilter? filter; - - @override - Route createRoute(BuildContext context) => CupertinoModalPopupRoute( - builder: builder, - barrierDismissible: barrierDismissible, - anchorPoint: anchorPoint, - barrierLabel: barrierLabel, - barrierColor: barrierColor, - filter: filter, - semanticsDismissible: semanticsDismissible, - settings: this, - ); -} diff --git a/lib/app/app_router/router.dart b/lib/app/app_router/router.dart new file mode 100644 index 0000000..97bae07 --- /dev/null +++ b/lib/app/app_router/router.dart @@ -0,0 +1,4 @@ +export 'app_router.dart'; +export 'go_router_observer.dart'; +export 'routes/routes.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/app/app_router/routes/dashboard_routes.dart b/lib/app/app_router/routes/dashboard_routes.dart deleted file mode 100644 index 94d515e..0000000 --- a/lib/app/app_router/routes/dashboard_routes.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:swiftify/album/album.dart'; -import 'package:swiftify/app/app_router/app_router.dart'; -import 'package:swiftify/app/app_router/scaffold_with_bottom_navigation.dart'; -import 'package:swiftify/favorites/favorites.dart'; - -final albumNavigatorKey = GlobalKey(debugLabel: 'albums'); -final favoritesNavigatorKey = - GlobalKey(debugLabel: 'favorites'); - -final dashBoardRoutes = StatefulShellRoute.indexedStack( - builder: (context, state, navigationShell) => ScaffoldWithBottomNavigation( - navigationShell: navigationShell, - ), - branches: [ - StatefulShellBranch( - navigatorKey: albumNavigatorKey, - routes: [ - AppRoute( - name: AlbumPage.routeName, - path: AlbumPage.routeName, - builder: (context, state) => const AlbumPage(), - ), - ], - ), - StatefulShellBranch( - navigatorKey: favoritesNavigatorKey, - routes: [ - AppRoute( - name: FavoritesPage.routeName, - path: FavoritesPage.routeName, - builder: (context, state) => const FavoritesPage(), - ), - ], - ), - ], -); diff --git a/lib/app/app_router/routes/routes.dart b/lib/app/app_router/routes/routes.dart new file mode 100644 index 0000000..be472fa --- /dev/null +++ b/lib/app/app_router/routes/routes.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:swiftify/album/album.dart'; +import 'package:swiftify/app/app_router/router.dart'; +import 'package:swiftify/favorites/favorites.dart'; +import 'package:swiftify/song_detail/song_detail.dart'; +import 'package:swiftify/songs/songs.dart'; +import 'package:swiftify/theme/theme.dart'; + +part 'routes.g.dart'; + +final shellNavigatorKey = GlobalKey(); +final rootNavigatorKey = GlobalKey(); + +@TypedShellRoute( + routes: >[ + TypedGoRoute( + path: AlbumPageRoute.path, + ), + TypedGoRoute( + path: FavoritesPageRoute.path, + name: FavoritesPageRoute.name, + ), + ], +) +class AppShellRoute extends ShellRouteData { + const AppShellRoute(); + + /// The navigator key for the shell navigator. + static final GlobalKey $navigatorKey = shellNavigatorKey; + + @override + Page pageBuilder( + BuildContext context, + GoRouterState state, + Widget navigator, + ) { + return CustomTransitionPage( + child: ScaffoldWithBottomNavigation(child: navigator), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} + +@TypedGoRoute( + path: ThemePageRoute.path, + name: ThemePageRoute.name, +) +class ThemePageRoute extends GoRouteData { + const ThemePageRoute(); + + static const path = '/theme'; + static const name = 'theme'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return ModalBottomSheet( + builder: (_) => const ThemeBottomSheet(), + ); + } +} + +class FavoritesPageRoute extends GoRouteData { + const FavoritesPageRoute(); + + static const path = '/favorites'; + static const name = 'favorites'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return CustomTransitionPage( + child: const FavoritesPage(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} + +@TypedGoRoute( + path: AlbumPageRoute.path, + routes: [ + TypedGoRoute( + path: SongsPageRoute.path, + name: SongsPageRoute.name, + ), + TypedGoRoute( + path: SongDetailPageRoute.path, + name: SongDetailPageRoute.name, + ), + ], +) +class AlbumPageRoute extends GoRouteData { + const AlbumPageRoute(); + static const path = '/'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return CustomTransitionPage( + child: const AlbumPage(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} + +class SongsPageRoute extends GoRouteData { + const SongsPageRoute({ + required this.albumId, + this.albumTitle, + this.coverAlbum, + this.albumReleaseDate, + }); + + /// The album id to display the songs for. + /// This is passed in the path as a parameter + final int albumId; + + /// The title of the album + /// This is passed in as a query parameter. + /// It is optional and can be null. + final String? albumTitle; + + /// The cover of the album + /// This is passed in as a query parameter. + /// It is optional and can be null. + final String? coverAlbum; + + /// The release date of the album + /// This is passed in as a query parameter. + /// It is optional and can be null. + final String? albumReleaseDate; + + static const path = 'songs/:albumId'; + static const name = 'songs'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return CustomTransitionPage( + key: state.pageKey, + child: SongsPage( + albumId: albumId, + albumTitle: albumTitle, + coverAlbum: coverAlbum, + ), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} + +class SongDetailPageRoute extends GoRouteData { + const SongDetailPageRoute({ + required this.songId, + this.songTitle, + this.lyrics, + this.coverAlbum, + }); + + final int songId; + final String? lyrics; + final String? songTitle; + final String? coverAlbum; + + static const path = '/song-detail/:songId'; + static const name = 'song-detail'; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return CustomTransitionPage( + key: state.pageKey, + child: SongDetailPage( + songId: songId, + songTitle: songTitle ?? '', + lyrics: lyrics ?? '', + coverAlbum: coverAlbum, + ), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} diff --git a/lib/app/app_router/routes/routes.g.dart b/lib/app/app_router/routes/routes.g.dart new file mode 100644 index 0000000..c7daf54 --- /dev/null +++ b/lib/app/app_router/routes/routes.g.dart @@ -0,0 +1,165 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'routes.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $appShellRoute, + $themePageRoute, + $albumPageRoute, + ]; + +RouteBase get $appShellRoute => ShellRouteData.$route( + navigatorKey: AppShellRoute.$navigatorKey, + factory: $AppShellRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/', + factory: $AlbumPageRouteExtension._fromState, + ), + GoRouteData.$route( + path: '/favorites', + name: 'favorites', + factory: $FavoritesPageRouteExtension._fromState, + ), + ], + ); + +extension $AppShellRouteExtension on AppShellRoute { + static AppShellRoute _fromState(GoRouterState state) => const AppShellRoute(); +} + +extension $AlbumPageRouteExtension on AlbumPageRoute { + static AlbumPageRoute _fromState(GoRouterState state) => + const AlbumPageRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +extension $FavoritesPageRouteExtension on FavoritesPageRoute { + static FavoritesPageRoute _fromState(GoRouterState state) => + const FavoritesPageRoute(); + + String get location => GoRouteData.$location( + '/favorites', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $themePageRoute => GoRouteData.$route( + path: '/theme', + name: 'theme', + factory: $ThemePageRouteExtension._fromState, + ); + +extension $ThemePageRouteExtension on ThemePageRoute { + static ThemePageRoute _fromState(GoRouterState state) => + const ThemePageRoute(); + + String get location => GoRouteData.$location( + '/theme', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $albumPageRoute => GoRouteData.$route( + path: '/', + factory: $AlbumPageRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'songs/:albumId', + name: 'songs', + factory: $SongsPageRouteExtension._fromState, + ), + GoRouteData.$route( + path: '/song-detail/:songId', + name: 'song-detail', + factory: $SongDetailPageRouteExtension._fromState, + ), + ], + ); + +extension $SongsPageRouteExtension on SongsPageRoute { + static SongsPageRoute _fromState(GoRouterState state) => SongsPageRoute( + albumId: int.parse(state.pathParameters['albumId']!), + albumTitle: state.uri.queryParameters['album-title'], + coverAlbum: state.uri.queryParameters['cover-album'], + albumReleaseDate: state.uri.queryParameters['album-release-date'], + ); + + String get location => GoRouteData.$location( + '/songs/${Uri.encodeComponent(albumId.toString())}', + queryParams: { + if (albumTitle != null) 'album-title': albumTitle, + if (coverAlbum != null) 'cover-album': coverAlbum, + if (albumReleaseDate != null) 'album-release-date': albumReleaseDate, + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +extension $SongDetailPageRouteExtension on SongDetailPageRoute { + static SongDetailPageRoute _fromState(GoRouterState state) => + SongDetailPageRoute( + songId: int.parse(state.pathParameters['songId']!), + songTitle: state.uri.queryParameters['song-title'], + lyrics: state.uri.queryParameters['lyrics'], + coverAlbum: state.uri.queryParameters['cover-album'], + ); + + String get location => GoRouteData.$location( + '/song-detail/${Uri.encodeComponent(songId.toString())}', + queryParams: { + if (songTitle != null) 'song-title': songTitle, + if (lyrics != null) 'lyrics': lyrics, + if (coverAlbum != null) 'cover-album': coverAlbum, + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/lib/app/app_router/routes/theme_route.dart b/lib/app/app_router/routes/theme_route.dart deleted file mode 100644 index 0ee8474..0000000 --- a/lib/app/app_router/routes/theme_route.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:swiftify/app/app_router/app_router.dart'; -import 'package:swiftify/app/app_router/modal_pop_up_page.dart'; -import 'package:swiftify/theme/view/theme_bottom_sheet.dart'; - -final themeRoute = AppRoute( - parentNavigatorKey: rootNavigatorKey, - name: ThemeBottomSheet.routeName, - path: ThemeBottomSheet.routeName, - pageBuilder: (context, state) => ModalPopupPage( - builder: (context) => ThemeBottomSheet.pageBuilder(context, state), - ), -); diff --git a/lib/app/app_router/widgets/modal_bottom_sheet_page.dart b/lib/app/app_router/widgets/modal_bottom_sheet_page.dart new file mode 100644 index 0000000..c462471 --- /dev/null +++ b/lib/app/app_router/widgets/modal_bottom_sheet_page.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class ModalBottomSheet extends Page { + const ModalBottomSheet({ + required this.builder, + super.key, + }); + final WidgetBuilder builder; + + @override + Route createRoute(BuildContext context) => ModalBottomSheetRoute( + builder: builder, + isScrollControlled: false, + settings: this, + ); +} diff --git a/lib/app/app_router/scaffold_with_bottom_navigation.dart b/lib/app/app_router/widgets/scaffold_with_bottom_navigation.dart similarity index 58% rename from lib/app/app_router/scaffold_with_bottom_navigation.dart rename to lib/app/app_router/widgets/scaffold_with_bottom_navigation.dart index 961a57a..a27765a 100644 --- a/lib/app/app_router/scaffold_with_bottom_navigation.dart +++ b/lib/app/app_router/widgets/scaffold_with_bottom_navigation.dart @@ -1,28 +1,39 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:swiftify/app/app_router/router.dart'; import 'package:swiftify/l10n/l10n.dart'; -import 'package:swiftify/theme/view/theme_bottom_sheet.dart'; class ScaffoldWithBottomNavigation extends StatelessWidget { const ScaffoldWithBottomNavigation({ - required this.navigationShell, + required this.child, super.key, }); - final StatefulNavigationShell navigationShell; + final Widget child; - void _goBranch(int index) { - navigationShell.goBranch( - index, - initialLocation: index == navigationShell.currentIndex, - ); + int getCurrentIndex(BuildContext context) { + final path = GoRouterState.of(context).uri.path; + if (path == AlbumPageRoute.path) { + return 0; + } else { + return 1; + } + } + + void _onTap(int index, BuildContext context) { + switch (index) { + case 0: + const AlbumPageRoute().go(context); + case 1: + const FavoritesPageRoute().go(context); + } } @override Widget build(BuildContext context) { final l10n = context.l10n; - final currentIndex = navigationShell.currentIndex; + final currentIndex = getCurrentIndex(context); return Scaffold( appBar: AppBar( @@ -32,14 +43,14 @@ class ScaffoldWithBottomNavigation extends StatelessWidget { actions: [ IconButton( icon: const Icon(Icons.lightbulb_outline), - onPressed: () => context.push(ThemeBottomSheet.routeName), + onPressed: () => const ThemePageRoute().push(context), ), ], ), - body: navigationShell, + body: child, bottomNavigationBar: BottomNavigationBar( - currentIndex: navigationShell.currentIndex, - onTap: _goBranch, + currentIndex: currentIndex, + onTap: (index) => _onTap(index, context), items: [ BottomNavigationBarItem( icon: const Icon(Icons.album_outlined), diff --git a/lib/app/app_router/widgets/widgets.dart b/lib/app/app_router/widgets/widgets.dart new file mode 100644 index 0000000..c634445 --- /dev/null +++ b/lib/app/app_router/widgets/widgets.dart @@ -0,0 +1,2 @@ +export 'modal_bottom_sheet_page.dart'; +export 'scaffold_with_bottom_navigation.dart'; diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index a230ca0..36b8100 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -6,7 +6,7 @@ import 'package:swiftify/theme/app_theme.dart'; import 'package:swiftify/theme/bloc/theme_bloc.dart'; import 'package:swiftify_repository/swiftify_repository.dart'; -class App extends StatefulWidget { +class App extends StatelessWidget { const App({ required SwiftifyRepository swiftifyRepository, super.key, @@ -14,16 +14,14 @@ class App extends StatefulWidget { final SwiftifyRepository _swiftifyRepository; - @override - State createState() => _AppState(); -} - -class _AppState extends State { @override Widget build(BuildContext context) { return RepositoryProvider.value( - value: widget._swiftifyRepository, - child: BlocProvider(create: (_) => ThemeBloc(), child: const AppView()), + value: _swiftifyRepository, + child: BlocProvider( + create: (_) => ThemeBloc(), + child: const AppView(), + ), ); } } diff --git a/lib/favorites/view/favorites_page.dart b/lib/favorites/view/favorites_page.dart index 21f1eb4..2c9dbbd 100644 --- a/lib/favorites/view/favorites_page.dart +++ b/lib/favorites/view/favorites_page.dart @@ -3,8 +3,6 @@ import 'package:flutter/widgets.dart'; class FavoritesPage extends StatelessWidget { const FavoritesPage({super.key}); - static const routeName = '/favorites'; - @override Widget build(BuildContext context) { return const Center( diff --git a/lib/song_detail/song_detail.dart b/lib/song_detail/song_detail.dart new file mode 100644 index 0000000..00ffcf9 --- /dev/null +++ b/lib/song_detail/song_detail.dart @@ -0,0 +1 @@ +export 'view/view.dart'; diff --git a/lib/song_detail/view/song_detail_page.dart b/lib/song_detail/view/song_detail_page.dart new file mode 100644 index 0000000..a898a3d --- /dev/null +++ b/lib/song_detail/view/song_detail_page.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:swiftify/song_detail/song_detail.dart'; + +class SongDetailPage extends StatelessWidget { + const SongDetailPage({ + required this.songId, + required this.lyrics, + required this.songTitle, + this.coverAlbum, + super.key, + }); + + final int songId; + final String lyrics; + final String? coverAlbum; + final String songTitle; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(songTitle), + ), + body: SongDetailView( + lyrics: lyrics, + coverAlbum: coverAlbum, + ), + ); + } +} diff --git a/lib/song_detail/view/song_detail_view.dart b/lib/song_detail/view/song_detail_view.dart new file mode 100644 index 0000000..349a5ed --- /dev/null +++ b/lib/song_detail/view/song_detail_view.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class SongDetailView extends StatelessWidget { + const SongDetailView({ + required this.lyrics, + this.coverAlbum, + super.key, + }); + + final String? coverAlbum; + final String? lyrics; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(lyrics ?? 'No lyrics found'), + ), + ); + } +} diff --git a/lib/song_detail/view/view.dart b/lib/song_detail/view/view.dart new file mode 100644 index 0000000..3d50d11 --- /dev/null +++ b/lib/song_detail/view/view.dart @@ -0,0 +1,2 @@ +export 'song_detail_page.dart'; +export 'song_detail_view.dart'; diff --git a/lib/songs/bloc/song_state.dart b/lib/songs/bloc/song_state.dart deleted file mode 100644 index 0236743..0000000 --- a/lib/songs/bloc/song_state.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of 'song_bloc.dart'; - -enum SongStatus { initial, loading, success, failure } - -class SongState extends Equatable { - const SongState({ - this.status = SongStatus.initial, - this.songs = const [], - }); - - final List songs; - final SongStatus status; - - bool get isLoading => status == SongStatus.loading; - bool get isSuccess => status == SongStatus.success; - bool get isFailure => status == SongStatus.failure; - - SongState copyWith({ - List? songs, - SongStatus? status, - }) { - return SongState( - songs: songs ?? this.songs, - status: status ?? this.status, - ); - } - - @override - List get props => [songs, status]; -} diff --git a/lib/songs/bloc/song_bloc.dart b/lib/songs/bloc/songs_bloc.dart similarity index 69% rename from lib/songs/bloc/song_bloc.dart rename to lib/songs/bloc/songs_bloc.dart index c0f6e75..cf6afc2 100644 --- a/lib/songs/bloc/song_bloc.dart +++ b/lib/songs/bloc/songs_bloc.dart @@ -2,14 +2,14 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:swiftify_repository/swiftify_repository.dart'; -part 'song_event.dart'; -part 'song_state.dart'; +part 'songs_event.dart'; +part 'songs_state.dart'; -class SongBloc extends Bloc { +class SongBloc extends Bloc { SongBloc({ required SwiftifyRepository swiftifyRepository, }) : _swiftifyRepository = swiftifyRepository, - super(const SongState()) { + super(const SongsState()) { on(_onSongsByAlbumRequested); } @@ -17,22 +17,22 @@ class SongBloc extends Bloc { Future _onSongsByAlbumRequested( SongsRequested event, - Emitter emit, + Emitter emit, ) async { + emit(state.copyWith(status: SongsStatus.loading)); try { - emit(state.copyWith(status: SongStatus.loading)); final songs = await _swiftifyRepository.getSongsByAlbum( albumId: event.albumId, ); emit( state.copyWith( songs: songs, - status: SongStatus.success, + status: SongsStatus.success, ), ); } catch (error, stackTrace) { addError(error, stackTrace); - emit(state.copyWith(status: SongStatus.failure)); + emit(state.copyWith(status: SongsStatus.failure)); } } } diff --git a/lib/songs/bloc/song_event.dart b/lib/songs/bloc/songs_event.dart similarity index 90% rename from lib/songs/bloc/song_event.dart rename to lib/songs/bloc/songs_event.dart index a7fce76..932b94a 100644 --- a/lib/songs/bloc/song_event.dart +++ b/lib/songs/bloc/songs_event.dart @@ -1,4 +1,4 @@ -part of 'song_bloc.dart'; +part of 'songs_bloc.dart'; sealed class SongEvent extends Equatable { const SongEvent(); diff --git a/lib/songs/bloc/songs_state.dart b/lib/songs/bloc/songs_state.dart new file mode 100644 index 0000000..03c6770 --- /dev/null +++ b/lib/songs/bloc/songs_state.dart @@ -0,0 +1,29 @@ +part of 'songs_bloc.dart'; + +enum SongsStatus { initial, loading, success, failure } + +class SongsState extends Equatable { + const SongsState({ + this.status = SongsStatus.initial, + this.songs = const [], + }); + + final List songs; + final SongsStatus status; + + bool get isLoading => status == SongsStatus.loading; + bool get isSuccess => status == SongsStatus.success; + + SongsState copyWith({ + List? songs, + SongsStatus? status, + }) { + return SongsState( + songs: songs ?? this.songs, + status: status ?? this.status, + ); + } + + @override + List get props => [songs, status]; +} diff --git a/lib/songs/song.dart b/lib/songs/song.dart deleted file mode 100644 index 98152e0..0000000 --- a/lib/songs/song.dart +++ /dev/null @@ -1 +0,0 @@ -export 'bloc/song_bloc.dart'; diff --git a/lib/songs/songs.dart b/lib/songs/songs.dart new file mode 100644 index 0000000..af120f5 --- /dev/null +++ b/lib/songs/songs.dart @@ -0,0 +1,2 @@ +export 'bloc/songs_bloc.dart'; +export 'view/view.dart'; diff --git a/lib/songs/view/songs_page.dart b/lib/songs/view/songs_page.dart new file mode 100644 index 0000000..7cbe26e --- /dev/null +++ b/lib/songs/view/songs_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:swiftify/songs/songs.dart'; +import 'package:swiftify_repository/swiftify_repository.dart'; + +class SongsPage extends StatelessWidget { + const SongsPage({ + required this.albumId, + this.albumTitle, + this.coverAlbum, + this.albumReleaseDate, + super.key, + }); + + final int albumId; + final String? albumTitle; + final String? coverAlbum; + final String? albumReleaseDate; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SongBloc( + swiftifyRepository: context.read(), + )..add( + SongsRequested(albumId: albumId), + ), + child: Scaffold( + appBar: AppBar( + title: const Text('Songs'), + ), + body: SongsView( + albumTitle: albumTitle ?? '', + coverAlbum: coverAlbum, + releaseDate: albumReleaseDate ?? '', + ), + ), + ); + } +} diff --git a/lib/songs/view/songs_view.dart b/lib/songs/view/songs_view.dart new file mode 100644 index 0000000..d60c269 --- /dev/null +++ b/lib/songs/view/songs_view.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:swiftify/app/app_router/router.dart'; +import 'package:swiftify/songs/songs.dart'; +import 'package:swiftify_repository/swiftify_repository.dart'; + +class SongsView extends StatelessWidget { + const SongsView({ + required this.albumTitle, + required this.coverAlbum, + required this.releaseDate, + super.key, + }); + + final String albumTitle; + final String? coverAlbum; + final String releaseDate; + + @override + Widget build(BuildContext context) { + final songs = context.select((SongBloc bloc) => bloc.state.songs); + final isLoading = context.select((SongBloc bloc) => bloc.state.isLoading); + final theme = Theme.of(context); + final textTheme = theme.textTheme; + + return isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 16, + bottom: 8, + ), + child: Text( + albumTitle, + style: textTheme.headlineLarge, + ), + ), + Expanded( + child: ListView.builder( + itemCount: songs.length, + itemBuilder: (context, index) { + final song = songs[index]; + return SongCard( + coverAlbum: coverAlbum, + song: song, + ); + }, + ), + ), + ], + ), + ); + } +} + +class SongCard extends StatelessWidget { + const SongCard({ + required this.coverAlbum, + required this.song, + super.key, + }); + + final String? coverAlbum; + final Song song; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => SongDetailPageRoute( + songId: song.songId, + songTitle: song.title, + lyrics: song.lyrics ?? '', + coverAlbum: coverAlbum, + ).push(context), + child: Card( + margin: const EdgeInsets.symmetric( + vertical: 8, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AlbumCoverImage(coverAlbum: coverAlbum), + SongInformation( + title: song.title, + duration: song.duration, + genres: song.genres, + ), + ], + ), + ), + ); + } +} + +class AlbumCoverImage extends StatelessWidget { + const AlbumCoverImage({ + required this.coverAlbum, + super.key, + }); + + final String? coverAlbum; + + @override + Widget build(BuildContext context) { + return Expanded( + child: coverAlbum != null + ? Container( + constraints: const BoxConstraints( + maxWidth: 80, + ), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Image.network( + coverAlbum!, + fit: BoxFit.cover, + ), + ) + : const SizedBox(), + ); + } +} + +class SongInformation extends StatelessWidget { + const SongInformation({ + required this.title, + required this.duration, + required this.genres, + super.key, + }); + + final String title; + final String? duration; + final List? genres; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + + return Flexible( + flex: 3, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.visible, + ), + Text(duration ?? '', style: textTheme.labelLarge), + if (genres != null && genres!.isNotEmpty) ...[ + const SizedBox(height: 4), + SongGenres(genres: genres), + ], + ], + ), + ), + ); + } +} + +class SongGenres extends StatelessWidget { + const SongGenres({ + required this.genres, + super.key, + }); + + final List? genres; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final colorScheme = theme.colorScheme; + + return Row( + children: [ + for (final genre in genres!) + Row( + children: [ + Text( + genre, + style: textTheme.bodySmall?.copyWith( + color: colorScheme.primary, + ), + ), + if (genre != genres!.last) + Text( + ' • ', + style: textTheme.bodySmall?.copyWith( + color: colorScheme.primary, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/songs/view/view.dart b/lib/songs/view/view.dart new file mode 100644 index 0000000..9c7537f --- /dev/null +++ b/lib/songs/view/view.dart @@ -0,0 +1,2 @@ +export 'songs_page.dart'; +export 'songs_view.dart'; diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 58aa9d9..a6c28b9 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -6,51 +6,51 @@ class AppTheme { ColorScheme lightScheme() { return const ColorScheme( brightness: Brightness.light, - primary: Color(0xff1a6b51), - surfaceTint: Color(0xff1a6b51), - onPrimary: Color(0xffffffff), - primaryContainer: Color(0xffa6f2d2), - onPrimaryContainer: Color(0xff002116), - secondary: Color.fromARGB(255, 33, 59, 48), - onSecondary: Color(0xffffffff), - secondaryContainer: Color(0xffcee9db), - onSecondaryContainer: Color(0xff092017), - tertiary: Color(0xff3f6375), - onTertiary: Color(0xffffffff), - tertiaryContainer: Color(0xffc2e8fd), - onTertiaryContainer: Color(0xff001f2a), + primary: Color(0xff5e5f5c), + surfaceTint: Color(0xff5e5f5c), + onPrimary: Color(0xffedece9), + primaryContainer: Color(0xffedece9), + onPrimaryContainer: Color(0xff6a6a68), + secondary: Color(0xff4a3d33), + onSecondary: Color(0xffedece9), + secondaryContainer: Color(0xff625449), + onSecondaryContainer: Color(0xffdcc9bb), + tertiary: Color(0xff5f5e60), + onTertiary: Color(0xffedece9), + tertiaryContainer: Color(0xffeeebed), + onTertiaryContainer: Color(0xff6b6a6c), error: Color(0xffba1a1a), - onError: Color(0xffffffff), + onError: Color(0xffedece9), errorContainer: Color(0xffffdad6), - onErrorContainer: Color(0xff410002), - surface: Color(0xfff5fbf5), - onSurface: Color(0xff171d1a), - onSurfaceVariant: Color(0xff404944), - outline: Color(0xff707974), - outlineVariant: Color(0xffbfc9c2), + onErrorContainer: Color(0xff93000a), + surface: Color(0xfffcf8f7), + onSurface: Color(0xff1c1b1b), + onSurfaceVariant: Color(0xff444844), + outline: Color(0xff757874), + outlineVariant: Color(0xffc5c7c2), shadow: Color(0xff000000), scrim: Color(0xff000000), - inverseSurface: Color(0xff2c322e), - inversePrimary: Color(0xff8ad6b7), - primaryFixed: Color(0xffa6f2d2), - onPrimaryFixed: Color(0xff002116), - primaryFixedDim: Color(0xff8ad6b7), - onPrimaryFixedVariant: Color(0xff00513c), - secondaryFixed: Color(0xffcee9db), - onSecondaryFixed: Color(0xff092017), - secondaryFixedDim: Color(0xffb3ccbf), - onSecondaryFixedVariant: Color(0xff354b42), - tertiaryFixed: Color(0xffc2e8fd), - onTertiaryFixed: Color(0xff001f2a), - tertiaryFixedDim: Color(0xffa6cce0), - onTertiaryFixedVariant: Color(0xff264b5c), - surfaceDim: Color(0xffd6dbd6), - surfaceBright: Color(0xfff5fbf5), - surfaceContainerLowest: Color(0xffffffff), - surfaceContainerLow: Color(0xffeff5f0), - surfaceContainer: Color(0xffe9efea), - surfaceContainerHigh: Color(0xffe4eae4), - surfaceContainerHighest: Color(0xffdee4df), + inverseSurface: Color(0xff313030), + inversePrimary: Color(0xffc7c6c4), + primaryFixed: Color(0xffe3e2df), + onPrimaryFixed: Color(0xff1b1c1a), + primaryFixedDim: Color(0xffc7c6c4), + onPrimaryFixedVariant: Color(0xff464745), + secondaryFixed: Color(0xfff3dfd0), + onSecondaryFixed: Color(0xff241a11), + secondaryFixedDim: Color(0xffd6c3b5), + onSecondaryFixedVariant: Color(0xff51443a), + tertiaryFixed: Color(0xffe4e2e4), + onTertiaryFixed: Color(0xff1b1b1d), + tertiaryFixedDim: Color(0xffc8c6c8), + onTertiaryFixedVariant: Color(0xff474648), + surfaceDim: Color(0xffddd9d8), + surfaceBright: Color(0xfffcf8f7), + surfaceContainerLowest: Color(0xffedece9), + surfaceContainerLow: Color(0xfff7f3f2), + surfaceContainer: Color(0xfff1edec), + surfaceContainerHigh: Color(0xffebe7e6), + surfaceContainerHighest: Color(0xffe5e2e1), ); } @@ -61,51 +61,51 @@ class AppTheme { ColorScheme lightHighContrastScheme() { return const ColorScheme( brightness: Brightness.light, - primary: Color(0xff00281c), - surfaceTint: Color(0xff1a6b51), - onPrimary: Color(0xffffffff), - primaryContainer: Color(0xff004d38), - onPrimaryContainer: Color(0xffffffff), - secondary: Color(0xff10261e), - onSecondary: Color(0xffffffff), - secondaryContainer: Color(0xff31483e), - onSecondaryContainer: Color(0xffffffff), - tertiary: Color(0xff002633), - onTertiary: Color(0xffffffff), - tertiaryContainer: Color(0xff214758), - onTertiaryContainer: Color(0xffffffff), - error: Color(0xff4e0002), - onError: Color(0xffffffff), - errorContainer: Color(0xff8c0009), - onErrorContainer: Color(0xffffffff), - surface: Color(0xfff5fbf5), + primary: Color(0xff2b2c2b), + surfaceTint: Color(0xff5e5f5c), + onPrimary: Color(0xffedece9), + primaryContainer: Color(0xff484947), + onPrimaryContainer: Color(0xffedece9), + secondary: Color(0xff352a20), + onSecondary: Color(0xffedece9), + secondaryContainer: Color(0xff54473c), + onSecondaryContainer: Color(0xffedece9), + tertiary: Color(0xff2c2c2e), + onTertiary: Color(0xffedece9), + tertiaryContainer: Color(0xff49494b), + onTertiaryContainer: Color(0xffedece9), + error: Color(0xff600004), + onError: Color(0xffedece9), + errorContainer: Color(0xff98000a), + onErrorContainer: Color(0xffedece9), + surface: Color(0xfffcf8f7), onSurface: Color(0xff000000), - onSurfaceVariant: Color(0xff1d2622), - outline: Color(0xff3c4540), - outlineVariant: Color(0xff3c4540), + onSurfaceVariant: Color(0xff000000), + outline: Color(0xff2a2d2a), + outlineVariant: Color(0xff474a46), shadow: Color(0xff000000), scrim: Color(0xff000000), - inverseSurface: Color(0xff2c322e), - inversePrimary: Color(0xffaffcdb), - primaryFixed: Color(0xff004d38), - onPrimaryFixed: Color(0xffffffff), - primaryFixedDim: Color(0xff003425), - onPrimaryFixedVariant: Color(0xffffffff), - secondaryFixed: Color(0xff31483e), - onSecondaryFixed: Color(0xffffffff), - secondaryFixedDim: Color(0xff1b3128), - onSecondaryFixedVariant: Color(0xffffffff), - tertiaryFixed: Color(0xff214758), - onTertiaryFixed: Color(0xffffffff), - tertiaryFixedDim: Color(0xff043141), - onTertiaryFixedVariant: Color(0xffffffff), - surfaceDim: Color(0xffd6dbd6), - surfaceBright: Color(0xfff5fbf5), - surfaceContainerLowest: Color(0xffffffff), - surfaceContainerLow: Color(0xffeff5f0), - surfaceContainer: Color(0xffe9efea), - surfaceContainerHigh: Color(0xffe4eae4), - surfaceContainerHighest: Color(0xffdee4df), + inverseSurface: Color(0xff313030), + inversePrimary: Color(0xffc7c6c4), + primaryFixed: Color(0xff484947), + onPrimaryFixed: Color(0xffedece9), + primaryFixedDim: Color(0xff323331), + onPrimaryFixedVariant: Color(0xffedece9), + secondaryFixed: Color(0xff54473c), + onSecondaryFixed: Color(0xffedece9), + secondaryFixedDim: Color(0xff3c3027), + onSecondaryFixedVariant: Color(0xffedece9), + tertiaryFixed: Color(0xff49494b), + onTertiaryFixed: Color(0xffedece9), + tertiaryFixedDim: Color(0xff333234), + onTertiaryFixedVariant: Color(0xffedece9), + surfaceDim: Color(0xffbbb8b7), + surfaceBright: Color(0xfffcf8f7), + surfaceContainerLowest: Color(0xffedece9), + surfaceContainerLow: Color(0xfff4f0ef), + surfaceContainer: Color(0xffe5e2e1), + surfaceContainerHigh: Color(0xffd7d4d3), + surfaceContainerHighest: Color(0xffc9c6c5), ); } @@ -116,51 +116,51 @@ class AppTheme { ColorScheme darkScheme() { return const ColorScheme( brightness: Brightness.dark, - primary: Color(0xff8ad6b7), - surfaceTint: Color(0xff8ad6b7), - onPrimary: Color(0xff003828), - primaryContainer: Color(0xff00513c), - onPrimaryContainer: Color(0xffa6f2d2), - secondary: Color(0xffb3ccbf), - onSecondary: Color(0xff1e352c), - secondaryContainer: Color(0xff354b42), - onSecondaryContainer: Color(0xffcee9db), - tertiary: Color(0xffa6cce0), - onTertiary: Color(0xff0a3545), - tertiaryContainer: Color(0xff264b5c), - onTertiaryContainer: Color(0xffc2e8fd), + primary: Color(0xffedece9), + surfaceTint: Color(0xffc7c6c4), + onPrimary: Color(0xff30312f), + primaryContainer: Color(0xffe3e2df), + onPrimaryContainer: Color(0xff646562), + secondary: Color(0xffd6c3b5), + onSecondary: Color(0xff3a2e25), + secondaryContainer: Color(0xff625449), + onSecondaryContainer: Color(0xffdcc9bb), + tertiary: Color(0xffedece9), + onTertiary: Color(0xff303032), + tertiaryContainer: Color(0xffe4e2e4), + onTertiaryContainer: Color(0xff656466), error: Color(0xffffb4ab), onError: Color(0xff690005), errorContainer: Color(0xff93000a), onErrorContainer: Color(0xffffdad6), - surface: Color(0xff0f1512), - onSurface: Color(0xffdee4df), - onSurfaceVariant: Color(0xffbfc9c2), - outline: Color(0xff89938d), - outlineVariant: Color(0xff404944), + surface: Color(0xff141313), + onSurface: Color(0xffe5e2e1), + onSurfaceVariant: Color(0xffc5c7c2), + outline: Color(0xff8f918d), + outlineVariant: Color(0xff444844), shadow: Color(0xff000000), scrim: Color(0xff000000), - inverseSurface: Color(0xffdee4df), - inversePrimary: Color(0xff1a6b51), - primaryFixed: Color(0xffa6f2d2), - onPrimaryFixed: Color(0xff002116), - primaryFixedDim: Color(0xff8ad6b7), - onPrimaryFixedVariant: Color(0xff00513c), - secondaryFixed: Color(0xffcee9db), - onSecondaryFixed: Color(0xff092017), - secondaryFixedDim: Color(0xffb3ccbf), - onSecondaryFixedVariant: Color(0xff354b42), - tertiaryFixed: Color(0xffc2e8fd), - onTertiaryFixed: Color(0xff001f2a), - tertiaryFixedDim: Color(0xffa6cce0), - onTertiaryFixedVariant: Color(0xff264b5c), - surfaceDim: Color(0xff0f1512), - surfaceBright: Color(0xff343b37), - surfaceContainerLowest: Color(0xff0a0f0d), - surfaceContainerLow: Color(0xff171d1a), - surfaceContainer: Color(0xff1b211e), - surfaceContainerHigh: Color(0xff252b28), - surfaceContainerHighest: Color(0xff303633), + inverseSurface: Color(0xffe5e2e1), + inversePrimary: Color(0xff5e5f5c), + primaryFixed: Color(0xffe3e2df), + onPrimaryFixed: Color(0xff1b1c1a), + primaryFixedDim: Color(0xffc7c6c4), + onPrimaryFixedVariant: Color(0xff464745), + secondaryFixed: Color(0xfff3dfd0), + onSecondaryFixed: Color(0xff241a11), + secondaryFixedDim: Color(0xffd6c3b5), + onSecondaryFixedVariant: Color(0xff51443a), + tertiaryFixed: Color(0xffe4e2e4), + onTertiaryFixed: Color(0xff1b1b1d), + tertiaryFixedDim: Color(0xffc8c6c8), + onTertiaryFixedVariant: Color(0xff474648), + surfaceDim: Color(0xff141313), + surfaceBright: Color(0xff3a3939), + surfaceContainerLowest: Color(0xff0e0e0e), + surfaceContainerLow: Color(0xff1c1b1b), + surfaceContainer: Color(0xff201f1f), + surfaceContainerHigh: Color(0xff2a2a29), + surfaceContainerHighest: Color(0xff353434), ); } @@ -171,51 +171,51 @@ class AppTheme { ColorScheme darkHighContrastScheme() { return const ColorScheme( brightness: Brightness.dark, - primary: Color(0xffedfff4), - surfaceTint: Color(0xff8ad6b7), + primary: Color(0xffedece9), + surfaceTint: Color(0xffc7c6c4), onPrimary: Color(0xff000000), - primaryContainer: Color(0xff8edabb), - onPrimaryContainer: Color(0xff000000), - secondary: Color(0xffedfff4), + primaryContainer: Color(0xffe3e2df), + onPrimaryContainer: Color(0xff292a29), + secondary: Color(0xffffede0), onSecondary: Color(0xff000000), - secondaryContainer: Color(0xffb7d1c3), - onSecondaryContainer: Color(0xff000000), - tertiary: Color(0xfff7fbff), + secondaryContainer: Color(0xffd2bfb1), + onSecondaryContainer: Color(0xff120904), + tertiary: Color(0xffedece9), onTertiary: Color(0xff000000), - tertiaryContainer: Color(0xffabd0e4), - onTertiaryContainer: Color(0xff000000), - error: Color(0xfffff9f9), + tertiaryContainer: Color(0xffe4e2e4), + onTertiaryContainer: Color(0xff2a2a2b), + error: Color(0xffffece9), onError: Color(0xff000000), - errorContainer: Color(0xffffbab1), - onErrorContainer: Color(0xff000000), - surface: Color(0xff0f1512), - onSurface: Color(0xffffffff), - onSurfaceVariant: Color(0xfff3fdf6), - outline: Color(0xffc3cdc6), - outlineVariant: Color(0xffc3cdc6), + errorContainer: Color(0xffffaea4), + onErrorContainer: Color(0xff220001), + surface: Color(0xff141313), + onSurface: Color(0xffedece9), + onSurfaceVariant: Color(0xffedece9), + outline: Color(0xffeff1eb), + outlineVariant: Color(0xffc1c3be), shadow: Color(0xff000000), scrim: Color(0xff000000), - inverseSurface: Color(0xffdee4df), - inversePrimary: Color(0xff003123), - primaryFixed: Color(0xffaaf7d6), + inverseSurface: Color(0xffe5e2e1), + inversePrimary: Color(0xff474846), + primaryFixed: Color(0xffe3e2df), onPrimaryFixed: Color(0xff000000), - primaryFixedDim: Color(0xff8edabb), - onPrimaryFixedVariant: Color(0xff001b11), - secondaryFixed: Color(0xffd3eddf), + primaryFixedDim: Color(0xffc7c6c4), + onPrimaryFixedVariant: Color(0xff101110), + secondaryFixed: Color(0xfff3dfd0), onSecondaryFixed: Color(0xff000000), - secondaryFixedDim: Color(0xffb7d1c3), - onSecondaryFixedVariant: Color(0xff041a12), - tertiaryFixed: Color(0xffcaecff), + secondaryFixedDim: Color(0xffd6c3b5), + onSecondaryFixedVariant: Color(0xff180f07), + tertiaryFixed: Color(0xffe4e2e4), onTertiaryFixed: Color(0xff000000), - tertiaryFixedDim: Color(0xffabd0e4), - onTertiaryFixedVariant: Color(0xff001923), - surfaceDim: Color(0xff0f1512), - surfaceBright: Color(0xff343b37), - surfaceContainerLowest: Color(0xff0a0f0d), - surfaceContainerLow: Color(0xff171d1a), - surfaceContainer: Color(0xff1b211e), - surfaceContainerHigh: Color(0xff252b28), - surfaceContainerHighest: Color(0xff303633), + tertiaryFixedDim: Color(0xffc8c6c8), + onTertiaryFixedVariant: Color(0xff111113), + surfaceDim: Color(0xff141313), + surfaceBright: Color(0xff51504f), + surfaceContainerLowest: Color(0xff000000), + surfaceContainerLow: Color(0xff201f1f), + surfaceContainer: Color(0xff313030), + surfaceContainerHigh: Color(0xff3c3b3b), + surfaceContainerHighest: Color(0xff484646), ); } @@ -231,7 +231,7 @@ class AppTheme { bodyColor: colorScheme.onSurface, displayColor: colorScheme.onSurface, ), - scaffoldBackgroundColor: colorScheme.surfaceContainer, + scaffoldBackgroundColor: colorScheme.surface, canvasColor: colorScheme.surface, appBarTheme: AppBarTheme( backgroundColor: colorScheme.primary, @@ -241,7 +241,6 @@ class AppTheme { backgroundColor: colorScheme.surface, selectedItemColor: colorScheme.primary, unselectedItemColor: colorScheme.onSurfaceVariant, - elevation: 4, ), ); } diff --git a/lib/theme/view/theme_bottom_sheet.dart b/lib/theme/view/theme_bottom_sheet.dart index 44a0c0e..0cb7104 100644 --- a/lib/theme/view/theme_bottom_sheet.dart +++ b/lib/theme/view/theme_bottom_sheet.dart @@ -7,46 +7,36 @@ import 'package:swiftify/widgets/bottom_sheet_base.dart'; class ThemeBottomSheet extends StatelessWidget { const ThemeBottomSheet({super.key}); - factory ThemeBottomSheet.pageBuilder(_, __) { - return const ThemeBottomSheet(); - } - - static const routeName = '/theme'; - @override Widget build(BuildContext context) { final l10n = context.l10n; return BottomSheetBase( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: GridView.count( - crossAxisCount: 3, - mainAxisSpacing: 8, - children: [ - CardItem( - title: l10n.system, - iconData: Icons.brightness_auto, - onTap: () => context - .read() - .add(const ThemeModeChanged(ThemeMode.system)), - ), - CardItem( - title: l10n.light, - iconData: Icons.sunny, - onTap: () => context - .read() - .add(const ThemeModeChanged(ThemeMode.light)), - ), - CardItem( - title: l10n.dark, - iconData: Icons.nightlight_round, - onTap: () => context - .read() - .add(const ThemeModeChanged(ThemeMode.dark)), - ), - ], - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CardItem( + title: l10n.system, + iconData: Icons.brightness_auto, + onTap: () => context + .read() + .add(const ThemeModeChanged(ThemeMode.system)), + ), + CardItem( + title: l10n.light, + iconData: Icons.sunny, + onTap: () => context + .read() + .add(const ThemeModeChanged(ThemeMode.light)), + ), + CardItem( + title: l10n.dark, + iconData: Icons.nightlight_round, + onTap: () => context + .read() + .add(const ThemeModeChanged(ThemeMode.dark)), + ), + ], ), ); } diff --git a/lib/widgets/bottom_sheet_base.dart b/lib/widgets/bottom_sheet_base.dart index dcbed2f..093765e 100644 --- a/lib/widgets/bottom_sheet_base.dart +++ b/lib/widgets/bottom_sheet_base.dart @@ -14,7 +14,7 @@ class BottomSheetBase extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return SizedBox( - height: height ?? MediaQuery.of(context).size.height * 0.3, + height: height ?? MediaQuery.of(context).size.height * 0.2, child: DecoratedBox( decoration: BoxDecoration( color: colorScheme.surfaceTint, @@ -22,10 +22,7 @@ class BottomSheetBase extends StatelessWidget { top: Radius.circular(12), ), ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: child, - ), + child: child, ), ); } diff --git a/packages/swiftify_repository/lib/src/swiftify_repository.dart b/packages/swiftify_repository/lib/src/swiftify_repository.dart index c6f2ba4..4ee2721 100644 --- a/packages/swiftify_repository/lib/src/swiftify_repository.dart +++ b/packages/swiftify_repository/lib/src/swiftify_repository.dart @@ -35,14 +35,16 @@ class SwiftifyRepository { }) async { try { final songsResponse = - await apiClient.get>>('albums/$albumId'); - return songsResponse.map(Song.fromJson).toList(); + await apiClient.get>('albums/$albumId'); + return songsResponse + .map((e) => Song.fromJson(e as Map)) + .toList(); } catch (e, st) { Error.throwWithStackTrace(GetSongsException(e), st); } } - /// Get lyrics for a song from the API. + /// Get lyrics by [songId] from the API. Future getSongLyrics({ required int songId, }) async { diff --git a/pubspec.lock b/pubspec.lock index f8f847a..7a64ab1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -71,6 +71,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" + url: "https://pub.dev" + source: hosted + version: "4.0.3" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + url: "https://pub.dev" + source: hosted + version: "2.4.14" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + url: "https://pub.dev" + source: hosted + version: "8.9.3" characters: dependency: transitive description: @@ -79,6 +143,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -87,6 +159,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" collection: dependency: transitive description: @@ -119,6 +199,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + url: "https://pub.dev" + source: hosted + version: "3.0.1" diff_match_patch: dependency: transitive description: @@ -159,6 +247,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -211,6 +307,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.6.3" + go_router_builder: + dependency: "direct dev" + description: + name: go_router_builder + sha256: "293e366566c209f70a05508bb5fdd6ce536fa3f75e58ccba1f83341a7943a6be" + url: "https://pub.dev" + source: hosted + version: "2.7.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" hive_ce: dependency: transitive description: @@ -483,6 +595,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.5" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" shelf: dependency: transitive description: @@ -520,6 +640,22 @@ packages: description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" source_map_stack_trace: dependency: transitive description: @@ -560,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -633,6 +777,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.5" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: @@ -723,4 +875,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.6.0 <4.0.0" - flutter: ">=3.24.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index a44ba81..ef00e28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,8 +25,10 @@ dependencies: dev_dependencies: bloc_test: ^10.0.0 + build_runner: ^2.4.14 flutter_test: sdk: flutter + go_router_builder: ^2.7.3 mocktail: ^1.0.4 very_good_analysis: ^7.0.0 diff --git a/test/album/view/album_view_test.dart b/test/album/view/album_view_test.dart index a9de108..3efff32 100644 --- a/test/album/view/album_view_test.dart +++ b/test/album/view/album_view_test.dart @@ -4,6 +4,7 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; import 'package:mocktail/mocktail.dart'; import 'package:mocktail_image_network/mocktail_image_network.dart'; import 'package:swiftify/album/album.dart'; @@ -14,8 +15,11 @@ import '../../helpers/helpers.dart'; class _MockAlbumBloc extends MockBloc implements AlbumBloc {} +class _MockGoRouter extends Mock implements GoRouter {} + void main() { late AlbumBloc albumBloc; + late GoRouter goRouter; final albums = List.generate( 3, (index) => Album( @@ -28,6 +32,7 @@ void main() { setUp(() { albumBloc = _MockAlbumBloc(); + goRouter = _MockGoRouter(); }); testWidgets('renders a CircularProgessIndicator when isLoading', @@ -81,4 +86,28 @@ void main() { expect(find.text('Failed to fetch albums'), findsOneWidget); }); + + testWidgets('navigate to SongsPage when click on a song', (tester) async { + when(() => goRouter.go(any())).thenAnswer((_) async => true); + when(() => albumBloc.state).thenReturn( + AlbumState( + status: AlbumStatus.success, + albums: albums, + ), + ); + + await mockNetworkImages( + () async => tester.pumpApp( + BlocProvider.value( + value: albumBloc, + child: AlbumView(), + ), + goRouter: goRouter, + ), + ); + + await tester.tap(find.text('title 0')); + await tester.pumpAndSettle(); + verify(() => goRouter.go(any())).called(1); + }); } diff --git a/test/app/app_router/app_router_test.dart b/test/app/app_router/app_router_test.dart index 2ffd0a6..481f1b2 100644 --- a/test/app/app_router/app_router_test.dart +++ b/test/app/app_router/app_router_test.dart @@ -1,10 +1,14 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:mocktail/mocktail.dart'; import 'package:swiftify/album/album.dart'; import 'package:swiftify/app/app_router/app_router.dart'; +import 'package:swiftify/app/app_router/routes/routes.dart'; import 'package:swiftify/favorites/favorites.dart'; +import 'package:swiftify/songs/songs.dart'; import 'package:swiftify/theme/theme.dart'; import '../../helpers/helpers.dart'; @@ -59,7 +63,7 @@ void main() { appRouter: appRouter, ); - appRouter.routes.go(AlbumPage.routeName); + appRouter.routes.go(AlbumPageRoute.path); await tester.pumpAndSettle(); expect(find.byType(AlbumPage), findsOneWidget); @@ -70,12 +74,7 @@ void main() { appRouter: appRouter, ); - final bottomNav = tester.widget( - find.byType(BottomNavigationBar), - ); - - bottomNav.onTap!(1); - appRouter.routes.go(FavoritesPage.routeName); + appRouter.routes.go(FavoritesPageRoute.path); await tester.pumpAndSettle(); expect(find.byType(FavoritesPage), findsOneWidget); @@ -85,17 +84,59 @@ void main() { await tester.pumpRoutes( appRouter: appRouter, ); - final iconButton = tester.widget( find.byType(IconButton), ); iconButton.onPressed!(); - appRouter.routes.go(ThemeBottomSheet.routeName); + + appRouter.routes.go(ThemePageRoute.path); await tester.pumpAndSettle(); expect(find.byType(ThemeBottomSheet), findsOneWidget); }); + + testWidgets('to SongPage', (tester) async { + await tester.pumpRoutes( + appRouter: appRouter, + ); + + appRouter.routes.go('/songs/1'); + await tester.pumpAndSettle(); + expect(find.byType(SongsPage), findsOneWidget); + }); + + group('navigates with BottomNavigationBar', () { + testWidgets('to AlbumPage', (tester) async { + await tester.pumpRoutes( + appRouter: appRouter, + ); + final bottomNav = tester.widget( + find.byType(BottomNavigationBar), + ); + + bottomNav.onTap!(0); + appRouter.routes.go(AlbumPageRoute.path); + + await tester.pumpAndSettle(); + expect(find.byType(AlbumPage), findsOneWidget); + }); + + testWidgets('to FavoritesPage', (tester) async { + await tester.pumpRoutes( + appRouter: appRouter, + ); + final bottomNav = tester.widget( + find.byType(BottomNavigationBar), + ); + + bottomNav.onTap!(1); + appRouter.routes.go(FavoritesPageRoute.path); + + await tester.pumpAndSettle(); + expect(find.byType(FavoritesPage), findsOneWidget); + }); + }); }); }); } diff --git a/test/songs/bloc/song_bloc_test.dart b/test/songs/bloc/song_bloc_test.dart index d3d65ed..4da58aa 100644 --- a/test/songs/bloc/song_bloc_test.dart +++ b/test/songs/bloc/song_bloc_test.dart @@ -3,7 +3,7 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:swiftify/songs/song.dart'; +import 'package:swiftify/songs/songs.dart'; import 'package:swiftify_repository/swiftify_repository.dart'; class _MockSwiftifyRepository extends Mock implements SwiftifyRepository {} @@ -21,12 +21,12 @@ void main() { SongBloc( swiftifyRepository: swiftifyRepository, ).state, - const SongState(), + const SongsState(), ); }); group('SongsByAlbumRequested', () { - blocTest( + blocTest( 'emits state with updated songs', setUp: () { when( @@ -44,15 +44,15 @@ void main() { ), ), expect: () => [ - const SongState(status: SongStatus.loading), - SongState( + const SongsState(status: SongsStatus.loading), + SongsState( songs: const [Song(title: 'willow')], - status: SongStatus.success, + status: SongsStatus.success, ), ], ); - blocTest( + blocTest( 'emits failure when an error occurs', setUp: () { when( @@ -69,9 +69,9 @@ void main() { albumId: 1, ), ), - expect: () => [ - const SongState(status: SongStatus.loading), - const SongState(status: SongStatus.failure), + expect: () => [ + const SongsState(status: SongsStatus.loading), + const SongsState(status: SongsStatus.failure), ], ); }); diff --git a/test/songs/bloc/song_event_test.dart b/test/songs/bloc/song_event_test.dart index 1a3a32c..1b41dd3 100644 --- a/test/songs/bloc/song_event_test.dart +++ b/test/songs/bloc/song_event_test.dart @@ -1,7 +1,7 @@ // ignore_for_file: prefer_const_constructors import 'package:flutter_test/flutter_test.dart'; -import 'package:swiftify/songs/song.dart'; +import 'package:swiftify/songs/songs.dart'; void main() { group('SongsEvent', () { diff --git a/test/songs/bloc/song_state_test.dart b/test/songs/bloc/song_state_test.dart index 5c98450..dcde78d 100644 --- a/test/songs/bloc/song_state_test.dart +++ b/test/songs/bloc/song_state_test.dart @@ -1,35 +1,35 @@ // ignore_for_file: prefer_const_constructors import 'package:flutter_test/flutter_test.dart'; -import 'package:swiftify/songs/song.dart'; +import 'package:swiftify/songs/songs.dart'; import 'package:swiftify_repository/swiftify_repository.dart'; void main() { group('SongState', () { test('supports value comparisons', () { expect( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ), equals( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ), ), ); expect( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ), isNot( equals( - SongState( + SongsState( songs: const [Song(title: 'different')], - status: SongStatus.loading, + status: SongsStatus.loading, ), ), ), @@ -38,57 +38,52 @@ void main() { test('copyWith comparisons', () { expect( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ).copyWith(), equals( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ), ), ); expect( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, + status: SongsStatus.loading, ).copyWith(songs: [Song(title: 'different')]), equals( - SongState( + SongsState( songs: const [Song(title: 'different')], - status: SongStatus.loading, + status: SongsStatus.loading, ), ), ); expect( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.loading, - ).copyWith(status: SongStatus.success), + status: SongsStatus.loading, + ).copyWith(status: SongsStatus.success), equals( - SongState( + SongsState( songs: const [Song(title: 'name')], - status: SongStatus.success, + status: SongsStatus.success, ), ), ); }); test('isSuccess', () { - final state = SongState(status: SongStatus.success); + final state = SongsState(status: SongsStatus.success); expect(state.isSuccess, isTrue); }); - test('isFailure', () { - final state = SongState(status: SongStatus.failure); - expect(state.isFailure, isTrue); - }); - test('isLoading', () { - final state = SongState(status: SongStatus.loading); + final state = SongsState(status: SongsStatus.loading); expect(state.isLoading, isTrue); }); }); diff --git a/test/theme/theme_test.dart b/test/theme/app_theme_test.dart similarity index 88% rename from test/theme/theme_test.dart rename to test/theme/app_theme_test.dart index ac9e462..7295366 100644 --- a/test/theme/theme_test.dart +++ b/test/theme/app_theme_test.dart @@ -16,7 +16,7 @@ void main() { test('colorScheme primary is correct', () { expect( appTheme.light.colorScheme.primary, - equals(Color(0xff1a6b51)), + equals(Color(0xff5e5f5c)), ); }); }); @@ -26,7 +26,7 @@ void main() { expect( appTheme.lightHighContrast.colorScheme.primary, equals( - Color(0xff00281c), + Color(0xff2b2c2b), ), ); }); @@ -36,7 +36,7 @@ void main() { test('colorScheme primary is correct', () { expect( appTheme.dark.colorScheme.primary, - equals(Color(0xff8ad6b7)), + equals(Color(0xffedece9)), ); }); }); @@ -45,7 +45,7 @@ void main() { test('colorScheme primary is correct', () { expect( AppTheme().darkHighContrast.colorScheme.primary, - equals(Color(0xffedfff4)), + equals(Color(0xffedece9)), ); }); });