6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 0886c8a5
Changed files
.../ios/Runner.xcodeproj/project.pbxproj | 2 +- concierge/example/pubspec.lock | 36 ++++--- concierge/example/pubspec.yaml | 2 +- .../data/remote/api/concierge_service.g.dart | 2 +- .../data/remote/models/area_sub_category.g.dart | 8 +- .../_generated/data/remote/models/product.g.dart | 2 +- .../bloc/hotel_overview_page_state.freezed.dart | 49 ++++++---- concierge/lib/concierge.dart | 2 +- concierge/lib/concierge_app.dart | 2 +- .../lib/data/remote/api/concierge_service.dart | 8 +- .../lib/data/remote/models/area_sub_category.dart | 7 +- concierge/lib/data/remote/models/media_image.dart | 7 +- concierge/lib/data/remote/models/product.dart | 2 +- .../domain/repositories/property_repository.dart | 104 +++++++++++++++++++++ concierge/lib/presentation/navigation/router.dart | 2 +- .../bloc/hotel_overview_page_cubit.dart | 18 ++++ .../bloc/hotel_overview_page_state.dart | 2 + .../hotel_overview_page_screen.dart | 71 +++++++++++++- .../widgets/product_list_tile.dart | 57 +++++++++++ .../lib/presentation/widgets/padded_column.dart | 26 ++++++ concierge/pubspec.yaml | 2 + 21 files changed, 350 insertions(+), 61 deletions(-)
Diff
diff --git a/comwell_key_app/ios/Runner.xcodeproj/project.pbxproj b/comwell_key_app/ios/Runner.xcodeproj/project.pbxproj
index ee2c44ba..e39c19aa 100644
--- a/comwell_key_app/ios/Runner.xcodeproj/project.pbxproj
+++ b/comwell_key_app/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 60;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
diff --git a/concierge/example/pubspec.lock b/concierge/example/pubspec.lock
index 539e0b94..9841a967 100644
--- a/concierge/example/pubspec.lock
+++ b/concierge/example/pubspec.lock
@@ -77,10 +77,10 @@ packages:
dependency: transitive
description:
name: characters
- sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+ sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
- version: "1.4.0"
+ version: "1.4.1"
checked_yaml:
dependency: transitive
description:
@@ -313,6 +313,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ fpdart:
+ dependency: transitive
+ description:
+ name: fpdart
+ sha256: f8e9d0989ba293946673e382c59ac513e30cb6746a9452df195f29e3357a73d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
freezed_annotation:
dependency: transitive
description:
@@ -354,10 +362,10 @@ packages:
dependency: "direct main"
description:
name: go_router
- sha256: d8f590a69729f719177ea68eb1e598295e8dbc41bbc247fed78b2c8a25660d7c
+ sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
url: "https://pub.dev"
source: hosted
- version: "16.3.0"
+ version: "17.1.0"
go_router_builder:
dependency: transitive
description:
@@ -463,26 +471,26 @@ packages:
dependency: transitive
description:
name: matcher
- sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+ sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
- version: "0.12.17"
+ version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+ sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
- version: "0.11.1"
+ version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
- sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.16.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -788,10 +796,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+ sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
- version: "0.7.6"
+ version: "0.7.9"
typed_data:
dependency: transitive
description:
@@ -897,5 +905,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
- dart: ">=3.8.1 <4.0.0"
- flutter: ">=3.32.0"
+ dart: ">=3.9.0 <4.0.0"
+ flutter: ">=3.35.0"
diff --git a/concierge/example/pubspec.yaml b/concierge/example/pubspec.yaml
index c906c058..89a8189f 100644
--- a/concierge/example/pubspec.yaml
+++ b/concierge/example/pubspec.yaml
@@ -25,7 +25,7 @@ dependencies:
# the parent directory to use the current plugin's version.
path: ../
- go_router: ^16.2.0
+ go_router: ^17.1.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
diff --git a/concierge/lib/_generated/data/remote/api/concierge_service.g.dart b/concierge/lib/_generated/data/remote/api/concierge_service.g.dart
index 8102688b..18b89352 100644
--- a/concierge/lib/_generated/data/remote/api/concierge_service.g.dart
+++ b/concierge/lib/_generated/data/remote/api/concierge_service.g.dart
@@ -80,7 +80,7 @@ class _ConciergeService implements ConciergeService {
}
@override
- Future<ApiResponse<Product>> getProduct(String productid) async {
+ Future<ApiResponse<Product>> getProduct(int productid) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
diff --git a/concierge/lib/_generated/data/remote/models/area_sub_category.g.dart b/concierge/lib/_generated/data/remote/models/area_sub_category.g.dart
index 48f9f821..89248a2c 100644
--- a/concierge/lib/_generated/data/remote/models/area_sub_category.g.dart
+++ b/concierge/lib/_generated/data/remote/models/area_sub_category.g.dart
@@ -12,9 +12,11 @@ AreaSubCategory _$AreaSubCategoryFromJson(Map json) => AreaSubCategory(
subTitle: json['sub_title'] as String,
heroTitle: json['hero_title'] as String,
description: json['description'] as String,
- heroImage: MediaImage.fromJson(
- Map<String, dynamic>.from(json['hero_image'] as Map),
- ),
+ heroImage: json['hero_image'] == null
+ ? MediaImage.empty()
+ : MediaImage.fromJson(
+ Map<String, dynamic>.from(json['hero_image'] as Map),
+ ),
products: (json['products'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
diff --git a/concierge/lib/_generated/data/remote/models/product.g.dart b/concierge/lib/_generated/data/remote/models/product.g.dart
index 035604cc..556639c4 100644
--- a/concierge/lib/_generated/data/remote/models/product.g.dart
+++ b/concierge/lib/_generated/data/remote/models/product.g.dart
@@ -13,7 +13,7 @@ Product _$ProductFromJson(Map json) => Product(
body: json['body'] as String,
allergies: json['allergies'] as String,
price: (json['price'] as num).toDouble(),
- priceComwellClub: (json['price_comwell_club'] as num).toDouble(),
+ priceComwellClub: (json['price_comwell_club'] as num?)?.toDouble(),
estimatedDeliveryTime: json['estimated_delivery_time'] as String,
isNew: const FlagConverter().fromJson((json['is_new'] as num).toInt()),
images: (json['images'] as List<dynamic>)
diff --git a/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart b/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart
index ef3f123c..5a3ff1b5 100644
--- a/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart
+++ b/concierge/lib/_generated/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.freezed.dart
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$HotelOverviewPageState {
- bool get isLoading; String get errorMessage; int get selectedAreaId; Map<int, DataState> get areas; Property? get property;
+ bool get isLoading; String get errorMessage; int get selectedAreaId; Map<int, DataState> get areas; Map<int, Product> get products; Property? get property;
/// Create a copy of HotelOverviewPageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $HotelOverviewPageStateCopyWith<HotelOverviewPageState> get copyWith => _$HotelO
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId)&&const DeepCollectionEquality().equals(other.areas, areas)&&(identical(other.property, property) || other.property == property));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId)&&const DeepCollectionEquality().equals(other.areas, areas)&&const DeepCollectionEquality().equals(other.products, products)&&(identical(other.property, property) || other.property == property));
}
@override
-int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,selectedAreaId,const DeepCollectionEquality().hash(areas),property);
+int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,selectedAreaId,const DeepCollectionEquality().hash(areas),const DeepCollectionEquality().hash(products),property);
@override
String toString() {
- return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, selectedAreaId: $selectedAreaId, areas: $areas, property: $property)';
+ return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, selectedAreaId: $selectedAreaId, areas: $areas, products: $products, property: $property)';
}
@@ -45,7 +45,7 @@ abstract mixin class $HotelOverviewPageStateCopyWith<$Res> {
factory $HotelOverviewPageStateCopyWith(HotelOverviewPageState value, $Res Function(HotelOverviewPageState) _then) = _$HotelOverviewPageStateCopyWithImpl;
@useResult
$Res call({
- bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Property? property
+ bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Map<int, Product> products, Property? property
});
@@ -62,13 +62,14 @@ class _$HotelOverviewPageStateCopyWithImpl<$Res>
/// Create a copy of HotelOverviewPageState
/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? errorMessage = null,Object? selectedAreaId = null,Object? areas = null,Object? property = freezed,}) {
+@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? errorMessage = null,Object? selectedAreaId = null,Object? areas = null,Object? products = null,Object? property = freezed,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,selectedAreaId: null == selectedAreaId ? _self.selectedAreaId : selectedAreaId // ignore: cast_nullable_to_non_nullable
as int,areas: null == areas ? _self.areas : areas // ignore: cast_nullable_to_non_nullable
-as Map<int, DataState>,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
+as Map<int, DataState>,products: null == products ? _self.products : products // ignore: cast_nullable_to_non_nullable
+as Map<int, Product>,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
as Property?,
));
}
@@ -154,10 +155,10 @@ return $default(_that);case _:
/// }
/// ```
-@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Property? property)? $default,{required TResult orElse(),}) {final _that = this;
+@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Map<int, Product> products, Property? property)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _HotelOverviewPageState() when $default != null:
-return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.property);case _:
+return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.products,_that.property);case _:
return orElse();
}
@@ -175,10 +176,10 @@ return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.ar
/// }
/// ```
-@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Property? property) $default,) {final _that = this;
+@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Map<int, Product> products, Property? property) $default,) {final _that = this;
switch (_that) {
case _HotelOverviewPageState():
-return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.property);case _:
+return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.products,_that.property);case _:
throw StateError('Unexpected subclass');
}
@@ -195,10 +196,10 @@ return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.ar
/// }
/// ```
-@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Property? property)? $default,) {final _that = this;
+@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Map<int, Product> products, Property? property)? $default,) {final _that = this;
switch (_that) {
case _HotelOverviewPageState() when $default != null:
-return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.property);case _:
+return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.areas,_that.products,_that.property);case _:
return null;
}
@@ -210,7 +211,7 @@ return $default(_that.isLoading,_that.errorMessage,_that.selectedAreaId,_that.ar
class _HotelOverviewPageState implements HotelOverviewPageState {
- const _HotelOverviewPageState({this.isLoading = false, this.errorMessage = "", this.selectedAreaId = 0, final Map<int, DataState> areas = const {}, this.property}): _areas = areas;
+ const _HotelOverviewPageState({this.isLoading = false, this.errorMessage = "", this.selectedAreaId = 0, final Map<int, DataState> areas = const {}, final Map<int, Product> products = const {}, this.property}): _areas = areas,_products = products;
@override@JsonKey() final bool isLoading;
@@ -223,6 +224,13 @@ class _HotelOverviewPageState implements HotelOverviewPageState {
return EqualUnmodifiableMapView(_areas);
}
+ final Map<int, Product> _products;
+@override@JsonKey() Map<int, Product> get products {
+ if (_products is EqualUnmodifiableMapView) return _products;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableMapView(_products);
+}
+
@override final Property? property;
/// Create a copy of HotelOverviewPageState
@@ -235,16 +243,16 @@ _$HotelOverviewPageStateCopyWith<_HotelOverviewPageState> get copyWith => __$Hot
@override
bool operator ==(Object other) {
- return identical(this, other) || (other.runtimeType == runtimeType&&other is _HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId)&&const DeepCollectionEquality().equals(other._areas, _areas)&&(identical(other.property, property) || other.property == property));
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _HotelOverviewPageState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.selectedAreaId, selectedAreaId) || other.selectedAreaId == selectedAreaId)&&const DeepCollectionEquality().equals(other._areas, _areas)&&const DeepCollectionEquality().equals(other._products, _products)&&(identical(other.property, property) || other.property == property));
}
@override
-int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,selectedAreaId,const DeepCollectionEquality().hash(_areas),property);
+int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,selectedAreaId,const DeepCollectionEquality().hash(_areas),const DeepCollectionEquality().hash(_products),property);
@override
String toString() {
- return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, selectedAreaId: $selectedAreaId, areas: $areas, property: $property)';
+ return 'HotelOverviewPageState(isLoading: $isLoading, errorMessage: $errorMessage, selectedAreaId: $selectedAreaId, areas: $areas, products: $products, property: $property)';
}
@@ -255,7 +263,7 @@ abstract mixin class _$HotelOverviewPageStateCopyWith<$Res> implements $HotelOve
factory _$HotelOverviewPageStateCopyWith(_HotelOverviewPageState value, $Res Function(_HotelOverviewPageState) _then) = __$HotelOverviewPageStateCopyWithImpl;
@override @useResult
$Res call({
- bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Property? property
+ bool isLoading, String errorMessage, int selectedAreaId, Map<int, DataState> areas, Map<int, Product> products, Property? property
});
@@ -272,13 +280,14 @@ class __$HotelOverviewPageStateCopyWithImpl<$Res>
/// Create a copy of HotelOverviewPageState
/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? errorMessage = null,Object? selectedAreaId = null,Object? areas = null,Object? property = freezed,}) {
+@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? errorMessage = null,Object? selectedAreaId = null,Object? areas = null,Object? products = null,Object? property = freezed,}) {
return _then(_HotelOverviewPageState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,selectedAreaId: null == selectedAreaId ? _self.selectedAreaId : selectedAreaId // ignore: cast_nullable_to_non_nullable
as int,areas: null == areas ? _self._areas : areas // ignore: cast_nullable_to_non_nullable
-as Map<int, DataState>,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
+as Map<int, DataState>,products: null == products ? _self._products : products // ignore: cast_nullable_to_non_nullable
+as Map<int, Product>,property: freezed == property ? _self.property : property // ignore: cast_nullable_to_non_nullable
as Property?,
));
}
diff --git a/concierge/lib/concierge.dart b/concierge/lib/concierge.dart
index 537312e1..f1909851 100644
--- a/concierge/lib/concierge.dart
+++ b/concierge/lib/concierge.dart
@@ -8,4 +8,4 @@ class Concierge {
Future<String?> getPlatformVersion() {
return ConciergePlatform.instance.getPlatformVersion();
}
-}
+}
\ No newline at end of file
diff --git a/concierge/lib/concierge_app.dart b/concierge/lib/concierge_app.dart
index a1e066cd..9ca6e1a3 100644
--- a/concierge/lib/concierge_app.dart
+++ b/concierge/lib/concierge_app.dart
@@ -101,7 +101,7 @@ class ConciergeApp extends StatelessWidget {
child: Builder(
builder: (context) {
return MaterialApp.router(
- routerConfig: router(),
+ routerConfig: router,
);
},
),
diff --git a/concierge/lib/data/remote/api/concierge_service.dart b/concierge/lib/data/remote/api/concierge_service.dart
index 8e21127e..ec645653 100644
--- a/concierge/lib/data/remote/api/concierge_service.dart
+++ b/concierge/lib/data/remote/api/concierge_service.dart
@@ -17,11 +17,11 @@ abstract class ConciergeService {
@GET("/hotels/domains/{hotelCode}")
Future<ApiResponse<Property>> getHotelOverview(@Path("hotelCode") String hotelCode);
- @GET("/areas/{areaid}")
- Future<ApiResponse<AreaDetails>> getArea(@Path("areaid") int areaId);
+ @GET("/areas/{areaId}")
+ Future<ApiResponse<AreaDetails>> getArea(@Path("areaId") int areaId);
- @GET("/products/{productid}")
- Future<ApiResponse<Product>> getProduct(@Path("productid") String productid);
+ @GET("/products/{productId}")
+ Future<ApiResponse<Product>> getProduct(@Path("productId") int productid);
@POST("/orders/review")
Future<ApiResponse<OrderReview>> getOrderReview(@Body() Map<String, dynamic> body);
diff --git a/concierge/lib/data/remote/models/area_sub_category.dart b/concierge/lib/data/remote/models/area_sub_category.dart
index 2450dc1e..815903d2 100644
--- a/concierge/lib/data/remote/models/area_sub_category.dart
+++ b/concierge/lib/data/remote/models/area_sub_category.dart
@@ -1,3 +1,4 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'media_image.dart';
@@ -11,6 +12,7 @@ class AreaSubCategory {
final String subTitle;
final String heroTitle;
final String description;
+ @JsonKey(defaultValue: MediaImage.empty)
final MediaImage heroImage;
final List<int> products;
@@ -24,10 +26,7 @@ class AreaSubCategory {
required this.products,
});
- factory AreaSubCategory.fromJson(Map<String, dynamic> json) =>
- _$AreaSubCategoryFromJson(json);
+ factory AreaSubCategory.fromJson(Map<String, dynamic> json) => _$AreaSubCategoryFromJson(json);
Map<String, dynamic> toJson() => _$AreaSubCategoryToJson(this);
}
-
-
diff --git a/concierge/lib/data/remote/models/media_image.dart b/concierge/lib/data/remote/models/media_image.dart
index b2671ef2..704e3b5f 100644
--- a/concierge/lib/data/remote/models/media_image.dart
+++ b/concierge/lib/data/remote/models/media_image.dart
@@ -18,10 +18,9 @@ class MediaImage {
required this.alt,
});
- factory MediaImage.fromJson(Map<String, dynamic> json) =>
- _$MediaImageFromJson(json);
+ static empty() => const MediaImage(url: "", preload: "", thumbnail: "", productHero: "", alt: "");
+
+ factory MediaImage.fromJson(Map<String, dynamic> json) => _$MediaImageFromJson(json);
Map<String, dynamic> toJson() => _$MediaImageToJson(this);
}
-
-
diff --git a/concierge/lib/data/remote/models/product.dart b/concierge/lib/data/remote/models/product.dart
index b4e38cb9..212814da 100644
--- a/concierge/lib/data/remote/models/product.dart
+++ b/concierge/lib/data/remote/models/product.dart
@@ -12,7 +12,7 @@ class Product {
final String body;
final String allergies;
final double price;
- final double priceComwellClub;
+ final double? priceComwellClub;
final String estimatedDeliveryTime;
@FlagConverter()
final bool isNew;
diff --git a/concierge/lib/domain/repositories/property_repository.dart b/concierge/lib/domain/repositories/property_repository.dart
index 7a45c61a..b3563fe4 100644
--- a/concierge/lib/domain/repositories/property_repository.dart
+++ b/concierge/lib/domain/repositories/property_repository.dart
@@ -2,6 +2,7 @@ import 'package:concierge/data/remote/api/concierge_service.dart';
import 'package:concierge/data/remote/models/area_details.dart';
import 'package:concierge/data/remote/models/order.dart';
import 'package:concierge/data/remote/models/order_review.dart';
+import 'package:concierge/data/remote/models/product.dart';
import 'package:concierge/data/remote/models/property.dart';
class PropertyRepository {
@@ -15,10 +16,16 @@ class PropertyRepository {
}
Future<AreaDetails> getArea(int areaId) async {
+ return AreaDetails.fromJson(mockAreaResponse);
final response = await _service.getArea(areaId);
return response.data;
}
+ Future<Product> getProduct(int productId) async {
+ final response = await _service.getProduct(productId);
+ return response.data;
+ }
+
Future<Order> createOrder() async {
final body = {
"area_id": 70,
@@ -64,3 +71,100 @@ class PropertyRepository {
return response.data;
}
}
+
+const mockAreaResponse = {
+ "id": 28,
+ "name": "Værelse",
+ "pickup_text": "Din bestilling skal afhentes i baren ved receptionen",
+ "delivery_text": "til værelse",
+ "is_open": 1,
+ "is_meeting_area": 0,
+ "is_guest_area": 1,
+ "is_prearrival_area": 0,
+ "theme": "SAND",
+ "icon": "BED",
+ "delivery_price": 75,
+ "is_delivery_default": 1,
+ "has_delivery": 1,
+ "events": [
+ {
+ "id": 37,
+ "title": "Værelse",
+ "sub_title":
+ "Nyd ro og maksimal afslapning på vores hyggelige værelser. Få din mad og drikke bragt direkte til døren",
+ "cta_link": null,
+ "hero_image": {
+ "url":
+ "https://admin-stage.concierge.comwell.com/storage/media/754/RS20693_Standard-double-room-Borupgaard_Helsingør3.jpg",
+ "preload":
+ "https://admin-stage.concierge.comwell.com/storage/media/754/conversions/RS20693_Standard-double-room-Borupgaard_Helsingør3-preload.jpg",
+ "thumbnail":
+ "https://admin-stage.concierge.comwell.com/storage/media/754/conversions/RS20693_Standard-double-room-Borupgaard_Helsingør3-thumbnail.jpg",
+ "product_hero":
+ "https://admin-stage.concierge.comwell.com/storage/media/754/conversions/RS20693_Standard-double-room-Borupgaard_Helsingør3-product_hero.jpg",
+ "alt": "RS20693_Standard-double-room-Borupgaard_Helsingør3.jpg",
+ },
+ },
+ ],
+ "skip_location": false,
+ "categories": [
+ {
+ "id": 22,
+ "name": "Sodavand, tonic, juice m.m.",
+ "sub_categories": [
+ {
+ "id": 55,
+ "name": "Sodavand",
+ "sub_title": "",
+ "hero_title": "Alle de klassiske favoritter",
+ "description": "",
+ "hero_image": {
+ "url":
+ "https://admin-stage.concierge.comwell.com/storage/media/2509/Comwell_Consierge_RAIS1684.jpg",
+ "preload":
+ "https://admin-stage.concierge.comwell.com/storage/media/2509/conversions/Comwell_Consierge_RAIS1684-preload.jpg",
+ "thumbnail":
+ "https://admin-stage.concierge.comwell.com/storage/media/2509/conversions/Comwell_Consierge_RAIS1684-thumbnail.jpg",
+ "product_hero":
+ "https://admin-stage.concierge.comwell.com/storage/media/2509/conversions/Comwell_Consierge_RAIS1684-product_hero.jpg",
+ "alt": "Comwell_Consierge_RAIS1684.jpg",
+ },
+ "products": [1603, 1600, 1597, 1594, 1609, 1591],
+ },
+ {
+ "id": 292,
+ "name": "Juice & saft",
+ "sub_title": "",
+ "hero_title": "Få dig en forfriskende juice el. saft",
+ "description": "",
+ "hero_image": {
+ "url":
+ "https://admin-stage.concierge.comwell.com/storage/media/2512/Comwell_Consierge_RAIS1712.jpg",
+ "preload":
+ "https://admin-stage.concierge.comwell.com/storage/media/2512/conversions/Comwell_Consierge_RAIS1712-preload.jpg",
+ "thumbnail":
+ "https://admin-stage.concierge.comwell.com/storage/media/2512/conversions/Comwell_Consierge_RAIS1712-thumbnail.jpg",
+ "product_hero":
+ "https://admin-stage.concierge.comwell.com/storage/media/2512/conversions/Comwell_Consierge_RAIS1712-product_hero.jpg",
+ "alt": "Comwell_Consierge_RAIS1712.jpg",
+ },
+ "products": [1585, 1579, 1582, 1588],
+ },
+ ],
+ },
+ ],
+ "promotion": {"title": "", "products": []},
+ "delivery_times": [
+ "17:10",
+ "17:25",
+ "17:40",
+ "17:55",
+ "18:10",
+ "18:25",
+ "18:40",
+ "18:55",
+ "19:10",
+ "19:25",
+ ],
+ "support_items": [],
+};
diff --git a/concierge/lib/presentation/navigation/router.dart b/concierge/lib/presentation/navigation/router.dart
index 397b364a..bcd26ffa 100644
--- a/concierge/lib/presentation/navigation/router.dart
+++ b/concierge/lib/presentation/navigation/router.dart
@@ -8,7 +8,7 @@ import '../screens/hotel_overview_page/hotel_overview_page_route.dart';
final rootNavigatorKey = GlobalKey<NavigatorState>();
-GoRouter router() => GoRouter(
+GoRouter router = GoRouter(
navigatorKey: rootNavigatorKey,
observers: [],
initialLocation: AppRoutes.hotelOverviewPage,
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart
index 01acf044..8a75f5b4 100644
--- a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart
+++ b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart
@@ -3,6 +3,7 @@ import 'package:concierge/domain/models/data_state.dart';
import 'package:concierge/domain/repositories/property_repository.dart';
import 'package:concierge/presentation/base/base_cubit.dart';
import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart';
+import 'package:fpdart/fpdart.dart';
class HotelOverviewPageCubit extends BaseCubit<HotelOverviewPageState> {
final String hotelCode;
@@ -39,6 +40,14 @@ class HotelOverviewPageCubit extends BaseCubit<HotelOverviewPageState> {
try {
_updateAreaDataState(areaId, DataState.loading);
final response = await _propertyRepository.getArea(areaId);
+ final productIds = response.categories
+ .map((cat) => cat.subCategories)
+ .flatten
+ .map((sub) => sub.products)
+ .flatten
+ .toSet();
+ print("qqq loadArea=$areaId, products=${productIds.length}");
+ await _loadProducts(productIds);
_updateAreaDataState(areaId, DataState.success(response));
} catch (e, st) {
handleError(e, st);
@@ -46,6 +55,15 @@ class HotelOverviewPageCubit extends BaseCubit<HotelOverviewPageState> {
}
}
+ Future<void> _loadProducts(Iterable<int> pIds) async {
+ final futures = pIds.map(_propertyRepository.getProduct);
+ final products = await Future.wait(futures);
+ final pMap = Map.fromEntries(products.map((p) => MapEntry(p.id, p)));
+ final stateCopy = Map.of(state.products);
+ stateCopy.addAll(pMap);
+ safeEmit(state.copyWith(products: stateCopy));
+ }
+
void _updateAreaDataState(int areaId, DataState dataState) {
final copy = Map.of(state.areas);
copy[areaId] = dataState;
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart
index 9f1b0e17..31fa47c5 100644
--- a/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart
+++ b/concierge/lib/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart
@@ -1,3 +1,4 @@
+import 'package:concierge/data/remote/models/product.dart';
import 'package:concierge/data/remote/models/property.dart';
import 'package:concierge/domain/models/data_state.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,6 +12,7 @@ abstract class HotelOverviewPageState with _$HotelOverviewPageState {
@Default("") String errorMessage,
@Default(0) int selectedAreaId,
@Default({}) Map<int, DataState> areas,
+ @Default({}) Map<int, Product> products,
Property? property,
}) = _HotelOverviewPageState;
}
\ No newline at end of file
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart
index 37b04827..b27ee560 100644
--- a/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart
+++ b/concierge/lib/presentation/screens/hotel_overview_page/hotel_overview_page_screen.dart
@@ -1,10 +1,14 @@
+import 'package:concierge/data/remote/models/area_category.dart';
import 'package:concierge/data/remote/models/area_details.dart';
+import 'package:concierge/data/remote/models/area_sub_category.dart';
import 'package:concierge/domain/models/data_state.dart';
+import 'package:concierge/presentation/screens/hotel_overview_page/widgets/product_list_tile.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_cubit.dart';
import 'package:concierge/presentation/screens/hotel_overview_page/bloc/hotel_overview_page_state.dart';
import 'package:concierge/presentation/widgets/hotel_overview_app_bar.dart';
+import 'package:gap/gap.dart';
class HotelOverviewPageScreen extends StatelessWidget {
const HotelOverviewPageScreen({super.key});
@@ -40,10 +44,11 @@ class HotelOverviewPageScreen extends StatelessWidget {
return Center(child: Text((areaState).error.toString()));
}
final areaDetails = (areaState as Success<AreaDetails>).data;
- return SingleChildScrollView(
- child: Column(
- children: [Text(areaDetails.toJson().toString())],
- ),
+ return CustomScrollView(
+ slivers: [
+ ...buildCategories(context, areaDetails.categories),
+ const SliverToBoxAdapter(child: SizedBox(height: 100)),
+ ],
);
},
),
@@ -52,4 +57,62 @@ class HotelOverviewPageScreen extends StatelessWidget {
},
);
}
+
+ Iterable<SingleChildRenderObjectWidget> buildCategories(
+ BuildContext context,
+ List<AreaCategory> categories,
+ ) sync* {
+ for (final category in categories) {
+ yield SliverToBoxAdapter(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Text(
+ category.name,
+ style: TextStyle(fontSize: 24),
+ ),
+ ),
+ );
+ yield SliverToBoxAdapter(child: Gap(16));
+ yield* buildSubCategories(
+ context,
+ category.subCategories,
+ );
+ }
+ }
+
+ Iterable<SingleChildRenderObjectWidget> buildSubCategories(
+ BuildContext context,
+ List<AreaSubCategory> subCategories,
+ ) sync* {
+ for (final category in subCategories) {
+ final products = category.products;
+ yield SliverToBoxAdapter(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Text(
+ category.name,
+ style: TextStyle(fontSize: 16),
+ ),
+ ),
+ );
+ yield SliverToBoxAdapter(
+ child: SizedBox(
+ height: 320,
+ child: ListView.builder(
+ itemCount: products.length,
+ scrollDirection: Axis.horizontal,
+ padding: EdgeInsets.symmetric(horizontal: 12),
+ itemBuilder: (context, index) {
+ final pid = products[index];
+ final product = context.read<HotelOverviewPageCubit>().state.products[pid]!;
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 4.0),
+ child: ProductListTile(product: product),
+ );
+ },
+ ),
+ ),
+ );
+ }
+ }
}
diff --git a/concierge/lib/presentation/screens/hotel_overview_page/widgets/product_list_tile.dart b/concierge/lib/presentation/screens/hotel_overview_page/widgets/product_list_tile.dart
new file mode 100644
index 00000000..ace8720c
--- /dev/null
+++ b/concierge/lib/presentation/screens/hotel_overview_page/widgets/product_list_tile.dart
@@ -0,0 +1,57 @@
+import 'package:concierge/data/remote/models/product.dart';
+import 'package:concierge/presentation/widgets/padded_column.dart';
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+
+class ProductListTile extends StatelessWidget {
+ const ProductListTile({super.key, required this.product});
+
+ final Product product;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: MediaQuery.of(context).size.width * 0.8,
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.grey, width: 1),
+ borderRadius: BorderRadius.all(Radius.circular(16)),
+ ),
+ clipBehavior: Clip.antiAlias,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (product.images.isNotEmpty)
+ Image.network(
+ product.images.first.url,
+ fit: BoxFit.fitWidth,
+ height: 180,
+ width: double.infinity,
+ )
+ else
+ SizedBox(height: 180),
+ PaddedColumn(
+ padding: EdgeInsets.all(16),
+ children: [
+ Row(
+ children: [
+ Icon(Icons.watch_later_outlined),
+ Text(product.estimatedDeliveryTime),
+ ],
+ ),
+ Gap(16),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(product.title),
+ Text(product.price.toString()),
+ ],
+ ),
+ Gap(8),
+ Text(product.subTitle),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/concierge/lib/presentation/widgets/padded_column.dart b/concierge/lib/presentation/widgets/padded_column.dart
new file mode 100644
index 00000000..fd069dad
--- /dev/null
+++ b/concierge/lib/presentation/widgets/padded_column.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/cupertino.dart';
+
+class PaddedColumn extends StatelessWidget {
+ const PaddedColumn({
+ super.key,
+ required this.padding,
+ required this.children,
+ this.crossAxisAlignment = CrossAxisAlignment.start,
+ });
+
+ final EdgeInsets padding;
+ final List<Widget> children;
+ final CrossAxisAlignment crossAxisAlignment;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: padding,
+ child: Column(
+ crossAxisAlignment: crossAxisAlignment,
+ mainAxisSize: MainAxisSize.min,
+ children: children,
+ ),
+ );
+ }
+}
diff --git a/concierge/pubspec.yaml b/concierge/pubspec.yaml
index 988e9edb..57bd382b 100644
--- a/concierge/pubspec.yaml
+++ b/concierge/pubspec.yaml
@@ -28,6 +28,8 @@ dependencies:
go_router_builder: ^4.1.0
flutter_svg: ^2.2.1
flutter_secure_storage: ^9.2.4
+ fpdart: ^1.1.1
+
dev_dependencies:
flutter_test:
sdk: flutter