6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 42deab1d

AuthorMikkel Thygesen<mth@dwarf.dk>
Date2025-02-13 15:53:37 +0100
549: Implemented UI for checkout flow

Changed files

comwell_key_app/assets/icons/ic_card.svg           |  3 +
 comwell_key_app/assets/translations/da-DK.json     | 23 +++++-
 comwell_key_app/assets/translations/en-US.json     | 22 ++++-
 .../lib/booking_details/booking_details_page.dart  |  3 +
 .../components/check_out_button.dart               | 73 +++++++++++++++++
 .../lib/check_out/bloc/check_out_cubit.dart        | 83 +++++++++++++++++++
 .../lib/check_out/bloc/check_out_state.dart        | 75 +++++++++++++++++
 comwell_key_app/lib/check_out/check_out_flow.dart  | 37 +++++++++
 .../check_out/components/accept_terms_toggle.dart  | 33 ++++++++
 .../check_out/components/apply_club_points.dart    | 41 ++++++++++
 .../components/check_out_bill_list_item.dart       | 26 ++++++
 .../components/check_out_bottom_sheet.dart         | 69 ++++++++++++++++
 .../check_out/components/check_out_countdown.dart  | 72 +++++++++++++++++
 .../components/check_out_payment_card.dart         | 58 ++++++++++++++
 .../components/checkout_itemized_bill.dart         | 53 ++++++++++++
 .../components/confirm_check_out_dialog.dart       | 76 ++++++++++++++++++
 .../lib/check_out/components/payment_button.dart   | 38 +++++++++
 .../lib/check_out/models/check_out_item.dart       |  6 ++
 .../models/checkout_processing_state.dart          |  7 ++
 .../pages/check_out_confirmation_pages.dart        | 47 +++++++++++
 .../lib/check_out/pages/check_out_page.dart        | 24 ++++++
 .../check_out/pages/check_out_processing_page.dart | 93 ++++++++++++++++++++++
 .../check_out/pages/check_out_success_page.dart    | 60 ++++++++++++++
 .../lib/check_out/pages/checkout_payment_page.dart | 43 ++++++++++
 .../lib/common/components/comwell_app_bar.dart     | 10 ++-
 comwell_key_app/lib/routing/app_router.dart        | 22 ++++-
 comwell_key_app/lib/routing/app_routes.dart        |  1 +
 .../interceptors/response_handle_interceptor.dart  |  9 +--
 comwell_key_app/lib/themes/dark_theme.dart         |  2 +-
 comwell_key_app/lib/themes/light_theme.dart        |  3 +-
 comwell_key_app/lib/utils/lottie_utils.dart        | 17 ++--
 comwell_key_app/lib/utils/time_utils.dart          |  7 ++
 32 files changed, 1116 insertions(+), 20 deletions(-)

Diff

