diff --git a/.gitignore b/.gitignore index 72b25470..121f88c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ # Env managment lib/dart_define.gen.dart -# start of code generated by package:dart_define, DO NOT modify or remove these comments -dart_define.json -# end of code generated by package:dart_define, DO NOT modify or remove these comments + # l10n lib/l10n/app_localizations*.dart @@ -175,3 +173,8 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser + +# start of code generated by package:dart_define, DO NOT modify or remove these comments +dart_define.json +dart_define.json +# end of code generated by package:dart_define, DO NOT modify or remove these comments \ No newline at end of file diff --git a/lib/app/app_router.dart b/lib/app/app_router.dart index e5de2087..e35fefe3 100644 --- a/lib/app/app_router.dart +++ b/lib/app/app_router.dart @@ -9,7 +9,8 @@ import "../features/auth/presentation/auth_page/auth_page.dart"; import "../features/dashboard/presentation/dashboard_page/dashboard_page.dart"; import "../features/dashboard/presentation/wrapper_page/wrapper_page.dart"; import "../features/gpa_calculator/presentation/gpa_page/gpa_page.dart"; -import "../features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page.dart"; +import "../features/pirate_coins/presentation/pirate_coins_page/pirate_coins_student_page.dart"; +import "../features/pirate_coins/presentation/pirate_coins_page/pirate_coins_teacher_page.dart"; import "../features/pirate_coins/presentation/stats_page/stats_page.dart"; part "app_router.gr.dart"; @@ -42,8 +43,20 @@ class AppRouter extends _$AppRouter { ], children: [ AutoRoute( - page: PirateCoinsRoute.page, - path: "pirate-coins", + page: PirateCoinsTeacherRoute.page, + path: "pirate-coins-teacher", + children: [ + AutoRoute( + page: StatsRoute.page, + path: "stats", + title: (context, route) => "Stats", + ), + ], + title: (context, route) => "Pirate Coins", + ), + AutoRoute( + page: PirateCoinsStudentRoute.page, + path: "pirate-coins-student", children: [ AutoRoute( page: StatsRoute.page, diff --git a/lib/features/dashboard/application/dashboard_service.dart b/lib/features/dashboard/application/dashboard_service.dart index 4244682c..e4115d28 100644 --- a/lib/features/dashboard/application/dashboard_service.dart +++ b/lib/features/dashboard/application/dashboard_service.dart @@ -35,7 +35,8 @@ List get _applets { image: appletsFolder.pirateCoins.path, color: const Color.fromARGB(255, 122, 194, 129), // TODO(lishaduck): figure out how to use routes to keep this type-safe. - location: "/pirate-coins", + location: + "/pirate-coins-student", //not sure how to make this dynamic based on account type ), AppletEntity( image: appletsFolder.gpaCalculator.path, diff --git a/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_student_page.dart b/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_student_page.dart new file mode 100644 index 00000000..d43cf4e8 --- /dev/null +++ b/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_student_page.dart @@ -0,0 +1,79 @@ +/// This library contains the Pirate Coins feature's main page. +library; + +import "package:auto_route/auto_route.dart"; +import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; + +import "../../../../l10n/l10n.dart"; +import "../../../../widgets/big_card/big_card.dart"; +// import "../../../auth/domain/account_type.dart"; +import "../../application/coins_service.dart"; +import "../../domain/coins_model.dart"; + +/// The page located at `/pirate-coins`. +@RoutePage() +class PirateCoinsStudentPage extends ConsumerWidget { + /// Create a new instance of [PirateCoinsStudentPage]. + const PirateCoinsStudentPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // final accountType = ref.watch(accountTypeProvider).valueOrNull; + + return const Center( + /// If the user is not a teacher, redirect them to the student page. + child: _StudentView(), + ); + } +} + +class _StudentView extends ConsumerWidget { + const _StudentView({ + // Temporary ignore, see . + // ignore: unused_element + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final data = ref.watch(currentUserCoinsProvider); + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _ViewCoins(data: data), + ], + ); + } +} + +class _ViewCoins extends StatelessWidget { + const _ViewCoins({ + required this.data, + // Temporary ignore, see . + // ignore: unused_element + super.key, + }); + + final AsyncValue data; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Padding( + padding: const EdgeInsets.all(8), + child: switch (data) { + AsyncData(:final value) => BigCard("${value.coins.coins}"), + AsyncError(:final error) => BigCard(l10n.error("$error")), + AsyncLoading() => Column( + children: [ + const CircularProgressIndicator(), + Text(l10n.loading), + ], + ), + }, + ); + } +} diff --git a/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page.dart b/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_teacher_page.dart similarity index 96% rename from lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page.dart rename to lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_teacher_page.dart index 1963e7ea..40f5e5fc 100644 --- a/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page.dart +++ b/lib/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_teacher_page.dart @@ -19,15 +19,16 @@ import "../../domain/coins_model.dart"; /// The page located at `/pirate-coins`. @RoutePage() -class PirateCoinsPage extends ConsumerWidget { - /// Create a new instance of [PirateCoinsPage]. - const PirateCoinsPage({super.key}); +class PirateCoinsTeacherPage extends ConsumerWidget { + /// Create a new instance of [PirateCoinsTeacherPage]. + const PirateCoinsTeacherPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final accountType = ref.watch(accountTypeProvider).valueOrNull; return Center( + /// If the user is not a teacher, redirect them to the student page. child: switch (accountType) { AccountType.student => const _StudentView(), AccountType.teacher => const _TeacherView(), diff --git a/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_test.dart b/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_student_test.dart similarity index 92% rename from test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_test.dart rename to test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_student_test.dart index 21f3c89a..32a4bbe5 100644 --- a/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_test.dart +++ b/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_student_test.dart @@ -1,7 +1,7 @@ import "package:flutter_test/flutter_test.dart"; import "package:mocktail/mocktail.dart"; import "package:pirate_code/features/auth/data/auth_repository.dart"; -import "package:pirate_code/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page.dart"; +import "package:pirate_code/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_teacher_page.dart"; import "../../../../helpers/pump_app.dart"; @@ -16,7 +16,7 @@ void main() { overrides: [ authProvider.overrideWithValue(mockAuthRepository), ], - const PirateCoinsPage(), + const PirateCoinsTeacherPage(), ); verify(() => mockAuthRepository.authenticate(anonymous: true)) @@ -34,7 +34,7 @@ void main() { overrides: [ authProvider.overrideWithValue(mockAuthRepository), ], - const PirateCoinsPage(), + const PirateCoinsTeacherPage(), ); verify(() => mockAuthRepository.authenticate(anonymous: true)) @@ -52,7 +52,7 @@ void main() { overrides: [ authProvider.overrideWithValue(mockAuthRepository), ], - const PirateCoinsPage(), + const PirateCoinsTeacherPage(), ); verify(() => mockAuthRepository.authenticate(anonymous: true)) @@ -70,7 +70,7 @@ void main() { overrides: [ authProvider.overrideWithValue(mockAuthRepository), ], - const PirateCoinsPage(), + const PirateCoinsTeacherPage(), ); verify(() => mockAuthRepository.authenticate(anonymous: true)) diff --git a/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_teacher_test.dart b/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_teacher_test.dart new file mode 100644 index 00000000..42f537e2 --- /dev/null +++ b/test/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_page_teacher_test.dart @@ -0,0 +1,87 @@ +import "package:flutter_test/flutter_test.dart"; +import "package:mocktail/mocktail.dart"; +import "package:pirate_code/features/auth/data/auth_repository.dart"; +import "package:pirate_code/features/pirate_coins/presentation/pirate_coins_page/pirate_coins_student_page.dart"; + +import "../../../../helpers/pump_app.dart"; + +void main() { + group("Pirate Coins page is accessible...", () { + group("is accessible...", () { + testWidgets("on Android.", (tester) async { + final mockAuthRepository = _MockAuthRepository(); + verifyZeroInteractions(mockAuthRepository); + + await tester.pumpApp( + overrides: [ + authProvider.overrideWithValue(mockAuthRepository), + ], + const PirateCoinsStudentPage(), + ); + + verify(() => mockAuthRepository.authenticate(anonymous: true)) + .called(1); + + final handle = tester.ensureSemantics(); + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + handle.dispose(); + }); + testWidgets("on iOS.", (tester) async { + final mockAuthRepository = _MockAuthRepository(); + verifyZeroInteractions(mockAuthRepository); + + await tester.pumpApp( + overrides: [ + authProvider.overrideWithValue(mockAuthRepository), + ], + const PirateCoinsStudentPage(), + ); + + verify(() => mockAuthRepository.authenticate(anonymous: true)) + .called(1); + + final handle = tester.ensureSemantics(); + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + handle.dispose(); + }); + testWidgets("according to the WCAG.", (tester) async { + final mockAuthRepository = _MockAuthRepository(); + verifyZeroInteractions(mockAuthRepository); + + await tester.pumpApp( + overrides: [ + authProvider.overrideWithValue(mockAuthRepository), + ], + const PirateCoinsStudentPage(), + ); + + verify(() => mockAuthRepository.authenticate(anonymous: true)) + .called(1); + + final handle = tester.ensureSemantics(); + await expectLater(tester, meetsGuideline(textContrastGuideline)); + handle.dispose(); + }); + testWidgets("with regard to labeling buttons.", (tester) async { + final mockAuthRepository = _MockAuthRepository(); + verifyZeroInteractions(mockAuthRepository); + + await tester.pumpApp( + overrides: [ + authProvider.overrideWithValue(mockAuthRepository), + ], + const PirateCoinsStudentPage(), + ); + + verify(() => mockAuthRepository.authenticate(anonymous: true)) + .called(1); + + final handle = tester.ensureSemantics(); + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + handle.dispose(); + }); + }); + }); +} + +class _MockAuthRepository extends Mock implements AuthRepository {}