Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ val localPropertiesFile = rootDir.resolve("local.properties")
if (localPropertiesFile.exists()) {
localProperties.load(FileInputStream(localPropertiesFile))
}

plugins {
id("com.android.application")
// START: FlutterFire Configuration
Expand All @@ -20,7 +21,7 @@ plugins {
android {
namespace = "uk.org.cherry.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = localProperties.getProperty("flutter.ndkVersion")
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
Expand All @@ -37,8 +38,8 @@ android {
applicationId = "uk.org.cherry.app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 25
targetSdk = 34
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
Expand Down
14 changes: 12 additions & 2 deletions lib/core/config/app_strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ class AppStrings {
// Donation
static const donationsText = "Donate";
static const donationOptionsText = "Donation Options";
static const giveYourBuyerText = "Give your buyer the option pick a cause they care about.";
static const easyWayText = "Its an easy way to make your listing more impactful.";
static const giveYourBuyerText = "Give your buyer the option to pick a cause they care about.";
static const easyWayText = "It's an easy way to make your listing more impactful.";
static const openToOtherCharitiesText = "Open to other charities";
static const openToOffersText = "Open to offers";
static const applicableForBuyerDiscountsText = "Applicable for donor discounts";
Expand Down Expand Up @@ -227,6 +227,16 @@ class AppStrings {
static const checkoutDeliveryOptionRequired = 'Please choose a delivery option';
static const checkoutPickupLockerRequired = 'Please select a pickup locker';
static const checkoutPaymentMethodRequired = 'Please select a payment method';
static const checkoutDeliveryAddressRequired = 'Please confirm your delivery address';
static const checkoutPaymentFailed = 'Payment could not be completed. Please try again.';
static const checkoutPaymentCancelled = 'Payment was cancelled.';
static const checkoutPaymentDetailsUnavailable = 'Payment details could not be confirmed. Please try again.';
static const checkoutOrderCreationFailed = 'We could not create your order after payment. Please try again.';
static const checkoutShipmentFailed =
'Your order was created, but delivery could not be confirmed. Please contact support if this continues.';
static const checkoutShipmentPending = 'Payment complete. Your order is placed and delivery is still being arranged.';
static const checkoutRetryOrder = 'Retry order';
static const checkoutDeliveryPending = 'Delivery pending';

// Card Details
static const cardDetailsTitle = 'Card Details';
Expand Down
23 changes: 13 additions & 10 deletions lib/core/services/network/api_service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:cherry_mvp/core/config/environment_config.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:cherry_mvp/core/services/error_string.dart';
import 'package:cherry_mvp/core/utils/result.dart';
import 'package:firebase_auth/firebase_auth.dart';
Expand Down Expand Up @@ -84,15 +85,17 @@ class DioApiService implements ApiService {
),
);

_dio.interceptors.add(
PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseBody: true,
error: true,
compact: true,
),
);
if (kDebugMode) {
_dio.interceptors.add(
PrettyDioLogger(
requestHeader: false,
requestBody: false,
responseBody: false,
error: true,
compact: true,
),
);
}
}

@override
Expand Down Expand Up @@ -177,7 +180,7 @@ class DioApiService implements ApiService {
final statusCode = e.response?.statusCode;
final data = e.response?.data;

_log.warning('Bad response: $statusCode - $data');
_log.warning('Bad response: $statusCode');

switch (statusCode) {
case 400:
Expand Down
81 changes: 40 additions & 41 deletions lib/features/checkout/checkout_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:cherry_mvp/core/config/app_colors.dart';
import 'package:cherry_mvp/core/config/app_strings.dart';
import 'package:cherry_mvp/core/router/nav_routes.dart';
import 'package:cherry_mvp/core/utils/status.dart';
import 'package:cherry_mvp/features/checkout/checkout_view_model.dart';
import 'package:cherry_mvp/features/checkout/payment_type.dart';
import 'package:cherry_mvp/features/checkout/widgets/basket_list_item.dart';
import 'package:cherry_mvp/features/checkout/widgets/delivery_options.dart';
import 'package:cherry_mvp/features/checkout/widgets/select_payment_type_bottom_sheet.dart';
Expand Down Expand Up @@ -53,7 +51,11 @@ class _CheckoutPageState extends State<CheckoutPage> {
}

if (status == StatusType.success) {
Fluttertoast.showToast(msg: "Payment Successful");
Fluttertoast.showToast(
msg: vm.checkoutFlowState == CheckoutFlowState.shipmentPending
? AppStrings.checkoutShipmentPending
: AppStrings.checkoutOrderPlaced,
);
vm.resetCreateOrderStatus();
vm.gotoCheckoutComplete();
}
Expand Down Expand Up @@ -162,49 +164,46 @@ class _CheckoutPageState extends State<CheckoutPage> {
width: double.infinity,
child: Consumer<CheckoutViewModel>(
builder: (context, viewModel, _) {
final isLoading = viewModel.createOrderStatus.type == StatusType.loading;

final hasDeliveryChoice = (viewModel.deliveryChoice ?? '').isNotEmpty;
final isPickup = viewModel.deliveryChoice == 'pickup';
final isLoading = viewModel.isCheckoutProcessing;
final canRetryOrderCreation =
viewModel.canRetryOrderCreation && !isLoading && !viewModel.hasCompletedCheckout;
final hasSelectedPaymentMethod = viewModel.selectedPaymentType != null;

final hasValidDelivery =
hasDeliveryChoice &&
(isPickup
? viewModel.selectedInpost != null
: viewModel.isShippingAddressConfirmed && viewModel.hasShippingAddress);
final canSubmitCheckout =
hasSelectedPaymentMethod &&
viewModel.hasValidDeliveryDetails &&
viewModel.total > 0 &&
viewModel.checkoutFlowState != CheckoutFlowState.shipmentFailure &&
!isLoading &&
!viewModel.hasCompletedCheckout;
Comment thread
CherryCIC marked this conversation as resolved.

final canAttemptPayment = hasValidDelivery && basket.total > 0 && !isLoading;
final canAttemptPayment = canRetryOrderCreation || canSubmitCheckout;

return FilledButton(
onPressed: canAttemptPayment
? () async {
// ✅ require payment method
if (!viewModel.hasPaymentMethod) {
if (canRetryOrderCreation) {
setState(() => _errorMessage = '');
await viewModel.retryOrderCreation();
return;
}

if (!viewModel.hasValidDeliveryDetails) {
setState(() {
_errorMessage = AppStrings.checkoutPaymentMethodRequired;
_errorMessage = AppStrings.checkoutDeliveryAddressRequired;
});
return;
}

await showModalBottomSheet<PaymentType>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => const SelectPaymentTypeBottomSheet(),
);

if (!viewModel.hasPaymentMethod) return;
if (!hasSelectedPaymentMethod) {
setState(() {
_errorMessage = AppStrings.checkoutPaymentMethodRequired;
});
return;
}

setState(() => _errorMessage = '');

await viewModel.storeOrderInFirestore();

final paid = await viewModel.payWithPaymentSheet(
amount: basket.total,
);

if (paid) {
await viewModel.createOrder();
}
await viewModel.submitCheckout();
}
: null,
child: isLoading
Expand All @@ -216,20 +215,20 @@ class _CheckoutPageState extends State<CheckoutPage> {
color: Colors.white,
),
)
: Text(AppStrings.checkoutPay),
: Text(
viewModel.checkoutFlowState == CheckoutFlowState.shipmentPending
? AppStrings.checkoutDeliveryPending
: canRetryOrderCreation
? AppStrings.checkoutRetryOrder
: AppStrings.checkoutPay,
),
);
},
),
),
);
}

Future<void> gotoCheckoutComplete() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
Navigator.pushReplacementNamed(context, AppRoutes.checkoutComplete);
}

@override
void dispose() {
_vm?.removeListener(_handleOrderStatus); // ✅ safe
Expand Down
10 changes: 2 additions & 8 deletions lib/features/checkout/checkout_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ final class CheckoutRepository implements ICheckoutRepository {
return Result.success(jsonList);
} else {
return Result.failure(
result.error ??
'Pickup points currently unavailable, please try again later',
result.error ?? 'Pickup points currently unavailable, please try again later',
);
}
} catch (e) {
Expand Down Expand Up @@ -86,7 +85,6 @@ final class CheckoutRepository implements ICheckoutRepository {
'updated_at': DateTime.now().toIso8601String(),
};


final result = await _firestoreService.saveDocument(
FirestoreConstants.orders,
orderId,
Expand Down Expand Up @@ -143,11 +141,7 @@ final class CheckoutRepository implements ICheckoutRepository {
data: order,
);
if (result.isSuccess && result.value != null) {
final data = result.value;

final jsonList = data['data'] ?? data;

return Result.success(jsonList);
return Result.success(result.value);
} else {
return Result.failure(
result.error ?? 'Error creating payment, please try again later',
Expand Down
Loading