import 'package:comwell_key_app/domain/models/app_error.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/presentation/screens/pregistration/cubit/preregistration_state.dart';
import 'package:comwell_key_app/presentation/screens/pregistration/prereg_request_model.dart';
import 'package:comwell_key_app/domain/repositories/pregistration_repository.dart';
import 'package:comwell_key_app/presentation/screens/pregistration/utils/utils.dart';
import 'package:comwell_key_app/presentation/base/base_cubit.dart';
import 'package:comwell_key_app/domain/repositories/profile_repository.dart';
import 'package:comwell_key_app/domain/models/address.dart';
import 'package:comwell_key_app/domain/repositories/profile_settings_repository.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/up_sales/models/addon_upgrade.dart';
import 'package:comwell_key_app/up_sales/models/room_upgrade.dart';
import 'package:comwell_key_app/up_sales/models/upgrade.dart';
import 'package:comwell_key_app/up_sales/up_sales_repository.dart';
import 'package:comwell_key_app/utils/l10n_utils.dart';
import 'package:comwell_key_app/utils/locator.dart';
import 'package:comwell_key_app/utils/phone_utils.dart';
import 'package:country_code_picker/country_code_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:payment_plugin/domain/models/stored_payment_method.dart';
class PreregistrationCubit extends BaseCubit<PreregistrationState> {
final _profileRepository = locator<ProfileRepository>();
final _profileSettingsRepository = locator<ProfileSettingsRepository>();
final _tracking = locator<ComwellTracking>();
final _preregistrationRepository = locator<PreregistrationRepository>();
final _upSalesRepository = locator<UpSalesRepository>();
final Booking booking;
final pageController = PageController();
final addressTextController = TextEditingController();
final postalCodeTextController = TextEditingController();
final cityTextController = TextEditingController();
final firstNameTextController = TextEditingController();
final lastNameTextController = TextEditingController();
final emailTextController = TextEditingController();
final phoneNumberTextController = TextEditingController();
final documentNumberTextController = TextEditingController();
final commentTextController = TextEditingController();
CountryCode? countryCode;
String? phoneNumber;
final List<String> favoriteCountries = ['DK', 'SE', 'NO', 'FI'];
IdType selectedDocumentType = IdType.passport;
String selectedCountry = '';
String selectedNationality = '';
PreregistrationPage get currentPage =>
PreregistrationPage.fromIndex(pageController.page?.toInt() ?? 0);
bool _isAnimating = false;
PreregistrationCubit({required this.booking}) : super(const PreregistrationState()) {
init();
}
void init() async {
setupListeners();
safeEmit(state.copyWith(isLoading: true));
try {
final user = await _profileRepository.fetchProfileSettings();
addressTextController.text = user.address.street;
postalCodeTextController.text = user.address.zipCode;
cityTextController.text = user.address.city;
selectedNationality = user.nationality != '' ? user.nationality : 'DK';
selectedCountry = user.nationality != '' ? user.nationality : 'DK';
countryCode = getCountryCodeFromPhoneNumber(user.phoneNumber).$1;
phoneNumber = getCountryCodeFromPhoneNumber(user.phoneNumber).$2;
firstNameTextController.text = user.firstName;
lastNameTextController.text = user.lastName;
emailTextController.text = user.email;
phoneNumberTextController.text = phoneNumber ?? '';
safeEmit(state.copyWith(user: user));
final upSales = await _upSalesRepository.getRemoteUpSales(
booking.confirmationNumber,
booking.hotelCode,
);
safeEmit(
state.copyWith(
isLoading: false,
availableRoomUpgrades: upSales.roomUpgrades,
addOnUpgrades: upSales.addOnUpgrades,
selectedCountry: countryCode?.code ?? 'DK',
countryCode: countryCode,
phoneNumber: phoneNumber,
),
);
} catch (e, st) {
logError(e, st);
safeEmit(state.copyWith(isLoading: false));
}
}
void setupListeners() {
phoneNumberTextController.addListener(() {
if (state.missingInformation) {
safeEmit(state.copyWith(missingInformation: false));
} else {
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
}
});
addressTextController.addListener(() {
if (state.missingInformation) {
safeEmit(state.copyWith(missingInformation: false));
} else {
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
}
});
postalCodeTextController.addListener(() {
if (state.missingInformation) {
safeEmit(state.copyWith(missingInformation: false));
} else {
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
}
});
cityTextController.addListener(() {
if (state.missingInformation) {
safeEmit(state.copyWith(missingInformation: false));
} else {
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
}
});
}
void onDocumentTypeSelected(IdType documentType) {
selectedDocumentType = documentType;
safeEmit(state.copyWith(selectedDocumentType: documentType));
}
void onAddressContinueClicked() {
if (isAddressValid && isCityValid && isPostalCodeValid) {
final updatedUser = state.user!.copyWith(
address: Address(
street: addressTextController.text,
zipCode: postalCodeTextController.text,
city: cityTextController.text,
country: state.selectedCountry,
),
);
safeEmit(state.copyWith(user: updatedUser));
_navigateNextPage();
} else {
safeEmit(state.copyWith(missingInformation: true));
}
}
void onProfileContinueClicked() {
if (isFirstNameValid && isLastNameValid && isPhoneNumberValid && isBirthDateValid) {
final phoneNumber = concatCountryCodeAndPhoneNumber(
countryCode!,
phoneNumberTextController.text,
);
final updatedUser = state.user!.copyWith(
firstName: firstNameTextController.text,
lastName: lastNameTextController.text,
phoneNumber: phoneNumber,
nationality: selectedNationality,
);
safeEmit(state.copyWith(user: updatedUser));
_navigateNextPage();
} else {
safeEmit(state.copyWith(missingInformation: true));
}
}
void onUpSalesContinueClicked() {
_navigateNextPage();
}
void onContinueClicked(BuildContext context) {
if (_isAnimating) return;
switch (currentPage) {
case PreregistrationPage.profile:
onProfileContinueClicked();
break;
case PreregistrationPage.address:
onAddressContinueClicked();
break;
case PreregistrationPage.upSales:
onUpSalesContinueClicked();
break;
case PreregistrationPage.confirmation:
_onConfirmPressed(context);
break;
}
}
void onPaymentMethodSelected(StoredPaymentMethod paymentMethod) {
safeEmit(
state.copyWith(
selectedPaymentMethod: paymentMethod,
missingInformation: false,
),
);
}
void addToCart() {
final analyticsEventItem = AnalyticsEventItem(
hotelName: booking.hotelName,
currency: "DKK",
value: extrasTotalPrice,
placement: "pre-registration",
items: selectedAddOnUpgrades.map((e) => e.name).toList(),
itemIds: selectedAddOnUpgrades.map((e) => e.id).toList(),
bookingReference: booking.confirmationNumber,
);
_tracking.trackAddToCart(analyticsEventItem);
}
void _onConfirmPressed(BuildContext context) async {
safeEmit(state.copyWith(isLoading: true));
try {
// Build the PreregRequestModel with all required fields
final String? sendIdType = isFavoriteCountry ? null : selectedDocumentType.code;
final String? sendRoomType = state.selectedRoomUpgrade.isNotEmpty
? state.selectedRoomUpgrade
: null;
final List<AddOnListDto>? sendAddOnList = numOfExtras > 0
? selectedAddOnUpgrades
.map((e) => AddOnListDto(itemCode: e.id, quantity: e.quantity))
.toList()
: null;
final String? sendArrivalTime = state.servingTime.toString().isNotEmpty
? state.servingTime?.toString()
: null;
final preregRequest = PreregRequestDto(
firstName: state.user!.firstName,
lastName: state.user!.lastName,
email: state.user!.email,
phoneNumber: state.user!.phoneNumber,
birthDay: state.user!.birthDate!.toIso8601String(),
address: state.user!.address.street,
zipCode: state.user!.address.zipCode,
city: state.user!.address.city,
country: state.user!.address.country,
nationality: state.user!.nationality,
confirmationNumber: booking.confirmationNumber,
idType: sendIdType,
idNumber: documentNumberTextController.text,
idCountry: selectedNationality,
arrivalTime: sendArrivalTime,
comment: commentTextController.text,
hotelCode: booking.hotelCode,
roomType: sendRoomType,
addOnList: sendAddOnList,
);
await _profileSettingsRepository.updateUser(state.user!);
final preRegResponse = await _preregistrationRepository.createPreregistration(preregRequest);
if (preRegResponse != null) {
Future.delayed(const Duration(seconds: 3), () {
safeEmit(state.copyWith(isLoading: false));
if (!context.mounted) return;
context.pop(preRegResponse);
});
} else {
safeEmit(state.copyWith(error: AppError.unknown('Pre-registration failed'), isLoading: false));
if (!context.mounted) return;
context.pop(null);
}
} catch (e, st) {
logError(e, st);
safeEmit(state.copyWith(error: AppError.unknown(e.toString()), isLoading: false));
}
}
void onEditProfileClicked() {
_navigateTo(PreregistrationPage.profile);
}
void onEditAddressClicked() {
_navigateTo(PreregistrationPage.address);
}
void onEditExtrasClicked() {
_navigateTo(PreregistrationPage.upSales);
}
void onPhoneNumberChanged(String phoneNumber) {
phoneNumberTextController.text = phoneNumber;
safeEmit(state.copyWith(isPhoneNumberValid: isPhoneNumberValid));
}
void onCountryCodeSelected(CountryCode country) {
countryCode = country;
safeEmit(state.copyWith(countryCode: country, selectedCountry: country.code ?? ''));
}
void onNationalitySelected(String nationality) {
selectedNationality = nationality;
safeEmit(state.copyWith(selectedNationality: selectedNationality));
}
void onSelectedCountrySelected(String country) {
selectedCountry = country;
safeEmit(state.copyWith(selectedCountry: country));
}
Future<void> onBirthDateSelected(DateTime date) async {
final updatedUser = state.user!.copyWith(birthDate: date);
safeEmit(state.copyWith(user: updatedUser));
}
bool onBackClicked() {
if (_isAnimating) return true;
final hasPage = pageController.page != null;
if (hasPage && pageController.page! >= 1.0) {
_navigatePreviousPage();
return true;
}
return false;
}
void _navigateTo(PreregistrationPage page) async {
if (_isAnimating) return;
_isAnimating = true;
await pageController.animateToPage(
page.index,
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
);
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
_isAnimating = false;
}
void _navigateNextPage() async {
if (_isAnimating) return;
_isAnimating = true;
await pageController.nextPage(
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
);
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
_isAnimating = false;
}
void _navigatePreviousPage() async {
if (_isAnimating) return;
_isAnimating = true;
await pageController.previousPage(
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
);
safeEmit(state.copyWith(forceUpdate: !state.forceUpdate));
_isAnimating = false;
}
int get extrasTotalPrice {
//This is the total price of the selected up sales and the price of the selected
//room upgrade if it is selected
return selectedAddOnUpgrades.fold(0, (sum, upgrade) => sum + upgrade.price * upgrade.quantity) +
(state.selectedRoomUpgrade.isNotEmpty
? state.availableRoomUpgrades.firstWhere((e) => e.id == state.selectedRoomUpgrade).price
: 0);
}
int get numOfExtras =>
selectedAddOnUpgrades.fold(0, (sum, upgrade) => sum + upgrade.quantity) +
(state.selectedRoomUpgrade.isNotEmpty ? 1 : 0);
List<AddOnUpgrade> get otherUpgrades {
final selectedUpgrades = state.addOnUpgrades.where((upgrade) => !upgrade.isService).toList();
return selectedUpgrades;
}
List<AddOnUpgrade> get selectedAddOnUpgrades {
final selectedUpgrades = state.addOnUpgrades.where((upgrade) => upgrade.isAddedToCart).toList();
return selectedUpgrades;
}
@override
Future<void> close() {
addressTextController.dispose();
postalCodeTextController.dispose();
cityTextController.dispose();
pageController.dispose();
firstNameTextController.dispose();
lastNameTextController.dispose();
emailTextController.dispose();
phoneNumberTextController.dispose();
documentNumberTextController.dispose();
commentTextController.dispose();
return super.close();
}
bool get isAddressValid => addressTextController.text.isNotEmpty;
bool get isPostalCodeValid => postalCodeTextController.text.isNotEmpty;
bool get isCityValid => cityTextController.text.isNotEmpty;
bool get isDocumentNumberValid => documentNumberTextController.text.isNotEmpty;
bool get isPhoneNumberValid =>
phoneNumberTextController.text.length >= 7 && phoneNumberTextController.text.length < 16;
bool get isFirstNameValid => firstNameTextController.text.isNotEmpty;
bool get isLastNameValid => lastNameTextController.text.isNotEmpty;
bool get isBirthDateValid => state.user!.birthDate!.isBefore(validBirthDate);
DateTime get validBirthDate => DateTime.now().subtract(const Duration(days: 365 * 18));
bool get isFavoriteCountry => favoriteCountries.contains(selectedNationality);
bool get canContinue {
int page = pageController.page?.ceil() ?? 0;
final preregPage = PreregistrationPage.fromIndex(page);
switch (preregPage) {
case PreregistrationPage.profile:
return isFirstNameValid && isLastNameValid && isPhoneNumberValid && isBirthDateValid;
case PreregistrationPage.address:
return isAddressValid && isPostalCodeValid && isCityValid;
case PreregistrationPage.upSales:
return true;
case PreregistrationPage.confirmation:
return state.termsAndConditionsAccepted;
}
}
String buttonText(BuildContext context) {
int page = pageController.page?.ceil() ?? 0;
final preregPage = PreregistrationPage.fromIndex(page);
switch (preregPage) {
case PreregistrationPage.profile:
return context.strings.generic_continue;
case PreregistrationPage.address:
return context.strings.generic_continue;
case PreregistrationPage.upSales:
if (selectedAddOnUpgrades.isEmpty && state.selectedRoomUpgrade.isEmpty) {
return context.strings.continue_without_up_sales;
}
return context.strings.generic_continue;
case PreregistrationPage.confirmation:
return context.strings.generic_confirm;
}
}
void onTermsAndConditionsToggled(bool toggle) {
safeEmit(state.copyWith(termsAndConditionsAccepted: toggle));
}
void onServingTimeSelected(TimeOfDay time) {
safeEmit(state.copyWith(servingTime: time));
}
void toggleSelectedUpgrade(Upgrade upgrade) {
if (upgrade is RoomUpgrade) {
if (state.selectedRoomUpgrade == upgrade.id) {
safeEmit(state.copyWith(selectedRoomUpgrade: ''));
} else {
safeEmit(state.copyWith(selectedRoomUpgrade: upgrade.id));
}
} else if (upgrade is AddOnUpgrade) {
final bool willSelect = !upgrade.isSelected && upgrade.quantity == 0;
final updatedUpgrade = upgrade.copyWith(
isSelected: willSelect,
quantity: willSelect ? 1 : 0,
);
final updatedAddOnUpgrades = state.addOnUpgrades.toList();
final index = updatedAddOnUpgrades.indexWhere((e) => e.id == updatedUpgrade.id);
updatedAddOnUpgrades[index] = updatedUpgrade;
safeEmit(
state.copyWith(
addOnUpgrades: updatedAddOnUpgrades,
),
);
}
}
void onContinue() {
safeEmit(state.copyWith(selected: false));
}
void updateAddonUpgradeQuantity(AddOnUpgrade upgrade, int quantity) {
final updatedUpgrade = upgrade.copyWith(quantity: quantity);
final updatedAddOnUpgrades = state.addOnUpgrades.toList();
final index = updatedAddOnUpgrades.indexWhere((e) => e.id == updatedUpgrade.id);
updatedAddOnUpgrades[index] = updatedUpgrade;
safeEmit(state.copyWith(addOnUpgrades: updatedAddOnUpgrades));
}
String get paymentMethodImage {
switch (state.selectedPaymentMethod?.brand) {
case 'mastercard':
return 'assets/images/master.svg';
case 'visa':
return 'assets/images/visa.svg';
case 'maestro':
return 'assets/images/maestro.svg';
default:
return 'assets/images/visa.png';
}
}
}