diff --git a/lib/core/external/screen_brightness.dart b/lib/core/external/screen_brightness.dart new file mode 100644 index 000000000..22f8c380d --- /dev/null +++ b/lib/core/external/screen_brightness.dart @@ -0,0 +1,8 @@ +import 'package:screen_brightness/screen_brightness.dart' as sb; + +class ScreenBrightness { + Future setScreenBrightness(double brightness) async => + sb.ScreenBrightness().setScreenBrightness(brightness); + Future resetScreenBrightness() async => + sb.ScreenBrightness().resetScreenBrightness(); +} diff --git a/lib/core/network/network_request_executor_mapping.dart b/lib/core/network/network_request_executor_mapping.dart index 8a0f11dac..fc4b708b4 100644 --- a/lib/core/network/network_request_executor_mapping.dart +++ b/lib/core/network/network_request_executor_mapping.dart @@ -11,7 +11,6 @@ extension ExecutorMapX on _ExecutorResult { extension ExecutorMapAllX on _ExecutorResult> { /// If the result of the [Future] is a [Right], /// maps all values to a [List] of [C]. - // TODO(marfavi): return Iterable instead of List? _ExecutorResult> mapAll(C Function(R) mapper) async { final result = await this; return result.map((items) => items.map(mapper).toList()); diff --git a/lib/core/strings.dart b/lib/core/strings.dart index 7d7fa449a..954a09203 100644 --- a/lib/core/strings.dart +++ b/lib/core/strings.dart @@ -243,8 +243,12 @@ abstract final class Strings { static const voucherCode = 'Voucher code'; static const voucherEmpty = 'Enter a voucher code'; static const voucherUsed = 'That voucher is already used'; - static const youRedeemed = 'You redeemed'; static const voucherRedeemed = 'Voucher redeemed 🎉'; + static String voucherYouRedeemedProducts( + int numberOfTickets, + String productName, + ) => + 'You redeemed $numberOfTickets $productName!'; // Settings static const settingsPageTitle = 'Settings'; diff --git a/lib/core/widgets/components/loading_overlay.dart b/lib/core/widgets/components/loading_overlay.dart index b1a96b5d1..91cd43cf5 100644 --- a/lib/core/widgets/components/loading_overlay.dart +++ b/lib/core/widgets/components/loading_overlay.dart @@ -1,24 +1,22 @@ import 'package:coffeecard/core/styles/app_colors.dart'; import 'package:flutter/material.dart'; -void showLoadingOverlay(BuildContext context) { - final _ = showDialog( - context: context, - barrierColor: AppColors.scrim, - barrierDismissible: false, - useRootNavigator: true, - builder: (_) { - return WillPopScope( - // Will prevent Android back button from closing overlay. - onWillPop: () async => false, - child: const Center( - child: CircularProgressIndicator(color: AppColors.white), - ), +class LoadingOverlay { + static Future show(BuildContext context) => showDialog( + context: context, + barrierColor: AppColors.scrim, + barrierDismissible: false, + builder: (_) { + return WillPopScope( + // Will prevent Android back button from closing overlay. + onWillPop: () async => false, + child: const Center( + child: CircularProgressIndicator(color: AppColors.white), + ), + ); + }, ); - }, - ); -} -void hideLoadingOverlay(BuildContext context) { - Navigator.of(context, rootNavigator: true).pop(); + static void hide(BuildContext context) => + Navigator.of(context, rootNavigator: true).pop(); } diff --git a/lib/core/widgets/pages/splash/splash_error_page.dart b/lib/core/widgets/pages/splash/splash_error_page.dart index 39330aef7..f6ae79b65 100644 --- a/lib/core/widgets/pages/splash/splash_error_page.dart +++ b/lib/core/widgets/pages/splash/splash_error_page.dart @@ -1,3 +1,4 @@ +import 'package:coffeecard/core/ignore_value.dart'; import 'package:coffeecard/core/strings.dart'; import 'package:coffeecard/core/styles/app_colors.dart'; import 'package:coffeecard/core/styles/app_text_styles.dart'; @@ -43,12 +44,12 @@ class _SplashErrorPageState extends State { onPressed: () async { final environmentLoaded = context.read().getConfig(); - showLoadingOverlay(context); + ignoreValue(LoadingOverlay.show(context)); // Delay since it is otherwise not obvious // a load is happening with no internet final _ = await Future.delayed(const Duration(milliseconds: 200)); await environmentLoaded; - if (mounted) hideLoadingOverlay(context); + if (mounted) LoadingOverlay.hide(context); }, child: Text( Strings.retry, diff --git a/lib/features/login/presentation/pages/login_page_passcode.dart b/lib/features/login/presentation/pages/login_page_passcode.dart index 1de19e6b7..88b445747 100644 --- a/lib/features/login/presentation/pages/login_page_passcode.dart +++ b/lib/features/login/presentation/pages/login_page_passcode.dart @@ -1,3 +1,4 @@ +import 'package:coffeecard/core/ignore_value.dart'; import 'package:coffeecard/core/strings.dart'; import 'package:coffeecard/core/widgets/components/dialog.dart'; import 'package:coffeecard/core/widgets/components/loading_overlay.dart'; @@ -68,9 +69,9 @@ class _LoginPagePasscodeState extends State { previous is LoginLoading || current is LoginLoading, listener: (context, state) { if (state is LoginLoading) { - showLoadingOverlay(context); + ignoreValue(LoadingOverlay.show(context)); } else { - hideLoadingOverlay(context); + LoadingOverlay.hide(context); } }, buildWhen: (_, current) => current is! LoginLoading, diff --git a/lib/features/login/presentation/widgets/forgot_passcode_form.dart b/lib/features/login/presentation/widgets/forgot_passcode_form.dart index 2e7e3ddd3..0da3c35a5 100644 --- a/lib/features/login/presentation/widgets/forgot_passcode_form.dart +++ b/lib/features/login/presentation/widgets/forgot_passcode_form.dart @@ -1,3 +1,4 @@ +import 'package:coffeecard/core/ignore_value.dart'; import 'package:coffeecard/core/strings.dart'; import 'package:coffeecard/core/validator/input_validator.dart'; import 'package:coffeecard/core/widgets/components/dialog.dart'; @@ -50,7 +51,7 @@ class ForgotPasscodeForm extends StatelessWidget { } Future _onSubmit(BuildContext context, String email) async { - showLoadingOverlay(context); + ignoreValue(LoadingOverlay.show(context)); final either = await sl().requestPasscodeReset(email); @@ -74,7 +75,7 @@ class ForgotPasscodeForm extends StatelessWidget { TextButton( onPressed: () { closeAppDialog(context); - hideLoadingOverlay(context); + LoadingOverlay.hide(context); // Exits the forgot passcode flow Navigator.pop(context); }, diff --git a/lib/features/product/presentation/pages/buy_tickets_page.dart b/lib/features/product/presentation/pages/buy_tickets_page.dart index 02d9df724..b6038768c 100644 --- a/lib/features/product/presentation/pages/buy_tickets_page.dart +++ b/lib/features/product/presentation/pages/buy_tickets_page.dart @@ -116,12 +116,13 @@ class BuyTicketsPage extends StatelessWidget { final updateReceiptsRequest = context.read().fetchReceipts(); - ReceiptOverlay.of(context).show( + final _ = ReceiptOverlay.show( isTestEnvironment: envState is EnvironmentLoaded && envState.env.isTest, status: Strings.purchased, productName: payment.productName, timeUsed: payment.purchaseTime, + context: context, ); await updateTicketsRequest; await updateReceiptsRequest; diff --git a/lib/features/receipt/presentation/widgets/receipt_overlay.dart b/lib/features/receipt/presentation/widgets/receipt_overlay.dart index 1625e40d4..97c056e00 100644 --- a/lib/features/receipt/presentation/widgets/receipt_overlay.dart +++ b/lib/features/receipt/presentation/widgets/receipt_overlay.dart @@ -1,30 +1,31 @@ +import 'package:coffeecard/core/external/screen_brightness.dart'; import 'package:coffeecard/core/strings.dart'; import 'package:coffeecard/core/styles/app_colors.dart'; import 'package:coffeecard/core/styles/app_text_styles.dart'; import 'package:coffeecard/core/widgets/components/helpers/responsive.dart'; import 'package:coffeecard/features/receipt/presentation/widgets/receipt_card.dart'; +import 'package:coffeecard/service_locator.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:screen_brightness/screen_brightness.dart'; class ReceiptOverlay { - // TODO(marfavi): see if a more generic version can be made that supports both the loading overlay and this one, https://github.com/AnalogIO/coffeecard_app/issues/386 - BuildContext _context; + static final ScreenBrightness screenBrightness = sl(); - void hide() { - Navigator.of(_context).pop(); - } + static void hide(BuildContext context) => Navigator.of(context).pop(); - Future show({ + static Future show({ required String productName, required DateTime timeUsed, required bool isTestEnvironment, required String status, + required BuildContext context, }) async { - await ScreenBrightness().setScreenBrightness(1); - if (_context.mounted) { - final _ = await showDialog( - context: _context, + T? result; + + await screenBrightness.setScreenBrightness(1); + if (context.mounted) { + result = await showDialog( + context: context, barrierColor: AppColors.scrim, builder: (context) { return Padding( @@ -52,12 +53,8 @@ class ReceiptOverlay { }, ); } - await ScreenBrightness().resetScreenBrightness(); - } - - ReceiptOverlay.__create(this._context); + await screenBrightness.resetScreenBrightness(); - factory ReceiptOverlay.of(BuildContext context) { - return ReceiptOverlay.__create(context); + return result; } } diff --git a/lib/features/register/presentation/pages/register_page_name.dart b/lib/features/register/presentation/pages/register_page_name.dart index 4a211d2d7..b1a2417d7 100644 --- a/lib/features/register/presentation/pages/register_page_name.dart +++ b/lib/features/register/presentation/pages/register_page_name.dart @@ -79,7 +79,7 @@ class RegisterPageName extends StatelessWidget { TextButton( onPressed: () { closeAppDialog(context); - hideLoadingOverlay(context); + LoadingOverlay.hide(context); // Exits the register flow Navigator.of(context, rootNavigator: true).pop(); }, @@ -104,7 +104,7 @@ class RegisterPageName extends StatelessWidget { TextButton( onPressed: () { closeAppDialog(context); - hideLoadingOverlay(context); + LoadingOverlay.hide(context); }, child: const Text(Strings.buttonClose), ), diff --git a/lib/features/register/presentation/widgets/forms/register_name_form.dart b/lib/features/register/presentation/widgets/forms/register_name_form.dart index ab029b6a8..548467dc8 100644 --- a/lib/features/register/presentation/widgets/forms/register_name_form.dart +++ b/lib/features/register/presentation/widgets/forms/register_name_form.dart @@ -1,3 +1,4 @@ +import 'package:coffeecard/core/ignore_value.dart'; import 'package:coffeecard/core/strings.dart'; import 'package:coffeecard/core/validator/input_validator.dart'; import 'package:coffeecard/core/widgets/components/dialog.dart'; @@ -40,9 +41,9 @@ class _RegisterNameFormState extends State { } Future _showTerms(BuildContext context, String name) async { - showLoadingOverlay(context); + ignoreValue(LoadingOverlay.show(context)); // Allow keyboard to disappear before showing dialog - final _ = await Future.delayed(const Duration(milliseconds: 350)); + ignoreValue(await Future.delayed(const Duration(milliseconds: 350))); if (!mounted) return; // Shows the terms to the user before proceeding with the registration. @@ -64,7 +65,7 @@ class _RegisterNameFormState extends State { child: const Text(Strings.buttonDecline), onPressed: () { closeAppDialog(context); - if (mounted) hideLoadingOverlay(context); + if (mounted) LoadingOverlay.hide(context); }, ), TextButton( diff --git a/lib/features/ticket/presentation/widgets/tickets_section.dart b/lib/features/ticket/presentation/widgets/tickets_section.dart index 8e9fbeae1..34d1dd7c1 100644 --- a/lib/features/ticket/presentation/widgets/tickets_section.dart +++ b/lib/features/ticket/presentation/widgets/tickets_section.dart @@ -37,7 +37,7 @@ class TicketSection extends StatelessWidget { Navigator.of(context, rootNavigator: true).pop(); } - showLoadingOverlay(context); + final _ = LoadingOverlay.show(context); } if (state is TicketUsed) { // Refresh or load user info (for updated rank stats) @@ -45,8 +45,8 @@ class TicketSection extends StatelessWidget { context.read().fetchUserDetails(); final envState = context.read().state; - hideLoadingOverlay(context); - ReceiptOverlay.of(context).show( + LoadingOverlay.hide(context); + final _ = ReceiptOverlay.show( isTestEnvironment: envState is EnvironmentLoaded && envState.env.isTest, status: state.receipt is PurchaseReceipt @@ -56,10 +56,11 @@ class TicketSection extends StatelessWidget { : Strings.swiped, productName: state.receipt.productName, timeUsed: state.receipt.timeUsed, + context: context, ); } if (state is TicketsUseError) { - hideLoadingOverlay(context); + LoadingOverlay.hide(context); appDialog( context: context, title: Strings.error, diff --git a/lib/features/voucher/presentation/pages/redeem_voucher_page.dart b/lib/features/voucher/presentation/pages/redeem_voucher_page.dart index 1dbd07b1d..0fef7d85d 100644 --- a/lib/features/voucher/presentation/pages/redeem_voucher_page.dart +++ b/lib/features/voucher/presentation/pages/redeem_voucher_page.dart @@ -25,8 +25,11 @@ class RedeemVoucherPage extends StatelessWidget { create: (_) => sl(), child: BlocListener( listener: (context, state) { - if (state is VoucherLoading) return showLoadingOverlay(context); - hideLoadingOverlay(context); + if (state is VoucherLoading) { + final _ = LoadingOverlay.show(context); + return; + } + LoadingOverlay.hide(context); if (state is VoucherSuccess) return _onSuccess(context, state); if (state is VoucherError) return _onError(context, state); }, @@ -55,7 +58,10 @@ class RedeemVoucherPage extends StatelessWidget { ], children: [ Text( - '${Strings.youRedeemed} ${state.redeemedVoucher.numberOfTickets} ${state.redeemedVoucher.productName}!', + Strings.voucherYouRedeemedProducts( + state.redeemedVoucher.numberOfTickets, + state.redeemedVoucher.productName, + ), style: AppTextStyle.settingKey, ), ], diff --git a/lib/service_locator.dart b/lib/service_locator.dart index 6ac6d1aa3..d5baa29c5 100644 --- a/lib/service_locator.dart +++ b/lib/service_locator.dart @@ -2,6 +2,7 @@ import 'package:chopper/chopper.dart'; import 'package:coffeecard/core/api_uri_constants.dart'; import 'package:coffeecard/core/external/date_service.dart'; import 'package:coffeecard/core/external/external_url_launcher.dart'; +import 'package:coffeecard/core/external/screen_brightness.dart'; import 'package:coffeecard/core/firebase_analytics_event_logging.dart'; import 'package:coffeecard/core/ignore_value.dart'; import 'package:coffeecard/core/network/network_request_executor.dart'; @@ -118,7 +119,7 @@ void configureServices() { ); ignoreValue(sl.registerFactory(() => DateService())); - + ignoreValue(sl.registerFactory(() => ScreenBrightness())); ignoreValue(sl.registerLazySingleton(() => ExternalUrlLauncher())); // provide the account repository to the reactivation authenticator