From 75a22dc6194bb547b21741b8ed452200a6c7acbf Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Sat, 27 May 2023 11:19:54 +0200 Subject: [PATCH 1/2] fix: Avoid type casting 204 return types from API (#479) --- .../datasources/receipt_remote_data_source.dart | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart index 0c98ec81a..0eb8f08c1 100644 --- a/lib/features/receipt/data/datasources/receipt_remote_data_source.dart +++ b/lib/features/receipt/data/datasources/receipt_remote_data_source.dart @@ -27,17 +27,9 @@ class ReceiptRemoteDataSource { /// Retrieves all of the users purchase receipts Future>> getUserPurchasesReceipts() async { - // The API CAN return null if the user has no tickets, - // but the generator doesn't pick up on this, hence the type parameter - return executor?>( - apiV2.apiV2PurchasesGet, - ).bindFuture((result) { - // If the user has no purchases, the API returns 204 No Content (body is - // null). The generator is bad and doesn't handle this case - if (result == null) return List.empty(); - return result - .map(PurchaseReceiptModel.fromSimplePurchaseResponse) - .toList(); - }); + return executor(apiV2.apiV2PurchasesGet).bindFuture( + (result) => + result.map(PurchaseReceiptModel.fromSimplePurchaseResponse).toList(), + ); } } From b30ed85911304fbf0ab5272823b30a239c405056 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Sat, 27 May 2023 22:41:09 +0200 Subject: [PATCH 2/2] fix(receipts): Remove purchase receipt view & refactor payment status display (#478) This commit closes #477. - `PurchaseReceiptListEntry` widget now has 'tappable' set to false, disabling purchase receipts. - Updated `PaymentStatus` string values to provide clearer descriptions. - Renamed 'purchaseStatus' field to 'status' across multiple generic receipt widgets. - Refactored `PurchaseReceiptListEntry` to show price differently (or hide it), based on status. - misc: Updated `_formatter` to `_formatDate` in `ReceiptListEntry` and `ReceiptCard`. - misc: Removed rounded edges from `ReceiptListEntry`. --- lib/base/strings.dart | 7 +++++ .../domain/entities/payment_status.dart | 14 +++++---- .../presentation/pages/buy_tickets_page.dart | 2 +- .../presentation/pages/view_receipt_page.dart | 2 +- .../placeholder_receipt_list_entry.dart | 2 +- .../purchase_receipt_list_entry.dart | 24 ++++++++++++--- .../list_entry/receipt_list_entry.dart | 30 +++++++++++++++---- .../list_entry/swipe_receipt_list_entry.dart | 2 +- .../presentation/widgets/receipt_card.dart | 10 +++---- .../presentation/widgets/receipt_overlay.dart | 4 +-- .../presentation/widgets/tickets_section.dart | 2 +- 11 files changed, 71 insertions(+), 28 deletions(-) diff --git a/lib/base/strings.dart b/lib/base/strings.dart index 2f651868e..3b487436f 100644 --- a/lib/base/strings.dart +++ b/lib/base/strings.dart @@ -202,6 +202,13 @@ abstract final class Strings { static String noReceiptsOfTypeMessage(String buyOrSwipe) => 'When you $buyOrSwipe tickets, they will show up here.\nGo to the Tickets tab to $buyOrSwipe tickets.'; + // PaymentStatus enum + static const paymentStatusCompleted = 'Purchased'; + static const paymentStatusRefunded = 'Purchase refunded'; + static const paymentStatusAwaitingPayment = 'Payment pending'; + static const paymentStatusReserved = 'Payment reserved'; + static const paymentStatusFailed = 'Payment failed'; + // Statistics page static const statsYourStats = 'Your stats'; static const statsLeaderboards = 'Leaderboards'; diff --git a/lib/features/purchase/domain/entities/payment_status.dart b/lib/features/purchase/domain/entities/payment_status.dart index a0daa5cd0..1e59068b8 100644 --- a/lib/features/purchase/domain/entities/payment_status.dart +++ b/lib/features/purchase/domain/entities/payment_status.dart @@ -1,3 +1,4 @@ +import 'package:coffeecard/base/strings.dart'; import 'package:coffeecard/generated/api/coffeecard_api_v2.enums.swagger.dart'; enum PaymentStatus { @@ -32,12 +33,13 @@ enum PaymentStatus { @override String toString() { return switch (this) { - PaymentStatus.completed => 'Completed', - PaymentStatus.rejectedPayment => 'Rejected', - PaymentStatus.awaitingPayment => 'Pending', - PaymentStatus.refunded => 'Refunded', - PaymentStatus.error => 'Error', - PaymentStatus.reserved => 'Reserved', + PaymentStatus.completed => Strings.paymentStatusCompleted, + PaymentStatus.refunded => Strings.paymentStatusRefunded, + PaymentStatus.awaitingPayment => Strings.paymentStatusAwaitingPayment, + PaymentStatus.reserved => Strings.paymentStatusReserved, + PaymentStatus.rejectedPayment || + PaymentStatus.error => + Strings.paymentStatusFailed, }; } } diff --git a/lib/features/purchase/presentation/pages/buy_tickets_page.dart b/lib/features/purchase/presentation/pages/buy_tickets_page.dart index 3d0cdba6e..708a4f6ae 100644 --- a/lib/features/purchase/presentation/pages/buy_tickets_page.dart +++ b/lib/features/purchase/presentation/pages/buy_tickets_page.dart @@ -121,7 +121,7 @@ class BuyTicketsPage extends StatelessWidget { ReceiptOverlay.of(context).show( isTestEnvironment: envState is EnvironmentLoaded && envState.env.isTest, - paymentStatus: Strings.purchased, + status: Strings.purchased, productName: payment.productName, timeUsed: payment.purchaseTime, ); diff --git a/lib/features/receipt/presentation/pages/view_receipt_page.dart b/lib/features/receipt/presentation/pages/view_receipt_page.dart index 77a589b70..873db7b39 100644 --- a/lib/features/receipt/presentation/pages/view_receipt_page.dart +++ b/lib/features/receipt/presentation/pages/view_receipt_page.dart @@ -34,7 +34,7 @@ class ViewReceiptPage extends StatelessWidget { isInOverlay: false, isTestEnvironment: state is EnvironmentLoaded && state.env.isTest, - paymentStatus: paymentStatus, + status: paymentStatus, ), ], ), diff --git a/lib/features/receipt/presentation/widgets/list_entry/placeholder_receipt_list_entry.dart b/lib/features/receipt/presentation/widgets/list_entry/placeholder_receipt_list_entry.dart index 3980328db..8aaedfaea 100644 --- a/lib/features/receipt/presentation/widgets/list_entry/placeholder_receipt_list_entry.dart +++ b/lib/features/receipt/presentation/widgets/list_entry/placeholder_receipt_list_entry.dart @@ -16,7 +16,7 @@ class PlaceholderReceiptListEntry extends StatelessWidget { topText: Strings.receiptPlaceholderName, rightText: Strings.oneTicket, backgroundColor: Colors.transparent, - purchaseStatus: '', + status: '', ); } } diff --git a/lib/features/receipt/presentation/widgets/list_entry/purchase_receipt_list_entry.dart b/lib/features/receipt/presentation/widgets/list_entry/purchase_receipt_list_entry.dart index 067ced6da..5b3ed9f18 100644 --- a/lib/features/receipt/presentation/widgets/list_entry/purchase_receipt_list_entry.dart +++ b/lib/features/receipt/presentation/widgets/list_entry/purchase_receipt_list_entry.dart @@ -1,4 +1,6 @@ +import 'package:coffeecard/base/strings.dart'; import 'package:coffeecard/base/style/colors.dart'; +import 'package:coffeecard/features/purchase/domain/entities/payment_status.dart'; import 'package:coffeecard/features/receipt/domain/entities/receipt.dart'; import 'package:coffeecard/features/receipt/presentation/widgets/list_entry/receipt_list_entry.dart'; import 'package:flutter/material.dart'; @@ -10,18 +12,32 @@ class PurchaseReceiptListEntry extends StatelessWidget { required this.receipt, }); + String get priceText { + final price = Strings.price(receipt.price); + return switch (receipt.paymentStatus) { + PaymentStatus.completed => price, + PaymentStatus.awaitingPayment || + PaymentStatus.reserved || + PaymentStatus.refunded => + '($price)', + PaymentStatus.error || PaymentStatus.rejectedPayment => '', + }; + } + @override Widget build(BuildContext context) { return ReceiptListEntry( - tappable: true, + tappable: false, name: receipt.productName, time: receipt.timeUsed, isPurchase: true, showShimmer: false, topText: '${receipt.amountPurchased} ${receipt.productName}', - rightText: '${receipt.price},-', - backgroundColor: AppColor.slightlyHighlighted, - purchaseStatus: '${receipt.paymentStatus}', + rightText: priceText, + backgroundColor: receipt.paymentStatus == PaymentStatus.completed + ? AppColor.slightlyHighlighted + : AppColor.lightGray.withOpacity(0.5), + status: '${receipt.paymentStatus}', ); } } diff --git a/lib/features/receipt/presentation/widgets/list_entry/receipt_list_entry.dart b/lib/features/receipt/presentation/widgets/list_entry/receipt_list_entry.dart index 55c02c189..13f1e3402 100644 --- a/lib/features/receipt/presentation/widgets/list_entry/receipt_list_entry.dart +++ b/lib/features/receipt/presentation/widgets/list_entry/receipt_list_entry.dart @@ -1,4 +1,5 @@ import 'package:animations/animations.dart'; +import 'package:coffeecard/base/style/colors.dart'; import 'package:coffeecard/base/style/text_styles.dart'; import 'package:coffeecard/features/receipt/presentation/pages/view_receipt_page.dart'; import 'package:coffeecard/widgets/components/helpers/shimmer_builder.dart'; @@ -6,7 +7,7 @@ import 'package:coffeecard/widgets/components/list_entry.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -final _formatter = DateFormat('dd.MM.yyyy'); +final _formatDateTime = DateFormat('dd/MM/y HH:mm').format; class ReceiptListEntry extends StatelessWidget { final bool tappable; @@ -17,7 +18,7 @@ class ReceiptListEntry extends StatelessWidget { final String topText; final String rightText; final Color backgroundColor; - final String purchaseStatus; + final String status; const ReceiptListEntry({ required this.tappable, @@ -28,18 +29,28 @@ class ReceiptListEntry extends StatelessWidget { required this.topText, required this.rightText, required this.backgroundColor, - required this.purchaseStatus, + required this.status, }); + TextStyle get statusTextStyle { + return tappable + ? AppTextStyle.receiptItemDate + : AppTextStyle.receiptItemDate.copyWith(color: AppColor.gray); + } + @override Widget build(BuildContext context) { + final time = this.time.toLocal(); + return OpenContainer( tappable: tappable, + // Remove rounded edges + closedShape: const RoundedRectangleBorder(), openBuilder: (context, _) { return ViewReceiptPage( name: name, time: time, - paymentStatus: purchaseStatus, + paymentStatus: status, ); }, closedBuilder: (context, openContainer) { @@ -61,8 +72,15 @@ class ReceiptListEntry extends StatelessWidget { ColoredBox( color: colorIfShimmer, child: Text( - '$purchaseStatus ${_formatter.format(time)}', - style: AppTextStyle.receiptItemDate, + status, + style: statusTextStyle, + ), + ), + ColoredBox( + color: colorIfShimmer, + child: Text( + _formatDateTime(time), + style: statusTextStyle, ), ), ], diff --git a/lib/features/receipt/presentation/widgets/list_entry/swipe_receipt_list_entry.dart b/lib/features/receipt/presentation/widgets/list_entry/swipe_receipt_list_entry.dart index e8883ceaa..6de282d79 100644 --- a/lib/features/receipt/presentation/widgets/list_entry/swipe_receipt_list_entry.dart +++ b/lib/features/receipt/presentation/widgets/list_entry/swipe_receipt_list_entry.dart @@ -20,7 +20,7 @@ class SwipeReceiptListEntry extends StatelessWidget { topText: receipt.productName, rightText: Strings.oneTicket, backgroundColor: AppColor.white, - purchaseStatus: Strings.swiped, + status: Strings.swiped, ); } } diff --git a/lib/features/receipt/presentation/widgets/receipt_card.dart b/lib/features/receipt/presentation/widgets/receipt_card.dart index 8a3a111d8..226cbb55e 100644 --- a/lib/features/receipt/presentation/widgets/receipt_card.dart +++ b/lib/features/receipt/presentation/widgets/receipt_card.dart @@ -9,21 +9,21 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:intl/intl.dart'; -DateFormat get _formatter => DateFormat('EEEE d/M/y HH:mm'); +final _formatDate = DateFormat('EEEE d/M/y HH:mm').format; class ReceiptCard extends StatelessWidget { final String productName; final DateTime time; final bool isInOverlay; final bool isTestEnvironment; - final String paymentStatus; + final String status; const ReceiptCard({ required this.productName, required this.time, required this.isInOverlay, required this.isTestEnvironment, - required this.paymentStatus, + required this.status, }); @override @@ -39,7 +39,7 @@ class ReceiptCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - paymentStatus, + status, style: AppTextStyle.textField, ), const Gap(16), @@ -50,7 +50,7 @@ class ReceiptCard extends StatelessWidget { style: AppTextStyle.textFieldBold, ), Text( - _formatter.format(localTime), + _formatDate(localTime), style: AppTextStyle.textField, ), ], diff --git a/lib/features/receipt/presentation/widgets/receipt_overlay.dart b/lib/features/receipt/presentation/widgets/receipt_overlay.dart index 5809e8446..8147e9cbc 100644 --- a/lib/features/receipt/presentation/widgets/receipt_overlay.dart +++ b/lib/features/receipt/presentation/widgets/receipt_overlay.dart @@ -19,7 +19,7 @@ class ReceiptOverlay { required String productName, required DateTime timeUsed, required bool isTestEnvironment, - required String paymentStatus, + required String status, }) async { await ScreenBrightness().setScreenBrightness(1); if (_context.mounted) { @@ -38,7 +38,7 @@ class ReceiptOverlay { time: timeUsed, isInOverlay: true, isTestEnvironment: isTestEnvironment, - paymentStatus: paymentStatus, + status: status, ), const Gap(12), Text( diff --git a/lib/features/ticket/presentation/widgets/tickets_section.dart b/lib/features/ticket/presentation/widgets/tickets_section.dart index 4fd711d62..9c3bca8e3 100644 --- a/lib/features/ticket/presentation/widgets/tickets_section.dart +++ b/lib/features/ticket/presentation/widgets/tickets_section.dart @@ -49,7 +49,7 @@ class TicketSection extends StatelessWidget { ReceiptOverlay.of(context).show( isTestEnvironment: envState is EnvironmentLoaded && envState.env.isTest, - paymentStatus: state.receipt is PurchaseReceipt + status: state.receipt is PurchaseReceipt ? (state.receipt as PurchaseReceipt) .paymentStatus .toString()