import 'package:adyen_checkout/adyen_checkout.dart';
import 'package:comwell_key_app/base/base_cubit.dart';
import 'package:comwell_key_app/check_out/bloc/check_out_state.dart';
import 'package:comwell_key_app/check_out/check_out_repository.dart';
import 'package:comwell_key_app/check_out/pages/check_out_page.dart';
import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/domain/repositories/pregistration_repository.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/domain/repositories/profile_repository.dart';
import 'package:comwell_key_app/routing/app_routes.dart';
import 'package:comwell_key_app/services/models/booking_dto.dart';
import 'package:comwell_key_app/tracking/comwell_tracking.dart';
import 'package:comwell_key_app/tracking/models/analytics_event_item.dart';
import 'package:comwell_key_app/utils/locator.dart';
import 'package:comwell_key_app/utils/urls.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:payment_plugin/presentation/app/bloc/payment_cubit.dart';
import 'package:url_launcher/url_launcher.dart';
class CheckoutCubit extends BaseCubit<CheckoutState> {
final ProfileRepository profileRepository = locator<ProfileRepository>();
final PreregistrationRepository preregistrationRepository = locator<PreregistrationRepository>();
final BookingDetailsRepository bookingDetailsRepository = locator<BookingDetailsRepository>();
final CheckOutRepository checkOutRepository;
final _tracking = locator<ComwellTracking>();
final pageController = PageController();
bool _isAnimating = false;
late Booking booking;
final PaymentCubit paymentServicesCubit;
CheckoutPage get currentPage => CheckoutPage.fromIndex(pageController.page?.toInt() ?? 0);
CheckoutCubit(this.booking, this.checkOutRepository, this.paymentServicesCubit)
: super(CheckoutState.initial(booking.balance ?? 0));
void init() async {
try {
//TODO: Fetch club points eventually
safeEmit(state.loading());
final user = await profileRepository.fetchProfileSettings();
safeEmit(state.clubPointsFetched(user.points));
booking = await bookingDetailsRepository.getRemoteBookingDetails(
booking.confirmationNumber,
booking.hotelCode,
);
setItems(booking.addOnItems ?? []);
safeEmit(state.loaded());
} catch (e, st) {
logError(e, st);
safeEmit(state.checkoutError());
}
}
void onApplyClubPointsClicked(bool value) {
if (value) {
safeEmit(state.clubPointsApplied());
} else {
safeEmit(state.clubPointsRemoved());
}
}
void setItems(Iterable<BookingAddonItem> items) {
safeEmit(state.itemsUpdated(items));
}
Future<void> processPayment() async {
final analyticsEventItem = AnalyticsEventItem(
hotelName: booking.hotelName,
currency: "DKK",
value: booking.balance?.toInt() ?? 0,
placement: "check-out",
itemIds: booking.addOnItemCodes,
items: booking.addOnItemDescriptions,
price: totalAddonPrice,
quantity: booking.addOnItems?.length ?? 0,
bookingReference: booking.confirmationNumber,
);
_tracking.trackBeginCheckout(analyticsEventItem);
try {
await paymentServicesCubit.createSession(
booking.balance?.toInt() ?? 0,
booking.confirmationNumber,
state.applyClubPoints,
booking.hotelCode,
);
await Future<void>.delayed(const Duration(milliseconds: 4000));
} catch (e, st) {
logError(e, st);
safeEmit(state.checkoutError());
}
}
Future<void> processCheckoutWithoutPaying() async {
try {
checkOut();
} catch (e, st) {
logError(e, st);
await Future<void>.delayed(const Duration(milliseconds: 1000));
}
}
void onContinueClicked(BuildContext context) {
if (_isAnimating) return;
_isAnimating = true;
switch (currentPage) {
case CheckoutPage.confirmation:
if (booking.balance == 0 || booking.balance == null) {
safeEmit(state.paymentProcessingNotNeeded());
return _navigateToProcessing(context);
}
return _navigateTo(CheckoutPage.payment);
case CheckoutPage.payment:
return _navigateToProcessing(context);
}
}
void _navigateToProcessing(BuildContext context) {
_isAnimating = false;
if (!state.isTermsAccepted && (booking.balance ?? 0.0) > 0.0) {
safeEmit(state.showAcceptTermsError());
} else {
if (booking.balance == 0 || booking.balance == null) {
processCheckoutWithoutPaying();
} else {
context.push(AppRoutes.paymentProcessing);
processPayment();
}
}
}
bool onBackPressed() {
if (pageController.page == 0.0) return false;
if (_isAnimating) return true;
_isAnimating = true;
pageController
.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn)
.then((_) {
_isAnimating = false;
safeEmit(state.pageChanged(currentPage));
});
return true;
}
void _navigateTo(CheckoutPage page) async {
await pageController.animateToPage(
page.index,
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
);
safeEmit(state.pageChanged(page));
_isAnimating = false;
}
@override
Future<void> close() {
pageController.dispose();
return super.close();
}
void onAcceptTermsChanged(bool value) {
if (value) {
safeEmit(state.termsAccepted());
} else {
safeEmit(state.termsDenied());
}
}
Future<void> checkOut() async {
try {
booking = await bookingDetailsRepository.getRemoteBookingDetails(
booking.confirmationNumber,
booking.hotelCode,
);
await checkOutRepository.checkOut(booking.confirmationNumber);
safeEmit(state.checkoutSuccess());
} catch (e, st) {
logError(e, st);
safeEmit(state.checkoutError());
await Future<void>.delayed(const Duration(milliseconds: 1000));
}
}
Future<void> onPaymentResult(PaymentResult result) async {
switch (result) {
case PaymentAdvancedFinished():
case PaymentSessionFinished():
//This is here to add time so that the payment is represented in the BookingDetails
await Future<void>.delayed(const Duration(milliseconds: 2000));
break;
case PaymentCancelledByUser():
case PaymentError():
}
}
void showTermsAndConditions() {
launchUrl(Uri.parse(ComwellUrls.termsAndConditions));
}
//Removes the decimal point from the balance if it is .0
int get trimmedBalance {
final balance = booking.balance;
if (balance == null) return 0;
if (balance == balance.toInt()) {
return balance.toInt();
} else {
return balance.toInt();
}
}
int get totalAddonPrice {
return booking.addOnItems?.fold(0, (total, item) => total! + item.price) ?? 0;
}
}