Skip to content

Commit 0abf130

Browse files
authored
Hotel details. (#318)
1 parent 03190a7 commit 0abf130

File tree

7 files changed

+192
-113
lines changed

7 files changed

+192
-113
lines changed
File renamed without changes.

examples/travel_app/lib/src/catalog/travel_carousel.dart

Lines changed: 165 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import 'package:dart_schema_builder/dart_schema_builder.dart';
88
import 'package:flutter/material.dart';
99
import 'package:flutter_genui/flutter_genui.dart';
1010

11+
import '../tools/booking/booking_service.dart';
12+
import '../tools/booking/model.dart';
13+
1114
final _schema = S.object(
1215
properties: {
1316
'title': S.string(
@@ -17,7 +20,13 @@ final _schema = S.object(
1720
description: 'A list of items to display in the carousel.',
1821
items: S.object(
1922
properties: {
20-
'title': S.string(description: 'The title of the carousel item.'),
23+
'description': S.string(
24+
description:
25+
'The short description of the carousel item. '
26+
'It may include the price and location if applicable. '
27+
'It should be very concise. '
28+
'Example: "The Dart Inn in Sunnyvale, CA for \$150"',
29+
),
2130
'imageChildId': S.string(
2231
description:
2332
'The ID of the Image widget to display as the carousel item '
@@ -30,7 +39,7 @@ final _schema = S.object(
3039
'a list of hotels or other bookable items.',
3140
),
3241
},
33-
required: ['title', 'imageChildId'],
42+
required: ['description', 'imageChildId'],
3443
),
3544
),
3645
},
@@ -67,7 +76,7 @@ final travelCarousel = CatalogItem(
6776
items: items
6877
.map(
6978
(e) => _TravelCarouselItemData(
70-
title: e.title,
79+
description: e.description,
7180
imageChild: buildChild(e.imageChildId),
7281
listingSelectionId: e.listingSelectionId,
7382
),
@@ -77,95 +86,7 @@ final travelCarousel = CatalogItem(
7786
dispatchEvent: dispatchEvent,
7887
);
7988
},
80-
exampleData: [
81-
() => {
82-
'root': 'greece_inspiration_column',
83-
'widgets': [
84-
{
85-
'id': 'greece_inspiration_column',
86-
'widget': {
87-
'Column': {
88-
'children': ['inspiration_title', 'inspiration_carousel'],
89-
},
90-
},
91-
},
92-
{
93-
'id': 'inspiration_title',
94-
'widget': {
95-
'Text': {
96-
'text':
97-
"Let's plan your dream trip to Greece! "
98-
'What kind of experience'
99-
' are you looking for?',
100-
},
101-
},
102-
},
103-
{
104-
'widget': {
105-
'TravelCarousel': {
106-
'items': [
107-
{
108-
'title': 'Relaxing Beach Holiday',
109-
'imageChildId': 'santorini_beach_image',
110-
'listingSelectionId': '12345',
111-
},
112-
{
113-
'imageChildId': 'akrotiri_fresco_image',
114-
'title': 'Cultural Exploration',
115-
'listingSelectionId': '12346',
116-
},
117-
{
118-
'imageChildId': 'santorini_caldera_image',
119-
'title': 'Adventure & Outdoors',
120-
'listingSelectionId': '12347',
121-
},
122-
{'title': 'Foodie Tour', 'imageChildId': 'greece_food_image'},
123-
],
124-
},
125-
},
126-
'id': 'inspiration_carousel',
127-
},
128-
{
129-
'id': 'santorini_beach_image',
130-
'widget': {
131-
'Image': {
132-
'fit': 'cover',
133-
'assetName': 'assets/travel_images/santorini_panorama.jpg',
134-
},
135-
},
136-
},
137-
{
138-
'id': 'akrotiri_fresco_image',
139-
'widget': {
140-
'Image': {
141-
'fit': 'cover',
142-
'assetName':
143-
'assets/travel_images/akrotiri_spring_fresco_santorini.jpg',
144-
},
145-
},
146-
},
147-
{
148-
'id': 'santorini_caldera_image',
149-
'widget': {
150-
'Image': {
151-
'assetName': 'assets/travel_images/santorini_from_space.jpg',
152-
'fit': 'cover',
153-
},
154-
},
155-
},
156-
{
157-
'widget': {
158-
'Image': {
159-
'fit': 'cover',
160-
'assetName':
161-
'assets/travel_images/saffron_gatherers_fresco_santorini.jpg',
162-
},
163-
},
164-
'id': 'greece_food_image',
165-
},
166-
],
167-
},
168-
],
89+
exampleData: [_inspirationExample, _hotelExample],
16990
);
17091

17192
extension type _TravelCarouselData.fromMap(Map<String, Object?> _json) {
@@ -189,16 +110,16 @@ extension type _TravelCarouselItemSchemaData.fromMap(
189110
Map<String, Object?> _json
190111
) {
191112
factory _TravelCarouselItemSchemaData({
192-
required String title,
113+
required String description,
193114
required String imageChildId,
194115
String? listingSelectionId,
195116
}) => _TravelCarouselItemSchemaData.fromMap({
196-
'title': title,
117+
'description': description,
197118
'imageChildId': imageChildId,
198119
if (listingSelectionId != null) 'listingSelectionId': listingSelectionId,
199120
});
200121

201-
String get title => _json['title'] as String;
122+
String get description => _json['description'] as String;
202123
String get imageChildId => _json['imageChildId'] as String;
203124
String? get listingSelectionId => _json['listingSelectionId'] as String?;
204125
}
@@ -240,7 +161,7 @@ class _TravelCarousel extends StatelessWidget {
240161
const SizedBox(height: 16.0),
241162
],
242163
SizedBox(
243-
height: 220,
164+
height: 240,
244165
child: ScrollConfiguration(
245166
behavior: _DesktopAndWebScrollBehavior(),
246167
child: ListView.separated(
@@ -264,12 +185,12 @@ class _TravelCarousel extends StatelessWidget {
264185
}
265186

266187
class _TravelCarouselItemData {
267-
final String title;
188+
final String description;
268189
final Widget imageChild;
269190
final String? listingSelectionId;
270191

271192
_TravelCarouselItemData({
272-
required this.title,
193+
required this.description,
273194
required this.imageChild,
274195
this.listingSelectionId,
275196
});
@@ -297,7 +218,7 @@ class _TravelCarouselItem extends StatelessWidget {
297218
widgetId: widgetId,
298219
eventType: 'itemSelected',
299220
value: {
300-
'title': data.title,
221+
'description': data.description,
301222
if (data.listingSelectionId != null)
302223
'listingSelectionId': data.listingSelectionId,
303224
},
@@ -312,12 +233,16 @@ class _TravelCarouselItem extends StatelessWidget {
312233
borderRadius: BorderRadius.circular(10.0),
313234
child: SizedBox(height: 150, width: 190, child: data.imageChild),
314235
),
315-
Padding(
236+
Container(
237+
height: 90,
316238
padding: const EdgeInsets.all(8.0),
239+
alignment: Alignment.center,
317240
child: Text(
318-
data.title,
241+
data.description,
242+
textAlign: TextAlign.center,
319243
style: Theme.of(context).textTheme.titleMedium,
320-
maxLines: 1,
244+
softWrap: true,
245+
maxLines: 3,
321246
overflow: TextOverflow.ellipsis,
322247
),
323248
),
@@ -327,3 +252,141 @@ class _TravelCarouselItem extends StatelessWidget {
327252
);
328253
}
329254
}
255+
256+
JsonMap _hotelExample() {
257+
final hotels = BookingService.instance.listHotelsSync(
258+
HotelSearch(
259+
query: '',
260+
checkIn: DateTime.now(),
261+
checkOut: DateTime.now().add(const Duration(days: 7)),
262+
guests: 2,
263+
),
264+
);
265+
final hotel1 = hotels.listings[0];
266+
final hotel2 = hotels.listings[1];
267+
268+
return {
269+
'root': 'hotel_carousel',
270+
'widgets': [
271+
{
272+
'widget': {
273+
'TravelCarousel': {
274+
'items': [
275+
{
276+
'description': hotel1.description,
277+
'imageChildId': 'image_1',
278+
'listingSelectionId': '12345',
279+
},
280+
{
281+
'description': hotel2.description,
282+
'imageChildId': 'image_2',
283+
'listingSelectionId': '12346',
284+
},
285+
],
286+
},
287+
},
288+
'id': 'hotel_carousel',
289+
},
290+
{
291+
'id': 'image_1',
292+
'widget': {
293+
'Image': {'fit': 'cover', 'assetName': hotel1.images[0]},
294+
},
295+
},
296+
{
297+
'id': 'image_2',
298+
'widget': {
299+
'Image': {'fit': 'cover', 'assetName': hotel2.images[0]},
300+
},
301+
},
302+
],
303+
};
304+
}
305+
306+
JsonMap _inspirationExample() => {
307+
'root': 'greece_inspiration_column',
308+
'widgets': [
309+
{
310+
'id': 'greece_inspiration_column',
311+
'widget': {
312+
'Column': {
313+
'children': ['inspiration_title', 'inspiration_carousel'],
314+
},
315+
},
316+
},
317+
{
318+
'id': 'inspiration_title',
319+
'widget': {
320+
'Text': {
321+
'text':
322+
"Let's plan your dream trip to Greece! "
323+
'What kind of experience'
324+
' are you looking for?',
325+
},
326+
},
327+
},
328+
{
329+
'widget': {
330+
'TravelCarousel': {
331+
'items': [
332+
{
333+
'description': 'Relaxing Beach Holiday',
334+
'imageChildId': 'santorini_beach_image',
335+
'listingSelectionId': '12345',
336+
},
337+
{
338+
'imageChildId': 'akrotiri_fresco_image',
339+
'description': 'Cultural Exploration',
340+
'listingSelectionId': '12346',
341+
},
342+
{
343+
'imageChildId': 'santorini_caldera_image',
344+
'description': 'Adventure & Outdoors',
345+
'listingSelectionId': '12347',
346+
},
347+
{'description': 'Foodie Tour', 'imageChildId': 'greece_food_image'},
348+
],
349+
},
350+
},
351+
'id': 'inspiration_carousel',
352+
},
353+
{
354+
'id': 'santorini_beach_image',
355+
'widget': {
356+
'Image': {
357+
'fit': 'cover',
358+
'assetName': 'assets/travel_images/santorini_panorama.jpg',
359+
},
360+
},
361+
},
362+
{
363+
'id': 'akrotiri_fresco_image',
364+
'widget': {
365+
'Image': {
366+
'fit': 'cover',
367+
'assetName':
368+
'assets/travel_images/akrotiri_spring_fresco_santorini.jpg',
369+
},
370+
},
371+
},
372+
{
373+
'id': 'santorini_caldera_image',
374+
'widget': {
375+
'Image': {
376+
'assetName': 'assets/travel_images/santorini_from_space.jpg',
377+
'fit': 'cover',
378+
},
379+
},
380+
},
381+
{
382+
'widget': {
383+
'Image': {
384+
'fit': 'cover',
385+
'assetName':
386+
'assets/travel_images/saffron_gatherers_fresco_santorini.jpg',
387+
},
388+
},
389+
'id': 'greece_food_image',
390+
},
391+
],
392+
};

examples/travel_app/lib/src/tools/booking/booking_service.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ class BookingService {
5454
),
5555
_rememberListing(
5656
HotelListing(
57-
name: 'The Grand Flutter Hotel',
57+
name: 'The Flutter Hotel',
5858
location: 'Mountain View, CA',
5959
pricePerNight: 250.0,
6060
listingSelectionId: _generateListingSelectionId(),
61-
images: ['assets/booking_service/the_grand_flutter_hotel.jpeg'],
61+
images: ['assets/booking_service/flutter_hotel.jpeg'],
6262
search: search,
6363
),
6464
),

examples/travel_app/lib/src/tools/booking/list_hotels_tool.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ class ListHotelsTool extends AiTool<Map<String, Object?>> {
4343
@override
4444
Future<JsonMap> invoke(JsonMap args) async {
4545
final search = HotelSearch.fromJson(args);
46-
return (await onListHotels(search)).toJson();
46+
return (await onListHotels(search)).toAiInput();
4747
}
4848
}

0 commit comments

Comments
 (0)