6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 1e5955bd
Changed files
.../lib/booking_details/booking_details_page.dart | 165 ++++++++++++------ .../components/booking_details_bottom_sheet.dart | 192 +++++++++++---------- 2 files changed, 214 insertions(+), 143 deletions(-)
Diff
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 6e5cb147..71bcb4d3 100644
--- a/comwell_key_app/lib/booking_details/booking_details_page.dart
+++ b/comwell_key_app/lib/booking_details/booking_details_page.dart
@@ -11,6 +11,7 @@ import 'package:comwell_key_app/routing/app_routes.dart';
import 'package:comwell_key_app/services/mappers/booking_mapper.dart';
import 'package:comwell_key_app/themes/light_theme.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -53,8 +54,11 @@ class BookingDetailsPage extends StatelessWidget {
Widget _buildBookingDetailsPage(BuildContext context,
BookingDetailsState state, BookingDetailsBloc cubit) {
final theme = Theme.of(context);
+ final screenHeight = MediaQuery.of(context).size.height;
+
return Stack(
children: [
+ // Background image - fixed
Container(
decoration: const BoxDecoration(
image: DecorationImage(
@@ -63,6 +67,7 @@ class BookingDetailsPage extends StatelessWidget {
),
),
),
+ // Gradient overlay - fixed
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
@@ -75,55 +80,28 @@ class BookingDetailsPage extends StatelessWidget {
),
),
),
- Stack(
- alignment: Alignment.center,
- children: [
- if (cubit.booking.reservationStatus ==
- ReservationStatus.preregistered ||
- cubit.booking.reservationStatus ==
- ReservationStatus.preregistered ||
- cubit.booking.reservationStatus == ReservationStatus.checkedin)
- SafeArea(
- child: cubit.booking.isPrimaryGuest &&cubit.booking.reservationStatus == ReservationStatus.checkedin ? ShareButton(guests: state.guests) : const SizedBox(),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Spacer(),
- if (cubit.booking.roomNumber != '')
- RoomNumberContainer(cubit: cubit),
- _buildBookingDetailsInformation(context, state, cubit, theme),
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- child: Divider(color: colorDivider),
- ),
- const SizedBox(height: 10),
- if (
- state.keys.isNotEmpty &&
- cubit.booking.roomNumber != '' &&
- state.keys.any((key) => key.label == cubit.booking.roomNumber)
- )
- UnlockRoomButton(roomNumber: cubit.booking.roomNumber)
- else if (cubit.booking.reservationStatus == ReservationStatus.checkedin
- )
- const GetKeysButton()
- else if (cubit.booking.reservationStatus ==
- ReservationStatus.preregistered)
- const CheckInButtonTimer()
- else if (cubit.booking.reservationStatus ==
- ReservationStatus.newreservation)
- const PreregisterButton()
- else
- const SizedBox(),
- const SizedBox(
- height: 200,
- ),
- ],
- ),
- BookingDetailsBottomSheet(cubit: cubit, state: state),
- ],
- )
+ // Share button - fixed at top
+ if (cubit.booking.reservationStatus ==
+ ReservationStatus.preregistered ||
+ cubit.booking.reservationStatus ==
+ ReservationStatus.preregistered ||
+ cubit.booking.reservationStatus == ReservationStatus.checkedin)
+ SafeArea(
+ child: cubit.booking.isPrimaryGuest &&
+ cubit.booking.reservationStatus ==
+ ReservationStatus.checkedin
+ ? ShareButton(guests: state.guests)
+ : const SizedBox(),
+ ),
+ // Scrollable content with haptic feedback
+ _ScrollableBookingContent(
+ screenHeight: screenHeight,
+ cubit: cubit,
+ state: state,
+ theme: theme,
+ buildBookingDetailsInformation: () =>
+ _buildBookingDetailsInformation(context, state, cubit, theme),
+ ),
],
);
}
@@ -187,3 +165,92 @@ class BookingDetailsPage extends StatelessWidget {
);
}
}
+
+class _ScrollableBookingContent extends StatefulWidget {
+ final double screenHeight;
+ final BookingDetailsBloc cubit;
+ final BookingDetailsState state;
+ final ThemeData theme;
+ final Widget Function() buildBookingDetailsInformation;
+
+ const _ScrollableBookingContent({
+ required this.screenHeight,
+ required this.cubit,
+ required this.state,
+ required this.theme,
+ required this.buildBookingDetailsInformation,
+ });
+
+ @override
+ State<_ScrollableBookingContent> createState() =>
+ _ScrollableBookingContentState();
+}
+
+class _ScrollableBookingContentState extends State<_ScrollableBookingContent> {
+ bool _hasTriggeredHaptic = false;
+
+ void _onScroll(double offset) {
+ if (offset > 0 && !_hasTriggeredHaptic) {
+ HapticFeedback.lightImpact();
+ _hasTriggeredHaptic = true;
+ } else if (offset == 0) {
+ _hasTriggeredHaptic = false;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return NotificationListener<ScrollNotification>(
+ onNotification: (notification) {
+ _onScroll(notification.metrics.pixels);
+ return false;
+ },
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ // Top section with room info - leaves 100px for sheet peek
+ SizedBox(
+ height: widget.screenHeight - 100,
+ child: SafeArea(
+ bottom: false,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ if (widget.cubit.booking.roomNumber != '')
+ RoomNumberContainer(cubit: widget.cubit),
+ widget.buildBookingDetailsInformation(),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ child: Divider(color: colorDivider),
+ ),
+ const SizedBox(height: 10),
+ if (widget.state.keys.isNotEmpty &&
+ widget.cubit.booking.roomNumber != '' &&
+ widget.state.keys.any(
+ (key) => key.label == widget.cubit.booking.roomNumber))
+ UnlockRoomButton(roomNumber: widget.cubit.booking.roomNumber)
+ else if (widget.cubit.booking.reservationStatus ==
+ ReservationStatus.checkedin)
+ const GetKeysButton()
+ else if (widget.cubit.booking.reservationStatus ==
+ ReservationStatus.preregistered)
+ const CheckInButtonTimer()
+ else if (widget.cubit.booking.reservationStatus ==
+ ReservationStatus.newreservation)
+ const PreregisterButton()
+ else
+ const SizedBox(),
+ const SizedBox(height: 24),
+ ],
+ ),
+ ),
+ ),
+ // Bottom sheet content (scrollable, styled like a sheet)
+ BookingDetailsBottomSheet(cubit: widget.cubit, state: widget.state),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart b/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
index 7c36701d..da00f389 100644
--- a/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
+++ b/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
@@ -2,12 +2,12 @@ import 'package:comwell_key_app/booking_details/bloc/booking_details_bloc.dart';
import 'package:comwell_key_app/booking_details/components/check_out_button.dart';
import 'package:comwell_key_app/booking_details/components/housekeeping_button.dart';
import 'package:comwell_key_app/booking_details/components/practical_information_button.dart';
-import 'package:comwell_key_app/common/components/bottom_sheet_widget.dart';
import 'package:comwell_key_app/common/components/comwell_error_widget.dart';
import 'package:comwell_key_app/common/components/outlined_pill_button.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/profile/components/comwell_club_container.dart';
import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
import 'package:comwell_key_app/up_sales/components/catalog/service_catalog.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -22,105 +22,109 @@ class BookingDetailsBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final isActive =
- cubit.user?.isClubMember ?? false;
+ final isActive = cubit.user?.isClubMember ?? false;
- return BottomSheetWidget(
- widgetChildren: [
- const SizedBox(height: 16),
- cubit.booking.reservationStatus == ReservationStatus.checkedin &&
- cubit.getCheckOutTime().isBefore(DateTime.now())
- ? const Padding(
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- child: CheckOutButton(),
- )
- : const SizedBox(),
- cubit.booking.reservationStatus == ReservationStatus.checkedin &&
- cubit.isHouseKeepingTime
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 16),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
- child: HousekeepingButton(
- key: ValueKey(state.isHouseKeepingOrdered),
- booking: cubit.booking),
- ),
- ],
- )
- : const SizedBox(),
- const SizedBox(height: 20),
- _buildUpSalesCatalogButton(cubit.booking.reservationStatus, context),
- const SizedBox(height: 20),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text("booking_details_page_practical_information".tr(),
- style: theme.textTheme.headlineMedium),
- ),
- const SizedBox(height: 20),
- Row(children: [
- Expanded(
- child: AspectRatio(
- aspectRatio: 175 / 220,
- child: Padding(
- padding: const EdgeInsets.only(left: 16),
- child: PracticalInformationButton(
- iconPath: "assets/icons/ic_bed.svg",
- title: "booking_details_page_hotel_information_button_title"
- .tr(),
- subtitle:
- "booking_details_page_hotel_information_button_subtitle"
- .tr(),
- onClick: () {
- context.pushNamed(AppRoutes.hotelInformation.name,
- extra: cubit.booking);
- }),
+ return Material(
+ elevation: 8,
+ color: Colors.white,
+ borderRadius: const BorderRadius.vertical(top: Radius.circular(40)),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // Handle indicator
+ Center(
+ child: Container(
+ margin: const EdgeInsets.only(top: 20),
+ width: 70,
+ height: 5,
+ decoration: BoxDecoration(
+ color: colorDivider,
+ borderRadius: BorderRadius.circular(5),
),
),
),
- const SizedBox(width: 8),
- Expanded(
- child: AspectRatio(
- aspectRatio: 175 / 220,
- child: Padding(
- padding: const EdgeInsets.only(right: 16),
- child: PracticalInformationButton(
- iconPath: "assets/icons/ic_telephone.svg",
- title: "booking_details_page_contact_button_title".tr(),
- subtitle:
- "booking_details_page_contact_button_subtitle".tr(),
- onClick: () {
- context.pushNamed(AppRoutes.contact.name,
- extra: cubit.booking);
- }),
- ),
+ const SizedBox(height: 16),
+ if (cubit.booking.reservationStatus == ReservationStatus.checkedin &&
+ cubit.getCheckOutTime().isBefore(DateTime.now()))
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ child: CheckOutButton(),
),
+ if (cubit.booking.reservationStatus == ReservationStatus.checkedin &&
+ cubit.isHouseKeepingTime)
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 16),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: HousekeepingButton(
+ key: ValueKey(state.isHouseKeepingOrdered),
+ booking: cubit.booking),
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ _buildUpSalesCatalogButton(cubit.booking.reservationStatus, context),
+ const SizedBox(height: 20),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Text("booking_details_page_practical_information".tr(),
+ style: theme.textTheme.headlineMedium),
),
- ]),
- const SizedBox(height: 16),
- if (!isActive && cubit.user != null)
- ComwellClubContainer(
- user: cubit.user!,
- onSignupClick: () {
- cubit.add(const InitialEvent(fetchRemote: true));
- }),
- const SizedBox(height: 16),
- // if (cubit.booking.rooms.length > 1)
- // Padding(
- // padding: const EdgeInsets.symmetric(horizontal: 16),
- // child: Text('rooms'.tr(), style: theme.textTheme.headlineMedium),
- // ),
- // const SizedBox(height: 16),
- // if (state.keys.isNotEmpty)
- // Padding(
- // padding: const EdgeInsets.symmetric(horizontal: 16),
- // child:
- // Text('room_keys'.tr(), style: theme.textTheme.headlineMedium),
- // ),
- // const SizedBox(height: 16),
- const SizedBox(height: 50),
- ],
+ const SizedBox(height: 20),
+ Row(children: [
+ Expanded(
+ child: AspectRatio(
+ aspectRatio: 175 / 220,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 16),
+ child: PracticalInformationButton(
+ iconPath: "assets/icons/ic_bed.svg",
+ title:
+ "booking_details_page_hotel_information_button_title"
+ .tr(),
+ subtitle:
+ "booking_details_page_hotel_information_button_subtitle"
+ .tr(),
+ onClick: () {
+ context.pushNamed(AppRoutes.hotelInformation.name,
+ extra: cubit.booking);
+ }),
+ ),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: AspectRatio(
+ aspectRatio: 175 / 220,
+ child: Padding(
+ padding: const EdgeInsets.only(right: 16),
+ child: PracticalInformationButton(
+ iconPath: "assets/icons/ic_telephone.svg",
+ title: "booking_details_page_contact_button_title".tr(),
+ subtitle:
+ "booking_details_page_contact_button_subtitle".tr(),
+ onClick: () {
+ context.pushNamed(AppRoutes.contact.name,
+ extra: cubit.booking);
+ }),
+ ),
+ ),
+ ),
+ ]),
+ const SizedBox(height: 16),
+ if (!isActive && cubit.user != null)
+ ComwellClubContainer(
+ user: cubit.user!,
+ onSignupClick: () {
+ cubit.add(const InitialEvent(fetchRemote: true));
+ }),
+ const SizedBox(height: 16),
+ const SizedBox(height: 50),
+ ],
+ ),
);
}