diff --git a/comwell_key_app/assets/icons/ic_card.svg b/comwell_key_app/assets/icons/ic_card.svg
new file mode 100644
index 00000000..11ba404c
--- /dev/null
+++ b/comwell_key_app/assets/icons/ic_card.svg
@@ -0,0 +1,3 @@
+<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M20.5 2V1.5H20H2H1.5V2V4.25V4.75H2H20H20.5V4.25V2ZM1.5 14V14.5H2H20H20.5V14V5.75V5.25H20H2H1.5V5.75V14ZM2 1H20C20.2652 1 20.5196 1.10536 20.7071 1.29289C20.8946 1.48043 21 1.73478 21 2V14C21 14.2652 20.8946 14.5196 20.7071 14.7071C20.5196 14.8946 20.2652 15 20 15H2C1.73478 15 1.48043 14.8946 1.29289 14.7071C1.10536 14.5196 1 14.2652 1 14V2C1 1.73478 1.10536 1.48043 1.29289 1.29289C1.48043 1.10536 1.73478 1 2 1ZM4 12V11.5H10.5V12H4Z" fill="black" stroke="black"/>
+</svg>
diff --git a/comwell_key_app/assets/translations/da-DK.json b/comwell_key_app/assets/translations/da-DK.json
index cdd23932..618ce0c6 100644
--- a/comwell_key_app/assets/translations/da-DK.json
+++ b/comwell_key_app/assets/translations/da-DK.json
@@ -1,5 +1,6 @@
{
"generic_continue": "Fortsæt",
+ "generic_ok": "OK ",
"generic_information_required": "Dette felt er påkrævet",
"generic_confirm": "Bekræft",
"generic_done": "Færdig",
@@ -136,6 +137,8 @@
"booking_details_page_housekeeping_button_title": "Bestil housekeeping",
"booking_details_page_housekeeping_button_subtitle": "Ønsker du ekstra rengøring eller opfyldning på værelset, kan du altid bestille det her - uden omkostninger",
"booking_details_page_practical_information": "Praktisk information",
+ "booking_details_page_checkout_title": "Check-out",
+ "booking_details_page_checkout_subtitle": "Start dit check-out her",
"need_help": "Har du brug for hjælp?",
"call_us": "Ring til os",
"call_us_description": "Har du brug for at komme i kontakt med et af vores hoteller? Benyt knappen nedenfor for at ringe op.",
@@ -148,5 +151,23 @@
"restaurant_page_opening_hours": "Åbningstider",
"restaurant_page_book_table": "Book bord",
"restaurant_page_send_email": "Skriv en email",
- "overview_page_check_in_button_subtitle": "Få dit digitale nøglekort med det samme"
+ "overview_page_check_in_button_subtitle": "Få dit digitale nøglekort med det samme",
+ "checkout_page_confirmation_title": "Check-out",
+ "checkout_page_confirmation_subtitle": "Når du bekræfter dit check-out har du 30 minutter til at forlade værelset før dit nøglekort vil stoppe med at virke.",
+ "checkout_page_confirmation_deadline": "Dit check-out skal ske senest kl. {}",
+ "checkout_page_confirmation_price_title": "Til betaling",
+ "checkout_page_confirmation_continue": "Gå til betaling",
+ "checkout_page_payment_title": "Betalingsoversigt",
+ "checkout_page_payment_total": "Total",
+ "checkout_page_payment_club_points_title": "Brug Comwell Club Point",
+ "checkout_page_payment_club_points_subtitle": "Du har {} point, anvend og spar {} kr.",
+ "checkout_page_payment_payment_title": "Betal med {}",
+ "checkout_page_payment_accept_terms": "Accepter betingelserne",
+ "checkout_page_payment_dialog_title": "Er du sikker på du vil checke ud af hotellet?",
+ "checkout_page_payment_dialog_subtitle": "Når du bekræfter, har du 30 minutter til at forlade dit værelse.",
+ "checkout_page_payment_dialog_confirm": "Ja, check ud nu",
+ "checkout_page_payment_dialog_cancel": "Nej",
+ "checkout_page_processing_success_title": "Check-out bekræftet",
+ "checkout_page_processing_success_subtitle": "Det check-out er nu bekræftet og du har nu 30 minutter til at forlade dit værelse. Herefter vil du ikke længere kunne bruge dit nøglekort."
+
}
\ No newline at end of file
diff --git a/comwell_key_app/assets/translations/en-US.json b/comwell_key_app/assets/translations/en-US.json
index efe518a0..13024b77 100644
--- a/comwell_key_app/assets/translations/en-US.json
+++ b/comwell_key_app/assets/translations/en-US.json
@@ -3,6 +3,7 @@
"generic_information_required": "This information is required",
"generic_confirm": "Confirm",
"generic_done": "Done",
+ "generic_ok": "OK",
"welcome_headline": "Welcome at Comwell Hotels",
"welcome_button": "Continue",
"welcome_error": "An error occurred. Please try again later.",
@@ -136,6 +137,8 @@
"booking_details_page_housekeeping_button_title": "Order housekeeping",
"booking_details_page_housekeeping_button_subtitle": "Order here",
"booking_details_page_practical_information": "Practical information",
+ "booking_details_page_checkout_title": "Check-out",
+ "booking_details_page_checkout_subtitle": "Start your check-out here",
"hotel_information_page_menu_restaurants_title": "Restaurants",
"hotel_information_page_menu_restaurants_subtitle": "Read about our restaurant",
"hotel_information_page_menu_spa_title": "Spa",
@@ -148,5 +151,22 @@
"restaurant_page_address": "Addrese",
"restaurant_page_opening_hours": "Opening hours",
"restaurant_page_book_table": "Book table",
- "restaurant_page_send_email": "Write an email"
+ "restaurant_page_send_email": "Write an email",
+ "checkout_page_confirmation_title": "Check-out",
+ "checkout_page_confirmation_subtitle": "When you check out, you have 30 minutes to leave your room",
+ "checkout_page_confirmation_deadline": "Check out latest at {}",
+ "checkout_page_confirmation_price_title": "To be payed",
+ "checkout_page_confirmation_continue": "Go to payment",
+ "checkout_page_payment_title": "Payment overview",
+ "checkout_page_payment_total": "Total",
+ "checkout_page_payment_club_points_title": "Use Comwell Club Points",
+ "checkout_page_payment_club_points_subtitle": "You have {} point, use them and save {} kr.",
+ "checkout_page_payment_payment_title": "Pay with {}",
+ "checkout_page_payment_accept_terms": "Accept terms and conditions",
+ "checkout_page_payment_dialog_title": "Are you sure you want to check out?",
+ "checkout_page_payment_dialog_subtitle": "When you check out, you have 30 minutes to leave your room",
+ "checkout_page_payment_dialog_confirm": "Yes, check out now",
+ "checkout_page_payment_dialog_cancel": "No",
+ "checkout_page_processing_success_title": "Check-out confirmed",
+ "checkout_page_processing_success_subtitle": "You have 30 minutes to leave your room"
}
\ No newline at end of file
diff --git a/comwell_key_app/lib/booking_details/booking_details_page.dart b/comwell_key_app/lib/booking_details/booking_details_page.dart
index 8a8043a3..d0b25cef 100644
--- a/comwell_key_app/lib/booking_details/booking_details_page.dart
+++ b/comwell_key_app/lib/booking_details/booking_details_page.dart
@@ -12,6 +12,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'bloc/booking_details_bloc.dart';
+import 'components/check_out_button.dart';
import 'components/unlock_room_button.dart';
class BookingDetailsPage extends StatelessWidget {
@@ -58,6 +59,8 @@ class BookingDetailsPage extends StatelessWidget {
HousekeepingButton(
key: ValueKey(state.isHouseKeepingOrdered)),
const SizedBox(height: 20),
+ const CheckOutButton(),
+ const SizedBox(height: 20),
Text("booking_details_page_practical_information".tr()),
const SizedBox(height: 20),
Row(children: [
diff --git a/comwell_key_app/lib/booking_details/components/check_out_button.dart b/comwell_key_app/lib/booking_details/components/check_out_button.dart
new file mode 100644
index 00000000..c9e6445f
--- /dev/null
+++ b/comwell_key_app/lib/booking_details/components/check_out_button.dart
@@ -0,0 +1,73 @@
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+
+
+class CheckOutButton extends StatelessWidget {
+ const CheckOutButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ borderRadius: const BorderRadius.all(Radius.circular(15)),
+ onTap: () {
+ context.pushNamed(AppRoutes.checkOut.name);
+ },
+ child: Ink(
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: Colors.grey,
+ ),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Container(
+ height: 36,
+ width: 36,
+ decoration: const BoxDecoration(
+ color: colorSecondary,
+ shape: BoxShape.circle,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: SvgPicture.asset(
+ colorFilter: const ColorFilter.mode(
+ Colors.black, BlendMode.srcATop),
+ "assets/icons/ic_exit.svg"),
+ ),
+ ),
+ const SizedBox(width: 12),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("booking_details_page_checkout_title".tr()),
+ Text(
+ "booking_details_page_checkout_subtitle".tr(),
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: Colors.grey),
+ ),
+ ],
+ ),
+ const Expanded(child: SizedBox()),
+ Text("10:00"),
+ SvgPicture.asset("assets/icons/arrow-left.svg",
+ colorFilter: const ColorFilter.mode(
+ Colors.black,
+ BlendMode.dst,
+ ))
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/bloc/check_out_cubit.dart b/comwell_key_app/lib/check_out/bloc/check_out_cubit.dart
new file mode 100644
index 00000000..1ddf89d4
--- /dev/null
+++ b/comwell_key_app/lib/check_out/bloc/check_out_cubit.dart
@@ -0,0 +1,83 @@
+import 'package:bloc/bloc.dart';
+import 'package:comwell_key_app/check_out/bloc/check_out_state.dart';
+import 'package:comwell_key_app/check_out/models/check_out_item.dart';
+import 'package:comwell_key_app/check_out/models/checkout_processing_state.dart';
+import 'package:comwell_key_app/check_out/pages/check_out_page.dart';
+import 'package:flutter/material.dart';
+
+class CheckoutCubit extends Cubit<CheckoutState> {
+ final pageController = PageController();
+ final clubPoints = 200; // TODO Is this available on user object?
+ bool _isAnimating = false;
+
+ CheckoutPage get currentPage =>
+ CheckoutPage.fromIndex(pageController.page?.toInt() ?? 0);
+
+ CheckoutCubit() : super(CheckoutState.initial());
+
+ void onApplyClubPointsClicked(bool value) {
+ emit(state.setApplyClubPoints(value));
+ }
+
+ // TODO Where do we get the items from?
+ void setItems(Iterable<CheckoutItem> items) {
+ emit(state.setItems(items));
+ }
+
+ // TODO connect to checkout endpoint, handle error state (missing in figma)
+ void processCheckout() async {
+ emit(state.setProcessingState(CheckoutProcessingState.processing));
+ await Future<void>.delayed(const Duration(seconds: 2));
+ emit(state.setProcessingState(CheckoutProcessingState.success));
+ await Future<void>.delayed(const Duration(seconds: 2));
+ emit(state.setProcessingState(CheckoutProcessingState.confirmed));
+ }
+
+ void onContinueClicked() {
+ if (_isAnimating) return;
+ _isAnimating = true;
+ switch (currentPage) {
+ case CheckoutPage.confirmation:
+ return _navigateTo(CheckoutPage.payment);
+ case CheckoutPage.payment:
+ return processCheckout();
+ }
+ }
+
+ 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;
+ emit(state.setPage(currentPage));
+ });
+ return true;
+ }
+
+ void _navigateTo(CheckoutPage page) async {
+ await pageController.animateToPage(page.index,
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.fastOutSlowIn);
+ emit(state.setPage(page));
+ _isAnimating = false;
+ }
+
+ @override
+ Future<void> close() {
+ pageController.dispose();
+ return super.close();
+ }
+
+ void onChangePaymentClicked() {
+ // TODO finish adyen task
+ }
+
+ void onAcceptTermsChanged(bool value) {
+ emit(state.setAcceptTerms(value));
+ }
+}
diff --git a/comwell_key_app/lib/check_out/bloc/check_out_state.dart b/comwell_key_app/lib/check_out/bloc/check_out_state.dart
new file mode 100644
index 00000000..c2ac6acf
--- /dev/null
+++ b/comwell_key_app/lib/check_out/bloc/check_out_state.dart
@@ -0,0 +1,75 @@
+import 'package:comwell_key_app/check_out/models/check_out_item.dart';
+import 'package:comwell_key_app/check_out/models/checkout_processing_state.dart';
+import 'package:comwell_key_app/check_out/pages/check_out_page.dart';
+import 'package:equatable/equatable.dart';
+
+class CheckoutState extends Equatable {
+ final Iterable<CheckoutItem> _items;
+ final bool termsAccepted;
+ final bool applyClubPoints;
+ final CheckoutProcessingState processingState;
+ final CheckoutPage page;
+
+ num get totalPrice {
+ if (_items.isEmpty) return 0;
+ return _items
+ .map((item) => item.price)
+ .reduce((total, price) => total + price);
+ }
+
+ Iterable<CheckoutItem> get items {
+ if (applyClubPoints) return [..._items, CheckoutItem("Rabat", -200)];
+ return _items;
+ }
+
+ const CheckoutState({
+ required Iterable<CheckoutItem> items,
+ required this.termsAccepted,
+ required this.page,
+ required this.applyClubPoints,
+ required this.processingState,
+ }) : _items = items;
+
+ CheckoutState.initial()
+ : _items = _mockItems(),
+ termsAccepted = false,
+ page = CheckoutPage.confirmation,
+ processingState = CheckoutProcessingState.notStarted,
+ applyClubPoints = false;
+
+ CheckoutState setItems(Iterable<CheckoutItem> items) =>
+ _copyWith(items: items);
+
+ CheckoutState setAcceptTerms(bool accept) => _copyWith(termsAccepted: accept);
+
+ CheckoutState setApplyClubPoints(bool apply) =>
+ _copyWith(applyClubPoints: apply);
+
+ CheckoutState setProcessingState(CheckoutProcessingState processingState) =>
+ _copyWith(processingState: processingState);
+
+ CheckoutState setPage(CheckoutPage page) => _copyWith(page: page);
+
+ CheckoutState _copyWith({
+ Iterable<CheckoutItem>? items,
+ bool? termsAccepted,
+ bool? applyClubPoints,
+ CheckoutProcessingState? processingState,
+ CheckoutPage? page,
+ }) {
+ return CheckoutState(
+ items: items ?? _items,
+ page: page ?? this.page,
+ processingState: processingState ?? this.processingState,
+ termsAccepted: termsAccepted ?? this.termsAccepted,
+ applyClubPoints: applyClubPoints ?? this.applyClubPoints,
+ );
+ }
+
+ static Iterable<CheckoutItem> _mockItems() =>
+ [1, 2, 3].map((i) => CheckoutItem("item $i", i * 100));
+
+ @override
+ List<Object?> get props =>
+ [_items, termsAccepted, applyClubPoints, processingState, page];
+}
diff --git a/comwell_key_app/lib/check_out/check_out_flow.dart b/comwell_key_app/lib/check_out/check_out_flow.dart
new file mode 100644
index 00000000..012fce9e
--- /dev/null
+++ b/comwell_key_app/lib/check_out/check_out_flow.dart
@@ -0,0 +1,37 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/check_out/components/check_out_bottom_sheet.dart';
+import 'package:comwell_key_app/check_out/models/checkout_processing_state.dart';
+import 'package:comwell_key_app/check_out/pages/check_out_page.dart';
+import 'package:comwell_key_app/check_out/pages/check_out_processing_page.dart';
+import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class CheckOutFlow extends StatelessWidget {
+ const CheckOutFlow({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ if (cubit.state.processingState != CheckoutProcessingState.notStarted) {
+ return const CheckOutProcessingPage();
+ }
+ return Scaffold(
+ appBar: ComwellAppBar(
+ shouldShowProfileButton: false,
+ onBackPressed: () {
+ final didScroll = cubit.onBackPressed();
+ if (!didScroll) Navigator.of(context).pop();
+ },
+ ),
+ bottomSheet: const CheckOutBottomSheet(),
+ backgroundColor: Colors.white,
+ body: PageView(
+ controller: cubit.pageController,
+ key: const PageStorageKey("check_out_flow"),
+ physics: const NeverScrollableScrollPhysics(),
+ children: CheckoutPage.getPages(ValueKey(cubit.state)).toList(),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/accept_terms_toggle.dart b/comwell_key_app/lib/check_out/components/accept_terms_toggle.dart
new file mode 100644
index 00000000..65d7095a
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/accept_terms_toggle.dart
@@ -0,0 +1,33 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class AcceptTermsToggle extends StatelessWidget {
+ const AcceptTermsToggle({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return Row(
+ children: [
+ Checkbox(
+ value: cubit.state.termsAccepted,
+ visualDensity: VisualDensity.comfortable,
+ checkColor: Colors.white,
+ activeColor: sandColor[80],
+ onChanged: (value) {
+ cubit.onAcceptTermsChanged(value ?? false);
+ },
+ ),
+ const SizedBox(width: 4),
+ TextButton(
+ onPressed: () {
+ cubit.onAcceptTermsChanged(!cubit.state.termsAccepted);
+ },
+ child: Text("checkout_page_payment_accept_terms".tr())),
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/apply_club_points.dart b/comwell_key_app/lib/check_out/components/apply_club_points.dart
new file mode 100644
index 00000000..0d6a527e
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/apply_club_points.dart
@@ -0,0 +1,41 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class ApplyClubPoints extends StatelessWidget {
+ const ApplyClubPoints({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("checkout_page_payment_club_points_title".tr()),
+ Text(
+ "checkout_page_payment_club_points_subtitle".tr(
+ args: ["${cubit.clubPoints}", "${cubit.clubPoints}"],
+ ),
+ style: const TextStyle(color: Color(0x55000000)),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 20),
+ Switch(
+ value: cubit.state.applyClubPoints,
+ activeColor: sandColor[80],
+ onChanged: (value) {
+ cubit.onApplyClubPointsClicked(value);
+ })
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/check_out_bill_list_item.dart b/comwell_key_app/lib/check_out/components/check_out_bill_list_item.dart
new file mode 100644
index 00000000..ccacc303
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/check_out_bill_list_item.dart
@@ -0,0 +1,26 @@
+import 'package:comwell_key_app/check_out/models/check_out_item.dart';
+import 'package:flutter/material.dart';
+
+class CheckOutBillListItem extends StatelessWidget {
+ final CheckoutItem item;
+
+ const CheckOutBillListItem({super.key, required this.item});
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ item.name,
+ style: Theme.of(context).textTheme.bodyMedium,
+ ),
+ Text(
+ "${item.price}",
+ style: Theme.of(context).textTheme.bodyMedium,
+ ),
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/check_out_bottom_sheet.dart b/comwell_key_app/lib/check_out/components/check_out_bottom_sheet.dart
new file mode 100644
index 00000000..6d938325
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/check_out_bottom_sheet.dart
@@ -0,0 +1,69 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../pages/check_out_page.dart';
+import 'confirm_check_out_dialog.dart';
+
+class CheckOutBottomSheet extends StatelessWidget {
+ const CheckOutBottomSheet({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return Container(
+ decoration:
+ const BoxDecoration(shape: BoxShape.rectangle, color: Colors.white),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Divider(color: colorDivider),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Row(
+ children: [
+ Expanded(
+ child: Builder(builder: (context) {
+ if (cubit.state.page == CheckoutPage.confirmation) {
+ return ElevatedButton(
+ onPressed: cubit.onContinueClicked,
+ style: ButtonStyle(
+ backgroundColor:
+ WidgetStatePropertyAll(sandColor[100]),
+ foregroundColor:
+ const WidgetStatePropertyAll(Colors.white)),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 17.0),
+ child: Text(
+ "checkout_page_confirmation_continue".tr()),
+ ));
+ } else {
+ return OutlinedButton(
+ onPressed: () async {
+ await showDialog<void>(
+ context: context,
+ builder: (context) =>
+ ConfirmCheckOutDialog(onConfirm: () {
+ Navigator.of(context).pop();
+ cubit.onContinueClicked();
+ }));
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 17.0),
+ child: Text("checkout_page_payment_payment_title"
+ .tr(args: ["kreditkort"])),
+ ));
+ }
+ }),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 8)
+ ],
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/check_out_countdown.dart b/comwell_key_app/lib/check_out/components/check_out_countdown.dart
new file mode 100644
index 00000000..ae48c7d0
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/check_out_countdown.dart
@@ -0,0 +1,72 @@
+import 'package:comwell_key_app/utils/time_utils.dart';
+import 'package:flutter/material.dart';
+
+class CheckOutCountdown extends StatefulWidget {
+ const CheckOutCountdown({super.key});
+
+ @override
+ State<CheckOutCountdown> createState() => _CheckOutCountdownState();
+}
+
+class _CheckOutCountdownState extends State<CheckOutCountdown> {
+ int minutes = 30;
+ int seconds = 0;
+
+ void _countdown() async {
+ await Future<void>.delayed(const Duration(seconds: 1));
+ if (mounted) {
+ setState(() {
+ seconds--;
+ if (seconds < 0) {
+ minutes--;
+ seconds = 59;
+ }
+ });
+ _countdown();
+ }
+ }
+
+ @override
+ void initState() {
+ _countdown();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(30),
+ border: Border.all(
+ color: Colors.white,
+ width: 3,
+ )),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ minutes.toClockString(),
+ style: Theme.of(context)
+ .textTheme
+ .displayLarge
+ ?.copyWith(color: Colors.white),
+ ),
+ Text(":",
+ style: Theme.of(context)
+ .textTheme
+ .displayLarge
+ ?.copyWith(color: Colors.white)),
+ Text(seconds.toClockString(),
+ style: Theme.of(context)
+ .textTheme
+ .displayLarge
+ ?.copyWith(color: Colors.white)),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/check_out_payment_card.dart b/comwell_key_app/lib/check_out/components/check_out_payment_card.dart
new file mode 100644
index 00000000..191f1d82
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/check_out_payment_card.dart
@@ -0,0 +1,58 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class CheckOutPaymentCard extends StatelessWidget {
+ const CheckOutPaymentCard({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10),
+ color: sandColor[10],
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 25.0, vertical: 20),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "checkout_page_confirmation_price_title".tr(),
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: const Color(0x55000000)),
+ ),
+ const SizedBox(height: 4),
+ Text("${cubit.state.totalPrice} kr.",
+ style: Theme.of(context).textTheme.displaySmall),
+ ],
+ ),
+ Container(
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: sandColor[40],
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: SvgPicture.asset(
+ "assets/icons/ic_card.svg",
+ fit: BoxFit.fitWidth,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/checkout_itemized_bill.dart b/comwell_key_app/lib/check_out/components/checkout_itemized_bill.dart
new file mode 100644
index 00000000..0fbe0108
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/checkout_itemized_bill.dart
@@ -0,0 +1,53 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/check_out/components/check_out_bill_list_item.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class CheckoutItemizedBill extends StatelessWidget {
+ const CheckoutItemizedBill({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ final items = cubit.state.items;
+ return Column(
+ children: [
+ ...items.map((item) => Column(
+ children: [
+ CheckOutBillListItem(item: item),
+ const SizedBox(height: 14)
+ ],
+ )),
+ const Divider(color: colorDivider),
+ const SizedBox(height: 12),
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text("checkout_page_payment_total".tr()),
+ if (cubit.state.applyClubPoints)
+ Row(
+ children: [
+ Text(
+ "${cubit.state.totalPrice}",
+ style: const TextStyle(
+ decoration: TextDecoration.lineThrough,
+ decorationColor: colorDivider,
+ color: colorDivider),
+ ),
+ const SizedBox(width: 4),
+ Text("${cubit.state.totalPrice - cubit.clubPoints}"),
+ ],
+ )
+ else
+ Text("${cubit.state.totalPrice}"),
+ ],
+ ),
+ const SizedBox(height: 12),
+ const Divider(color: colorDivider),
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/confirm_check_out_dialog.dart b/comwell_key_app/lib/check_out/components/confirm_check_out_dialog.dart
new file mode 100644
index 00000000..a9dacffc
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/confirm_check_out_dialog.dart
@@ -0,0 +1,76 @@
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+
+class ConfirmCheckOutDialog extends StatelessWidget {
+ final VoidCallback onConfirm;
+
+ const ConfirmCheckOutDialog({super.key, required this.onConfirm});
+
+ @override
+ Widget build(BuildContext context) {
+ return Dialog(
+ backgroundColor: Colors.white,
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ "checkout_page_payment_dialog_title".tr(),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 12),
+ Text(
+ "checkout_page_payment_dialog_subtitle".tr(),
+ textAlign: TextAlign.center,
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: const Color(0x55000000)),
+ ),
+ const SizedBox(height: 32),
+ Row(
+ children: [
+ Expanded(
+ child: ElevatedButton(
+ onPressed: onConfirm,
+ style: ButtonStyle(
+ backgroundColor: WidgetStatePropertyAll(sandColor[100]),
+ foregroundColor:
+ const WidgetStatePropertyAll(Colors.white)),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 17.0),
+ child: Text(
+ "checkout_page_payment_dialog_confirm".tr(),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Expanded(
+ child: OutlinedButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ style: const ButtonStyle(
+ backgroundColor: WidgetStatePropertyAll(Colors.white)),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 17.0),
+ child: Text("checkout_page_payment_dialog_cancel".tr()),
+ ),
+ ),
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/components/payment_button.dart b/comwell_key_app/lib/check_out/components/payment_button.dart
new file mode 100644
index 00000000..9d16299c
--- /dev/null
+++ b/comwell_key_app/lib/check_out/components/payment_button.dart
@@ -0,0 +1,38 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+
+
+class PaymentButton extends StatelessWidget {
+ const PaymentButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return InkWell(
+ onTap: cubit.onChangePaymentClicked,
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: colorDivider),
+ ),
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SvgPicture.asset("assets/icons/visa.svg"),
+ const SizedBox(width: 12),
+ Text(
+ "checkout_page_payment_payment_title".tr(args: ["kreditkort"]),
+ style: Theme.of(context).textTheme.bodyMedium,
+ ),
+ const Expanded(child: SizedBox()),
+ SvgPicture.asset("assets/icons/arrow-left.svg"),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/models/check_out_item.dart b/comwell_key_app/lib/check_out/models/check_out_item.dart
new file mode 100644
index 00000000..0f94e445
--- /dev/null
+++ b/comwell_key_app/lib/check_out/models/check_out_item.dart
@@ -0,0 +1,6 @@
+class CheckoutItem {
+ final String name;
+ final num price;
+
+ CheckoutItem(this.name, this.price);
+}
\ No newline at end of file
diff --git a/comwell_key_app/lib/check_out/models/checkout_processing_state.dart b/comwell_key_app/lib/check_out/models/checkout_processing_state.dart
new file mode 100644
index 00000000..953e3b08
--- /dev/null
+++ b/comwell_key_app/lib/check_out/models/checkout_processing_state.dart
@@ -0,0 +1,7 @@
+enum CheckoutProcessingState {
+ notStarted,
+ processing,
+ success,
+ confirmed,
+ error;
+}
\ No newline at end of file
diff --git a/comwell_key_app/lib/check_out/pages/check_out_confirmation_pages.dart b/comwell_key_app/lib/check_out/pages/check_out_confirmation_pages.dart
new file mode 100644
index 00000000..ba6e05ee
--- /dev/null
+++ b/comwell_key_app/lib/check_out/pages/check_out_confirmation_pages.dart
@@ -0,0 +1,47 @@
+import 'package:comwell_key_app/check_out/components/check_out_countdown.dart';
+import 'package:comwell_key_app/check_out/components/check_out_payment_card.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+class CheckOutConfirmationPage extends StatelessWidget {
+ const CheckOutConfirmationPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ CheckOutCountdown(),
+ const SizedBox(height: 40),
+ Text(
+ "checkout_page_confirmation_title".tr(),
+ style: Theme.of(context).textTheme.headlineLarge,
+ ),
+ const SizedBox(height: 18),
+ Text(
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: const Color(0x55000000)),
+ "checkout_page_confirmation_subtitle".tr(),
+ ),
+ const SizedBox(height: 16),
+ Text(
+ "checkout_page_confirmation_deadline".tr(args: ["10:00"]),
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: const Color(0x55000000)),
+ ),
+ const SizedBox(height: 24),
+ const Divider(color: colorDivider),
+ const SizedBox(height: 24),
+ const CheckOutPaymentCard(),
+ ],
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/pages/check_out_page.dart b/comwell_key_app/lib/check_out/pages/check_out_page.dart
new file mode 100644
index 00000000..f17d0308
--- /dev/null
+++ b/comwell_key_app/lib/check_out/pages/check_out_page.dart
@@ -0,0 +1,24 @@
+import 'package:comwell_key_app/check_out/pages/check_out_confirmation_pages.dart';
+import 'package:flutter/cupertino.dart';
+
+import 'checkout_payment_page.dart';
+
+enum CheckoutPage {
+ confirmation,
+ payment;
+
+ static CheckoutPage fromIndex(int index) {
+ return CheckoutPage.values[index];
+ }
+
+ static Iterable<Widget> getPages(Key key) {
+ return CheckoutPage.values.map((page) {
+ switch (page) {
+ case CheckoutPage.confirmation:
+ return CheckOutConfirmationPage(key: key);
+ case CheckoutPage.payment:
+ return CheckoutPaymentPage(key: key);
+ }
+ });
+ }
+}
diff --git a/comwell_key_app/lib/check_out/pages/check_out_processing_page.dart b/comwell_key_app/lib/check_out/pages/check_out_processing_page.dart
new file mode 100644
index 00000000..0c44289e
--- /dev/null
+++ b/comwell_key_app/lib/check_out/pages/check_out_processing_page.dart
@@ -0,0 +1,93 @@
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/check_out/models/checkout_processing_state.dart';
+import 'package:comwell_key_app/check_out/pages/check_out_success_page.dart';
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:comwell_key_app/utils/lottie_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:lottie/lottie.dart';
+
+class CheckOutProcessingPage extends StatefulWidget {
+ const CheckOutProcessingPage({super.key});
+
+ @override
+ State<CheckOutProcessingPage> createState() => _CheckOutProcessingPageState();
+}
+
+class _CheckOutProcessingPageState extends State<CheckOutProcessingPage>
+ with SingleTickerProviderStateMixin {
+ late final LottieComposition loadingComposition;
+ late final AnimationController animationController;
+
+ @override
+ void initState() {
+ animationController = AnimationController(vsync: this);
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ animationController.dispose();
+ super.dispose();
+ }
+
+ void onAnimationEnd() {
+ playLoading();
+ }
+
+ void playLoading() {
+ loadingComposition.playBetween(animationController, "spinner",
+ markerEnd: "success", repeat: true);
+ }
+
+ void playError() {
+ loadingComposition.playBetween(animationController, "error");
+ }
+
+ void playSuccess() async {
+ await loadingComposition.playBetween(
+ animationController,
+ "success",
+ markerEnd: "error",
+ );
+ await Future<void>.delayed(const Duration(seconds: 1));
+ if (mounted) Navigator.of(context).pop();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<CheckoutCubit>();
+ return Scaffold(
+ body: Container(
+ alignment: Alignment.center,
+ color: sandColor[80],
+ child: Builder(builder: (context) {
+ if (cubit.state.processingState == CheckoutProcessingState.confirmed) {
+ return const CheckOutSuccessPage();
+ }
+ return Lottie.asset(
+ 'assets/animations/load_animation.json',
+ controller: animationController,
+ onLoaded: (composition) {
+ loadingComposition = composition;
+ animationController.duration = composition.duration;
+ switch (cubit.state.processingState) {
+ case CheckoutProcessingState.processing:
+ playLoading();
+ case CheckoutProcessingState.success:
+ playSuccess();
+ case CheckoutProcessingState.error:
+ playError();
+ default:
+ // no-op
+ }
+ },
+ fit: BoxFit.cover,
+ width: 64,
+ height: 64,
+ );
+ }),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/pages/check_out_success_page.dart b/comwell_key_app/lib/check_out/pages/check_out_success_page.dart
new file mode 100644
index 00000000..a0cbf72a
--- /dev/null
+++ b/comwell_key_app/lib/check_out/pages/check_out_success_page.dart
@@ -0,0 +1,60 @@
+import 'package:comwell_key_app/check_out/components/check_out_countdown.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class CheckOutSuccessPage extends StatelessWidget {
+ const CheckOutSuccessPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 40.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const SizedBox(),
+ const CheckOutCountdown(),
+ Column(
+ children: [
+ Text(
+ "checkout_page_processing_success_title".tr(),
+ style: Theme.of(context)
+ .textTheme
+ .headlineMedium
+ ?.copyWith(color: Colors.white),
+ ),
+ Text(
+ "checkout_page_processing_success_subtitle".tr(),
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: const Color(0xBBFFFFFF)))
+ ],
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: ElevatedButton(
+ onPressed: () {
+ context.pop();
+ },
+ style: const ButtonStyle(
+ backgroundColor: WidgetStatePropertyAll(Colors.white)
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Text("generic_ok".tr(), style: const TextStyle(color: Colors.black),),
+ )),
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/check_out/pages/checkout_payment_page.dart b/comwell_key_app/lib/check_out/pages/checkout_payment_page.dart
new file mode 100644
index 00000000..f0bb9253
--- /dev/null
+++ b/comwell_key_app/lib/check_out/pages/checkout_payment_page.dart
@@ -0,0 +1,43 @@
+import 'package:comwell_key_app/check_out/components/accept_terms_toggle.dart';
+import 'package:comwell_key_app/check_out/components/apply_club_points.dart';
+import 'package:comwell_key_app/check_out/components/checkout_itemized_bill.dart';
+import 'package:comwell_key_app/check_out/components/payment_button.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+
+class CheckoutPaymentPage extends StatelessWidget {
+ const CheckoutPaymentPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SingleChildScrollView(
+ key: PageStorageKey("$runtimeType"),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 40),
+ Text(
+ "checkout_page_payment_title".tr(),
+ style: Theme.of(context).textTheme.headlineLarge,
+ ),
+ const SizedBox(height: 26),
+ const CheckoutItemizedBill(),
+ const SizedBox(height: 52),
+ const ApplyClubPoints(),
+ const SizedBox(height: 20),
+ const Divider(color: colorDivider),
+ const SizedBox(height: 20),
+ const PaymentButton(),
+ const SizedBox(height: 100),
+ const AcceptTermsToggle(),
+ const SizedBox(height: 100),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/common/components/comwell_app_bar.dart b/comwell_key_app/lib/common/components/comwell_app_bar.dart
index 189470f5..e1eef311 100644
--- a/comwell_key_app/lib/common/components/comwell_app_bar.dart
+++ b/comwell_key_app/lib/common/components/comwell_app_bar.dart
@@ -8,12 +8,14 @@ class ComwellAppBar extends StatelessWidget implements PreferredSizeWidget {
final bool shouldShowAppBar;
final bool shouldShowBackButton;
final bool shouldShowProfileButton;
+ final VoidCallback? onBackPressed;
const ComwellAppBar({
super.key,
this.shouldShowAppBar = true,
this.shouldShowBackButton = true,
this.shouldShowProfileButton = true,
+ this.onBackPressed,
});
@override
@@ -39,7 +41,11 @@ class ComwellAppBar extends StatelessWidget implements PreferredSizeWidget {
color: Colors.white,
icon: "assets/icons/arrow-right.svg",
onPressed: () {
- Navigator.of(context).pop();
+ if (onBackPressed != null) {
+ onBackPressed!.call();
+ } else {
+ Navigator.of(context).pop();
+ }
},
),
),
@@ -48,7 +54,7 @@ class ComwellAppBar extends StatelessWidget implements PreferredSizeWidget {
child: RoundIconButton(
color: Colors.white,
icon: "assets/icons/user-open.svg",
-
+
onPressed: () {
context.goNamed(AppRoutes.profile.name);
},
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index 606388ec..f4625ab9 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -1,7 +1,9 @@
-import 'package:comwell_key_app/authentication/authentication_repository.dart';
import 'package:comwell_key_app/authentication/bloc/authentication_bloc.dart';
import 'package:comwell_key_app/check_in/bloc/check_in_cubit.dart';
import 'package:comwell_key_app/check_in/check_in_page.dart';
+import 'package:comwell_key_app/check_out/bloc/check_out_cubit.dart';
+import 'package:comwell_key_app/check_out/bloc/check_out_state.dart';
+import 'package:comwell_key_app/check_out/check_out_flow.dart';
import 'package:comwell_key_app/contact/contact_page.dart';
import 'package:comwell_key_app/find_booking/find_booking_page.dart';
import 'package:comwell_key_app/find_booking/loading_page.dart';
@@ -183,7 +185,8 @@ GoRouter goRouter(AuthenticationBloc authBloc) {
return BlocProvider<BookingDetailsBloc>(
create: (BuildContext context) => BookingDetailsBloc(
booking,
- bookingDetailsRepository: locator<BookingDetailsRepository>(),
+ bookingDetailsRepository:
+ locator<BookingDetailsRepository>(),
),
child: const BookingDetailsPage(),
);
@@ -222,7 +225,20 @@ GoRouter goRouter(AuthenticationBloc authBloc) {
builder: (context, state) {
return const HousekeepingPage();
},
- )
+ ),
+ GoRoute(
+ path: "/${AppRoutes.checkOut.name}",
+ name: AppRoutes.checkOut.name,
+ builder: (context, state) {
+ return BlocProvider(
+ create: (context) => CheckoutCubit(),
+ child: BlocBuilder<CheckoutCubit, CheckoutState>(
+ builder: (context, state) {
+ return CheckOutFlow(key: ValueKey(state));
+ }));
+ },
+ ),
+
/* GoRoute(
path: "/keys",
name: AppRoutes.keys.name,
diff --git a/comwell_key_app/lib/routing/app_routes.dart b/comwell_key_app/lib/routing/app_routes.dart
index e3c5172b..4d620d03 100644
--- a/comwell_key_app/lib/routing/app_routes.dart
+++ b/comwell_key_app/lib/routing/app_routes.dart
@@ -23,4 +23,5 @@ enum AppRoutes {
spa,
parking,
houseKeeping,
+ checkOut,
}
diff --git a/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart b/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
index e78d74d0..9b9e70cd 100644
--- a/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
+++ b/comwell_key_app/lib/services/interceptors/response_handle_interceptor.dart
@@ -46,12 +46,10 @@ class ResponseHandleInterceptor extends Interceptor {
if (response == null) throw Exception("Missing response");
final statusCode = response.statusCode;
try {
- if (response.data == null) {
+ if (response.statusCode == 404) {
throw DioException(
- requestOptions: response.requestOptions,
- error: 'No data received from the server');
- }
- if (statusCode == 401) {
+ requestOptions: response.requestOptions, error: 'Not found');
+ } else if (statusCode == 401) {
final newToken = await _refreshToken();
if (newToken != null) {
// Retry the original request with the new token
@@ -65,7 +63,6 @@ class ResponseHandleInterceptor extends Interceptor {
options: opts,
data: response.requestOptions.data,
queryParameters: response.requestOptions.queryParameters);
-
return handler.resolve(retryRequest);
}
} else {
diff --git a/comwell_key_app/lib/themes/dark_theme.dart b/comwell_key_app/lib/themes/dark_theme.dart
index 0fc57d74..3117aaf1 100644
--- a/comwell_key_app/lib/themes/dark_theme.dart
+++ b/comwell_key_app/lib/themes/dark_theme.dart
@@ -71,5 +71,5 @@ const sandColor = MaterialColor(_sandColor, <int,Color>
60: Color.fromRGBO(215, 201, 185, 1.0),
40: Color.fromRGBO(237, 227, 216, 1.0),
20: Color.fromRGBO(240, 234, 226, 1.0),
- 10: Color.fromRGBO(249, 246, 242, 1.0),
+ 10: Color(0xFFF9F6F2),
});
\ No newline at end of file
diff --git a/comwell_key_app/lib/themes/light_theme.dart b/comwell_key_app/lib/themes/light_theme.dart
index dec9e2b3..1e57a0b0 100644
--- a/comwell_key_app/lib/themes/light_theme.dart
+++ b/comwell_key_app/lib/themes/light_theme.dart
@@ -15,6 +15,7 @@ ThemeData lightTheme = ThemeData(
textTheme: const TextTheme(
displayLarge: TextStyle(fontSize: 36.0, fontWeight: FontWeight.w600),
displayMedium: TextStyle(fontSize: 34.0, fontWeight: FontWeight.w600),
+ displaySmall: TextStyle(fontSize: 32, fontWeight: FontWeight.w600),
headlineLarge: TextStyle(fontSize: 24.0, fontWeight: FontWeight.w600),
headlineMedium: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
headlineSmall: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w600),
@@ -77,5 +78,5 @@ const sandColor = MaterialColor(_sandColor, <int,Color>
60: Color.fromRGBO(215, 201, 185, 1.0),
40: Color.fromRGBO(237, 227, 216, 1.0),
20: Color.fromRGBO(240, 234, 226, 1.0),
- 10: Color.fromRGBO(249, 246, 242, 1.0),
+ 10: Color(0xFFF9F6F2),
});
\ No newline at end of file
diff --git a/comwell_key_app/lib/utils/lottie_utils.dart b/comwell_key_app/lib/utils/lottie_utils.dart
index ecab07fa..47d5eee9 100644
--- a/comwell_key_app/lib/utils/lottie_utils.dart
+++ b/comwell_key_app/lib/utils/lottie_utils.dart
@@ -2,17 +2,24 @@ import 'package:flutter/animation.dart';
import 'package:lottie/lottie.dart';
extension LottieUtils on LottieComposition {
- void playBetween(AnimationController animationController, String markerStart,
- {String markerEnd = "", bool repeat = false}) {
+ Future<void> playBetween(
+ AnimationController animationController,
+ String markerStart, {
+ String markerEnd = "",
+ bool repeat = false,
+ }) async {
final startMarker = getMarker(markerStart)!.start;
final endMarker = markerEnd == "" ? 1.0 : getMarker(markerEnd)!.start;
final duration = this.duration * (endMarker - startMarker).abs();
animationController.value = startMarker;
if (repeat) {
- animationController.repeat(
- min: startMarker, max: endMarker, period: duration);
+ await animationController.repeat(
+ min: startMarker,
+ max: endMarker,
+ period: duration,
+ );
} else {
- animationController.animateTo(endMarker, duration: duration);
+ await animationController.animateTo(endMarker, duration: duration);
}
}
}
diff --git a/comwell_key_app/lib/utils/time_utils.dart b/comwell_key_app/lib/utils/time_utils.dart
new file mode 100644
index 00000000..0090204a
--- /dev/null
+++ b/comwell_key_app/lib/utils/time_utils.dart
@@ -0,0 +1,7 @@
+extension TimeStringUtls on num {
+
+ String toClockString() {
+ if(this < 10) return "0$this";
+ return "$this";
+ }
+}
\ No newline at end of file