6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit a1c7ab2d
Changed files
.../bloc/booking_details_state.freezed.dart | 316 ------------------ .../bloc/booking_details_state.freezed.dart | 316 ++++++++++++++++++ .../bloc/booking_details_cubit.dart | 279 ---------------- .../bloc/booking_details_state.dart | 25 -- .../lib/booking_details/booking_details_page.dart | 261 --------------- .../booking_details_repository.dart | 67 ---- .../lib/booking_details/booking_details_route.dart | 38 --- .../components/booking_details_bottom_sheet.dart | 221 ------------- .../components/check_in_button.dart | 100 ------ .../components/check_in_button_timer.dart | 148 --------- .../components/check_out_button.dart | 78 ----- .../components/concierge_button.dart | 82 ----- .../components/get_keys_button.dart | 75 ----- .../lib/booking_details/components/guest_list.dart | 131 -------- .../components/housekeeping_button.dart | 93 ------ .../components/practical_information_button.dart | 75 ----- .../components/preregister_button.dart | 59 ---- .../components/remove_guest_dialog.dart | 93 ------ .../components/room_key_widget.dart | 70 ---- .../components/room_number_container.dart | 34 -- .../booking_details/components/share_button.dart | 368 --------------------- .../components/unlock_room_button.dart | 44 --- .../lib/check_in/check_in_repository.dart | 3 +- .../lib/check_out/bloc/check_out_cubit.dart | 2 +- comwell_key_app/lib/comwell_app.dart | 2 +- .../repositories/booking_details_repository.dart | 67 ++++ comwell_key_app/lib/key/bloc/key_bloc.dart | 3 +- .../lib/my_booking/my_booking_page.dart | 6 +- .../components/current_booking_list_item_view.dart | 2 +- .../bloc/booking_details_cubit.dart | 279 ++++++++++++++++ .../bloc/booking_details_state.dart | 25 ++ .../booking_details/booking_details_page.dart | 262 +++++++++++++++ .../booking_details/booking_details_route.dart | 38 +++ .../components/booking_details_bottom_sheet.dart | 222 +++++++++++++ .../components/check_in_button.dart | 100 ++++++ .../components/check_in_button_timer.dart | 148 +++++++++ .../components/check_out_button.dart | 78 +++++ .../components/concierge_button.dart | 82 +++++ .../components/get_keys_button.dart | 75 +++++ .../booking_details/components/guest_list.dart | 131 ++++++++ .../components/housekeeping_button.dart | 93 ++++++ .../components/practical_information_button.dart | 75 +++++ .../components/preregister_button.dart | 59 ++++ .../components/remove_guest_dialog.dart | 93 ++++++ .../components/room_key_widget.dart | 70 ++++ .../components/room_number_container.dart | 34 ++ .../booking_details/components/share_button.dart | 368 +++++++++++++++++++++ .../components/unlock_room_button.dart | 44 +++ .../bloc/received_shared_booking_cubit.dart | 2 +- comwell_key_app/lib/routing/app_router.dart | 2 +- .../components/catalog/service_catalog.dart | 2 +- comwell_key_app/lib/utils/locator.dart | 2 +- comwell_key_app/test/key_test/key_bloc_test.dart | 2 +- 53 files changed, 2673 insertions(+), 2671 deletions(-)
Diff
diff --git a/comwell_key_app/lib/.generated/booking_details/bloc/booking_details_state.freezed.dart b/comwell_key_app/lib/.generated/booking_details/bloc/booking_details_state.freezed.dart
deleted file mode 100644
index f16f0ff9..00000000
--- a/comwell_key_app/lib/.generated/booking_details/bloc/booking_details_state.freezed.dart
+++ /dev/null
@@ -1,316 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-// coverage:ignore-file
-// ignore_for_file: type=lint
-// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
-
-part of '../../../booking_details/bloc/booking_details_state.dart';
-
-// **************************************************************************
-// FreezedGenerator
-// **************************************************************************
-
-// dart format off
-T _$identity<T>(T value) => value;
-/// @nodoc
-mixin _$BookingDetailsState {
-
- bool get isHouseKeepingOrdered; MobileKeysKey? get key; List<MobileKeysKey> get keys; List<Guest> get guests; Duration get remainingTime; AppError get error; bool get isLoading; bool get isLoadingKeys; UpSales? get upSales; List<Upgrade> get selectedUpSales;
-/// Create a copy of BookingDetailsState
-/// with the given fields replaced by the non-null parameter values.
-@JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-$BookingDetailsStateCopyWith<BookingDetailsState> get copyWith => _$BookingDetailsStateCopyWithImpl<BookingDetailsState>(this as BookingDetailsState, _$identity);
-
-
-
-@override
-bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is BookingDetailsState&&(identical(other.isHouseKeepingOrdered, isHouseKeepingOrdered) || other.isHouseKeepingOrdered == isHouseKeepingOrdered)&&(identical(other.key, key) || other.key == key)&&const DeepCollectionEquality().equals(other.keys, keys)&&const DeepCollectionEquality().equals(other.guests, guests)&&(identical(other.remainingTime, remainingTime) || other.remainingTime == remainingTime)&&(identical(other.error, error) || other.error == error)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingKeys, isLoadingKeys) || other.isLoadingKeys == isLoadingKeys)&&(identical(other.upSales, upSales) || other.upSales == upSales)&&const DeepCollectionEquality().equals(other.selectedUpSales, selectedUpSales));
-}
-
-
-@override
-int get hashCode => Object.hash(runtimeType,isHouseKeepingOrdered,key,const DeepCollectionEquality().hash(keys),const DeepCollectionEquality().hash(guests),remainingTime,error,isLoading,isLoadingKeys,upSales,const DeepCollectionEquality().hash(selectedUpSales));
-
-@override
-String toString() {
- return 'BookingDetailsState(isHouseKeepingOrdered: $isHouseKeepingOrdered, key: $key, keys: $keys, guests: $guests, remainingTime: $remainingTime, error: $error, isLoading: $isLoading, isLoadingKeys: $isLoadingKeys, upSales: $upSales, selectedUpSales: $selectedUpSales)';
-}
-
-
-}
-
-/// @nodoc
-abstract mixin class $BookingDetailsStateCopyWith<$Res> {
- factory $BookingDetailsStateCopyWith(BookingDetailsState value, $Res Function(BookingDetailsState) _then) = _$BookingDetailsStateCopyWithImpl;
-@useResult
-$Res call({
- bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales
-});
-
-
-
-
-}
-/// @nodoc
-class _$BookingDetailsStateCopyWithImpl<$Res>
- implements $BookingDetailsStateCopyWith<$Res> {
- _$BookingDetailsStateCopyWithImpl(this._self, this._then);
-
- final BookingDetailsState _self;
- final $Res Function(BookingDetailsState) _then;
-
-/// Create a copy of BookingDetailsState
-/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? isHouseKeepingOrdered = null,Object? key = freezed,Object? keys = null,Object? guests = null,Object? remainingTime = null,Object? error = null,Object? isLoading = null,Object? isLoadingKeys = null,Object? upSales = freezed,Object? selectedUpSales = null,}) {
- return _then(_self.copyWith(
-isHouseKeepingOrdered: null == isHouseKeepingOrdered ? _self.isHouseKeepingOrdered : isHouseKeepingOrdered // ignore: cast_nullable_to_non_nullable
-as bool,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
-as MobileKeysKey?,keys: null == keys ? _self.keys : keys // ignore: cast_nullable_to_non_nullable
-as List<MobileKeysKey>,guests: null == guests ? _self.guests : guests // ignore: cast_nullable_to_non_nullable
-as List<Guest>,remainingTime: null == remainingTime ? _self.remainingTime : remainingTime // ignore: cast_nullable_to_non_nullable
-as Duration,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
-as AppError,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
-as bool,isLoadingKeys: null == isLoadingKeys ? _self.isLoadingKeys : isLoadingKeys // ignore: cast_nullable_to_non_nullable
-as bool,upSales: freezed == upSales ? _self.upSales : upSales // ignore: cast_nullable_to_non_nullable
-as UpSales?,selectedUpSales: null == selectedUpSales ? _self.selectedUpSales : selectedUpSales // ignore: cast_nullable_to_non_nullable
-as List<Upgrade>,
- ));
-}
-
-}
-
-
-/// Adds pattern-matching-related methods to [BookingDetailsState].
-extension BookingDetailsStatePatterns on BookingDetailsState {
-/// A variant of `map` that fallback to returning `orElse`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case _:
-/// return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BookingDetailsState value)? $default,{required TResult orElse(),}){
-final _that = this;
-switch (_that) {
-case _BookingDetailsState() when $default != null:
-return $default(_that);case _:
- return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// Callbacks receives the raw object, upcasted.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case final Subclass2 value:
-/// return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BookingDetailsState value) $default,){
-final _that = this;
-switch (_that) {
-case _BookingDetailsState():
-return $default(_that);case _:
- throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `map` that fallback to returning `null`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case final Subclass value:
-/// return ...;
-/// case _:
-/// return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BookingDetailsState value)? $default,){
-final _that = this;
-switch (_that) {
-case _BookingDetailsState() when $default != null:
-return $default(_that);case _:
- return null;
-
-}
-}
-/// A variant of `when` that fallback to an `orElse` callback.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case _:
-/// return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales)? $default,{required TResult orElse(),}) {final _that = this;
-switch (_that) {
-case _BookingDetailsState() when $default != null:
-return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
- return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// As opposed to `map`, this offers destructuring.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case Subclass2(:final field2):
-/// return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales) $default,) {final _that = this;
-switch (_that) {
-case _BookingDetailsState():
-return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
- throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `when` that fallback to returning `null`
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-/// case Subclass(:final field):
-/// return ...;
-/// case _:
-/// return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales)? $default,) {final _that = this;
-switch (_that) {
-case _BookingDetailsState() when $default != null:
-return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
- return null;
-
-}
-}
-
-}
-
-/// @nodoc
-
-
-class _BookingDetailsState implements BookingDetailsState {
- const _BookingDetailsState({this.isHouseKeepingOrdered = false, this.key = null, final List<MobileKeysKey> keys = const [], final List<Guest> guests = const [], this.remainingTime = Duration.zero, this.error = AppError.none, this.isLoading = false, this.isLoadingKeys = false, this.upSales = null, final List<Upgrade> selectedUpSales = const []}): _keys = keys,_guests = guests,_selectedUpSales = selectedUpSales;
-
-
-@override@JsonKey() final bool isHouseKeepingOrdered;
-@override@JsonKey() final MobileKeysKey? key;
- final List<MobileKeysKey> _keys;
-@override@JsonKey() List<MobileKeysKey> get keys {
- if (_keys is EqualUnmodifiableListView) return _keys;
- // ignore: implicit_dynamic_type
- return EqualUnmodifiableListView(_keys);
-}
-
- final List<Guest> _guests;
-@override@JsonKey() List<Guest> get guests {
- if (_guests is EqualUnmodifiableListView) return _guests;
- // ignore: implicit_dynamic_type
- return EqualUnmodifiableListView(_guests);
-}
-
-@override@JsonKey() final Duration remainingTime;
-@override@JsonKey() final AppError error;
-@override@JsonKey() final bool isLoading;
-@override@JsonKey() final bool isLoadingKeys;
-@override@JsonKey() final UpSales? upSales;
- final List<Upgrade> _selectedUpSales;
-@override@JsonKey() List<Upgrade> get selectedUpSales {
- if (_selectedUpSales is EqualUnmodifiableListView) return _selectedUpSales;
- // ignore: implicit_dynamic_type
- return EqualUnmodifiableListView(_selectedUpSales);
-}
-
-
-/// Create a copy of BookingDetailsState
-/// with the given fields replaced by the non-null parameter values.
-@override @JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-_$BookingDetailsStateCopyWith<_BookingDetailsState> get copyWith => __$BookingDetailsStateCopyWithImpl<_BookingDetailsState>(this, _$identity);
-
-
-
-@override
-bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookingDetailsState&&(identical(other.isHouseKeepingOrdered, isHouseKeepingOrdered) || other.isHouseKeepingOrdered == isHouseKeepingOrdered)&&(identical(other.key, key) || other.key == key)&&const DeepCollectionEquality().equals(other._keys, _keys)&&const DeepCollectionEquality().equals(other._guests, _guests)&&(identical(other.remainingTime, remainingTime) || other.remainingTime == remainingTime)&&(identical(other.error, error) || other.error == error)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingKeys, isLoadingKeys) || other.isLoadingKeys == isLoadingKeys)&&(identical(other.upSales, upSales) || other.upSales == upSales)&&const DeepCollectionEquality().equals(other._selectedUpSales, _selectedUpSales));
-}
-
-
-@override
-int get hashCode => Object.hash(runtimeType,isHouseKeepingOrdered,key,const DeepCollectionEquality().hash(_keys),const DeepCollectionEquality().hash(_guests),remainingTime,error,isLoading,isLoadingKeys,upSales,const DeepCollectionEquality().hash(_selectedUpSales));
-
-@override
-String toString() {
- return 'BookingDetailsState(isHouseKeepingOrdered: $isHouseKeepingOrdered, key: $key, keys: $keys, guests: $guests, remainingTime: $remainingTime, error: $error, isLoading: $isLoading, isLoadingKeys: $isLoadingKeys, upSales: $upSales, selectedUpSales: $selectedUpSales)';
-}
-
-
-}
-
-/// @nodoc
-abstract mixin class _$BookingDetailsStateCopyWith<$Res> implements $BookingDetailsStateCopyWith<$Res> {
- factory _$BookingDetailsStateCopyWith(_BookingDetailsState value, $Res Function(_BookingDetailsState) _then) = __$BookingDetailsStateCopyWithImpl;
-@override @useResult
-$Res call({
- bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales
-});
-
-
-
-
-}
-/// @nodoc
-class __$BookingDetailsStateCopyWithImpl<$Res>
- implements _$BookingDetailsStateCopyWith<$Res> {
- __$BookingDetailsStateCopyWithImpl(this._self, this._then);
-
- final _BookingDetailsState _self;
- final $Res Function(_BookingDetailsState) _then;
-
-/// Create a copy of BookingDetailsState
-/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? isHouseKeepingOrdered = null,Object? key = freezed,Object? keys = null,Object? guests = null,Object? remainingTime = null,Object? error = null,Object? isLoading = null,Object? isLoadingKeys = null,Object? upSales = freezed,Object? selectedUpSales = null,}) {
- return _then(_BookingDetailsState(
-isHouseKeepingOrdered: null == isHouseKeepingOrdered ? _self.isHouseKeepingOrdered : isHouseKeepingOrdered // ignore: cast_nullable_to_non_nullable
-as bool,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
-as MobileKeysKey?,keys: null == keys ? _self._keys : keys // ignore: cast_nullable_to_non_nullable
-as List<MobileKeysKey>,guests: null == guests ? _self._guests : guests // ignore: cast_nullable_to_non_nullable
-as List<Guest>,remainingTime: null == remainingTime ? _self.remainingTime : remainingTime // ignore: cast_nullable_to_non_nullable
-as Duration,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
-as AppError,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
-as bool,isLoadingKeys: null == isLoadingKeys ? _self.isLoadingKeys : isLoadingKeys // ignore: cast_nullable_to_non_nullable
-as bool,upSales: freezed == upSales ? _self.upSales : upSales // ignore: cast_nullable_to_non_nullable
-as UpSales?,selectedUpSales: null == selectedUpSales ? _self._selectedUpSales : selectedUpSales // ignore: cast_nullable_to_non_nullable
-as List<Upgrade>,
- ));
-}
-
-
-}
-
-// dart format on
diff --git a/comwell_key_app/lib/.generated/presentation/screens/booking_details/bloc/booking_details_state.freezed.dart b/comwell_key_app/lib/.generated/presentation/screens/booking_details/bloc/booking_details_state.freezed.dart
new file mode 100644
index 00000000..f1555556
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/screens/booking_details/bloc/booking_details_state.freezed.dart
@@ -0,0 +1,316 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of '../../../../../presentation/screens/booking_details/bloc/booking_details_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+/// @nodoc
+mixin _$BookingDetailsState {
+
+ bool get isHouseKeepingOrdered; MobileKeysKey? get key; List<MobileKeysKey> get keys; List<Guest> get guests; Duration get remainingTime; AppError get error; bool get isLoading; bool get isLoadingKeys; UpSales? get upSales; List<Upgrade> get selectedUpSales;
+/// Create a copy of BookingDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$BookingDetailsStateCopyWith<BookingDetailsState> get copyWith => _$BookingDetailsStateCopyWithImpl<BookingDetailsState>(this as BookingDetailsState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is BookingDetailsState&&(identical(other.isHouseKeepingOrdered, isHouseKeepingOrdered) || other.isHouseKeepingOrdered == isHouseKeepingOrdered)&&(identical(other.key, key) || other.key == key)&&const DeepCollectionEquality().equals(other.keys, keys)&&const DeepCollectionEquality().equals(other.guests, guests)&&(identical(other.remainingTime, remainingTime) || other.remainingTime == remainingTime)&&(identical(other.error, error) || other.error == error)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingKeys, isLoadingKeys) || other.isLoadingKeys == isLoadingKeys)&&(identical(other.upSales, upSales) || other.upSales == upSales)&&const DeepCollectionEquality().equals(other.selectedUpSales, selectedUpSales));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isHouseKeepingOrdered,key,const DeepCollectionEquality().hash(keys),const DeepCollectionEquality().hash(guests),remainingTime,error,isLoading,isLoadingKeys,upSales,const DeepCollectionEquality().hash(selectedUpSales));
+
+@override
+String toString() {
+ return 'BookingDetailsState(isHouseKeepingOrdered: $isHouseKeepingOrdered, key: $key, keys: $keys, guests: $guests, remainingTime: $remainingTime, error: $error, isLoading: $isLoading, isLoadingKeys: $isLoadingKeys, upSales: $upSales, selectedUpSales: $selectedUpSales)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $BookingDetailsStateCopyWith<$Res> {
+ factory $BookingDetailsStateCopyWith(BookingDetailsState value, $Res Function(BookingDetailsState) _then) = _$BookingDetailsStateCopyWithImpl;
+@useResult
+$Res call({
+ bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales
+});
+
+
+
+
+}
+/// @nodoc
+class _$BookingDetailsStateCopyWithImpl<$Res>
+ implements $BookingDetailsStateCopyWith<$Res> {
+ _$BookingDetailsStateCopyWithImpl(this._self, this._then);
+
+ final BookingDetailsState _self;
+ final $Res Function(BookingDetailsState) _then;
+
+/// Create a copy of BookingDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? isHouseKeepingOrdered = null,Object? key = freezed,Object? keys = null,Object? guests = null,Object? remainingTime = null,Object? error = null,Object? isLoading = null,Object? isLoadingKeys = null,Object? upSales = freezed,Object? selectedUpSales = null,}) {
+ return _then(_self.copyWith(
+isHouseKeepingOrdered: null == isHouseKeepingOrdered ? _self.isHouseKeepingOrdered : isHouseKeepingOrdered // ignore: cast_nullable_to_non_nullable
+as bool,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
+as MobileKeysKey?,keys: null == keys ? _self.keys : keys // ignore: cast_nullable_to_non_nullable
+as List<MobileKeysKey>,guests: null == guests ? _self.guests : guests // ignore: cast_nullable_to_non_nullable
+as List<Guest>,remainingTime: null == remainingTime ? _self.remainingTime : remainingTime // ignore: cast_nullable_to_non_nullable
+as Duration,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,isLoadingKeys: null == isLoadingKeys ? _self.isLoadingKeys : isLoadingKeys // ignore: cast_nullable_to_non_nullable
+as bool,upSales: freezed == upSales ? _self.upSales : upSales // ignore: cast_nullable_to_non_nullable
+as UpSales?,selectedUpSales: null == selectedUpSales ? _self.selectedUpSales : selectedUpSales // ignore: cast_nullable_to_non_nullable
+as List<Upgrade>,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [BookingDetailsState].
+extension BookingDetailsStatePatterns on BookingDetailsState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BookingDetailsState value)? $default,{required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _BookingDetailsState() when $default != null:
+return $default(_that);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BookingDetailsState value) $default,){
+final _that = this;
+switch (_that) {
+case _BookingDetailsState():
+return $default(_that);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BookingDetailsState value)? $default,){
+final _that = this;
+switch (_that) {
+case _BookingDetailsState() when $default != null:
+return $default(_that);case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales)? $default,{required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _BookingDetailsState() when $default != null:
+return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales) $default,) {final _that = this;
+switch (_that) {
+case _BookingDetailsState():
+return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales)? $default,) {final _that = this;
+switch (_that) {
+case _BookingDetailsState() when $default != null:
+return $default(_that.isHouseKeepingOrdered,_that.key,_that.keys,_that.guests,_that.remainingTime,_that.error,_that.isLoading,_that.isLoadingKeys,_that.upSales,_that.selectedUpSales);case _:
+ return null;
+
+}
+}
+
+}
+
+/// @nodoc
+
+
+class _BookingDetailsState implements BookingDetailsState {
+ const _BookingDetailsState({this.isHouseKeepingOrdered = false, this.key = null, final List<MobileKeysKey> keys = const [], final List<Guest> guests = const [], this.remainingTime = Duration.zero, this.error = AppError.none, this.isLoading = false, this.isLoadingKeys = false, this.upSales = null, final List<Upgrade> selectedUpSales = const []}): _keys = keys,_guests = guests,_selectedUpSales = selectedUpSales;
+
+
+@override@JsonKey() final bool isHouseKeepingOrdered;
+@override@JsonKey() final MobileKeysKey? key;
+ final List<MobileKeysKey> _keys;
+@override@JsonKey() List<MobileKeysKey> get keys {
+ if (_keys is EqualUnmodifiableListView) return _keys;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_keys);
+}
+
+ final List<Guest> _guests;
+@override@JsonKey() List<Guest> get guests {
+ if (_guests is EqualUnmodifiableListView) return _guests;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_guests);
+}
+
+@override@JsonKey() final Duration remainingTime;
+@override@JsonKey() final AppError error;
+@override@JsonKey() final bool isLoading;
+@override@JsonKey() final bool isLoadingKeys;
+@override@JsonKey() final UpSales? upSales;
+ final List<Upgrade> _selectedUpSales;
+@override@JsonKey() List<Upgrade> get selectedUpSales {
+ if (_selectedUpSales is EqualUnmodifiableListView) return _selectedUpSales;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_selectedUpSales);
+}
+
+
+/// Create a copy of BookingDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$BookingDetailsStateCopyWith<_BookingDetailsState> get copyWith => __$BookingDetailsStateCopyWithImpl<_BookingDetailsState>(this, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookingDetailsState&&(identical(other.isHouseKeepingOrdered, isHouseKeepingOrdered) || other.isHouseKeepingOrdered == isHouseKeepingOrdered)&&(identical(other.key, key) || other.key == key)&&const DeepCollectionEquality().equals(other._keys, _keys)&&const DeepCollectionEquality().equals(other._guests, _guests)&&(identical(other.remainingTime, remainingTime) || other.remainingTime == remainingTime)&&(identical(other.error, error) || other.error == error)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isLoadingKeys, isLoadingKeys) || other.isLoadingKeys == isLoadingKeys)&&(identical(other.upSales, upSales) || other.upSales == upSales)&&const DeepCollectionEquality().equals(other._selectedUpSales, _selectedUpSales));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isHouseKeepingOrdered,key,const DeepCollectionEquality().hash(_keys),const DeepCollectionEquality().hash(_guests),remainingTime,error,isLoading,isLoadingKeys,upSales,const DeepCollectionEquality().hash(_selectedUpSales));
+
+@override
+String toString() {
+ return 'BookingDetailsState(isHouseKeepingOrdered: $isHouseKeepingOrdered, key: $key, keys: $keys, guests: $guests, remainingTime: $remainingTime, error: $error, isLoading: $isLoading, isLoadingKeys: $isLoadingKeys, upSales: $upSales, selectedUpSales: $selectedUpSales)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$BookingDetailsStateCopyWith<$Res> implements $BookingDetailsStateCopyWith<$Res> {
+ factory _$BookingDetailsStateCopyWith(_BookingDetailsState value, $Res Function(_BookingDetailsState) _then) = __$BookingDetailsStateCopyWithImpl;
+@override @useResult
+$Res call({
+ bool isHouseKeepingOrdered, MobileKeysKey? key, List<MobileKeysKey> keys, List<Guest> guests, Duration remainingTime, AppError error, bool isLoading, bool isLoadingKeys, UpSales? upSales, List<Upgrade> selectedUpSales
+});
+
+
+
+
+}
+/// @nodoc
+class __$BookingDetailsStateCopyWithImpl<$Res>
+ implements _$BookingDetailsStateCopyWith<$Res> {
+ __$BookingDetailsStateCopyWithImpl(this._self, this._then);
+
+ final _BookingDetailsState _self;
+ final $Res Function(_BookingDetailsState) _then;
+
+/// Create a copy of BookingDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? isHouseKeepingOrdered = null,Object? key = freezed,Object? keys = null,Object? guests = null,Object? remainingTime = null,Object? error = null,Object? isLoading = null,Object? isLoadingKeys = null,Object? upSales = freezed,Object? selectedUpSales = null,}) {
+ return _then(_BookingDetailsState(
+isHouseKeepingOrdered: null == isHouseKeepingOrdered ? _self.isHouseKeepingOrdered : isHouseKeepingOrdered // ignore: cast_nullable_to_non_nullable
+as bool,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
+as MobileKeysKey?,keys: null == keys ? _self._keys : keys // ignore: cast_nullable_to_non_nullable
+as List<MobileKeysKey>,guests: null == guests ? _self._guests : guests // ignore: cast_nullable_to_non_nullable
+as List<Guest>,remainingTime: null == remainingTime ? _self.remainingTime : remainingTime // ignore: cast_nullable_to_non_nullable
+as Duration,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,isLoadingKeys: null == isLoadingKeys ? _self.isLoadingKeys : isLoadingKeys // ignore: cast_nullable_to_non_nullable
+as bool,upSales: freezed == upSales ? _self.upSales : upSales // ignore: cast_nullable_to_non_nullable
+as UpSales?,selectedUpSales: null == selectedUpSales ? _self._selectedUpSales : selectedUpSales // ignore: cast_nullable_to_non_nullable
+as List<Upgrade>,
+ ));
+}
+
+
+}
+
+// dart format on
diff --git a/comwell_key_app/lib/booking_details/bloc/booking_details_cubit.dart b/comwell_key_app/lib/booking_details/bloc/booking_details_cubit.dart
deleted file mode 100644
index 042b15f5..00000000
--- a/comwell_key_app/lib/booking_details/bloc/booking_details_cubit.dart
+++ /dev/null
@@ -1,279 +0,0 @@
-import 'dart:async';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_state.dart';
-import 'package:comwell_key_app/booking_details/booking_details_repository.dart';
-import 'package:comwell_key_app/domain/models/app_error.dart';
-import 'package:comwell_key_app/housekeeping/components/housekeeping_service.dart';
-import 'package:comwell_key_app/housekeeping/housekeeping_repository.dart';
-import 'package:comwell_key_app/housekeeping/models/housekeeping.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-import 'package:comwell_key_app/presentation/base/base_cubit.dart';
-import 'package:comwell_key_app/profile/profile_repository.dart';
-import 'package:comwell_key_app/profile_settings/model/user.dart';
-import 'package:comwell_key_app/share/share_booking_repository.dart';
-import 'package:comwell_key_app/up_sales/up_sales_repository.dart';
-import 'package:comwell_key_app/utils/seos_repository.dart';
-import 'package:flutter/foundation.dart';
-
-class BookingDetailsCubit extends BaseCubit<BookingDetailsState> {
- late Booking booking;
- User? user;
- Timer? _timer;
- final BookingDetailsRepository _bookingDetailsRepository;
- final ProfileRepository _profileRepository;
- final SeosRepository _seosRepository;
- final UpSalesRepository _upSaleRepository;
- final HouseKeepingRepository _houseKeepingRepository;
- final ShareBookingRepository _shareBookingRepository;
- Duration _remainingTime = Duration.zero;
-
- BookingDetailsCubit(
- this._bookingDetailsRepository,
- this._profileRepository,
- this._seosRepository,
- this._upSaleRepository,
- this._shareBookingRepository,
- this._houseKeepingRepository, {
- required this.booking,
- }) : super(const BookingDetailsState()) {
- init();
- }
-
- Future<void> init() async {
- try {
- safeEmit(state.copyWith(isLoading: true));
- await getUser();
- _startTimer();
- await checkIfHouseKeepingOrdered();
- await checkMobileKeys();
- await getBookingDetails(
- booking.confirmationNumber,
- hotelCode: booking.hotelCode,
- fetchRemote: true,
- );
- await updateRemainingTime();
- } catch (e) {
- if (kDebugMode) print("err=$e");
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- } finally {
- safeEmit(state.copyWith(isLoading: false));
- }
- }
-
- Future<void> getUpSales({bool fetchRemote = false}) async {
- try {
- if (fetchRemote) {
- final response = await _upSaleRepository.getRemoteUpSales(
- booking.confirmationNumber,
- booking.hotelCode,
- );
- safeEmit(state.copyWith(upSales: response));
- } else {
- final response = await _upSaleRepository.getUpSales(
- booking.confirmationNumber,
- booking.hotelCode,
- );
- safeEmit(state.copyWith(upSales: response));
- }
- } catch (e) {
- if (kDebugMode) print("err=$e");
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- }
- }
-
- Future<Booking> getBookingDetails(
- String hmsConfirmationNumber, {
- bool fetchRemote = false,
- required String hotelCode,
- }) async {
- safeEmit(state.copyWith(isLoading: true));
- try {
- if (fetchRemote) {
- final bookingDetails = await _bookingDetailsRepository.getRemoteBookingDetails(
- hmsConfirmationNumber,
- hotelCode,
- );
- booking = bookingDetails;
- return booking;
- } else {
- user = await _profileRepository.fetchProfileSettings();
- final bookingDetails = await _bookingDetailsRepository.getBookingDetails(
- hmsConfirmationNumber,
- hotelCode,
- );
- booking = bookingDetails;
-
- return booking;
- }
- } catch (e) {
- if (kDebugMode) print("err=$e");
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- rethrow;
- }
- }
-
- Future<void> checkMobileKeys() async {
- try {
- final isKeyLoadedForBooking = state.keys.any((key) => key.label == booking.roomNumber);
- safeEmit(state.copyWith(isLoadingKeys: true));
- await checkIfSetup();
-
- final keys = await _seosRepository.refreshKeys();
- safeEmit(state.copyWith(keys: keys));
-
- if (keys.isEmpty || !isKeyLoadedForBooking) {
- await _seosRepository.provisionKey(bookingId: booking.id, hotelCode: booking.hotelCode);
- final keys = await _seosRepository.refreshKeys();
- safeEmit(state.copyWith(keys: keys));
- }
- } catch (e) {
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- } finally {
- safeEmit(state.copyWith(isLoadingKeys: false));
- }
- }
-
- Future<void> checkIfSetup() async {
- final isSetup = await _seosRepository.isEndpointSetup();
- if (!isSetup) await _seosRepository.startMobilePlugin();
- }
-
- Future<void> checkIfHouseKeepingOrdered() async {
- final isHouseKeepingOrdered = await _houseKeepingRepository.isHousesKeepingOrdered(
- booking.roomNumber,
- );
-
- if (isHouseKeepingOrdered) {
- emit(state.copyWith(isHouseKeepingOrdered: true));
- }
- }
-
- Future<void> preregisterEvent() async {
- try {
- safeEmit(state.copyWith(isLoading: true));
- await getBookingDetails(
- booking.confirmationNumber,
- fetchRemote: true,
- hotelCode: booking.hotelCode,
- );
- await getUpSales(fetchRemote: true);
- safeEmit(state.copyWith(isLoading: false));
- } catch (e) {
- if (kDebugMode) print("err=$e");
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- }
- }
-
- Future<void> checkInEvent() async {
- try {
- safeEmit(state.copyWith(isLoading: true));
- await getBookingDetails(
- booking.confirmationNumber,
- fetchRemote: true,
- hotelCode: booking.hotelCode,
- );
- await checkMobileKeys();
- await getUpSales(fetchRemote: true);
- safeEmit(state.copyWith(isLoading: false));
- } catch (e) {
- if (kDebugMode) print("err=$e");
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- }
- }
-
- Future<void> orderHouseKeeping(
- List<String> selectedServices,
- ) async {
- final housekeeping = Housekeeping.toJson(
- booking.hotelCode,
- booking.roomNumber,
- selectedServices
- .map((e) => HouseKeepingService.values.firstWhere((element) => element.name == e))
- .toList(),
- );
- await _houseKeepingRepository.saveHouseKeepingOrdered(housekeeping);
- safeEmit(state.copyWith(isHouseKeepingOrdered: true));
- }
-
- Future<void> updateBooking(Booking booking) async {
- try {
- //TODO: add this when we have the backend
- // await bookingDetailsRepository.updateBooking(event.booking);
- safeEmit(state.copyWith(guests: booking.guests));
- } catch (e) {
- safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
- }
- }
-
- void _startTimer() {
- _timer?.cancel();
- _timer = Timer.periodic(
- const Duration(seconds: 1),
- (_) => updateRemainingTime(),
- );
- }
-
- @override
- Future<void> close() {
- _timer?.cancel();
- return super.close();
- }
-
- Future<void> updateRemainingTime() async {
- final now = DateTime.now();
- _remainingTime = getCheckInTime().difference(now);
-
- safeEmit(state.copyWith(remainingTime: _remainingTime));
-
- if (_remainingTime.isNegative) {
- _timer?.cancel();
- safeEmit(state.copyWith(remainingTime: _remainingTime));
- }
- }
-
- String get trimmedBalance {
- final balance = booking.balance;
- if (balance == null) return '';
- if (balance == balance.toInt()) {
- return balance.toInt().toString();
- } else {
- return balance.toStringAsFixed(2);
- }
- }
-
- void bypassTimer() {
- _timer?.cancel();
- safeEmit(state.copyWith(remainingTime: const Duration(seconds: -1)));
- }
-
- DateTime getCheckInTime() => booking.startDate.add(const Duration(hours: 15));
- DateTime getCheckOutTime() => booking.endDate.add(const Duration(hours: 5));
-
- bool get canCheckOut {
- final now = DateTime.now();
- final today = DateTime(now.year, now.month, now.day);
- final checkInDate = DateTime(
- booking.startDate.year,
- booking.startDate.month,
- booking.startDate.day,
- );
- final checkOutDate = DateTime(booking.endDate.year, booking.endDate.month, booking.endDate.day);
-
- if (checkInDate == checkOutDate) {
- return false;
- }
-
- return checkOutDate.isBefore(today);
- }
-
- bool get canOrderHousekeeping {
- final now = DateTime.now();
- final tomorrow = DateTime(now.year, now.month, now.day + 1);
- final checkoutDay = DateTime(booking.endDate.year, booking.endDate.month, booking.endDate.day);
- if (checkoutDay == tomorrow) return false;
- return booking.endDate.difference(booking.startDate).inDays >= 2;
- }
-
- Future<void> getUser({bool fetchRemote = false}) async {
- user = await _profileRepository.fetchProfileSettings(fetchRemote: fetchRemote);
- }
-}
diff --git a/comwell_key_app/lib/booking_details/bloc/booking_details_state.dart b/comwell_key_app/lib/booking_details/bloc/booking_details_state.dart
deleted file mode 100644
index 9b982121..00000000
--- a/comwell_key_app/lib/booking_details/bloc/booking_details_state.dart
+++ /dev/null
@@ -1,25 +0,0 @@
-import 'package:comwell_key_app/domain/models/app_error.dart';
-import 'package:comwell_key_app/overview/models/guest.dart';
-import 'package:comwell_key_app/up_sales/models/up_sales.dart';
-import 'package:comwell_key_app/up_sales/models/upgrade.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:seos_mobile_keys_plugin/app_usage_api.dart';
-
-part '../../.generated/booking_details/bloc/booking_details_state.freezed.dart';
-
-@freezed
-abstract class BookingDetailsState with _$BookingDetailsState {
- const factory BookingDetailsState({
- @Default(false) bool isHouseKeepingOrdered,
- @Default(null) MobileKeysKey? key,
- @Default([]) List<MobileKeysKey> keys,
- @Default([]) List<Guest> guests,
- @Default(Duration.zero) Duration remainingTime,
- @Default(AppError.none) AppError error,
- @Default(false) bool isLoading,
- @Default(false) bool isLoadingKeys,
- @Default(null) UpSales? upSales,
- @Default([]) List<Upgrade> selectedUpSales,
- }) = _BookingDetailsState;
-}
-
diff --git a/comwell_key_app/lib/booking_details/booking_details_page.dart b/comwell_key_app/lib/booking_details/booking_details_page.dart
deleted file mode 100644
index f2b21896..00000000
--- a/comwell_key_app/lib/booking_details/booking_details_page.dart
+++ /dev/null
@@ -1,261 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_state.dart';
-import 'package:comwell_key_app/booking_details/components/booking_details_bottom_sheet.dart';
-import 'package:comwell_key_app/booking_details/components/check_in_button_timer.dart';
-import 'package:comwell_key_app/booking_details/components/get_keys_button.dart';
-import 'package:comwell_key_app/booking_details/components/preregister_button.dart';
-import 'package:comwell_key_app/booking_details/components/room_number_container.dart';
-import 'package:comwell_key_app/booking_details/components/share_button.dart';
-import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
-import 'package:comwell_key_app/common/components/shimmer_loader/booking_details_shimmer_loader.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-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';
-
-import '../.generated/assets/assets.gen.dart';
-import 'components/unlock_room_button.dart';
-
-class BookingDetailsPage extends StatelessWidget {
- const BookingDetailsPage({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocConsumer<BookingDetailsCubit, BookingDetailsState>(
- listener: (context, state) {},
- builder: (context, state) {
- final cubit = context.read<BookingDetailsCubit>();
-
- return Scaffold(
- extendBodyBehindAppBar: true,
- backgroundColor: colorBackground,
- appBar: const ComwellAppBar(),
- body: Builder(
- builder: (context) {
- if (state.isLoading) {
- return const Center(
- child: BookingDetailsShimmerLoader(),
- );
- } else {
- return _buildBookingDetailsPage(context, state, cubit);
- }
- },
- ),
- );
- },
- );
- }
-
- Widget _buildBookingDetailsPage(
- BuildContext context,
- BookingDetailsState state,
- BookingDetailsCubit cubit,
- ) {
- final theme = Theme.of(context);
- final screenHeight = MediaQuery.of(context).size.height;
-
- return Stack(
- children: [
- // Background image - fixed
- Container(
- decoration: BoxDecoration(
- image: DecorationImage(
- image: AssetImage(Assets.images.bookingBackground.path),
- fit: BoxFit.cover,
- ),
- ),
- ),
- // Gradient overlay - fixed
- Container(
- decoration: const BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [
- Colors.black26,
- Colors.black54,
- ],
- ),
- ),
- ),
- // 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),
- ),
- ],
- );
- }
-
- Widget _buildBookingDetailsInformation(
- BuildContext context,
- BookingDetailsState state,
- BookingDetailsCubit cubit,
- ThemeData theme,
- ) {
- return InkWell(
- onTap: () async {
- await context.push(AppRoutes.myBooking, extra: cubit.booking);
-
- },
- child: Container(
- width: double.infinity,
- height: 50,
- decoration: BoxDecoration(
- color: Colors.transparent,
- borderRadius: BorderRadius.circular(4),
- ),
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- cubit.booking.confirmationNumber,
- style: theme.textTheme.labelLarge?.copyWith(color: colorBackground),
- ),
- Text(
- cubit.booking.toRoomType(),
- style: theme.textTheme.headlineMedium?.copyWith(
- color: colorBackground,
- ),
- ),
- ],
- ),
- ],
- ),
- ],
- ),
- ),
- const Icon(
- Icons.chevron_right,
- color: colorBackground,
- size: 36,
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _ScrollableBookingContent extends StatefulWidget {
- final double screenHeight;
- final BookingDetailsCubit 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) {
- final keys = widget.state.keys;
- final isKeyLoadedForBooking = keys.any((key) => key.label == widget.cubit.booking.roomNumber);
- final isKeysEmpty = keys.isEmpty;
- final hasRoomNumber = widget.cubit.booking.roomNumber != '';
-
- 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 (!isKeysEmpty &&
- hasRoomNumber &&
- isKeyLoadedForBooking)
- 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/booking_details_repository.dart b/comwell_key_app/lib/booking_details/booking_details_repository.dart
deleted file mode 100644
index fba8efa1..00000000
--- a/comwell_key_app/lib/booking_details/booking_details_repository.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-import 'package:comwell_key_app/database/comwell_db.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-import 'package:comwell_key_app/services/api.dart';
-import 'package:comwell_key_app/services/mappers/booking_mapper.dart';
-import 'package:comwell_key_app/services/models/bookings_dto.dart';
-import 'package:comwell_key_app/utils/locator.dart';
-import 'package:comwell_key_app/utils/secure_storage.dart';
-import 'package:flutter/widgets.dart';
-
-class BookingDetailsRepository {
- final api = Api();
- final secureStorage = locator<SecureStorage>();
-
- Future<String?> doesInvitationCodeExist(String key) async {
- return await secureStorage.read(key);
- }
-
- Future<Booking> getRemoteBookingDetails(
- String hmsConfirmationNumber, String hotelCode) async {
- final response =
- await api.getBookingDetails(hmsConfirmationNumber, hotelCode);
- return response!.toBooking();
- }
-
- Future<Booking> getBookingDetails(
- String hmsConfirmationNumber, String hotelCode) async {
-
-
- final booking = await _checkIfBookingDetailsExists(hmsConfirmationNumber);
- if (booking != null) {
- return booking;
- }
-
- final newBooking = await _fetchAndSaveBookingDetailsToDatabase(
- hmsConfirmationNumber, hotelCode);
- return newBooking;
- }
-
- Future<Booking?> _checkIfBookingDetailsExists(
- String hmsConfirmationNumber) async {
- try {
- final booking = await locator<ComwellDatabase>()
- .bookingsDao
- .getBookingDetails(hmsConfirmationNumber);
- return booking;
- } catch (e) {
- debugPrint("Error checking if booking details exists: $e");
- return null;
- }
- }
-
- Future<Booking> _fetchAndSaveBookingDetailsToDatabase(
- String hmsConfirmationNumber, String hotelCode) async {
- try {
- final response =
- await api.getBookingDetails(hmsConfirmationNumber, hotelCode);
- await locator<ComwellDatabase>().bookingsDao.insertBookings(
- BookingsDTO(current: [response!], past: [], cancelled: []));
- final booking = response.toBooking();
- debugPrint("Booking saved to database: ${booking.confirmationNumber}");
- return booking;
- } on Exception catch (e) {
- debugPrint("Error fetching booking details: $e");
- rethrow;
- }
- }
-}
diff --git a/comwell_key_app/lib/booking_details/booking_details_route.dart b/comwell_key_app/lib/booking_details/booking_details_route.dart
deleted file mode 100644
index a7e5e441..00000000
--- a/comwell_key_app/lib/booking_details/booking_details_route.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/booking_details_page.dart';
-import 'package:comwell_key_app/find_booking/loading_page.dart';
-import 'package:comwell_key_app/overview/cubit/overview_cubit.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
-import 'package:comwell_key_app/utils/locator.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-
-final bookingDetailsRoute = GoRoute(
- path: AppRoutes.bookingDetails,
- builder: (context, state) {
- final booking = state.extra as Booking;
- return BlocProvider<BookingDetailsCubit>(
- create: (BuildContext context) => BookingDetailsCubit(
- booking: booking,
- locator(),
- locator(),
- locator(),
- locator(),
- locator(),
- locator(),
- ),
- child: const BookingDetailsPage(),
- );
- },
-);
-
-final bookingDetailsWithIdRoute = GoRoute(
- path: AppRoutes.bookingDetailsWithId,
- builder: (context, state) {
- final bookingId = state.pathParameters['id']!;
- context.read<OverviewCubit>().findBookingById(bookingId);
- return const LoadingPage();
- },
-);
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
deleted file mode 100644
index cefa1d8e..00000000
--- a/comwell_key_app/lib/booking_details/components/booking_details_bottom_sheet.dart
+++ /dev/null
@@ -1,221 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_state.dart';
-import 'package:comwell_key_app/booking_details/components/check_out_button.dart';
-import 'package:comwell_key_app/booking_details/components/concierge_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/comwell_error_widget.dart';
-import 'package:comwell_key_app/common/components/outlined_pill_button.dart';
-import 'package:comwell_key_app/domain/models/app_error.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:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
-
-class BookingDetailsBottomSheet extends StatelessWidget {
- final BookingDetailsCubit cubit;
- final BookingDetailsState state;
-
- const BookingDetailsBottomSheet({super.key, required this.cubit, required this.state});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final isActive = cubit.user?.isClubMember ?? false;
-
- 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(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.canOrderHousekeeping)
- 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(
- context.strings.booking_details_page_practical_information,
- 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: context.strings.booking_details_page_hotel_information_button_title,
- subtitle:
- context.strings.booking_details_page_hotel_information_button_subtitle,
- onClick: () {
- context.push(AppRoutes.hotelInformation, 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: context.strings.booking_details_page_contact_button_title,
- subtitle: context.strings.booking_details_page_contact_button_subtitle,
- onClick: () {
- context.push(AppRoutes.contact, extra: cubit.booking);
- },
- ),
- ),
- ),
- ),
- ],
- ),
- const SizedBox(height: 16),
- if (!isActive && cubit.user != null)
- ComwellClubContainer(
- user: cubit.user!,
- onSignupClick: () {
- cubit.init();
- },
- ),
- const SizedBox(height: 16),
- const SizedBox(height: 50),
- ],
- ),
- );
- }
-
- Widget _buildUpSalesCatalogButton(ReservationStatus reservationStatus, BuildContext context) {
- switch (reservationStatus) {
- case ReservationStatus.checkedin:
- return ConciergeButton(hotelCode: cubit.booking.hotelCode);
- case ReservationStatus.newreservation:
- return _buildServices(context);
- case ReservationStatus.preregistered:
- return const SizedBox();
- case ReservationStatus.cancelled:
- return const SizedBox();
- case ReservationStatus.checkedout:
- return const SizedBox();
- default:
- return const SizedBox();
- }
- }
-
- Widget _buildServices(BuildContext context) {
- final theme = Theme.of(context);
- final serviceUpgrades =
- cubit.state.upSales?.addOnUpgrades.where((upgrade) => upgrade.isService).toList() ?? [];
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- state.error != AppError.none
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text(context.strings.services, style: theme.textTheme.headlineMedium),
- ),
- const SizedBox(width: 8),
- ComwellErrorWidget(
- title: context.strings.up_sales_error_title,
- subtitle: context.strings.up_sales_error_subtitle,
- border: true,
- ),
- ],
- )
- : Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- if (state.upSales?.addOnUpgrades.isNotEmpty ?? false) ...[
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Text(context.strings.services, style: theme.textTheme.headlineMedium),
- ),
- const SizedBox(width: 8),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: OutlinedPillButton(
- text: context.strings.up_sales_see_all,
- onTap: () async {
- await context.push(
- AppRoutes.upSalesCatalog,
- extra: [cubit.booking],
- );
- cubit.init();
- },
- ),
- ),
- ] else
- const SizedBox(),
- ],
- ),
- const SizedBox(height: 16),
- if (serviceUpgrades.isNotEmpty)
- ServiceCatalog(
- bookingDetailsCubit: cubit,
- booking: cubit.booking,
- upSales: serviceUpgrades,
- height: 252,
- showRadioButton: false,
- isSinglePurchase: true,
- onTap: (upgrade) {
- // No OP
- },
- onServiceSelected: (upgrade, isSelected) {
- // No OP
- },
- ),
- ],
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/check_in_button.dart b/comwell_key_app/lib/booking_details/components/check_in_button.dart
deleted file mode 100644
index 24c11ae9..00000000
--- a/comwell_key_app/lib/booking_details/components/check_in_button.dart
+++ /dev/null
@@ -1,100 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:go_router/go_router.dart';
-import '../../routing/app_routes.dart';
-import '../../themes/dark_theme.dart';
-
-class CheckInButton extends StatelessWidget {
- const CheckInButton({super.key});
-
- @override
- Widget build(BuildContext context) {
- final cubit = context.read<BookingDetailsCubit>();
- return cubit.booking.digitalCard
- ? getDigitalCardWidget(context, cubit)
- : getPhysicalCardWidget(context);
- }
-
- Widget getDigitalCardWidget(BuildContext context, BookingDetailsCubit cubit) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 10),
- child: ElevatedButton(
- onPressed: () async {
- final (result) = await context.push(AppRoutes.checkIn, extra: [cubit.booking, false]);
- if (result == true && !cubit.isClosed) {
- cubit.checkInEvent();
- }
- },
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Row(
- children: [
- SvgPicture.asset("assets/icons/ic_unlocked.svg"),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.check_in,
- style: Theme.of(
- context,
- ).textTheme.titleMedium?.copyWith(color: colorBackground),
- ),
- Text(
- context.strings.overview_page_check_in_button_subtitle,
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: colorBackground),
- softWrap: true,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-
- Widget getPhysicalCardWidget(BuildContext context) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 10),
- child: Container(
- decoration: BoxDecoration(
- color: sandColor[80],
- borderRadius: BorderRadius.circular(30),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
- child: Row(
- children: [
- SvgPicture.asset("assets/icons/ic_unlocked.svg"),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.check_in,
- style: Theme.of(
- context,
- ).textTheme.titleMedium?.copyWith(color: colorBackground),
- ),
- Text(
- context.strings.overview_page_check_in_physical_card_subtitle,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(color: colorBackground),
- softWrap: true,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/check_in_button_timer.dart b/comwell_key_app/lib/booking_details/components/check_in_button_timer.dart
deleted file mode 100644
index 3bfcb9d7..00000000
--- a/comwell_key_app/lib/booking_details/components/check_in_button_timer.dart
+++ /dev/null
@@ -1,148 +0,0 @@
-import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_state.dart';
-import 'package:comwell_key_app/booking_details/components/check_in_button.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:comwell_key_app/utils/time_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-import 'package:intl/intl.dart';
-
-class CheckInButtonTimer extends StatefulWidget {
- const CheckInButtonTimer({
- super.key,
- });
-
- @override
- State<CheckInButtonTimer> createState() => _CheckInButtonTimerState();
-}
-
-class _CheckInButtonTimerState extends State<CheckInButtonTimer> {
- @override
- Widget build(BuildContext context) {
- return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
- builder: (context, state) {
- return AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- transitionBuilder: (Widget child, Animation<double> animation) {
- return FadeTransition(
- opacity: animation,
- child: child,
- );
- },
- child: state.remainingTime.isNegative
- ? const CheckInButton(key: ValueKey('check_in_button'))
- : getTimerWidget(),
- );
- },
- );
- }
-
- bool get _isDevOrStage {
- final flavor = appFlavor?.toLowerCase();
- return flavor == 'develop' || flavor == 'stage';
- }
-
- Widget getTimerWidget() {
- return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
- key: const ValueKey('timer_view'),
- builder: (context, state) {
- final cubit = context.read<BookingDetailsCubit>();
- final (days, hours, minutes, seconds) = getDurationInMinutes(state.remainingTime);
- final checkInTime = cubit.getCheckInTime();
- final dateStr = DateFormat('d. MMM', 'da').format(checkInTime);
-
- final theme = Theme.of(context);
- final timeStr = days > 0
- ? context.strings.check_in_button_timer_days_hours_minutes(
- days.toString(),
- hours.toString(),
- minutes.toString(),
- )
- : hours > 0
- ? context.strings.check_in_button_timer_hours_minutes(
- hours.toString(),
- minutes.toString(),
- )
- : minutes > 0
- ? context.strings.check_in_button_timer_minutes(minutes.toString())
- : context.strings.check_in_button_timer_seconds(seconds.toString());
-
- final timerContent = Container(
- margin: const EdgeInsets.symmetric(horizontal: 10),
- decoration: BoxDecoration(
- color: Colors.grey[900]?.withValues(alpha: 0.8),
- borderRadius: BorderRadius.circular(8),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
- child: Row(
- children: [
- SvgPicture.asset(
- Assets.icons.icLocked.path,
- colorFilter: const ColorFilter.mode(
- colorBackground,
- BlendMode.srcIn,
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'Check-in',
- style: theme.textTheme.titleMedium?.copyWith(
- color: colorBackground,
- fontWeight: FontWeight.w600,
- ),
- ),
- Text(
- timeStr,
- style: theme.textTheme.bodyMedium?.copyWith(
- color: colorBackground.withValues(alpha: 0.8),
- ),
- ),
- ],
- ),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Text(
- dateStr.toUpperCase(),
- style: theme.textTheme.bodySmall?.copyWith(
- color: colorBackground.withValues(alpha: 0.7),
- fontWeight: FontWeight.w600,
- ),
- ),
- Text(
- DateFormat('HH:mm').format(checkInTime),
- style: theme.textTheme.titleMedium?.copyWith(
- color: colorBackground,
- fontWeight: FontWeight.w600,
- ),
- ),
- ],
- ),
- ],
- ),
- );
-
- if (_isDevOrStage) {
- return GestureDetector(
- onTap: () {
- // Bypass timer in dev/stage - show check-in button
- cubit.bypassTimer();
- },
- child: timerContent,
- );
- }
-
- return timerContent;
- },
- );
- }
-}
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
deleted file mode 100644
index 2b154ada..00000000
--- a/comwell_key_app/lib/booking_details/components/check_out_button.dart
+++ /dev/null
@@ -1,78 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.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) {
- final cubit = context.read<BookingDetailsCubit>();
- return Material(
- color: Colors.transparent,
- shape: RoundedRectangleBorder(
- side: const BorderSide(
- color: colorDivider,
- ),
- borderRadius: BorderRadius.circular(10),
- ),
- child: InkWell(
- borderRadius: const BorderRadius.all(Radius.circular(15)),
- onTap: () async {
- await context.push(AppRoutes.checkOut,
- extra: cubit.booking);
- cubit.init();
- },
- 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(
- colorTertiary, BlendMode.srcATop),
- "assets/icons/ic_exit.svg"),
- ),
- ),
- const SizedBox(width: 12),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(context.strings.booking_details_page_checkout_title),
- Text(
- context.strings.booking_details_page_checkout_subtitle,
- style: Theme.of(context)
- .textTheme
- .bodySmall
- ?.copyWith(color: Colors.grey),
- ),
- ],
- ),
- const Expanded(child: SizedBox()),
- Text(context.strings.booking_details_page_checkout_time),
- SvgPicture.asset("assets/icons/arrow-left.svg",
- colorFilter: const ColorFilter.mode(
- colorTertiary,
- BlendMode.dst,
- ))
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/concierge_button.dart b/comwell_key_app/lib/booking_details/components/concierge_button.dart
deleted file mode 100644
index 22e45568..00000000
--- a/comwell_key_app/lib/booking_details/components/concierge_button.dart
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-import 'package:comwell_key_app/presentation/screens/concierge/concierge_route.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class ConciergeButton extends StatelessWidget {
- final String hotelCode;
- const ConciergeButton({super.key, required this.hotelCode});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
-
- return GestureDetector(
- onTap: () async {
- await ConciergeRoute(hotelCode: hotelCode).push<void>(context);
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
- child: Container(
- width: double.infinity,
- height: 211,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: colorDivider, width: 1),
- ),
- child: Stack(
- children: [
- ClipRRect(
- borderRadius: BorderRadius.circular(12),
- child: Image.asset(
- 'assets/images/catalog_image.png',
- width: double.infinity,
- height: double.infinity,
- fit: BoxFit.cover,
- ),
- ),
- Container(
- decoration: const BoxDecoration(
- borderRadius: BorderRadius.all(Radius.circular(12)),
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [
- Colors.black26,
- Colors.black54,
- ],
- ),
- ),
- ),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.end,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.up_sales_catalog_button_title,
- style: theme.textTheme.headlineMedium?.copyWith(
- color: Colors.white,
- ),
- ),
- Text(
- context.strings.up_sales_catalog_button_subtitle,
- style: theme.textTheme.bodySmall?.copyWith(
- color: Colors.white,
- ),
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/get_keys_button.dart b/comwell_key_app/lib/booking_details/components/get_keys_button.dart
deleted file mode 100644
index 02906c4a..00000000
--- a/comwell_key_app/lib/booking_details/components/get_keys_button.dart
+++ /dev/null
@@ -1,75 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_state.dart';
-import 'package:comwell_key_app/themes/dark_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_svg/svg.dart';
-
-class GetKeysButton extends StatelessWidget {
- const GetKeysButton({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
- builder: (context, state) {
- final cubit = context.read<BookingDetailsCubit>();
- final theme = Theme.of(context);
- if (state.isLoadingKeys) {
- return const Center(
- child: CircularProgressIndicator(
- color: sandColor,
- backgroundColor: colorBackground,
- strokeWidth: 2,
- ),
- );
- }
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 10),
- child: ElevatedButton(
- onPressed: () async {
- await cubit.checkMobileKeys();
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Row(
- children: [
- SvgPicture.asset(
- "assets/icons/Union.svg",
- colorFilter: const ColorFilter.mode(
- colorBackground,
- BlendMode.srcIn,
- ),
- width: 14,
- height: 14,
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.get_keys,
- style: Theme.of(
- context,
- ).textTheme.titleMedium?.copyWith(color: Colors.white),
- ),
- Text(
- context.strings.get_keys_subtitle,
- style: Theme.of(
- context,
- ).textTheme.bodySmall?.copyWith(color: Colors.white),
- softWrap: true,
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- },
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/guest_list.dart b/comwell_key_app/lib/booking_details/components/guest_list.dart
deleted file mode 100644
index 0a57f0b8..00000000
--- a/comwell_key_app/lib/booking_details/components/guest_list.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-import 'package:comwell_key_app/booking_details/components/remove_guest_dialog.dart';
-import 'package:comwell_key_app/overview/models/guest.dart';
-import 'package:comwell_key_app/themes/comwell_colors.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class GuestList extends StatelessWidget {
- final Iterable<Guest> guests;
- final Iterable<String> selectedGuests;
- final bool isOverview;
- final void Function(Iterable<String>) onGuestSelected;
- final void Function(int guestId) onGuestRemoved;
-
- const GuestList({
- super.key,
- required this.guests,
- required this.selectedGuests,
- required this.onGuestSelected,
- this.isOverview = false,
- required this.onGuestRemoved,
- });
-
- void handleCancelSharing(BuildContext context) {
- onGuestSelected([]);
- Navigator.pop(context);
- }
-
- @override
- Widget build(BuildContext context) {
- final guestNames =
- guests.map((guest) => "${guest.firstName} ${guest.lastName}").toList();
- return ListView.builder(
- shrinkWrap: true,
- itemCount: guestNames.length,
- itemBuilder: (context, index) {
- final guestName = guestNames.elementAt(index);
- final initials = guestName.split(' ').map((name) => name[0]).join('');
- final guestId = guests.elementAt(index).id;
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
- child: GestureDetector(
- onTap: () {
- final newSelectedGuests = List<String>.from(selectedGuests);
- if (selectedGuests.contains(guestName)) {
- newSelectedGuests.remove(guestName);
- } else {
- newSelectedGuests.add(guestName);
- }
- onGuestSelected(newSelectedGuests);
- },
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(
- color: colorDivider,
- width: 1.0,
- ),
- borderRadius: BorderRadius.circular(8.0),
- ),
- padding: const EdgeInsets.all(12.0),
- child: Row(
- children: [
- Container(
- width: 40,
- height: 40,
- decoration: const BoxDecoration(
- color: colorTertiary,
- shape: BoxShape.circle,
- ),
- child: Center(
- child: Text(
- initials,
- style: const TextStyle(
- color: colorBackground,
- fontWeight: FontWeight.w400,
- fontSize: 16,
- ),
- ),
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Text(
- guestName,
- style: Theme.of(context).textTheme.bodyLarge,
- ),
- ),
- !isOverview
- ? Checkbox(
- value: selectedGuests.contains(guestName),
- onChanged: (bool? value) {
- if (value != null) {
- final newSelectedGuests =
- List<String>.from(selectedGuests);
- if (value) {
- newSelectedGuests.add(guestName);
- } else {
- newSelectedGuests.remove(guestName);
- }
- onGuestSelected(newSelectedGuests);
- }
- },
- )
- : TextButton(
- onPressed: () async {
- final shouldRemove = await showDialog<bool>(
- context: context,
- builder: (context) => RemoveGuestDialog(
- selectedGuests: selectedGuests,
- onGuestSelected: onGuestSelected));
- if (shouldRemove == true && context.mounted) {
- onGuestRemoved(guestId);
- Navigator.pop(context);
- }
- },
- child: Text(context.strings.remove_guest_overview,
- style: Theme.of(context)
- .textTheme
- .bodyLarge
- ?.copyWith(
- color: colorError,
- fontWeight: FontWeight.w600)),
- ),
- ],
- ),
- ),
- ),
- );
- },
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/housekeeping_button.dart b/comwell_key_app/lib/booking_details/components/housekeeping_button.dart
deleted file mode 100644
index 6bc2a08f..00000000
--- a/comwell_key_app/lib/booking_details/components/housekeeping_button.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:go_router/go_router.dart';
-
-import '../../routing/app_routes.dart';
-
-class HousekeepingButton extends StatelessWidget {
- final Booking booking;
-
- const HousekeepingButton({super.key, required this.booking});
-
- @override
- Widget build(BuildContext context) {
- final cubit = context.read<BookingDetailsCubit>();
-
- return Material(
- color: Colors.transparent,
- shape: RoundedRectangleBorder(
- side: const BorderSide(
- color: colorDivider,
- ),
- borderRadius: BorderRadius.circular(10),
- ),
- child: InkWell(
- borderRadius: BorderRadius.circular(10),
- onTap: () async {
- if (cubit.state.isHouseKeepingOrdered) return;
-
- final result = await context.push(AppRoutes.houseKeeping, extra: booking);
- if (result != null && !cubit.isClosed) {
- cubit.orderHouseKeeping(result as List<String>);
- }
- if (!cubit.isClosed) cubit.checkIfHouseKeepingOrdered();
- },
- child: Padding(
- padding: const EdgeInsets.all(16.0),
- child: Row(
- 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(Assets.icons.iconHousekeepingCleaning.path),
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- cubit.state.isHouseKeepingOrdered == true
- ? context.strings.booking_details_page_housekeeping_button_title_ordered
- : context.strings.booking_details_page_housekeeping_button_title,
- style: Theme.of(context).textTheme.headlineSmall,
- maxLines: 1,
- ),
- Text(
- cubit.state.isHouseKeepingOrdered == true
- ? context
- .strings
- .booking_details_page_housekeeping_button_subtitle_ordered
- : context.strings.booking_details_page_housekeeping_button_subtitle,
- maxLines: 1,
- softWrap: true,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey),
- ),
- ],
- ),
- ),
- const SizedBox(width: 12),
- if (cubit.state.isHouseKeepingOrdered == true)
- SvgPicture.asset(Assets.icons.icCheckmark.path)
- else
- SvgPicture.asset(Assets.icons.arrowLeft.path),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/practical_information_button.dart b/comwell_key_app/lib/booking_details/components/practical_information_button.dart
deleted file mode 100644
index 3d30574f..00000000
--- a/comwell_key_app/lib/booking_details/components/practical_information_button.dart
+++ /dev/null
@@ -1,75 +0,0 @@
-import 'package:comwell_key_app/themes/dark_theme.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-
-class PracticalInformationButton extends StatelessWidget {
- final String iconPath;
- final String title;
- final String subtitle;
- final Function onClick;
-
- const PracticalInformationButton({
- super.key,
- required this.iconPath,
- required this.title,
- required this.subtitle,
- required this.onClick,
- });
-
- @override
- Widget build(BuildContext context) {
-
- return Material(
- color: sandColor[10],
- borderRadius: const BorderRadius.all(Radius.circular(16)),
- child: InkWell(
- splashColor: sandColor,
- borderRadius: const BorderRadius.all(Radius.circular(16)),
- onTap: () => onClick(),
- child: Padding(
- padding: const EdgeInsets.all(12),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: sandColor[40],
- ),
- child: Padding(
- padding: const EdgeInsets.all(12.0),
- child: SvgPicture.asset(iconPath,
- width: 24,
- height: 24,
- colorFilter: const ColorFilter.mode(
- colorTertiary,
- BlendMode.dst,
- )),
- ),
- ),
- const SizedBox(height: 16),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(title),
- SizedBox(
- height: 60,
- child: Text(
- subtitle,
- style: Theme.of(context)
- .textTheme
- .bodySmall
- ?.copyWith(color: Colors.grey),
- maxLines: 3,
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/preregister_button.dart b/comwell_key_app/lib/booking_details/components/preregister_button.dart
deleted file mode 100644
index c8fb6f9a..00000000
--- a/comwell_key_app/lib/booking_details/components/preregister_button.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:go_router/go_router.dart';
-
-import '../../routing/app_routes.dart';
-import '../../themes/dark_theme.dart';
-
-class PreregisterButton extends StatelessWidget {
- const PreregisterButton({super.key});
-
- @override
- Widget build(BuildContext context) {
- final cubit = context.read<BookingDetailsCubit>();
- final theme = Theme.of(context);
-
-
-
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 8),
- child: ElevatedButton(
- onPressed: () async {
- final (result) = await context.push(
- AppRoutes.preregistration,
- extra: [cubit.booking, cubit.state.upSales]);
- if (result != null) {
- cubit.preregisterEvent();
- }
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: Row(
- children: [
- SvgPicture.asset("assets/icons/ic_exit.svg"),
- const SizedBox(width: 16),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- context.strings.prepare_room,
- style: theme.textTheme.titleMedium?.copyWith(
- color: colorBackground, fontWeight: FontWeight.w600),
- ),
- Text(
- context.strings.jump_line_text,
- style: theme.textTheme.bodySmall
- ?.copyWith(color: colorBackground),
- ),
- ],
- )
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/remove_guest_dialog.dart b/comwell_key_app/lib/booking_details/components/remove_guest_dialog.dart
deleted file mode 100644
index 9963edd4..00000000
--- a/comwell_key_app/lib/booking_details/components/remove_guest_dialog.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class RemoveGuestDialog extends StatelessWidget {
- final Iterable<String> selectedGuests;
- final void Function(Iterable<String>) onGuestSelected;
-
- const RemoveGuestDialog(
- {super.key, required this.selectedGuests, required this.onGuestSelected});
-
- @override
- Widget build(BuildContext context) {
- return AlertDialog(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.0),
- ),
- contentPadding: const EdgeInsets.all(24.0),
- backgroundColor: colorBackground,
- title: Center(
- child: Text(
- context.strings.are_you_sure,
- style: const TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- context.strings.guest_removal_responsibility,
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 16,
- color: Colors.grey[500],
- ),
- ),
- const SizedBox(height: 16),
- ElevatedButton(
- onPressed: () {
- Navigator.pop(context, true);
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: colorBackground,
- side: BorderSide(color: Colors.grey[300]!, width: 1),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(25),
- ),
- elevation: 0,
- padding: const EdgeInsets.symmetric(vertical: 16),
- minimumSize: const Size.fromHeight(50),
- ),
- child: Text(
- selectedGuests.length > 1
- ? context.strings.remove_guests
- : context.strings.remove_guest,
- style: const TextStyle(
- color: colorTertiary,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- const SizedBox(height: 12),
- ElevatedButton(
- onPressed: () {
- Navigator.pop(context, false);
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: sandColor,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(25),
- ),
- padding: const EdgeInsets.symmetric(vertical: 16),
- minimumSize: const Size.fromHeight(50),
- ),
- child: Text(
- context.strings.cancel,
- style: const TextStyle(
- color: colorBackground,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
- ),
- actions: const [],
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/room_key_widget.dart b/comwell_key_app/lib/booking_details/components/room_key_widget.dart
deleted file mode 100644
index 664a4659..00000000
--- a/comwell_key_app/lib/booking_details/components/room_key_widget.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-// ignore_for_file: must_be_immutable
-
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class RoomKeyWidget extends StatelessWidget {
- RoomKeyWidget({super.key, this.isMultipleKeys = false, required this.onPressed});
-
- bool isMultipleKeys = false;
- Function onPressed;
-
- @override
- Widget build(BuildContext context) {
- return Card(
- elevation: 5,
- color: sandColor[20],
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- child: InkWell(
- onTap: () {
- if (isMultipleKeys) {
- // Navigate to multiple keys page
- } else {
- onPressed();
- }
- },
- child: Container(
- width: 150,
- height: 150,
- padding: const EdgeInsets.all(8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: sandColor[40],
- ),
- padding: const EdgeInsets.all(8),
- child: ImageIcon(
- const AssetImage('assets/images/key.png'),
- size: 24,
- color: Theme.of(context).colorScheme.onSecondary,
- ),
- ),
- const SizedBox(height: 20),
- Text(
- isMultipleKeys ? context.strings.room_keys : context.strings.room_key,
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.bold,
- ),
- ),
- const SizedBox(height: 5),
- Text(
- context.strings.room_key_description,
- style: TextStyle(
- fontSize: 12,
- color: Theme.of(context).colorScheme.onSecondary,
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/room_number_container.dart b/comwell_key_app/lib/booking_details/components/room_number_container.dart
deleted file mode 100644
index d2777faf..00000000
--- a/comwell_key_app/lib/booking_details/components/room_number_container.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class RoomNumberContainer extends StatelessWidget {
- final BookingDetailsCubit cubit;
- const RoomNumberContainer({super.key, required this.cubit});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
-
- return Align(
- alignment: Alignment.topLeft,
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(32),
- border: Border.all(color: Colors.white, width: 2),
- ),
- child: Text(
- context.strings.room_prefix(cubit.booking.roomNumber),
- style: theme.textTheme.titleLarge!.copyWith(
- color: Colors.white,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- ),
- );
- }
-}
\ No newline at end of file
diff --git a/comwell_key_app/lib/booking_details/components/share_button.dart b/comwell_key_app/lib/booking_details/components/share_button.dart
deleted file mode 100644
index 3ce87a91..00000000
--- a/comwell_key_app/lib/booking_details/components/share_button.dart
+++ /dev/null
@@ -1,368 +0,0 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/common/components/comwell_error_widget.dart';
-import 'package:comwell_key_app/overview/models/booking.dart';
-import 'package:comwell_key_app/overview/models/guest.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
-import 'package:comwell_key_app/booking_details/components/guest_list.dart';
-import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:comwell_key_app/utils/locator.dart';
-import 'package:comwell_key_app/utils/share_button_utils.dart';
-import 'package:comwell_key_app/share/cubit/share_booking_cubit.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-
-class ShareButton extends StatelessWidget {
- final List<Guest> guests;
- final Color? buttonColor;
- final double userButtonSize;
- final double userButtonOverlap;
- final bool isMyBooking;
-
- const ShareButton({
- super.key,
- required this.guests,
- this.buttonColor,
- this.userButtonSize = 45,
- this.userButtonOverlap = 10,
- this.isMyBooking = false,
- });
-
- @override
- Widget build(BuildContext context) {
- final cubit = context.read<BookingDetailsCubit>();
- final booking = cubit.booking;
-
- final numberOfUsers = guests.length;
- final allInitials = generateInitials(guests);
-
- return Stack(
- key: Key(guests.length.toString()),
- children: [
- Align(
- alignment: Alignment.topLeft,
- child: Padding(
- padding: EdgeInsets.only(left: isMyBooking ? 40 : 16.0, top: isMyBooking ? 24 : 16),
- child: Stack(
- children: [
- Positioned(
- left:
- (numberOfUsers - 1) * (userButtonSize - userButtonOverlap) +
- userButtonSize -
- userButtonOverlap,
- child: SizedBox(
- width: userButtonSize,
- height: userButtonSize,
- child: ElevatedButton(
- onPressed: () {
- context.push(AppRoutes.shareBooking, extra: booking);
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: buttonColor ?? sandColor[10],
- elevation: 0,
- shape: const CircleBorder(),
- padding: EdgeInsets.zero,
- ),
- child: Center(
- child: Icon(
- Icons.add,
- color: colorHeadlineText,
- size: isMyBooking ? 20 : 30,
- ),
- ),
- ),
- ),
- ),
- ...List.generate(
- numberOfUsers,
- (index) => Positioned(
- left: (numberOfUsers - 1 - index) * (userButtonSize - userButtonOverlap),
- child: SizedBox(
- width: userButtonSize,
- height: userButtonSize,
- child: ElevatedButton(
- onPressed: () async {
- if (isMyBooking) {
- return;
- }
- final results = await _showGuestList(
- context,
- index,
- guests,
- booking,
- );
-
- if (results is List<String>) {
- final updatedBooking = booking.updateGuests(results);
- cubit.updateBooking(updatedBooking);
- }
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: index % 2 == 0 ? sandColor : colorTertiary,
- elevation: 0,
- shape: const CircleBorder(),
- padding: EdgeInsets.zero,
- ),
- child: Center(
- child: Text(
- allInitials.elementAt(index),
- style: TextStyle(
- color: colorBackground,
- fontWeight: FontWeight.w400,
- fontSize: isMyBooking ? 14 : 18,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- }
-
- Future<dynamic> _showGuestList(
- BuildContext context,
- int index,
- List<Guest> guests,
- Booking booking,
- ) async {
- final theme = Theme.of(context);
- final selectedGuests = guests.map((e) => e.id.toString()).toList();
- return showModalBottomSheet<dynamic>(
- context: context,
- backgroundColor: colorBackground,
- isScrollControlled: false,
- builder: (BuildContext bottomSheetContext) {
- return BlocProvider(
- create: (context) => ShareBookingCubit(locator(), booking: booking),
- child: BlocConsumer<ShareBookingCubit, ShareBookingState>(
- listener: (context, state) {},
- builder: (context, state) {
- final cubit = context.read<ShareBookingCubit>();
-
- return ClipRRect(
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(16),
- topRight: Radius.circular(16),
- ),
- child: Scaffold(
- body: SizedBox(
- width: double.infinity,
- height: 450,
- child: Column(
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- context.strings.handle_guests_title,
- style: theme.textTheme.titleLarge?.copyWith(
- color: colorTertiary,
- fontWeight: FontWeight.w600,
- ),
- ),
- ElevatedButton(
- style: ElevatedButton.styleFrom(
- backgroundColor: sandColor[40],
- shape: const CircleBorder(),
- elevation: 0,
- minimumSize: const Size(40, 40),
- ),
- child: const Icon(Icons.close, color: colorTertiary),
- onPressed: () {
- Navigator.pop(bottomSheetContext);
- },
- ),
- ],
- ),
- ),
- Builder(
- builder: (context) {
- if (state.isLoading) {
- return const CircularProgressIndicator();
- }
- if (state.error != null) {
- return ComwellErrorWidget(
- title: context.strings.share_booking_error_title,
- subtitle: context.strings.share_booking_error_subtitle,
- border: true,
- );
- }
- return Expanded(
- child: GuestList(
- guests: guests,
- selectedGuests: selectedGuests,
- onGuestSelected: (Iterable<String> newSelection) {
- cubit.updateSelectedGuests(
- newSelection.map(
- (e) => Guest(
- id: int.parse(e),
- firstName: e.split(' ')[0],
- lastName: e.split(' ')[1],
- ),
- ),
- );
- },
- onGuestRemoved: (int guestId) {
- cubit.removeGuests([guestId]);
- },
- ),
- );
- },
- ),
- ],
- ),
- ),
- bottomNavigationBar: state.error != null
- ? null
- : SafeArea(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.end,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Divider(
- height: 1,
- color: Colors.grey.shade300,
- ),
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: ElevatedButton(
- onPressed: state.selectedGuests.isNotEmpty
- ? () async {
- final shouldRemove = await showDialog<bool>(
- context: bottomSheetContext,
- builder: (context) => _buildRemoveGuestDialog(
- context,
- cubit,
- bottomSheetContext,
- selectedGuests,
- ),
- );
-
- if (shouldRemove == true && context.mounted) {
- context.pop(state.selectedGuests);
- }
- }
- : null,
- style: ElevatedButton.styleFrom(
- backgroundColor: state.selectedGuests.isNotEmpty
- ? colorTertiary
- : const Color(0xffE0E0E0),
- minimumSize: const Size.fromHeight(50),
- elevation: 0,
- ),
- child: Text(
- context.strings.cancel_sharing,
- style: TextStyle(
- color: state.selectedGuests.isNotEmpty
- ? colorBackground
- : Colors.grey[500],
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- );
- },
- );
- }
-
- Widget _buildRemoveGuestDialog(
- BuildContext context,
- ShareBookingCubit cubit,
- BuildContext bottomSheetContext,
- Iterable<String> selectedGuests,
- ) {
- return AlertDialog(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.0),
- ),
- contentPadding: const EdgeInsets.all(24.0),
- backgroundColor: colorBackground,
- title: Center(
- child: Text(
- context.strings.are_you_sure,
- style: const TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- context.strings.guest_removal_responsibility,
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 16,
- color: Colors.grey[500],
- ),
- ),
- const SizedBox(height: 16),
- ElevatedButton(
- onPressed: () {
- Navigator.pop(bottomSheetContext, true);
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: colorBackground,
- side: BorderSide(color: Colors.grey[300]!, width: 1),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(25),
- ),
- elevation: 0,
- padding: const EdgeInsets.symmetric(vertical: 16),
- minimumSize: const Size.fromHeight(50),
- ),
- child: Text(
- selectedGuests.length > 1 ? context.strings.remove_guests : context.strings.remove_guest,
- style: const TextStyle(
- color: colorTertiary,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- const SizedBox(height: 12),
- ElevatedButton(
- onPressed: () {
- Navigator.pop(bottomSheetContext, false);
- },
- style: ElevatedButton.styleFrom(
- backgroundColor: sandColor,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(25),
- ),
- padding: const EdgeInsets.symmetric(vertical: 16),
- minimumSize: const Size.fromHeight(50),
- ),
- child: Text(
- context.strings.cancel,
- style: const TextStyle(
- color: colorBackground,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
- ),
- actions: const [],
- );
- }
-}
diff --git a/comwell_key_app/lib/booking_details/components/unlock_room_button.dart b/comwell_key_app/lib/booking_details/components/unlock_room_button.dart
deleted file mode 100644
index 571fb8f4..00000000
--- a/comwell_key_app/lib/booking_details/components/unlock_room_button.dart
+++ /dev/null
@@ -1,44 +0,0 @@
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:go_router/go_router.dart';
-import 'package:slider_button/slider_button.dart';
-
-import '../../routing/app_routes.dart';
-import '../../themes/dark_theme.dart';
-
-class UnlockRoomButton extends StatelessWidget {
- final String roomNumber;
-
- const UnlockRoomButton({super.key, required this.roomNumber});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final width = MediaQuery.of(context).size.width;
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: SliderButton(
- backgroundColor: sandColor,
- width: width - 32,
- vibrationFlag: true,
- icon: Center(
- child: SvgPicture.asset(
- 'assets/icons/Union.svg',
- width: 30,
- ),
- ),
- action: () async {
- context.push(AppRoutes.key, extra: roomNumber);
- return false;
- },
- alignLabel: Alignment.center,
- label: Text(
- context.strings.open_room,
- style: theme.textTheme.headlineSmall?.copyWith(color: Colors.white),
- textAlign: TextAlign.center,
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/check_in/check_in_repository.dart b/comwell_key_app/lib/check_in/check_in_repository.dart
index 20e0e738..1d840bc1 100644
--- a/comwell_key_app/lib/check_in/check_in_repository.dart
+++ b/comwell_key_app/lib/check_in/check_in_repository.dart
@@ -1,5 +1,6 @@
-import 'package:comwell_key_app/booking_details/booking_details_repository.dart';
+
import 'package:comwell_key_app/database/comwell_db.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/profile/profile_repository.dart';
import 'package:comwell_key_app/services/api.dart';
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
index e5335925..0b01704c 100644
--- a/comwell_key_app/lib/check_out/bloc/check_out_cubit.dart
+++ b/comwell_key_app/lib/check_out/bloc/check_out_cubit.dart
@@ -1,9 +1,9 @@
import 'package:adyen_checkout/adyen_checkout.dart';
import 'package:bloc/bloc.dart';
-import 'package:comwell_key_app/booking_details/booking_details_repository.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/overview/models/booking.dart';
import 'package:comwell_key_app/pregistration/pregistration_repository.dart';
import 'package:comwell_key_app/profile/profile_repository.dart';
diff --git a/comwell_key_app/lib/comwell_app.dart b/comwell_key_app/lib/comwell_app.dart
index a559db22..590cb385 100644
--- a/comwell_key_app/lib/comwell_app.dart
+++ b/comwell_key_app/lib/comwell_app.dart
@@ -1,5 +1,5 @@
import 'package:comwell_key_app/.generated/l10n/app_localizations.dart';
-import 'package:comwell_key_app/booking_details/booking_details_repository.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/connection_state/connection_state_cubit.dart';
import 'package:comwell_key_app/key/bloc/key_bloc.dart';
import 'package:comwell_key_app/key/repository/key_repository.dart';
diff --git a/comwell_key_app/lib/domain/repositories/booking_details_repository.dart b/comwell_key_app/lib/domain/repositories/booking_details_repository.dart
new file mode 100644
index 00000000..fba8efa1
--- /dev/null
+++ b/comwell_key_app/lib/domain/repositories/booking_details_repository.dart
@@ -0,0 +1,67 @@
+import 'package:comwell_key_app/database/comwell_db.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/services/api.dart';
+import 'package:comwell_key_app/services/mappers/booking_mapper.dart';
+import 'package:comwell_key_app/services/models/bookings_dto.dart';
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:comwell_key_app/utils/secure_storage.dart';
+import 'package:flutter/widgets.dart';
+
+class BookingDetailsRepository {
+ final api = Api();
+ final secureStorage = locator<SecureStorage>();
+
+ Future<String?> doesInvitationCodeExist(String key) async {
+ return await secureStorage.read(key);
+ }
+
+ Future<Booking> getRemoteBookingDetails(
+ String hmsConfirmationNumber, String hotelCode) async {
+ final response =
+ await api.getBookingDetails(hmsConfirmationNumber, hotelCode);
+ return response!.toBooking();
+ }
+
+ Future<Booking> getBookingDetails(
+ String hmsConfirmationNumber, String hotelCode) async {
+
+
+ final booking = await _checkIfBookingDetailsExists(hmsConfirmationNumber);
+ if (booking != null) {
+ return booking;
+ }
+
+ final newBooking = await _fetchAndSaveBookingDetailsToDatabase(
+ hmsConfirmationNumber, hotelCode);
+ return newBooking;
+ }
+
+ Future<Booking?> _checkIfBookingDetailsExists(
+ String hmsConfirmationNumber) async {
+ try {
+ final booking = await locator<ComwellDatabase>()
+ .bookingsDao
+ .getBookingDetails(hmsConfirmationNumber);
+ return booking;
+ } catch (e) {
+ debugPrint("Error checking if booking details exists: $e");
+ return null;
+ }
+ }
+
+ Future<Booking> _fetchAndSaveBookingDetailsToDatabase(
+ String hmsConfirmationNumber, String hotelCode) async {
+ try {
+ final response =
+ await api.getBookingDetails(hmsConfirmationNumber, hotelCode);
+ await locator<ComwellDatabase>().bookingsDao.insertBookings(
+ BookingsDTO(current: [response!], past: [], cancelled: []));
+ final booking = response.toBooking();
+ debugPrint("Booking saved to database: ${booking.confirmationNumber}");
+ return booking;
+ } on Exception catch (e) {
+ debugPrint("Error fetching booking details: $e");
+ rethrow;
+ }
+ }
+}
diff --git a/comwell_key_app/lib/key/bloc/key_bloc.dart b/comwell_key_app/lib/key/bloc/key_bloc.dart
index 439a4a98..e7aafffd 100644
--- a/comwell_key_app/lib/key/bloc/key_bloc.dart
+++ b/comwell_key_app/lib/key/bloc/key_bloc.dart
@@ -1,12 +1,11 @@
import 'dart:core';
import 'package:bloc/bloc.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/key/repository/key_repository.dart';
import 'package:comwell_key_app/utils/seos_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:seos_mobile_keys_plugin/app_usage_api.dart';
-import '../../booking_details/booking_details_repository.dart';
-
part 'key_event.dart';
part 'key_state.dart';
diff --git a/comwell_key_app/lib/my_booking/my_booking_page.dart b/comwell_key_app/lib/my_booking/my_booking_page.dart
index 17535085..cf218a5a 100644
--- a/comwell_key_app/lib/my_booking/my_booking_page.dart
+++ b/comwell_key_app/lib/my_booking/my_booking_page.dart
@@ -1,10 +1,10 @@
-
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
-import 'package:comwell_key_app/booking_details/components/share_button.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
import 'package:comwell_key_app/my_booking/cubit/my_booking_cubit.dart';
import 'package:comwell_key_app/my_booking/cubit/my_booking_state.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/share_button.dart';
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';
diff --git a/comwell_key_app/lib/overview/components/current_booking_list_item_view.dart b/comwell_key_app/lib/overview/components/current_booking_list_item_view.dart
index b1c7183f..1e45b79e 100644
--- a/comwell_key_app/lib/overview/components/current_booking_list_item_view.dart
+++ b/comwell_key_app/lib/overview/components/current_booking_list_item_view.dart
@@ -5,7 +5,7 @@ import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/overview/models/guest.dart';
import 'package:comwell_key_app/routing/app_routes.dart';
import 'package:comwell_key_app/themes/light_theme.dart';
-import 'package:comwell_key_app/booking_details/components/guest_list.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/guest_list.dart';
import 'package:comwell_key_app/utils/l10n_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_cubit.dart b/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_cubit.dart
new file mode 100644
index 00000000..97da729a
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_cubit.dart
@@ -0,0 +1,279 @@
+import 'dart:async';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/housekeeping/components/housekeeping_service.dart';
+import 'package:comwell_key_app/housekeeping/housekeeping_repository.dart';
+import 'package:comwell_key_app/housekeeping/models/housekeeping.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/presentation/base/base_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_state.dart';
+import 'package:comwell_key_app/profile/profile_repository.dart';
+import 'package:comwell_key_app/profile_settings/model/user.dart';
+import 'package:comwell_key_app/share/share_booking_repository.dart';
+import 'package:comwell_key_app/up_sales/up_sales_repository.dart';
+import 'package:comwell_key_app/utils/seos_repository.dart';
+import 'package:flutter/foundation.dart';
+
+class BookingDetailsCubit extends BaseCubit<BookingDetailsState> {
+ late Booking booking;
+ User? user;
+ Timer? _timer;
+ final BookingDetailsRepository _bookingDetailsRepository;
+ final ProfileRepository _profileRepository;
+ final SeosRepository _seosRepository;
+ final UpSalesRepository _upSaleRepository;
+ final HouseKeepingRepository _houseKeepingRepository;
+ final ShareBookingRepository _shareBookingRepository;
+ Duration _remainingTime = Duration.zero;
+
+ BookingDetailsCubit(
+ this._bookingDetailsRepository,
+ this._profileRepository,
+ this._seosRepository,
+ this._upSaleRepository,
+ this._shareBookingRepository,
+ this._houseKeepingRepository, {
+ required this.booking,
+ }) : super(const BookingDetailsState()) {
+ init();
+ }
+
+ Future<void> init() async {
+ try {
+ safeEmit(state.copyWith(isLoading: true));
+ await getUser();
+ _startTimer();
+ await checkIfHouseKeepingOrdered();
+ await checkMobileKeys();
+ await getBookingDetails(
+ booking.confirmationNumber,
+ hotelCode: booking.hotelCode,
+ fetchRemote: true,
+ );
+ await updateRemainingTime();
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ } finally {
+ safeEmit(state.copyWith(isLoading: false));
+ }
+ }
+
+ Future<void> getUpSales({bool fetchRemote = false}) async {
+ try {
+ if (fetchRemote) {
+ final response = await _upSaleRepository.getRemoteUpSales(
+ booking.confirmationNumber,
+ booking.hotelCode,
+ );
+ safeEmit(state.copyWith(upSales: response));
+ } else {
+ final response = await _upSaleRepository.getUpSales(
+ booking.confirmationNumber,
+ booking.hotelCode,
+ );
+ safeEmit(state.copyWith(upSales: response));
+ }
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ }
+ }
+
+ Future<Booking> getBookingDetails(
+ String hmsConfirmationNumber, {
+ bool fetchRemote = false,
+ required String hotelCode,
+ }) async {
+ safeEmit(state.copyWith(isLoading: true));
+ try {
+ if (fetchRemote) {
+ final bookingDetails = await _bookingDetailsRepository.getRemoteBookingDetails(
+ hmsConfirmationNumber,
+ hotelCode,
+ );
+ booking = bookingDetails;
+ return booking;
+ } else {
+ user = await _profileRepository.fetchProfileSettings();
+ final bookingDetails = await _bookingDetailsRepository.getBookingDetails(
+ hmsConfirmationNumber,
+ hotelCode,
+ );
+ booking = bookingDetails;
+
+ return booking;
+ }
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ rethrow;
+ }
+ }
+
+ Future<void> checkMobileKeys() async {
+ try {
+ final isKeyLoadedForBooking = state.keys.any((key) => key.label == booking.roomNumber);
+ safeEmit(state.copyWith(isLoadingKeys: true));
+ await checkIfSetup();
+
+ final keys = await _seosRepository.refreshKeys();
+ safeEmit(state.copyWith(keys: keys));
+
+ if (keys.isEmpty || !isKeyLoadedForBooking) {
+ await _seosRepository.provisionKey(bookingId: booking.id, hotelCode: booking.hotelCode);
+ final keys = await _seosRepository.refreshKeys();
+ safeEmit(state.copyWith(keys: keys));
+ }
+ } catch (e) {
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ } finally {
+ safeEmit(state.copyWith(isLoadingKeys: false));
+ }
+ }
+
+ Future<void> checkIfSetup() async {
+ final isSetup = await _seosRepository.isEndpointSetup();
+ if (!isSetup) await _seosRepository.startMobilePlugin();
+ }
+
+ Future<void> checkIfHouseKeepingOrdered() async {
+ final isHouseKeepingOrdered = await _houseKeepingRepository.isHousesKeepingOrdered(
+ booking.roomNumber,
+ );
+
+ if (isHouseKeepingOrdered) {
+ emit(state.copyWith(isHouseKeepingOrdered: true));
+ }
+ }
+
+ Future<void> preregisterEvent() async {
+ try {
+ safeEmit(state.copyWith(isLoading: true));
+ await getBookingDetails(
+ booking.confirmationNumber,
+ fetchRemote: true,
+ hotelCode: booking.hotelCode,
+ );
+ await getUpSales(fetchRemote: true);
+ safeEmit(state.copyWith(isLoading: false));
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ }
+ }
+
+ Future<void> checkInEvent() async {
+ try {
+ safeEmit(state.copyWith(isLoading: true));
+ await getBookingDetails(
+ booking.confirmationNumber,
+ fetchRemote: true,
+ hotelCode: booking.hotelCode,
+ );
+ await checkMobileKeys();
+ await getUpSales(fetchRemote: true);
+ safeEmit(state.copyWith(isLoading: false));
+ } catch (e) {
+ if (kDebugMode) print("err=$e");
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ }
+ }
+
+ Future<void> orderHouseKeeping(
+ List<String> selectedServices,
+ ) async {
+ final housekeeping = Housekeeping.toJson(
+ booking.hotelCode,
+ booking.roomNumber,
+ selectedServices
+ .map((e) => HouseKeepingService.values.firstWhere((element) => element.name == e))
+ .toList(),
+ );
+ await _houseKeepingRepository.saveHouseKeepingOrdered(housekeeping);
+ safeEmit(state.copyWith(isHouseKeepingOrdered: true));
+ }
+
+ Future<void> updateBooking(Booking booking) async {
+ try {
+ //TODO: add this when we have the backend
+ // await bookingDetailsRepository.updateBooking(event.booking);
+ safeEmit(state.copyWith(guests: booking.guests));
+ } catch (e) {
+ safeEmit(state.copyWith(error: AppError.unknown(e.toString())));
+ }
+ }
+
+ void _startTimer() {
+ _timer?.cancel();
+ _timer = Timer.periodic(
+ const Duration(seconds: 1),
+ (_) => updateRemainingTime(),
+ );
+ }
+
+ @override
+ Future<void> close() {
+ _timer?.cancel();
+ return super.close();
+ }
+
+ Future<void> updateRemainingTime() async {
+ final now = DateTime.now();
+ _remainingTime = getCheckInTime().difference(now);
+
+ safeEmit(state.copyWith(remainingTime: _remainingTime));
+
+ if (_remainingTime.isNegative) {
+ _timer?.cancel();
+ safeEmit(state.copyWith(remainingTime: _remainingTime));
+ }
+ }
+
+ String get trimmedBalance {
+ final balance = booking.balance;
+ if (balance == null) return '';
+ if (balance == balance.toInt()) {
+ return balance.toInt().toString();
+ } else {
+ return balance.toStringAsFixed(2);
+ }
+ }
+
+ void bypassTimer() {
+ _timer?.cancel();
+ safeEmit(state.copyWith(remainingTime: const Duration(seconds: -1)));
+ }
+
+ DateTime getCheckInTime() => booking.startDate.add(const Duration(hours: 15));
+ DateTime getCheckOutTime() => booking.endDate.add(const Duration(hours: 5));
+
+ bool get canCheckOut {
+ final now = DateTime.now();
+ final today = DateTime(now.year, now.month, now.day);
+ final checkInDate = DateTime(
+ booking.startDate.year,
+ booking.startDate.month,
+ booking.startDate.day,
+ );
+ final checkOutDate = DateTime(booking.endDate.year, booking.endDate.month, booking.endDate.day);
+
+ if (checkInDate == checkOutDate) {
+ return false;
+ }
+
+ return checkOutDate.isBefore(today);
+ }
+
+ bool get canOrderHousekeeping {
+ final now = DateTime.now();
+ final tomorrow = DateTime(now.year, now.month, now.day + 1);
+ final checkoutDay = DateTime(booking.endDate.year, booking.endDate.month, booking.endDate.day);
+ if (checkoutDay == tomorrow) return false;
+ return booking.endDate.difference(booking.startDate).inDays >= 2;
+ }
+
+ Future<void> getUser({bool fetchRemote = false}) async {
+ user = await _profileRepository.fetchProfileSettings(fetchRemote: fetchRemote);
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_state.dart b/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_state.dart
new file mode 100644
index 00000000..0ffcf926
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/bloc/booking_details_state.dart
@@ -0,0 +1,25 @@
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/overview/models/guest.dart';
+import 'package:comwell_key_app/up_sales/models/up_sales.dart';
+import 'package:comwell_key_app/up_sales/models/upgrade.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:seos_mobile_keys_plugin/app_usage_api.dart';
+
+part '../../../../.generated/presentation/screens/booking_details/bloc/booking_details_state.freezed.dart';
+
+@freezed
+abstract class BookingDetailsState with _$BookingDetailsState {
+ const factory BookingDetailsState({
+ @Default(false) bool isHouseKeepingOrdered,
+ @Default(null) MobileKeysKey? key,
+ @Default([]) List<MobileKeysKey> keys,
+ @Default([]) List<Guest> guests,
+ @Default(Duration.zero) Duration remainingTime,
+ @Default(AppError.none) AppError error,
+ @Default(false) bool isLoading,
+ @Default(false) bool isLoadingKeys,
+ @Default(null) UpSales? upSales,
+ @Default([]) List<Upgrade> selectedUpSales,
+ }) = _BookingDetailsState;
+}
+
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/booking_details_page.dart b/comwell_key_app/lib/presentation/screens/booking_details/booking_details_page.dart
new file mode 100644
index 00000000..a8e0aa71
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/booking_details_page.dart
@@ -0,0 +1,262 @@
+
+import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
+import 'package:comwell_key_app/common/components/comwell_app_bar.dart';
+import 'package:comwell_key_app/common/components/shimmer_loader/booking_details_shimmer_loader.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/booking_details_bottom_sheet.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/check_in_button_timer.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/get_keys_button.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/preregister_button.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/room_number_container.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/share_button.dart';
+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';
+
+import 'bloc/booking_details_cubit.dart';
+import 'bloc/booking_details_state.dart';
+import 'components/unlock_room_button.dart';
+
+class BookingDetailsPage extends StatelessWidget {
+ const BookingDetailsPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocConsumer<BookingDetailsCubit, BookingDetailsState>(
+ listener: (context, state) {},
+ builder: (context, state) {
+ final cubit = context.read<BookingDetailsCubit>();
+
+ return Scaffold(
+ extendBodyBehindAppBar: true,
+ backgroundColor: colorBackground,
+ appBar: const ComwellAppBar(),
+ body: Builder(
+ builder: (context) {
+ if (state.isLoading) {
+ return const Center(
+ child: BookingDetailsShimmerLoader(),
+ );
+ } else {
+ return _buildBookingDetailsPage(context, state, cubit);
+ }
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildBookingDetailsPage(
+ BuildContext context,
+ BookingDetailsState state,
+ BookingDetailsCubit cubit,
+ ) {
+ final theme = Theme.of(context);
+ final screenHeight = MediaQuery.of(context).size.height;
+
+ return Stack(
+ children: [
+ // Background image - fixed
+ Container(
+ decoration: BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage(Assets.images.bookingBackground.path),
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ // Gradient overlay - fixed
+ Container(
+ decoration: const BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Colors.black26,
+ Colors.black54,
+ ],
+ ),
+ ),
+ ),
+ // 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),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildBookingDetailsInformation(
+ BuildContext context,
+ BookingDetailsState state,
+ BookingDetailsCubit cubit,
+ ThemeData theme,
+ ) {
+ return InkWell(
+ onTap: () async {
+ await context.push(AppRoutes.myBooking, extra: cubit.booking);
+
+ },
+ child: Container(
+ width: double.infinity,
+ height: 50,
+ decoration: BoxDecoration(
+ color: Colors.transparent,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ cubit.booking.confirmationNumber,
+ style: theme.textTheme.labelLarge?.copyWith(color: colorBackground),
+ ),
+ Text(
+ cubit.booking.toRoomType(),
+ style: theme.textTheme.headlineMedium?.copyWith(
+ color: colorBackground,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ const Icon(
+ Icons.chevron_right,
+ color: colorBackground,
+ size: 36,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _ScrollableBookingContent extends StatefulWidget {
+ final double screenHeight;
+ final BookingDetailsCubit 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) {
+ final keys = widget.state.keys;
+ final isKeyLoadedForBooking = keys.any((key) => key.label == widget.cubit.booking.roomNumber);
+ final isKeysEmpty = keys.isEmpty;
+ final hasRoomNumber = widget.cubit.booking.roomNumber != '';
+
+ 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 (!isKeysEmpty &&
+ hasRoomNumber &&
+ isKeyLoadedForBooking)
+ 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/presentation/screens/booking_details/booking_details_route.dart b/comwell_key_app/lib/presentation/screens/booking_details/booking_details_route.dart
new file mode 100644
index 00000000..f364fb7d
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/booking_details_route.dart
@@ -0,0 +1,38 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/booking_details_page.dart';
+import 'package:comwell_key_app/find_booking/loading_page.dart';
+import 'package:comwell_key_app/overview/cubit/overview_cubit.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+
+final bookingDetailsRoute = GoRoute(
+ path: AppRoutes.bookingDetails,
+ builder: (context, state) {
+ final booking = state.extra as Booking;
+ return BlocProvider<BookingDetailsCubit>(
+ create: (BuildContext context) => BookingDetailsCubit(
+ booking: booking,
+ locator(),
+ locator(),
+ locator(),
+ locator(),
+ locator(),
+ locator(),
+ ),
+ child: const BookingDetailsPage(),
+ );
+ },
+);
+
+final bookingDetailsWithIdRoute = GoRoute(
+ path: AppRoutes.bookingDetailsWithId,
+ builder: (context, state) {
+ final bookingId = state.pathParameters['id']!;
+ context.read<OverviewCubit>().findBookingById(bookingId);
+ return const LoadingPage();
+ },
+);
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/booking_details_bottom_sheet.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/booking_details_bottom_sheet.dart
new file mode 100644
index 00000000..71a47f48
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/booking_details_bottom_sheet.dart
@@ -0,0 +1,222 @@
+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/domain/models/app_error.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/check_out_button.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/concierge_button.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/housekeeping_button.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/practical_information_button.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:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+import '../bloc/booking_details_cubit.dart';
+import '../bloc/booking_details_state.dart';
+
+class BookingDetailsBottomSheet extends StatelessWidget {
+ final BookingDetailsCubit cubit;
+ final BookingDetailsState state;
+
+ const BookingDetailsBottomSheet({super.key, required this.cubit, required this.state});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final isActive = cubit.user?.isClubMember ?? false;
+
+ 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(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.canOrderHousekeeping)
+ 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(
+ context.strings.booking_details_page_practical_information,
+ 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: context.strings.booking_details_page_hotel_information_button_title,
+ subtitle:
+ context.strings.booking_details_page_hotel_information_button_subtitle,
+ onClick: () {
+ context.push(AppRoutes.hotelInformation, 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: context.strings.booking_details_page_contact_button_title,
+ subtitle: context.strings.booking_details_page_contact_button_subtitle,
+ onClick: () {
+ context.push(AppRoutes.contact, extra: cubit.booking);
+ },
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ if (!isActive && cubit.user != null)
+ ComwellClubContainer(
+ user: cubit.user!,
+ onSignupClick: () {
+ cubit.init();
+ },
+ ),
+ const SizedBox(height: 16),
+ const SizedBox(height: 50),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildUpSalesCatalogButton(ReservationStatus reservationStatus, BuildContext context) {
+ switch (reservationStatus) {
+ case ReservationStatus.checkedin:
+ return ConciergeButton(hotelCode: cubit.booking.hotelCode);
+ case ReservationStatus.newreservation:
+ return _buildServices(context);
+ case ReservationStatus.preregistered:
+ return const SizedBox();
+ case ReservationStatus.cancelled:
+ return const SizedBox();
+ case ReservationStatus.checkedout:
+ return const SizedBox();
+ default:
+ return const SizedBox();
+ }
+ }
+
+ Widget _buildServices(BuildContext context) {
+ final theme = Theme.of(context);
+ final serviceUpgrades =
+ cubit.state.upSales?.addOnUpgrades.where((upgrade) => upgrade.isService).toList() ?? [];
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ state.error != AppError.none
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Text(context.strings.services, style: theme.textTheme.headlineMedium),
+ ),
+ const SizedBox(width: 8),
+ ComwellErrorWidget(
+ title: context.strings.up_sales_error_title,
+ subtitle: context.strings.up_sales_error_subtitle,
+ border: true,
+ ),
+ ],
+ )
+ : Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ if (state.upSales?.addOnUpgrades.isNotEmpty ?? false) ...[
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Text(context.strings.services, style: theme.textTheme.headlineMedium),
+ ),
+ const SizedBox(width: 8),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: OutlinedPillButton(
+ text: context.strings.up_sales_see_all,
+ onTap: () async {
+ await context.push(
+ AppRoutes.upSalesCatalog,
+ extra: [cubit.booking],
+ );
+ cubit.init();
+ },
+ ),
+ ),
+ ] else
+ const SizedBox(),
+ ],
+ ),
+ const SizedBox(height: 16),
+ if (serviceUpgrades.isNotEmpty)
+ ServiceCatalog(
+ bookingDetailsCubit: cubit,
+ booking: cubit.booking,
+ upSales: serviceUpgrades,
+ height: 252,
+ showRadioButton: false,
+ isSinglePurchase: true,
+ onTap: (upgrade) {
+ // No OP
+ },
+ onServiceSelected: (upgrade, isSelected) {
+ // No OP
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button.dart
new file mode 100644
index 00000000..2c1eeec4
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button.dart
@@ -0,0 +1,100 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+
+class CheckInButton extends StatelessWidget {
+ const CheckInButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<BookingDetailsCubit>();
+ return cubit.booking.digitalCard
+ ? getDigitalCardWidget(context, cubit)
+ : getPhysicalCardWidget(context);
+ }
+
+ Widget getDigitalCardWidget(BuildContext context, BookingDetailsCubit cubit) {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 10),
+ child: ElevatedButton(
+ onPressed: () async {
+ final (result) = await context.push(AppRoutes.checkIn, extra: [cubit.booking, false]);
+ if (result == true && !cubit.isClosed) {
+ cubit.checkInEvent();
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ SvgPicture.asset("assets/icons/ic_unlocked.svg"),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.check_in,
+ style: Theme.of(
+ context,
+ ).textTheme.titleMedium?.copyWith(color: colorBackground),
+ ),
+ Text(
+ context.strings.overview_page_check_in_button_subtitle,
+ style: Theme.of(
+ context,
+ ).textTheme.bodySmall?.copyWith(color: colorBackground),
+ softWrap: true,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget getPhysicalCardWidget(BuildContext context) {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 10),
+ child: Container(
+ decoration: BoxDecoration(
+ color: sandColor[80],
+ borderRadius: BorderRadius.circular(30),
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
+ child: Row(
+ children: [
+ SvgPicture.asset("assets/icons/ic_unlocked.svg"),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.check_in,
+ style: Theme.of(
+ context,
+ ).textTheme.titleMedium?.copyWith(color: colorBackground),
+ ),
+ Text(
+ context.strings.overview_page_check_in_physical_card_subtitle,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(color: colorBackground),
+ softWrap: true,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button_timer.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button_timer.dart
new file mode 100644
index 00000000..2d4aadcb
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/check_in_button_timer.dart
@@ -0,0 +1,148 @@
+import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_state.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/check_in_button.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:comwell_key_app/utils/time_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:intl/intl.dart';
+
+class CheckInButtonTimer extends StatefulWidget {
+ const CheckInButtonTimer({
+ super.key,
+ });
+
+ @override
+ State<CheckInButtonTimer> createState() => _CheckInButtonTimerState();
+}
+
+class _CheckInButtonTimerState extends State<CheckInButtonTimer> {
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
+ builder: (context, state) {
+ return AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ transitionBuilder: (Widget child, Animation<double> animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: child,
+ );
+ },
+ child: state.remainingTime.isNegative
+ ? const CheckInButton(key: ValueKey('check_in_button'))
+ : getTimerWidget(),
+ );
+ },
+ );
+ }
+
+ bool get _isDevOrStage {
+ final flavor = appFlavor?.toLowerCase();
+ return flavor == 'develop' || flavor == 'stage';
+ }
+
+ Widget getTimerWidget() {
+ return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
+ key: const ValueKey('timer_view'),
+ builder: (context, state) {
+ final cubit = context.read<BookingDetailsCubit>();
+ final (days, hours, minutes, seconds) = getDurationInMinutes(state.remainingTime);
+ final checkInTime = cubit.getCheckInTime();
+ final dateStr = DateFormat('d. MMM', 'da').format(checkInTime);
+
+ final theme = Theme.of(context);
+ final timeStr = days > 0
+ ? context.strings.check_in_button_timer_days_hours_minutes(
+ days.toString(),
+ hours.toString(),
+ minutes.toString(),
+ )
+ : hours > 0
+ ? context.strings.check_in_button_timer_hours_minutes(
+ hours.toString(),
+ minutes.toString(),
+ )
+ : minutes > 0
+ ? context.strings.check_in_button_timer_minutes(minutes.toString())
+ : context.strings.check_in_button_timer_seconds(seconds.toString());
+
+ final timerContent = Container(
+ margin: const EdgeInsets.symmetric(horizontal: 10),
+ decoration: BoxDecoration(
+ color: Colors.grey[900]?.withValues(alpha: 0.8),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ Assets.icons.icLocked.path,
+ colorFilter: const ColorFilter.mode(
+ colorBackground,
+ BlendMode.srcIn,
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Check-in',
+ style: theme.textTheme.titleMedium?.copyWith(
+ color: colorBackground,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ Text(
+ timeStr,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ color: colorBackground.withValues(alpha: 0.8),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text(
+ dateStr.toUpperCase(),
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: colorBackground.withValues(alpha: 0.7),
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ Text(
+ DateFormat('HH:mm').format(checkInTime),
+ style: theme.textTheme.titleMedium?.copyWith(
+ color: colorBackground,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+
+ if (_isDevOrStage) {
+ return GestureDetector(
+ onTap: () {
+ // Bypass timer in dev/stage - show check-in button
+ cubit.bypassTimer();
+ },
+ child: timerContent,
+ );
+ }
+
+ return timerContent;
+ },
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/check_out_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/check_out_button.dart
new file mode 100644
index 00000000..6bab1435
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/check_out_button.dart
@@ -0,0 +1,78 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.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) {
+ final cubit = context.read<BookingDetailsCubit>();
+ return Material(
+ color: Colors.transparent,
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(
+ color: colorDivider,
+ ),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: InkWell(
+ borderRadius: const BorderRadius.all(Radius.circular(15)),
+ onTap: () async {
+ await context.push(AppRoutes.checkOut,
+ extra: cubit.booking);
+ cubit.init();
+ },
+ 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(
+ colorTertiary, BlendMode.srcATop),
+ "assets/icons/ic_exit.svg"),
+ ),
+ ),
+ const SizedBox(width: 12),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(context.strings.booking_details_page_checkout_title),
+ Text(
+ context.strings.booking_details_page_checkout_subtitle,
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: Colors.grey),
+ ),
+ ],
+ ),
+ const Expanded(child: SizedBox()),
+ Text(context.strings.booking_details_page_checkout_time),
+ SvgPicture.asset("assets/icons/arrow-left.svg",
+ colorFilter: const ColorFilter.mode(
+ colorTertiary,
+ BlendMode.dst,
+ ))
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/concierge_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/concierge_button.dart
new file mode 100644
index 00000000..22e45568
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/concierge_button.dart
@@ -0,0 +1,82 @@
+
+
+import 'package:comwell_key_app/presentation/screens/concierge/concierge_route.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class ConciergeButton extends StatelessWidget {
+ final String hotelCode;
+ const ConciergeButton({super.key, required this.hotelCode});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+
+ return GestureDetector(
+ onTap: () async {
+ await ConciergeRoute(hotelCode: hotelCode).push<void>(context);
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ child: Container(
+ width: double.infinity,
+ height: 211,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: colorDivider, width: 1),
+ ),
+ child: Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(12),
+ child: Image.asset(
+ 'assets/images/catalog_image.png',
+ width: double.infinity,
+ height: double.infinity,
+ fit: BoxFit.cover,
+ ),
+ ),
+ Container(
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Colors.black26,
+ Colors.black54,
+ ],
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.up_sales_catalog_button_title,
+ style: theme.textTheme.headlineMedium?.copyWith(
+ color: Colors.white,
+ ),
+ ),
+ Text(
+ context.strings.up_sales_catalog_button_subtitle,
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: Colors.white,
+ ),
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/get_keys_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/get_keys_button.dart
new file mode 100644
index 00000000..fe668013
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/get_keys_button.dart
@@ -0,0 +1,75 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_state.dart';
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+
+class GetKeysButton extends StatelessWidget {
+ const GetKeysButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<BookingDetailsCubit, BookingDetailsState>(
+ builder: (context, state) {
+ final cubit = context.read<BookingDetailsCubit>();
+ final theme = Theme.of(context);
+ if (state.isLoadingKeys) {
+ return const Center(
+ child: CircularProgressIndicator(
+ color: sandColor,
+ backgroundColor: colorBackground,
+ strokeWidth: 2,
+ ),
+ );
+ }
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 10),
+ child: ElevatedButton(
+ onPressed: () async {
+ await cubit.checkMobileKeys();
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ "assets/icons/Union.svg",
+ colorFilter: const ColorFilter.mode(
+ colorBackground,
+ BlendMode.srcIn,
+ ),
+ width: 14,
+ height: 14,
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.get_keys,
+ style: Theme.of(
+ context,
+ ).textTheme.titleMedium?.copyWith(color: Colors.white),
+ ),
+ Text(
+ context.strings.get_keys_subtitle,
+ style: Theme.of(
+ context,
+ ).textTheme.bodySmall?.copyWith(color: Colors.white),
+ softWrap: true,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/guest_list.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/guest_list.dart
new file mode 100644
index 00000000..64c97ef5
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/guest_list.dart
@@ -0,0 +1,131 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/components/remove_guest_dialog.dart';
+import 'package:comwell_key_app/overview/models/guest.dart';
+import 'package:comwell_key_app/themes/comwell_colors.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class GuestList extends StatelessWidget {
+ final Iterable<Guest> guests;
+ final Iterable<String> selectedGuests;
+ final bool isOverview;
+ final void Function(Iterable<String>) onGuestSelected;
+ final void Function(int guestId) onGuestRemoved;
+
+ const GuestList({
+ super.key,
+ required this.guests,
+ required this.selectedGuests,
+ required this.onGuestSelected,
+ this.isOverview = false,
+ required this.onGuestRemoved,
+ });
+
+ void handleCancelSharing(BuildContext context) {
+ onGuestSelected([]);
+ Navigator.pop(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final guestNames =
+ guests.map((guest) => "${guest.firstName} ${guest.lastName}").toList();
+ return ListView.builder(
+ shrinkWrap: true,
+ itemCount: guestNames.length,
+ itemBuilder: (context, index) {
+ final guestName = guestNames.elementAt(index);
+ final initials = guestName.split(' ').map((name) => name[0]).join('');
+ final guestId = guests.elementAt(index).id;
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ child: GestureDetector(
+ onTap: () {
+ final newSelectedGuests = List<String>.from(selectedGuests);
+ if (selectedGuests.contains(guestName)) {
+ newSelectedGuests.remove(guestName);
+ } else {
+ newSelectedGuests.add(guestName);
+ }
+ onGuestSelected(newSelectedGuests);
+ },
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: colorDivider,
+ width: 1.0,
+ ),
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ padding: const EdgeInsets.all(12.0),
+ child: Row(
+ children: [
+ Container(
+ width: 40,
+ height: 40,
+ decoration: const BoxDecoration(
+ color: colorTertiary,
+ shape: BoxShape.circle,
+ ),
+ child: Center(
+ child: Text(
+ initials,
+ style: const TextStyle(
+ color: colorBackground,
+ fontWeight: FontWeight.w400,
+ fontSize: 16,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Text(
+ guestName,
+ style: Theme.of(context).textTheme.bodyLarge,
+ ),
+ ),
+ !isOverview
+ ? Checkbox(
+ value: selectedGuests.contains(guestName),
+ onChanged: (bool? value) {
+ if (value != null) {
+ final newSelectedGuests =
+ List<String>.from(selectedGuests);
+ if (value) {
+ newSelectedGuests.add(guestName);
+ } else {
+ newSelectedGuests.remove(guestName);
+ }
+ onGuestSelected(newSelectedGuests);
+ }
+ },
+ )
+ : TextButton(
+ onPressed: () async {
+ final shouldRemove = await showDialog<bool>(
+ context: context,
+ builder: (context) => RemoveGuestDialog(
+ selectedGuests: selectedGuests,
+ onGuestSelected: onGuestSelected));
+ if (shouldRemove == true && context.mounted) {
+ onGuestRemoved(guestId);
+ Navigator.pop(context);
+ }
+ },
+ child: Text(context.strings.remove_guest_overview,
+ style: Theme.of(context)
+ .textTheme
+ .bodyLarge
+ ?.copyWith(
+ color: colorError,
+ fontWeight: FontWeight.w600)),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/housekeeping_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/housekeeping_button.dart
new file mode 100644
index 00000000..67231ff6
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/housekeeping_button.dart
@@ -0,0 +1,93 @@
+import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+
+
+class HousekeepingButton extends StatelessWidget {
+ final Booking booking;
+
+ const HousekeepingButton({super.key, required this.booking});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<BookingDetailsCubit>();
+
+ return Material(
+ color: Colors.transparent,
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(
+ color: colorDivider,
+ ),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: InkWell(
+ borderRadius: BorderRadius.circular(10),
+ onTap: () async {
+ if (cubit.state.isHouseKeepingOrdered) return;
+
+ final result = await context.push(AppRoutes.houseKeeping, extra: booking);
+ if (result != null && !cubit.isClosed) {
+ cubit.orderHouseKeeping(result as List<String>);
+ }
+ if (!cubit.isClosed) cubit.checkIfHouseKeepingOrdered();
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ 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(Assets.icons.iconHousekeepingCleaning.path),
+ ),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ cubit.state.isHouseKeepingOrdered == true
+ ? context.strings.booking_details_page_housekeeping_button_title_ordered
+ : context.strings.booking_details_page_housekeeping_button_title,
+ style: Theme.of(context).textTheme.headlineSmall,
+ maxLines: 1,
+ ),
+ Text(
+ cubit.state.isHouseKeepingOrdered == true
+ ? context
+ .strings
+ .booking_details_page_housekeeping_button_subtitle_ordered
+ : context.strings.booking_details_page_housekeeping_button_subtitle,
+ maxLines: 1,
+ softWrap: true,
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 12),
+ if (cubit.state.isHouseKeepingOrdered == true)
+ SvgPicture.asset(Assets.icons.icCheckmark.path)
+ else
+ SvgPicture.asset(Assets.icons.arrowLeft.path),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/practical_information_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/practical_information_button.dart
new file mode 100644
index 00000000..3d30574f
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/practical_information_button.dart
@@ -0,0 +1,75 @@
+import 'package:comwell_key_app/themes/dark_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class PracticalInformationButton extends StatelessWidget {
+ final String iconPath;
+ final String title;
+ final String subtitle;
+ final Function onClick;
+
+ const PracticalInformationButton({
+ super.key,
+ required this.iconPath,
+ required this.title,
+ required this.subtitle,
+ required this.onClick,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+
+ return Material(
+ color: sandColor[10],
+ borderRadius: const BorderRadius.all(Radius.circular(16)),
+ child: InkWell(
+ splashColor: sandColor,
+ borderRadius: const BorderRadius.all(Radius.circular(16)),
+ onTap: () => onClick(),
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: sandColor[40],
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: SvgPicture.asset(iconPath,
+ width: 24,
+ height: 24,
+ colorFilter: const ColorFilter.mode(
+ colorTertiary,
+ BlendMode.dst,
+ )),
+ ),
+ ),
+ const SizedBox(height: 16),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(title),
+ SizedBox(
+ height: 60,
+ child: Text(
+ subtitle,
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(color: Colors.grey),
+ maxLines: 3,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/preregister_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/preregister_button.dart
new file mode 100644
index 00000000..0e0467ca
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/preregister_button.dart
@@ -0,0 +1,59 @@
+
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+
+class PreregisterButton extends StatelessWidget {
+ const PreregisterButton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<BookingDetailsCubit>();
+ final theme = Theme.of(context);
+
+
+
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 8),
+ child: ElevatedButton(
+ onPressed: () async {
+ final (result) = await context.push(
+ AppRoutes.preregistration,
+ extra: [cubit.booking, cubit.state.upSales]);
+ if (result != null) {
+ cubit.preregisterEvent();
+ }
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: Row(
+ children: [
+ SvgPicture.asset("assets/icons/ic_exit.svg"),
+ const SizedBox(width: 16),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ context.strings.prepare_room,
+ style: theme.textTheme.titleMedium?.copyWith(
+ color: colorBackground, fontWeight: FontWeight.w600),
+ ),
+ Text(
+ context.strings.jump_line_text,
+ style: theme.textTheme.bodySmall
+ ?.copyWith(color: colorBackground),
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/remove_guest_dialog.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/remove_guest_dialog.dart
new file mode 100644
index 00000000..9963edd4
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/remove_guest_dialog.dart
@@ -0,0 +1,93 @@
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class RemoveGuestDialog extends StatelessWidget {
+ final Iterable<String> selectedGuests;
+ final void Function(Iterable<String>) onGuestSelected;
+
+ const RemoveGuestDialog(
+ {super.key, required this.selectedGuests, required this.onGuestSelected});
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ contentPadding: const EdgeInsets.all(24.0),
+ backgroundColor: colorBackground,
+ title: Center(
+ child: Text(
+ context.strings.are_you_sure,
+ style: const TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ context.strings.guest_removal_responsibility,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.grey[500],
+ ),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pop(context, true);
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: colorBackground,
+ side: BorderSide(color: Colors.grey[300]!, width: 1),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(25),
+ ),
+ elevation: 0,
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ minimumSize: const Size.fromHeight(50),
+ ),
+ child: Text(
+ selectedGuests.length > 1
+ ? context.strings.remove_guests
+ : context.strings.remove_guest,
+ style: const TextStyle(
+ color: colorTertiary,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ const SizedBox(height: 12),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pop(context, false);
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: sandColor,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(25),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ minimumSize: const Size.fromHeight(50),
+ ),
+ child: Text(
+ context.strings.cancel,
+ style: const TextStyle(
+ color: colorBackground,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ actions: const [],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/room_key_widget.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/room_key_widget.dart
new file mode 100644
index 00000000..664a4659
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/room_key_widget.dart
@@ -0,0 +1,70 @@
+// ignore_for_file: must_be_immutable
+
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class RoomKeyWidget extends StatelessWidget {
+ RoomKeyWidget({super.key, this.isMultipleKeys = false, required this.onPressed});
+
+ bool isMultipleKeys = false;
+ Function onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: 5,
+ color: sandColor[20],
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: InkWell(
+ onTap: () {
+ if (isMultipleKeys) {
+ // Navigate to multiple keys page
+ } else {
+ onPressed();
+ }
+ },
+ child: Container(
+ width: 150,
+ height: 150,
+ padding: const EdgeInsets.all(8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: sandColor[40],
+ ),
+ padding: const EdgeInsets.all(8),
+ child: ImageIcon(
+ const AssetImage('assets/images/key.png'),
+ size: 24,
+ color: Theme.of(context).colorScheme.onSecondary,
+ ),
+ ),
+ const SizedBox(height: 20),
+ Text(
+ isMultipleKeys ? context.strings.room_keys : context.strings.room_key,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 5),
+ Text(
+ context.strings.room_key_description,
+ style: TextStyle(
+ fontSize: 12,
+ color: Theme.of(context).colorScheme.onSecondary,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/room_number_container.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/room_number_container.dart
new file mode 100644
index 00000000..234177a8
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/room_number_container.dart
@@ -0,0 +1,34 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class RoomNumberContainer extends StatelessWidget {
+ final BookingDetailsCubit cubit;
+ const RoomNumberContainer({super.key, required this.cubit});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+
+ return Align(
+ alignment: Alignment.topLeft,
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(32),
+ border: Border.all(color: Colors.white, width: 2),
+ ),
+ child: Text(
+ context.strings.room_prefix(cubit.booking.roomNumber),
+ style: theme.textTheme.titleLarge!.copyWith(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/share_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/share_button.dart
new file mode 100644
index 00000000..601531b9
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/share_button.dart
@@ -0,0 +1,368 @@
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/common/components/comwell_error_widget.dart';
+import 'package:comwell_key_app/overview/models/booking.dart';
+import 'package:comwell_key_app/overview/models/guest.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/components/guest_list.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:comwell_key_app/utils/locator.dart';
+import 'package:comwell_key_app/utils/share_button_utils.dart';
+import 'package:comwell_key_app/share/cubit/share_booking_cubit.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+
+class ShareButton extends StatelessWidget {
+ final List<Guest> guests;
+ final Color? buttonColor;
+ final double userButtonSize;
+ final double userButtonOverlap;
+ final bool isMyBooking;
+
+ const ShareButton({
+ super.key,
+ required this.guests,
+ this.buttonColor,
+ this.userButtonSize = 45,
+ this.userButtonOverlap = 10,
+ this.isMyBooking = false,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final cubit = context.read<BookingDetailsCubit>();
+ final booking = cubit.booking;
+
+ final numberOfUsers = guests.length;
+ final allInitials = generateInitials(guests);
+
+ return Stack(
+ key: Key(guests.length.toString()),
+ children: [
+ Align(
+ alignment: Alignment.topLeft,
+ child: Padding(
+ padding: EdgeInsets.only(left: isMyBooking ? 40 : 16.0, top: isMyBooking ? 24 : 16),
+ child: Stack(
+ children: [
+ Positioned(
+ left:
+ (numberOfUsers - 1) * (userButtonSize - userButtonOverlap) +
+ userButtonSize -
+ userButtonOverlap,
+ child: SizedBox(
+ width: userButtonSize,
+ height: userButtonSize,
+ child: ElevatedButton(
+ onPressed: () {
+ context.push(AppRoutes.shareBooking, extra: booking);
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: buttonColor ?? sandColor[10],
+ elevation: 0,
+ shape: const CircleBorder(),
+ padding: EdgeInsets.zero,
+ ),
+ child: Center(
+ child: Icon(
+ Icons.add,
+ color: colorHeadlineText,
+ size: isMyBooking ? 20 : 30,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ...List.generate(
+ numberOfUsers,
+ (index) => Positioned(
+ left: (numberOfUsers - 1 - index) * (userButtonSize - userButtonOverlap),
+ child: SizedBox(
+ width: userButtonSize,
+ height: userButtonSize,
+ child: ElevatedButton(
+ onPressed: () async {
+ if (isMyBooking) {
+ return;
+ }
+ final results = await _showGuestList(
+ context,
+ index,
+ guests,
+ booking,
+ );
+
+ if (results is List<String>) {
+ final updatedBooking = booking.updateGuests(results);
+ cubit.updateBooking(updatedBooking);
+ }
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: index % 2 == 0 ? sandColor : colorTertiary,
+ elevation: 0,
+ shape: const CircleBorder(),
+ padding: EdgeInsets.zero,
+ ),
+ child: Center(
+ child: Text(
+ allInitials.elementAt(index),
+ style: TextStyle(
+ color: colorBackground,
+ fontWeight: FontWeight.w400,
+ fontSize: isMyBooking ? 14 : 18,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Future<dynamic> _showGuestList(
+ BuildContext context,
+ int index,
+ List<Guest> guests,
+ Booking booking,
+ ) async {
+ final theme = Theme.of(context);
+ final selectedGuests = guests.map((e) => e.id.toString()).toList();
+ return showModalBottomSheet<dynamic>(
+ context: context,
+ backgroundColor: colorBackground,
+ isScrollControlled: false,
+ builder: (BuildContext bottomSheetContext) {
+ return BlocProvider(
+ create: (context) => ShareBookingCubit(locator(), booking: booking),
+ child: BlocConsumer<ShareBookingCubit, ShareBookingState>(
+ listener: (context, state) {},
+ builder: (context, state) {
+ final cubit = context.read<ShareBookingCubit>();
+
+ return ClipRRect(
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(16),
+ topRight: Radius.circular(16),
+ ),
+ child: Scaffold(
+ body: SizedBox(
+ width: double.infinity,
+ height: 450,
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ context.strings.handle_guests_title,
+ style: theme.textTheme.titleLarge?.copyWith(
+ color: colorTertiary,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: sandColor[40],
+ shape: const CircleBorder(),
+ elevation: 0,
+ minimumSize: const Size(40, 40),
+ ),
+ child: const Icon(Icons.close, color: colorTertiary),
+ onPressed: () {
+ Navigator.pop(bottomSheetContext);
+ },
+ ),
+ ],
+ ),
+ ),
+ Builder(
+ builder: (context) {
+ if (state.isLoading) {
+ return const CircularProgressIndicator();
+ }
+ if (state.error != null) {
+ return ComwellErrorWidget(
+ title: context.strings.share_booking_error_title,
+ subtitle: context.strings.share_booking_error_subtitle,
+ border: true,
+ );
+ }
+ return Expanded(
+ child: GuestList(
+ guests: guests,
+ selectedGuests: selectedGuests,
+ onGuestSelected: (Iterable<String> newSelection) {
+ cubit.updateSelectedGuests(
+ newSelection.map(
+ (e) => Guest(
+ id: int.parse(e),
+ firstName: e.split(' ')[0],
+ lastName: e.split(' ')[1],
+ ),
+ ),
+ );
+ },
+ onGuestRemoved: (int guestId) {
+ cubit.removeGuests([guestId]);
+ },
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ bottomNavigationBar: state.error != null
+ ? null
+ : SafeArea(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Divider(
+ height: 1,
+ color: Colors.grey.shade300,
+ ),
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: ElevatedButton(
+ onPressed: state.selectedGuests.isNotEmpty
+ ? () async {
+ final shouldRemove = await showDialog<bool>(
+ context: bottomSheetContext,
+ builder: (context) => _buildRemoveGuestDialog(
+ context,
+ cubit,
+ bottomSheetContext,
+ selectedGuests,
+ ),
+ );
+
+ if (shouldRemove == true && context.mounted) {
+ context.pop(state.selectedGuests);
+ }
+ }
+ : null,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: state.selectedGuests.isNotEmpty
+ ? colorTertiary
+ : const Color(0xffE0E0E0),
+ minimumSize: const Size.fromHeight(50),
+ elevation: 0,
+ ),
+ child: Text(
+ context.strings.cancel_sharing,
+ style: TextStyle(
+ color: state.selectedGuests.isNotEmpty
+ ? colorBackground
+ : Colors.grey[500],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildRemoveGuestDialog(
+ BuildContext context,
+ ShareBookingCubit cubit,
+ BuildContext bottomSheetContext,
+ Iterable<String> selectedGuests,
+ ) {
+ return AlertDialog(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ contentPadding: const EdgeInsets.all(24.0),
+ backgroundColor: colorBackground,
+ title: Center(
+ child: Text(
+ context.strings.are_you_sure,
+ style: const TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ context.strings.guest_removal_responsibility,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.grey[500],
+ ),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pop(bottomSheetContext, true);
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: colorBackground,
+ side: BorderSide(color: Colors.grey[300]!, width: 1),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(25),
+ ),
+ elevation: 0,
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ minimumSize: const Size.fromHeight(50),
+ ),
+ child: Text(
+ selectedGuests.length > 1 ? context.strings.remove_guests : context.strings.remove_guest,
+ style: const TextStyle(
+ color: colorTertiary,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ const SizedBox(height: 12),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pop(bottomSheetContext, false);
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: sandColor,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(25),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 16),
+ minimumSize: const Size.fromHeight(50),
+ ),
+ child: Text(
+ context.strings.cancel,
+ style: const TextStyle(
+ color: colorBackground,
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ actions: const [],
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/booking_details/components/unlock_room_button.dart b/comwell_key_app/lib/presentation/screens/booking_details/components/unlock_room_button.dart
new file mode 100644
index 00000000..4db5de7d
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/booking_details/components/unlock_room_button.dart
@@ -0,0 +1,44 @@
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:go_router/go_router.dart';
+import 'package:slider_button/slider_button.dart';
+
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:comwell_key_app/themes/light_theme.dart';
+
+class UnlockRoomButton extends StatelessWidget {
+ final String roomNumber;
+
+ const UnlockRoomButton({super.key, required this.roomNumber});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final width = MediaQuery.of(context).size.width;
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: SliderButton(
+ backgroundColor: sandColor,
+ width: width - 32,
+ vibrationFlag: true,
+ icon: Center(
+ child: SvgPicture.asset(
+ 'assets/icons/Union.svg',
+ width: 30,
+ ),
+ ),
+ action: () async {
+ context.push(AppRoutes.key, extra: roomNumber);
+ return false;
+ },
+ alignLabel: Alignment.center,
+ label: Text(
+ context.strings.open_room,
+ style: theme.textTheme.headlineSmall?.copyWith(color: Colors.white),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/received_shared_booking/bloc/received_shared_booking_cubit.dart b/comwell_key_app/lib/presentation/screens/received_shared_booking/bloc/received_shared_booking_cubit.dart
index a48dae74..9db1c3bf 100644
--- a/comwell_key_app/lib/presentation/screens/received_shared_booking/bloc/received_shared_booking_cubit.dart
+++ b/comwell_key_app/lib/presentation/screens/received_shared_booking/bloc/received_shared_booking_cubit.dart
@@ -1,9 +1,9 @@
import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/presentation/base/base_cubit.dart';
import 'package:comwell_key_app/presentation/screens/received_shared_booking/bloc/received_shared_booking_state.dart';
-import '../../../../booking_details/booking_details_repository.dart';
import '../../../../share/share_booking_repository.dart';
class ReceivedSharedBookingCubit extends BaseCubit<ReceivedSharedBookingState> {
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index fd2e69e7..1103c25c 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -1,5 +1,4 @@
import 'package:comwell_key_app/authentication/authentication_repository.dart';
-import 'package:comwell_key_app/booking_details/booking_details_route.dart';
import 'package:comwell_key_app/check_in/check_in_route.dart';
import 'package:comwell_key_app/check_out/checkout_routes.dart';
import 'package:comwell_key_app/choose_share_room/choose_share_room_route.dart';
@@ -8,6 +7,7 @@ import 'package:comwell_key_app/housekeeping/house_keeping_route.dart';
import 'package:comwell_key_app/my_booking/my_booking_route.dart';
import 'package:comwell_key_app/notifications/notifications_route.dart';
import 'package:comwell_key_app/pregistration/preregistration_route.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/booking_details_route.dart';
import 'package:comwell_key_app/presentation/screens/change_password/change_password_route.dart';
import 'package:comwell_key_app/presentation/screens/concierge/concierge_route.dart';
import 'package:comwell_key_app/presentation/screens/payment_processing/payment_processing_route.dart';
diff --git a/comwell_key_app/lib/up_sales/components/catalog/service_catalog.dart b/comwell_key_app/lib/up_sales/components/catalog/service_catalog.dart
index 76c84793..250f1dd4 100644
--- a/comwell_key_app/lib/up_sales/components/catalog/service_catalog.dart
+++ b/comwell_key_app/lib/up_sales/components/catalog/service_catalog.dart
@@ -1,4 +1,4 @@
-import 'package:comwell_key_app/booking_details/bloc/booking_details_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/booking_details/bloc/booking_details_cubit.dart';
import 'package:comwell_key_app/common/const.dart';
import 'package:comwell_key_app/overview/models/booking.dart';
import 'package:comwell_key_app/routing/app_routes.dart';
diff --git a/comwell_key_app/lib/utils/locator.dart b/comwell_key_app/lib/utils/locator.dart
index 37ab6861..a88bff99 100644
--- a/comwell_key_app/lib/utils/locator.dart
+++ b/comwell_key_app/lib/utils/locator.dart
@@ -5,6 +5,7 @@ import 'package:comwell_key_app/contact/repository/contact_repository.dart';
import 'package:comwell_key_app/data/remote/msal_service.dart';
import 'package:comwell_key_app/database/comwell_db.dart';
import 'package:comwell_key_app/domain/repositories/bluetooth_repository.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/domain/repositories/internet_status_repository.dart';
import 'package:comwell_key_app/find_booking/find_booking_repository.dart';
import 'package:comwell_key_app/hotel_information/repository/hotel_information_repository.dart';
@@ -31,7 +32,6 @@ import 'package:get_it/get_it.dart';
import 'package:payment_plugin/domain/repositories/adyen_repository.dart';
import 'package:seos_mobile_keys_plugin/seos_mobile_keys_plugin.dart';
-import '../booking_details/booking_details_repository.dart';
final locator = GetIt.I;
diff --git a/comwell_key_app/test/key_test/key_bloc_test.dart b/comwell_key_app/test/key_test/key_bloc_test.dart
index db911b12..ce10b27c 100644
--- a/comwell_key_app/test/key_test/key_bloc_test.dart
+++ b/comwell_key_app/test/key_test/key_bloc_test.dart
@@ -1,5 +1,5 @@
import 'package:bloc_test/bloc_test.dart';
-import 'package:comwell_key_app/booking_details/booking_details_repository.dart';
+import 'package:comwell_key_app/domain/repositories/booking_details_repository.dart';
import 'package:comwell_key_app/key/bloc/key_bloc.dart';
import 'package:comwell_key_app/key/repository/key_repository.dart';
import 'package:comwell_key_app/utils/seos_repository.dart';