mirror of https://github.com/flutter/samples.git
[Compass App] Booking screen (#2380)
This PR adds the Booking screen at the end of the main app flow. After the user selects `Activity`s, these get stored in the `ItineraryConfig` and then the user navigates to the `BookingScreen`. In the `BookingScreen`, a `Booking` is generated with the `BookingCreateComponent`. This component communicates with multiple repositories, and it is a bit more complex than the average view model, something that we want to show as discussed in the previous PRs. <details> <summary>Screenshots</summary> ![image](https://github.com/user-attachments/assets/6a9d8d5b-0d2c-4724-8aca-d750186651b7) ![image](https://github.com/user-attachments/assets/0ef4d00e-e67b-4ec6-9ea3-28511ed4c2b8) </details> In the `BookingScreen`, the user can tap on "share trip" which displays the OS share sheet functionality. This uses the plugin `share_plus`, but the functionality is also wrapped in the `BookingShareComponent`, which takes a `Booking` object and creates a shareable string, then calls to the `Share.share()` method from `share_plus`. But the `share_plus` dependency is also injected into the `BookingShareComponent`, allowing us to unit test without plugin dependencies. This is an example of a shared booking to instant messaging: ![image](https://github.com/user-attachments/assets/5a559080-4f9a-45e6-a736-ab849a7adc39) **TODO** - I want to take a look at the whole experience on mobile, as I noticed some inconsistent UI and navigation issues that I couldn't see on Desktop. I will submit those in a new PR. - We also talked about user authentication in the design document. I will work on that once we are happy with the main app flow. ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.mdpull/2389/head
parent
0305894b0e
commit
0c88289339
@ -0,0 +1,85 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/activity/activity_repository.dart';
|
||||
import '../../../data/repositories/destination/destination_repository.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Component for creating [Booking] objects from [ItineraryConfig].
|
||||
///
|
||||
/// Fetches [Destination] and [Activity] objects from repositories,
|
||||
/// checks if dates are set and creates a [Booking] object.
|
||||
class BookingCreateComponent {
|
||||
BookingCreateComponent({
|
||||
required DestinationRepository destinationRepository,
|
||||
required ActivityRepository activityRepository,
|
||||
}) : _destinationRepository = destinationRepository,
|
||||
_activityRepository = activityRepository;
|
||||
|
||||
final DestinationRepository _destinationRepository;
|
||||
final ActivityRepository _activityRepository;
|
||||
final _log = Logger('BookingComponent');
|
||||
|
||||
/// Create [Booking] from a stored [ItineraryConfig]
|
||||
Future<Result<Booking>> createFrom(ItineraryConfig itineraryConfig) async {
|
||||
// Get Destination object from repository
|
||||
if (itineraryConfig.destination == null) {
|
||||
_log.warning('Destination is not set');
|
||||
return Result.error(Exception('Destination is not set'));
|
||||
}
|
||||
final destinationResult =
|
||||
await _fetchDestination(itineraryConfig.destination!);
|
||||
if (destinationResult is Error<Destination>) {
|
||||
_log.warning('Error fetching destination: ${destinationResult.error}');
|
||||
return Result.error(destinationResult.error);
|
||||
}
|
||||
_log.fine('Destination loaded: ${destinationResult.asOk.value.ref}');
|
||||
|
||||
// Get Activity objects from repository
|
||||
if (itineraryConfig.activities.isEmpty) {
|
||||
_log.warning('Activities are not set');
|
||||
return Result.error(Exception('Activities are not set'));
|
||||
}
|
||||
final activitiesResult = await _activityRepository.getByDestination(
|
||||
itineraryConfig.destination!,
|
||||
);
|
||||
if (activitiesResult is Error<List<Activity>>) {
|
||||
_log.warning('Error fetching activities: ${activitiesResult.error}');
|
||||
return Result.error(activitiesResult.error);
|
||||
}
|
||||
final activities = activitiesResult.asOk.value
|
||||
.where(
|
||||
(activity) => itineraryConfig.activities.contains(activity.ref),
|
||||
)
|
||||
.toList();
|
||||
_log.fine('Activities loaded (${activities.length})');
|
||||
|
||||
// Check if dates are set
|
||||
if (itineraryConfig.startDate == null || itineraryConfig.endDate == null) {
|
||||
_log.warning('Dates are not set');
|
||||
return Result.error(Exception('Dates are not set'));
|
||||
}
|
||||
|
||||
// Create Booking object
|
||||
return Result.ok(
|
||||
Booking(
|
||||
startDate: itineraryConfig.startDate!,
|
||||
endDate: itineraryConfig.endDate!,
|
||||
destination: destinationResult.asOk.value,
|
||||
activity: activities,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<Destination>> _fetchDestination(String destinationRef) async {
|
||||
final result = await _destinationRepository.getDestinations();
|
||||
switch (result) {
|
||||
case Ok<List<Destination>>():
|
||||
final destination = result.value
|
||||
.firstWhere((destination) => destination.ref == destinationRef);
|
||||
return Ok(destination);
|
||||
case Error<List<Destination>>():
|
||||
return Result.error(result.error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import '../../core/ui/date_format_start_end.dart';
|
||||
|
||||
typedef ShareFunction = Future<void> Function(String text);
|
||||
|
||||
/// Component for sharing a booking.
|
||||
class BookingShareComponent {
|
||||
BookingShareComponent._(this._share);
|
||||
|
||||
/// Create a [BookingShareComponent] that uses `share_plus` package.
|
||||
factory BookingShareComponent.withSharePlus() =>
|
||||
BookingShareComponent._(Share.share);
|
||||
|
||||
/// Create a [BookingShareComponent] with a custom share function.
|
||||
factory BookingShareComponent.custom(ShareFunction share) =>
|
||||
BookingShareComponent._(share);
|
||||
|
||||
final ShareFunction _share;
|
||||
final _log = Logger('BookingShareComponent');
|
||||
|
||||
Future<Result<void>> shareBooking(Booking booking) async {
|
||||
final text = 'Trip to ${booking.destination.name}\n'
|
||||
'on ${dateFormatStartEnd(DateTimeRange(start: booking.startDate, end: booking.endDate))}\n'
|
||||
'Activities:\n'
|
||||
'${booking.activity.map((a) => ' - ${a.name}').join('\n')}.';
|
||||
|
||||
_log.info('Sharing booking: $text');
|
||||
try {
|
||||
await _share(text);
|
||||
_log.fine('Shared booking');
|
||||
return Result.ok(null);
|
||||
} on Exception catch (error) {
|
||||
_log.severe('Failed to share booking', error);
|
||||
return Result.error(error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../../../utils/command.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../components/booking_create_component.dart';
|
||||
import '../components/booking_share_component.dart';
|
||||
|
||||
class BookingViewModel extends ChangeNotifier {
|
||||
BookingViewModel({
|
||||
required BookingCreateComponent bookingComponent,
|
||||
required BookingShareComponent shareComponent,
|
||||
required ItineraryConfigRepository itineraryConfigRepository,
|
||||
}) : _createComponent = bookingComponent,
|
||||
_shareComponent = shareComponent,
|
||||
_itineraryConfigRepository = itineraryConfigRepository {
|
||||
loadBooking = Command0(_loadBooking)..execute();
|
||||
shareBooking = Command0(() => _shareComponent.shareBooking(_booking!));
|
||||
}
|
||||
|
||||
final BookingCreateComponent _createComponent;
|
||||
final BookingShareComponent _shareComponent;
|
||||
final ItineraryConfigRepository _itineraryConfigRepository;
|
||||
final _log = Logger('BookingViewModel');
|
||||
Booking? _booking;
|
||||
|
||||
Booking? get booking => _booking;
|
||||
|
||||
late final Command0 loadBooking;
|
||||
|
||||
/// Share the current booking using the OS share dialog.
|
||||
late final Command0 shareBooking;
|
||||
|
||||
Future<Result<void>> _loadBooking() async {
|
||||
_log.fine('Loading booking');
|
||||
final itineraryConfig =
|
||||
await _itineraryConfigRepository.getItineraryConfig();
|
||||
switch (itineraryConfig) {
|
||||
case Ok<ItineraryConfig>():
|
||||
_log.fine('Loaded stored ItineraryConfig');
|
||||
final result = await _createComponent.createFrom(itineraryConfig.value);
|
||||
switch (result) {
|
||||
case Ok<Booking>():
|
||||
_log.fine('Created Booking');
|
||||
_booking = result.value;
|
||||
notifyListeners();
|
||||
return Result.ok(null);
|
||||
case Error<Booking>():
|
||||
_log.warning('Booking error: ${result.error}');
|
||||
notifyListeners();
|
||||
return Result.error(result.asError.error);
|
||||
}
|
||||
case Error<ItineraryConfig>():
|
||||
_log.warning('ItineraryConfig error: ${itineraryConfig.error}');
|
||||
notifyListeners();
|
||||
return Result.error(itineraryConfig.error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../view_models/booking_viewmodel.dart';
|
||||
import 'booking_header.dart';
|
||||
|
||||
class BookingBody extends StatelessWidget {
|
||||
const BookingBody({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final BookingViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: viewModel,
|
||||
builder: (context, _) {
|
||||
final booking = viewModel.booking;
|
||||
if (booking == null) return const SizedBox();
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(child: BookingHeader(booking: booking)),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final activity = booking.activity[index];
|
||||
return _Activity(activity: activity);
|
||||
},
|
||||
childCount: booking.activity.length,
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 200)),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Activity extends StatelessWidget {
|
||||
const _Activity({
|
||||
required this.activity,
|
||||
});
|
||||
|
||||
final Activity activity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: Dimens.paddingVertical,
|
||||
left: Dimens.of(context).paddingScreenHorizontal,
|
||||
right: Dimens.of(context).paddingScreenHorizontal,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: activity.imageUrl,
|
||||
height: 80,
|
||||
width: 80,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
activity.timeOfDay.name.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
Text(
|
||||
activity.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
activity.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/colors.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../../core/ui/back_button.dart';
|
||||
import '../../core/ui/date_format_start_end.dart';
|
||||
import '../../core/ui/home_button.dart';
|
||||
import '../../core/ui/tag_chip.dart';
|
||||
|
||||
class BookingHeader extends StatelessWidget {
|
||||
const BookingHeader({
|
||||
super.key,
|
||||
required this.booking,
|
||||
});
|
||||
|
||||
final Booking booking;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Top(booking: booking),
|
||||
Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenHorizontal,
|
||||
child: Text(
|
||||
booking.destination.knownFor,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Dimens.paddingVertical),
|
||||
_Tags(booking: booking),
|
||||
const SizedBox(height: Dimens.paddingVertical),
|
||||
Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenHorizontal,
|
||||
child: Text(
|
||||
AppLocalization.of(context).yourChosenActivities,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Top extends StatelessWidget {
|
||||
const _Top({
|
||||
required this.booking,
|
||||
});
|
||||
|
||||
final Booking booking;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 260,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
_HeaderImage(booking: booking),
|
||||
const _Gradient(),
|
||||
_Headline(booking: booking),
|
||||
Positioned(
|
||||
left: Dimens.of(context).paddingScreenHorizontal,
|
||||
top: Dimens.of(context).paddingScreenVertical,
|
||||
child: SafeArea(
|
||||
top: true,
|
||||
child: CustomBackButton(
|
||||
onTap: () => context.go('/activities'),
|
||||
blur: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: Dimens.of(context).paddingScreenHorizontal,
|
||||
top: Dimens.of(context).paddingScreenVertical,
|
||||
child: const SafeArea(
|
||||
top: true,
|
||||
child: HomeButton(blur: true),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tags extends StatelessWidget {
|
||||
const _Tags({
|
||||
required this.booking,
|
||||
});
|
||||
|
||||
final Booking booking;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final chipColor = switch (brightness) {
|
||||
Brightness.dark => AppColors.whiteTransparent,
|
||||
Brightness.light => AppColors.blackTransparent,
|
||||
};
|
||||
return Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenHorizontal,
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: booking.destination.tags
|
||||
.map(
|
||||
(tag) => TagChip(
|
||||
tag: tag,
|
||||
fontSize: 16,
|
||||
height: 32,
|
||||
chipColor: chipColor,
|
||||
onChipColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Headline extends StatelessWidget {
|
||||
const _Headline({
|
||||
required this.booking,
|
||||
});
|
||||
|
||||
final Booking booking;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
child: Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenSymmetric,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
booking.destination.name,
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
Text(
|
||||
dateFormatStartEnd(
|
||||
DateTimeRange(
|
||||
start: booking.startDate,
|
||||
end: booking.endDate,
|
||||
),
|
||||
),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeaderImage extends StatelessWidget {
|
||||
const _HeaderImage({
|
||||
required this.booking,
|
||||
});
|
||||
|
||||
final Booking booking;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CachedNetworkImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
imageUrl: booking.destination.imageUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Gradient extends StatelessWidget {
|
||||
const _Gradient();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Theme.of(context).colorScheme.surface,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/ui/error_indicator.dart';
|
||||
import '../view_models/booking_viewmodel.dart';
|
||||
import 'booking_body.dart';
|
||||
import 'booking_share_button.dart';
|
||||
|
||||
class BookingScreen extends StatelessWidget {
|
||||
const BookingScreen({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final BookingViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (d, r) => context.go('/activities'),
|
||||
child: Scaffold(
|
||||
body: ListenableBuilder(
|
||||
listenable: viewModel.loadBooking,
|
||||
builder: (context, child) {
|
||||
if (viewModel.loadBooking.running) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
if (viewModel.loadBooking.error) {
|
||||
return Center(
|
||||
child: ErrorIndicator(
|
||||
title: AppLocalization.of(context).errorWhileLoadingBooking,
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: viewModel.loadBooking.execute,
|
||||
),
|
||||
);
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
BookingBody(viewModel: viewModel),
|
||||
BookingShareButton(viewModel: viewModel),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../../core/ui/blur_filter.dart';
|
||||
import '../view_models/booking_viewmodel.dart';
|
||||
|
||||
class BookingShareButton extends StatelessWidget {
|
||||
const BookingShareButton({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final BookingViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: SafeArea(
|
||||
bottom: true,
|
||||
top: false,
|
||||
child: ClipRect(
|
||||
child: SizedBox(
|
||||
height: (Dimens.of(context).paddingScreenVertical * 2) + 64,
|
||||
child: BackdropFilter(
|
||||
filter: kBlurFilter,
|
||||
child: Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenSymmetric,
|
||||
child: ListenableBuilder(
|
||||
listenable: viewModel,
|
||||
builder: (context, _) {
|
||||
return FilledButton(
|
||||
key: const Key('share-button'),
|
||||
onPressed: viewModel.booking != null
|
||||
? viewModel.shareBooking.execute
|
||||
: null,
|
||||
child: Text(AppLocalization.of(context).shareTrip),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import 'dart:ui';
|
||||
|
||||
final kBlurFilter = ImageFilter.blur(sigmaX: 2, sigmaY: 2);
|
@ -0,0 +1,31 @@
|
||||
import 'package:compass_app/ui/booking/components/booking_create_component.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../testing/fakes/repositories/fake_activities_repository.dart';
|
||||
import '../../../testing/fakes/repositories/fake_destination_repository.dart';
|
||||
import '../../../testing/models/activity.dart';
|
||||
import '../../../testing/models/booking.dart';
|
||||
import '../../../testing/models/destination.dart';
|
||||
|
||||
void main() {
|
||||
group('BookingCreateComponent tests', () {
|
||||
test('Create booking', () async {
|
||||
final component = BookingCreateComponent(
|
||||
activityRepository: FakeActivityRepository(),
|
||||
destinationRepository: FakeDestinationRepository(),
|
||||
);
|
||||
|
||||
final booking = await component.createFrom(
|
||||
ItineraryConfig(
|
||||
startDate: DateTime(2024, 01, 01),
|
||||
endDate: DateTime(2024, 02, 12),
|
||||
destination: kDestination1.ref,
|
||||
activities: [kActivity.ref],
|
||||
),
|
||||
);
|
||||
|
||||
expect(booking.asOk.value, kBooking);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import 'package:compass_app/ui/booking/components/booking_create_component.dart';
|
||||
import 'package:compass_app/ui/booking/components/booking_share_component.dart';
|
||||
import 'package:compass_app/ui/booking/view_models/booking_viewmodel.dart';
|
||||
import 'package:compass_app/ui/booking/widgets/booking_screen.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../testing/app.dart';
|
||||
import '../../../testing/fakes/repositories/fake_activities_repository.dart';
|
||||
import '../../../testing/fakes/repositories/fake_destination_repository.dart';
|
||||
import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart';
|
||||
import '../../../testing/mocks.dart';
|
||||
import '../../../testing/models/activity.dart';
|
||||
import '../../../testing/models/destination.dart';
|
||||
|
||||
void main() {
|
||||
group('BookingScreen widget tests', () {
|
||||
late MockGoRouter goRouter;
|
||||
late BookingViewModel viewModel;
|
||||
late bool shared;
|
||||
|
||||
setUp(() {
|
||||
shared = false;
|
||||
viewModel = BookingViewModel(
|
||||
itineraryConfigRepository: FakeItineraryConfigRepository(
|
||||
itineraryConfig: ItineraryConfig(
|
||||
continent: 'Europe',
|
||||
startDate: DateTime(2024, 01, 01),
|
||||
endDate: DateTime(2024, 01, 31),
|
||||
guests: 2,
|
||||
destination: kDestination1.ref,
|
||||
activities: [kActivity.ref],
|
||||
),
|
||||
),
|
||||
bookingComponent: BookingCreateComponent(
|
||||
activityRepository: FakeActivityRepository(),
|
||||
destinationRepository: FakeDestinationRepository(),
|
||||
),
|
||||
shareComponent: BookingShareComponent.custom((text) async {
|
||||
shared = true;
|
||||
}),
|
||||
);
|
||||
goRouter = MockGoRouter();
|
||||
});
|
||||
|
||||
Future<void> loadScreen(WidgetTester tester) async {
|
||||
await testApp(
|
||||
tester,
|
||||
BookingScreen(viewModel: viewModel),
|
||||
goRouter: goRouter,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should load screen', (WidgetTester tester) async {
|
||||
await loadScreen(tester);
|
||||
expect(find.byType(BookingScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should display booking', (WidgetTester tester) async {
|
||||
await loadScreen(tester);
|
||||
|
||||
// Wait for list to load
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('name1'), findsOneWidget);
|
||||
expect(find.text('tags1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should share booking', (WidgetTester tester) async {
|
||||
await loadScreen(tester);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('share-button')));
|
||||
expect(shared, true);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import 'package:compass_app/ui/booking/components/booking_share_component.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../testing/models/activity.dart';
|
||||
import '../../../testing/models/destination.dart';
|
||||
|
||||
void main() {
|
||||
group('BookingShareComponent tests', () {
|
||||
test('Share booking', () async {
|
||||
String? sharedText;
|
||||
final component = BookingShareComponent.custom((text) async {
|
||||
sharedText = text;
|
||||
});
|
||||
final booking = Booking(
|
||||
startDate: DateTime(2024, 01, 01),
|
||||
endDate: DateTime(2024, 02, 12),
|
||||
destination: kDestination1,
|
||||
activity: [kActivity],
|
||||
);
|
||||
await component.shareBooking(booking);
|
||||
expect(
|
||||
sharedText,
|
||||
'Trip to name1\n'
|
||||
'on 1 Jan - 12 Feb\n'
|
||||
'Activities:\n'
|
||||
' - NAME.',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import 'package:compass_app/data/repositories/activity/activity_repository.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:compass_model/src/model/activity/activity.dart';
|
||||
|
||||
class FakeActivityRepository implements ActivityRepository {
|
||||
Map<String, List<Activity>> activities = {
|
||||
"DESTINATION": [
|
||||
const Activity(
|
||||
description: 'DESCRIPTION',
|
||||
destinationRef: 'DESTINATION',
|
||||
duration: 3,
|
||||
familyFriendly: true,
|
||||
imageUrl: 'http://example.com/img.png',
|
||||
locationName: 'LOCATION NAME',
|
||||
name: 'NAME',
|
||||
price: 3,
|
||||
ref: 'REF',
|
||||
timeOfDay: TimeOfDay.afternoon,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@override
|
||||
Future<Result<List<Activity>>> getByDestination(String ref) async {
|
||||
return Result.ok(activities[ref]!);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:compass_app/data/repositories/destination/destination_repository.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
|
||||
// TODO: Move to a better place
|
||||
class FakeDestinationRepository implements DestinationRepository {
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() {
|
||||
return Future.value(
|
||||
Result.ok(
|
||||
[
|
||||
const Destination(
|
||||
ref: 'ref1',
|
||||
name: 'name1',
|
||||
country: 'country1',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor1',
|
||||
tags: ['tags1'],
|
||||
imageUrl: 'imageUrl1',
|
||||
),
|
||||
const Destination(
|
||||
ref: 'ref2',
|
||||
name: 'name2',
|
||||
country: 'country2',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor2',
|
||||
tags: ['tags2'],
|
||||
imageUrl: 'imageUrl2',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import 'package:compass_app/ui/core/localization/applocalization.dart';
|
||||
import 'package:compass_app/ui/core/themes/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mocktail_image_network/mocktail_image_network.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
|
||||
testApp(
|
||||
WidgetTester tester,
|
||||
Widget body, {
|
||||
GoRouter? goRouter,
|
||||
}) async {
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
await tester.binding.setSurfaceSize(const Size(1200, 800));
|
||||
await mockNetworkImages(() async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: [
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
AppLocalizationDelegate(),
|
||||
],
|
||||
theme: AppTheme.lightTheme,
|
||||
home: InheritedGoRouter(
|
||||
goRouter: goRouter ?? MockGoRouter(),
|
||||
child: Scaffold(
|
||||
body: body,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import 'package:compass_app/data/repositories/activity/activity_repository.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:compass_model/src/model/activity/activity.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../models/activity.dart';
|
||||
import '../../models/destination.dart';
|
||||
|
||||
class FakeActivityRepository implements ActivityRepository {
|
||||
Map<String, List<Activity>> activities = {
|
||||
"DESTINATION": [kActivity],
|
||||
kDestination1.ref: [kActivity],
|
||||
};
|
||||
|
||||
@override
|
||||
Future<Result<List<Activity>>> getByDestination(String ref) {
|
||||
return SynchronousFuture(Result.ok(activities[ref]!));
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:compass_app/data/repositories/continent/continent_repository.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class FakeContinentRepository implements ContinentRepository {
|
||||
@override
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
return Result.ok([
|
||||
Future<Result<List<Continent>>> getContinents() {
|
||||
return SynchronousFuture(Result.ok([
|
||||
const Continent(name: 'CONTINENT', imageUrl: 'URL'),
|
||||
const Continent(name: 'CONTINENT2', imageUrl: 'URL'),
|
||||
const Continent(name: 'CONTINENT3', imageUrl: 'URL'),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:compass_app/data/repositories/destination/destination_repository.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../models/destination.dart';
|
||||
|
||||
class FakeDestinationRepository implements DestinationRepository {
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() {
|
||||
return SynchronousFuture(Result.ok([kDestination1, kDestination2]));
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
const kActivity = Activity(
|
||||
description: 'DESCRIPTION',
|
||||
destinationRef: 'DESTINATION',
|
||||
duration: 3,
|
||||
familyFriendly: true,
|
||||
imageUrl: 'http://example.com/img.png',
|
||||
locationName: 'LOCATION NAME',
|
||||
name: 'NAME',
|
||||
price: 3,
|
||||
ref: 'REF',
|
||||
timeOfDay: TimeOfDay.afternoon,
|
||||
);
|
@ -0,0 +1,11 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
import 'activity.dart';
|
||||
import 'destination.dart';
|
||||
|
||||
final kBooking = Booking(
|
||||
startDate: DateTime(2024, 01, 01),
|
||||
endDate: DateTime(2024, 02, 12),
|
||||
destination: kDestination1,
|
||||
activity: [kActivity],
|
||||
);
|
@ -0,0 +1,21 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
const kDestination1 = Destination(
|
||||
ref: 'ref1',
|
||||
name: 'name1',
|
||||
country: 'country1',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor1',
|
||||
tags: ['tags1'],
|
||||
imageUrl: 'imageUrl1',
|
||||
);
|
||||
|
||||
const kDestination2 = Destination(
|
||||
ref: 'ref2',
|
||||
name: 'name2',
|
||||
country: 'country2',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor2',
|
||||
tags: ['tags2'],
|
||||
imageUrl: 'imageUrl2',
|
||||
);
|
@ -1,6 +1,7 @@
|
||||
library;
|
||||
|
||||
export 'src/model/activity/activity.dart';
|
||||
export 'src/model/booking/booking.dart';
|
||||
export 'src/model/continent/continent.dart';
|
||||
export 'src/model/destination/destination.dart';
|
||||
export 'src/model/itinerary_config/itinerary_config.dart';
|
||||
|
@ -0,0 +1,25 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'booking.freezed.dart';
|
||||
part 'booking.g.dart';
|
||||
|
||||
@freezed
|
||||
class Booking with _$Booking {
|
||||
const factory Booking({
|
||||
/// Start date of the trip
|
||||
required DateTime startDate,
|
||||
|
||||
/// End date of the trip
|
||||
required DateTime endDate,
|
||||
|
||||
/// Destination of the trip
|
||||
required Destination destination,
|
||||
|
||||
/// List of chosen activities
|
||||
required List<Activity> activity,
|
||||
}) = _Booking;
|
||||
|
||||
factory Booking.fromJson(Map<String, Object?> json) =>
|
||||
_$BookingFromJson(json);
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// 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.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
Booking _$BookingFromJson(Map<String, dynamic> json) {
|
||||
return _Booking.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Booking {
|
||||
/// Start date of the trip
|
||||
DateTime get startDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// End date of the trip
|
||||
DateTime get endDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// Destination of the trip
|
||||
Destination get destination => throw _privateConstructorUsedError;
|
||||
|
||||
/// List of chosen activities
|
||||
List<Activity> get activity => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Booking to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$BookingCopyWith<Booking> get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $BookingCopyWith<$Res> {
|
||||
factory $BookingCopyWith(Booking value, $Res Function(Booking) then) =
|
||||
_$BookingCopyWithImpl<$Res, Booking>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{DateTime startDate,
|
||||
DateTime endDate,
|
||||
Destination destination,
|
||||
List<Activity> activity});
|
||||
|
||||
$DestinationCopyWith<$Res> get destination;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$BookingCopyWithImpl<$Res, $Val extends Booking>
|
||||
implements $BookingCopyWith<$Res> {
|
||||
_$BookingCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? destination = null,
|
||||
Object? activity = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
startDate: null == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
endDate: null == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
destination: null == destination
|
||||
? _value.destination
|
||||
: destination // ignore: cast_nullable_to_non_nullable
|
||||
as Destination,
|
||||
activity: null == activity
|
||||
? _value.activity
|
||||
: activity // ignore: cast_nullable_to_non_nullable
|
||||
as List<Activity>,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$DestinationCopyWith<$Res> get destination {
|
||||
return $DestinationCopyWith<$Res>(_value.destination, (value) {
|
||||
return _then(_value.copyWith(destination: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$BookingImplCopyWith<$Res> implements $BookingCopyWith<$Res> {
|
||||
factory _$$BookingImplCopyWith(
|
||||
_$BookingImpl value, $Res Function(_$BookingImpl) then) =
|
||||
__$$BookingImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{DateTime startDate,
|
||||
DateTime endDate,
|
||||
Destination destination,
|
||||
List<Activity> activity});
|
||||
|
||||
@override
|
||||
$DestinationCopyWith<$Res> get destination;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$BookingImplCopyWithImpl<$Res>
|
||||
extends _$BookingCopyWithImpl<$Res, _$BookingImpl>
|
||||
implements _$$BookingImplCopyWith<$Res> {
|
||||
__$$BookingImplCopyWithImpl(
|
||||
_$BookingImpl _value, $Res Function(_$BookingImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? destination = null,
|
||||
Object? activity = null,
|
||||
}) {
|
||||
return _then(_$BookingImpl(
|
||||
startDate: null == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
endDate: null == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
destination: null == destination
|
||||
? _value.destination
|
||||
: destination // ignore: cast_nullable_to_non_nullable
|
||||
as Destination,
|
||||
activity: null == activity
|
||||
? _value._activity
|
||||
: activity // ignore: cast_nullable_to_non_nullable
|
||||
as List<Activity>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$BookingImpl implements _Booking {
|
||||
const _$BookingImpl(
|
||||
{required this.startDate,
|
||||
required this.endDate,
|
||||
required this.destination,
|
||||
required final List<Activity> activity})
|
||||
: _activity = activity;
|
||||
|
||||
factory _$BookingImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$BookingImplFromJson(json);
|
||||
|
||||
/// Start date of the trip
|
||||
@override
|
||||
final DateTime startDate;
|
||||
|
||||
/// End date of the trip
|
||||
@override
|
||||
final DateTime endDate;
|
||||
|
||||
/// Destination of the trip
|
||||
@override
|
||||
final Destination destination;
|
||||
|
||||
/// List of chosen activities
|
||||
final List<Activity> _activity;
|
||||
|
||||
/// List of chosen activities
|
||||
@override
|
||||
List<Activity> get activity {
|
||||
if (_activity is EqualUnmodifiableListView) return _activity;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_activity);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Booking(startDate: $startDate, endDate: $endDate, destination: $destination, activity: $activity)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$BookingImpl &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate) &&
|
||||
(identical(other.destination, destination) ||
|
||||
other.destination == destination) &&
|
||||
const DeepCollectionEquality().equals(other._activity, _activity));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, startDate, endDate, destination,
|
||||
const DeepCollectionEquality().hash(_activity));
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$BookingImplCopyWith<_$BookingImpl> get copyWith =>
|
||||
__$$BookingImplCopyWithImpl<_$BookingImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$BookingImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Booking implements Booking {
|
||||
const factory _Booking(
|
||||
{required final DateTime startDate,
|
||||
required final DateTime endDate,
|
||||
required final Destination destination,
|
||||
required final List<Activity> activity}) = _$BookingImpl;
|
||||
|
||||
factory _Booking.fromJson(Map<String, dynamic> json) = _$BookingImpl.fromJson;
|
||||
|
||||
/// Start date of the trip
|
||||
@override
|
||||
DateTime get startDate;
|
||||
|
||||
/// End date of the trip
|
||||
@override
|
||||
DateTime get endDate;
|
||||
|
||||
/// Destination of the trip
|
||||
@override
|
||||
Destination get destination;
|
||||
|
||||
/// List of chosen activities
|
||||
@override
|
||||
List<Activity> get activity;
|
||||
|
||||
/// Create a copy of Booking
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$BookingImplCopyWith<_$BookingImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'booking.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$BookingImpl _$$BookingImplFromJson(Map<String, dynamic> json) =>
|
||||
_$BookingImpl(
|
||||
startDate: DateTime.parse(json['startDate'] as String),
|
||||
endDate: DateTime.parse(json['endDate'] as String),
|
||||
destination:
|
||||
Destination.fromJson(json['destination'] as Map<String, dynamic>),
|
||||
activity: (json['activity'] as List<dynamic>)
|
||||
.map((e) => Activity.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$BookingImplToJson(_$BookingImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'startDate': instance.startDate.toIso8601String(),
|
||||
'endDate': instance.endDate.toIso8601String(),
|
||||
'destination': instance.destination,
|
||||
'activity': instance.activity,
|
||||
};
|
Loading…
Reference in new issue