mirror of
https://github.com/flutter/samples.git
synced 2026-04-20 14:03:38 +00:00
Compass app (#2446)
This commit is contained in:
8
compass_app/app/lib/config/assets.dart
Normal file
8
compass_app/app/lib/config/assets.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
class Assets {
|
||||
static const activities = 'assets/activities.json';
|
||||
static const destinations = 'assets/destinations.json';
|
||||
}
|
||||
144
compass_app/app/lib/config/dependencies.dart
Normal file
144
compass_app/app/lib/config/dependencies.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:provider/single_child_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../data/repositories/auth/auth_repository.dart';
|
||||
import '../data/repositories/auth/auth_repository_dev.dart';
|
||||
import '../data/repositories/auth/auth_repository_remote.dart';
|
||||
import '../data/repositories/booking/booking_repository.dart';
|
||||
import '../data/repositories/booking/booking_repository_local.dart';
|
||||
import '../data/repositories/booking/booking_repository_remote.dart';
|
||||
import '../data/repositories/user/user_repository.dart';
|
||||
import '../data/repositories/user/user_repository_local.dart';
|
||||
import '../data/repositories/user/user_repository_remote.dart';
|
||||
import '../data/services/api/auth_api_client.dart';
|
||||
import '../data/services/local/local_data_service.dart';
|
||||
import '../data/services/shared_preferences_service.dart';
|
||||
import '../data/repositories/activity/activity_repository.dart';
|
||||
import '../data/repositories/activity/activity_repository_local.dart';
|
||||
import '../data/repositories/activity/activity_repository_remote.dart';
|
||||
import '../data/repositories/continent/continent_repository.dart';
|
||||
import '../data/repositories/continent/continent_repository_local.dart';
|
||||
import '../data/repositories/continent/continent_repository_remote.dart';
|
||||
import '../data/repositories/destination/destination_repository.dart';
|
||||
import '../data/repositories/destination/destination_repository_local.dart';
|
||||
import '../data/repositories/destination/destination_repository_remote.dart';
|
||||
import '../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../data/repositories/itinerary_config/itinerary_config_repository_memory.dart';
|
||||
import '../data/services/api/api_client.dart';
|
||||
import '../domain/use_cases/booking/booking_create_use_case.dart';
|
||||
import '../domain/use_cases/booking/booking_share_use_case.dart';
|
||||
|
||||
/// Shared providers for all configurations.
|
||||
List<SingleChildWidget> _sharedProviders = [
|
||||
Provider(
|
||||
lazy: true,
|
||||
create: (context) => BookingCreateUseCase(
|
||||
destinationRepository: context.read(),
|
||||
activityRepository: context.read(),
|
||||
bookingRepository: context.read(),
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
lazy: true,
|
||||
create: (context) => BookingShareUseCase.withSharePlus(),
|
||||
),
|
||||
];
|
||||
|
||||
/// Configure dependencies for remote data.
|
||||
/// This dependency list uses repositories that connect to a remote server.
|
||||
List<SingleChildWidget> get providersRemote {
|
||||
return [
|
||||
Provider(
|
||||
create: (context) => AuthApiClient(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => ApiClient(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => SharedPreferencesService(),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => AuthRepositoryRemote(
|
||||
authApiClient: context.read(),
|
||||
apiClient: context.read(),
|
||||
sharedPreferencesService: context.read(),
|
||||
) as AuthRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => DestinationRepositoryRemote(
|
||||
apiClient: context.read(),
|
||||
) as DestinationRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => ContinentRepositoryRemote(
|
||||
apiClient: context.read(),
|
||||
) as ContinentRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => ActivityRepositoryRemote(
|
||||
apiClient: context.read(),
|
||||
) as ActivityRepository,
|
||||
),
|
||||
Provider.value(
|
||||
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => BookingRepositoryRemote(
|
||||
apiClient: context.read(),
|
||||
) as BookingRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => UserRepositoryRemote(
|
||||
apiClient: context.read(),
|
||||
) as UserRepository,
|
||||
),
|
||||
..._sharedProviders,
|
||||
];
|
||||
}
|
||||
|
||||
/// Configure dependencies for local data.
|
||||
/// This dependency list uses repositories that provide local data.
|
||||
/// The user is always logged in.
|
||||
List<SingleChildWidget> get providersLocal {
|
||||
return [
|
||||
ChangeNotifierProvider.value(
|
||||
value: AuthRepositoryDev() as AuthRepository,
|
||||
),
|
||||
Provider.value(
|
||||
value: LocalDataService(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => DestinationRepositoryLocal(
|
||||
localDataService: context.read(),
|
||||
) as DestinationRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => ContinentRepositoryLocal(
|
||||
localDataService: context.read(),
|
||||
) as ContinentRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => ActivityRepositoryLocal(
|
||||
localDataService: context.read(),
|
||||
) as ActivityRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => BookingRepositoryLocal(
|
||||
localDataService: context.read(),
|
||||
) as BookingRepository,
|
||||
),
|
||||
Provider.value(
|
||||
value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository,
|
||||
),
|
||||
Provider(
|
||||
create: (context) => UserRepositoryLocal(
|
||||
localDataService: context.read(),
|
||||
) as UserRepository,
|
||||
),
|
||||
..._sharedProviders,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Data source for activities.
|
||||
abstract class ActivityRepository {
|
||||
/// Get activities by [Destination] ref.
|
||||
Future<Result<List<Activity>>> getByDestination(String ref);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/local/local_data_service.dart';
|
||||
import 'activity_repository.dart';
|
||||
|
||||
/// Local implementation of ActivityRepository
|
||||
/// Uses data from assets folder
|
||||
class ActivityRepositoryLocal implements ActivityRepository {
|
||||
ActivityRepositoryLocal({
|
||||
required LocalDataService localDataService,
|
||||
}) : _localDataService = localDataService;
|
||||
|
||||
final LocalDataService _localDataService;
|
||||
|
||||
@override
|
||||
Future<Result<List<Activity>>> getByDestination(String ref) async {
|
||||
try {
|
||||
final activities = (await _localDataService.getActivities())
|
||||
.where((activity) => activity.destinationRef == ref)
|
||||
.toList();
|
||||
|
||||
return Result.ok(activities);
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import 'activity_repository.dart';
|
||||
|
||||
/// Remote data source for [Activity].
|
||||
/// Implements basic local caching.
|
||||
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
|
||||
class ActivityRepositoryRemote implements ActivityRepository {
|
||||
ActivityRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
final Map<String, List<Activity>> _cachedData = {};
|
||||
|
||||
@override
|
||||
Future<Result<List<Activity>>> getByDestination(String ref) async {
|
||||
if (!_cachedData.containsKey(ref)) {
|
||||
// No cached data, request activities
|
||||
final result = await _apiClient.getActivityByDestination(ref);
|
||||
if (result is Ok) {
|
||||
_cachedData[ref] = result.asOk.value;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Return cached data if available
|
||||
return Result.ok(_cachedData[ref]!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
abstract class AuthRepository extends ChangeNotifier {
|
||||
/// Returns true when the user is logged in
|
||||
/// Returns [Future] because it will load a stored auth state the first time.
|
||||
Future<bool> get isAuthenticated;
|
||||
|
||||
/// Perform login
|
||||
Future<Result<void>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
});
|
||||
|
||||
/// Perform logout
|
||||
Future<Result<void>> logout();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import 'auth_repository.dart';
|
||||
|
||||
class AuthRepositoryDev extends AuthRepository {
|
||||
/// User is always authenticated in dev scenarios
|
||||
@override
|
||||
Future<bool> get isAuthenticated => Future.value(true);
|
||||
|
||||
/// Login is always successful in dev scenarios
|
||||
@override
|
||||
Future<Result<void>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
return Result.ok(null);
|
||||
}
|
||||
|
||||
/// Logout is always successful in dev scenarios
|
||||
@override
|
||||
Future<Result<void>> logout() async {
|
||||
return Result.ok(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import '../../services/api/auth_api_client.dart';
|
||||
import '../../services/api/model/login_request/login_request.dart';
|
||||
import '../../services/api/model/login_response/login_response.dart';
|
||||
import '../../services/shared_preferences_service.dart';
|
||||
import 'auth_repository.dart';
|
||||
|
||||
class AuthRepositoryRemote extends AuthRepository {
|
||||
AuthRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
required AuthApiClient authApiClient,
|
||||
required SharedPreferencesService sharedPreferencesService,
|
||||
}) : _apiClient = apiClient,
|
||||
_authApiClient = authApiClient,
|
||||
_sharedPreferencesService = sharedPreferencesService {
|
||||
_apiClient.authHeaderProvider = _authHeaderProvider;
|
||||
}
|
||||
|
||||
final AuthApiClient _authApiClient;
|
||||
final ApiClient _apiClient;
|
||||
final SharedPreferencesService _sharedPreferencesService;
|
||||
|
||||
bool? _isAuthenticated;
|
||||
String? _authToken;
|
||||
final _log = Logger('AuthRepositoryRemote');
|
||||
|
||||
/// Fetch token from shared preferences
|
||||
Future<void> _fetch() async {
|
||||
final result = await _sharedPreferencesService.fetchToken();
|
||||
switch (result) {
|
||||
case Ok<String?>():
|
||||
_authToken = result.value;
|
||||
_isAuthenticated = result.value != null;
|
||||
case Error<String?>():
|
||||
_log.severe(
|
||||
'Failed to fech Token from SharedPreferences',
|
||||
result.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> get isAuthenticated async {
|
||||
// Status is cached
|
||||
if (_isAuthenticated != null) {
|
||||
return _isAuthenticated!;
|
||||
}
|
||||
// No status cached, fetch from storage
|
||||
await _fetch();
|
||||
return _isAuthenticated ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<void>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final result = await _authApiClient.login(
|
||||
LoginRequest(
|
||||
email: email,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
switch (result) {
|
||||
case Ok<LoginResponse>():
|
||||
_log.info('User logged int');
|
||||
// Set auth status
|
||||
_isAuthenticated = true;
|
||||
_authToken = result.value.token;
|
||||
// Store in Shared preferences
|
||||
return await _sharedPreferencesService.saveToken(result.value.token);
|
||||
case Error<LoginResponse>():
|
||||
_log.warning('Error logging in: ${result.error}');
|
||||
return Result.error(result.error);
|
||||
}
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<void>> logout() async {
|
||||
_log.info('User logged out');
|
||||
try {
|
||||
// Clear stored auth token
|
||||
final result = await _sharedPreferencesService.saveToken(null);
|
||||
if (result is Error<void>) {
|
||||
_log.severe('Failed to clear stored auth token');
|
||||
}
|
||||
|
||||
// Clear token in ApiClient
|
||||
_authToken = null;
|
||||
|
||||
// Clear authenticated status
|
||||
_isAuthenticated = false;
|
||||
return result;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
String? _authHeaderProvider() =>
|
||||
_authToken != null ? 'Bearer $_authToken' : null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/booking/booking.dart';
|
||||
import '../../../domain/models/booking/booking_summary.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
abstract class BookingRepository {
|
||||
/// Returns the list of [BookingSummary] for the current user.
|
||||
Future<Result<List<BookingSummary>>> getBookingsList();
|
||||
|
||||
/// Returns a full [Booking] given the id.
|
||||
Future<Result<Booking>> getBooking(int id);
|
||||
|
||||
/// Creates a new [Booking].
|
||||
Future<Result<void>> createBooking(Booking booking);
|
||||
|
||||
/// Delete booking
|
||||
Future<Result<void>> delete(int id);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import '../../../domain/models/booking/booking.dart';
|
||||
import '../../../domain/models/booking/booking_summary.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
import '../../services/local/local_data_service.dart';
|
||||
import 'booking_repository.dart';
|
||||
|
||||
class BookingRepositoryLocal implements BookingRepository {
|
||||
BookingRepositoryLocal({
|
||||
required LocalDataService localDataService,
|
||||
}) : _localDataService = localDataService;
|
||||
|
||||
// Only create default booking once
|
||||
bool _isInitialized = false;
|
||||
// Used to generate IDs for bookings
|
||||
int _sequentialId = 0;
|
||||
|
||||
final _bookings = List<Booking>.empty(growable: true);
|
||||
final LocalDataService _localDataService;
|
||||
|
||||
@override
|
||||
Future<Result<void>> createBooking(Booking booking) async {
|
||||
// Bookings created come without id, we need to assign one
|
||||
final bookingWithId = booking.copyWith(id: _sequentialId++);
|
||||
_bookings.add(bookingWithId);
|
||||
return Result.ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<Booking>> getBooking(int id) async {
|
||||
final booking = _bookings.firstWhereOrNull((booking) => booking.id == id);
|
||||
if (booking == null) {
|
||||
return Result.error(Exception('Booking not found'));
|
||||
}
|
||||
return Result.ok(booking);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<List<BookingSummary>>> getBookingsList() async {
|
||||
// Initialize the repository with a default booking
|
||||
if (!_isInitialized) {
|
||||
await _createDefaultBooking();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
return Result.ok(_createSummaries());
|
||||
}
|
||||
|
||||
List<BookingSummary> _createSummaries() {
|
||||
return _bookings
|
||||
.map(
|
||||
(booking) => BookingSummary(
|
||||
id: booking.id!,
|
||||
name:
|
||||
'${booking.destination.name}, ${booking.destination.continent}',
|
||||
startDate: booking.startDate,
|
||||
endDate: booking.endDate,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> _createDefaultBooking() async {
|
||||
// create a default booking the first time
|
||||
if (_bookings.isEmpty) {
|
||||
final destination = (await _localDataService.getDestinations()).first;
|
||||
final activities = (await _localDataService.getActivities())
|
||||
.where((activity) => activity.destinationRef == destination.ref)
|
||||
.take(4)
|
||||
.toList();
|
||||
|
||||
_bookings.add(
|
||||
Booking(
|
||||
id: _sequentialId++,
|
||||
startDate: DateTime(2024, 1, 1),
|
||||
endDate: DateTime(2024, 2, 1),
|
||||
destination: destination,
|
||||
activity: activities,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<void>> delete(int id) async {
|
||||
_bookings.removeWhere((booking) => booking.id == id);
|
||||
return Result.ok(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../domain/models/booking/booking.dart';
|
||||
import '../../../domain/models/booking/booking_summary.dart';
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import '../../services/api/model/booking/booking_api_model.dart';
|
||||
import 'booking_repository.dart';
|
||||
|
||||
class BookingRepositoryRemote implements BookingRepository {
|
||||
BookingRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
List<Destination>? _cachedDestinations;
|
||||
|
||||
@override
|
||||
Future<Result<void>> createBooking(Booking booking) async {
|
||||
try {
|
||||
final BookingApiModel bookingApiModel = BookingApiModel(
|
||||
startDate: booking.startDate,
|
||||
endDate: booking.endDate,
|
||||
name: '${booking.destination.name}, ${booking.destination.continent}',
|
||||
destinationRef: booking.destination.ref,
|
||||
activitiesRef:
|
||||
booking.activity.map((activity) => activity.ref).toList(),
|
||||
);
|
||||
return _apiClient.postBooking(bookingApiModel);
|
||||
} on Exception catch (e) {
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<Booking>> getBooking(int id) async {
|
||||
try {
|
||||
// Get booking by ID from server
|
||||
final resultBooking = await _apiClient.getBooking(id);
|
||||
if (resultBooking is Error<BookingApiModel>) {
|
||||
return Result.error(resultBooking.error);
|
||||
}
|
||||
final booking = resultBooking.asOk.value;
|
||||
|
||||
// Load destinations if not loaded yet
|
||||
if (_cachedDestinations == null) {
|
||||
final resultDestination = await _apiClient.getDestinations();
|
||||
if (resultDestination is Error<List<Destination>>) {
|
||||
return Result.error(resultDestination.error);
|
||||
}
|
||||
_cachedDestinations = resultDestination.asOk.value;
|
||||
}
|
||||
|
||||
// Get destination for booking
|
||||
final destination = _cachedDestinations!.firstWhere(
|
||||
(destination) => destination.ref == booking.destinationRef);
|
||||
|
||||
final resultActivities =
|
||||
await _apiClient.getActivityByDestination(destination.ref);
|
||||
|
||||
if (resultActivities is Error<List<Activity>>) {
|
||||
return Result.error(resultActivities.error);
|
||||
}
|
||||
final activities = resultActivities.asOk.value
|
||||
.where((activity) => booking.activitiesRef.contains(activity.ref))
|
||||
.toList();
|
||||
|
||||
return Result.ok(
|
||||
Booking(
|
||||
id: booking.id,
|
||||
startDate: booking.startDate,
|
||||
endDate: booking.endDate,
|
||||
destination: destination,
|
||||
activity: activities,
|
||||
),
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<List<BookingSummary>>> getBookingsList() async {
|
||||
try {
|
||||
final result = await _apiClient.getBookings();
|
||||
if (result is Error<List<BookingApiModel>>) {
|
||||
return Result.error(result.error);
|
||||
}
|
||||
final bookingsApi = result.asOk.value;
|
||||
return Result.ok(bookingsApi
|
||||
.map(
|
||||
(bookingApi) => BookingSummary(
|
||||
id: bookingApi.id!,
|
||||
name: bookingApi.name,
|
||||
startDate: bookingApi.startDate,
|
||||
endDate: bookingApi.endDate,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
} on Exception catch (e) {
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<void>> delete(int id) async {
|
||||
try {
|
||||
return _apiClient.deleteBooking(id);
|
||||
} on Exception catch (e) {
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/continent/continent.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Data source with all possible continents.
|
||||
abstract class ContinentRepository {
|
||||
/// Get complete list of continents.
|
||||
Future<Result<List<Continent>>> getContinents();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/continent/continent.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/local/local_data_service.dart';
|
||||
import 'continent_repository.dart';
|
||||
|
||||
/// Local data source with all possible continents.
|
||||
class ContinentRepositoryLocal implements ContinentRepository {
|
||||
ContinentRepositoryLocal({
|
||||
required LocalDataService localDataService,
|
||||
}) : _localDataService = localDataService;
|
||||
|
||||
final LocalDataService _localDataService;
|
||||
|
||||
@override
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
return Future.value(Result.ok(_localDataService.getContinents()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/continent/continent.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import 'continent_repository.dart';
|
||||
|
||||
/// Remote data source for [Continent].
|
||||
/// Implements basic local caching.
|
||||
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
|
||||
class ContinentRepositoryRemote implements ContinentRepository {
|
||||
ContinentRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
List<Continent>? _cachedData;
|
||||
|
||||
@override
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
if (_cachedData == null) {
|
||||
// No cached data, request continents
|
||||
final result = await _apiClient.getContinents();
|
||||
if (result is Ok) {
|
||||
// Store value if result Ok
|
||||
_cachedData = result.asOk.value;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Return cached data if available
|
||||
return Result.ok(_cachedData!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Data source with all possible destinations
|
||||
abstract class DestinationRepository {
|
||||
/// Get complete list of destinations
|
||||
Future<Result<List<Destination>>> getDestinations();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/local/local_data_service.dart';
|
||||
import 'destination_repository.dart';
|
||||
|
||||
/// Local implementation of DestinationRepository
|
||||
/// Uses data from assets folder
|
||||
class DestinationRepositoryLocal implements DestinationRepository {
|
||||
DestinationRepositoryLocal({
|
||||
required LocalDataService localDataService,
|
||||
}) : _localDataService = localDataService;
|
||||
|
||||
final LocalDataService _localDataService;
|
||||
|
||||
/// Obtain list of destinations from local assets
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
try {
|
||||
return Result.ok(await _localDataService.getDestinations());
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import 'destination_repository.dart';
|
||||
|
||||
/// Remote data source for [Destination].
|
||||
/// Implements basic local caching.
|
||||
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
|
||||
class DestinationRepositoryRemote implements DestinationRepository {
|
||||
DestinationRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
List<Destination>? _cachedData;
|
||||
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
if (_cachedData == null) {
|
||||
// No cached data, request destinations
|
||||
final result = await _apiClient.getDestinations();
|
||||
if (result is Ok) {
|
||||
// Store value if result Ok
|
||||
_cachedData = result.asOk.value;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Return cached data if available
|
||||
return Result.ok(_cachedData!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Data source for the current [ItineraryConfig]
|
||||
abstract class ItineraryConfigRepository {
|
||||
/// Get current [ItineraryConfig], may be empty if no configuration started.
|
||||
/// Method is async to support writing to database, file, etc.
|
||||
Future<Result<ItineraryConfig>> getItineraryConfig();
|
||||
|
||||
/// Sets [ItineraryConfig], overrides the previous one stored.
|
||||
/// Returns Result.Ok if set is successful.
|
||||
Future<Result<void>> setItineraryConfig(ItineraryConfig itineraryConfig);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import 'itinerary_config_repository.dart';
|
||||
|
||||
/// In-memory implementation of [ItineraryConfigRepository].
|
||||
class ItineraryConfigRepositoryMemory implements ItineraryConfigRepository {
|
||||
ItineraryConfig? _itineraryConfig;
|
||||
|
||||
@override
|
||||
Future<Result<ItineraryConfig>> getItineraryConfig() async {
|
||||
return Result.ok(_itineraryConfig ?? const ItineraryConfig());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<bool>> setItineraryConfig(
|
||||
ItineraryConfig itineraryConfig,
|
||||
) async {
|
||||
_itineraryConfig = itineraryConfig;
|
||||
return Result.ok(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/user/user.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
/// Data source for user related data
|
||||
abstract class UserRepository {
|
||||
/// Get current user
|
||||
Future<Result<User>> getUser();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/user/user.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/local/local_data_service.dart';
|
||||
import 'user_repository.dart';
|
||||
|
||||
class UserRepositoryLocal implements UserRepository {
|
||||
UserRepositoryLocal({
|
||||
required LocalDataService localDataService,
|
||||
}) : _localDataService = localDataService;
|
||||
|
||||
final LocalDataService _localDataService;
|
||||
|
||||
@override
|
||||
Future<Result<User>> getUser() async {
|
||||
return Result.ok(_localDataService.getUser());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../domain/models/user/user.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api/api_client.dart';
|
||||
import '../../services/api/model/user/user_api_model.dart';
|
||||
import 'user_repository.dart';
|
||||
|
||||
class UserRepositoryRemote implements UserRepository {
|
||||
UserRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
User? _cachedData;
|
||||
|
||||
@override
|
||||
Future<Result<User>> getUser() async {
|
||||
if (_cachedData != null) {
|
||||
return Future.value(Result.ok(_cachedData!));
|
||||
}
|
||||
|
||||
final result = await _apiClient.getUser();
|
||||
switch (result) {
|
||||
case Ok<UserApiModel>():
|
||||
final user = User(
|
||||
name: result.value.name,
|
||||
picture: result.value.picture,
|
||||
);
|
||||
_cachedData = user;
|
||||
return Result.ok(user);
|
||||
case Error<UserApiModel>():
|
||||
return Result.error(result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
210
compass_app/app/lib/data/services/api/api_client.dart
Normal file
210
compass_app/app/lib/data/services/api/api_client.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../domain/models/continent/continent.dart';
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import 'model/booking/booking_api_model.dart';
|
||||
import 'model/user/user_api_model.dart';
|
||||
|
||||
/// Adds the `Authentication` header to a header configuration.
|
||||
typedef AuthHeaderProvider = String? Function();
|
||||
|
||||
class ApiClient {
|
||||
ApiClient({
|
||||
String? host,
|
||||
int? port,
|
||||
HttpClient Function()? clientFactory,
|
||||
}) : _host = host ?? 'localhost',
|
||||
_port = port ?? 8080,
|
||||
_clientFactory = clientFactory ?? (() => HttpClient());
|
||||
|
||||
final String _host;
|
||||
final int _port;
|
||||
final HttpClient Function() _clientFactory;
|
||||
|
||||
AuthHeaderProvider? _authHeaderProvider;
|
||||
|
||||
set authHeaderProvider(AuthHeaderProvider authHeaderProvider) {
|
||||
_authHeaderProvider = authHeaderProvider;
|
||||
}
|
||||
|
||||
Future<void> _authHeader(HttpHeaders headers) async {
|
||||
final header = _authHeaderProvider?.call();
|
||||
if (header != null) {
|
||||
headers.add(HttpHeaders.authorizationHeader, header);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.get(_host, _port, '/continent');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
return Result.ok(
|
||||
json.map((element) => Continent.fromJson(element)).toList());
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.get(_host, _port, '/destination');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
return Result.ok(
|
||||
json.map((element) => Destination.fromJson(element)).toList());
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<List<Activity>>> getActivityByDestination(String ref) async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request =
|
||||
await client.get(_host, _port, '/destination/$ref/activity');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
final activities =
|
||||
json.map((element) => Activity.fromJson(element)).toList();
|
||||
return Result.ok(activities);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<List<BookingApiModel>>> getBookings() async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.get(_host, _port, '/booking');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
final bookings =
|
||||
json.map((element) => BookingApiModel.fromJson(element)).toList();
|
||||
return Result.ok(bookings);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<BookingApiModel>> getBooking(int id) async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.get(_host, _port, '/booking/$id');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final booking = BookingApiModel.fromJson(jsonDecode(stringData));
|
||||
return Result.ok(booking);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.post(_host, _port, '/booking');
|
||||
await _authHeader(request.headers);
|
||||
request.write(jsonEncode(booking));
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 201) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final booking = BookingApiModel.fromJson(jsonDecode(stringData));
|
||||
return Result.ok(booking);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<UserApiModel>> getUser() async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.get(_host, _port, '/user');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final user = UserApiModel.fromJson(jsonDecode(stringData));
|
||||
return Result.ok(user);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void>> deleteBooking(int id) async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.delete(_host, _port, '/booking/$id');
|
||||
await _authHeader(request.headers);
|
||||
final response = await request.close();
|
||||
// Response 204 "No Content", delete was successful
|
||||
if (response.statusCode == 204) {
|
||||
return Result.ok(null);
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
compass_app/app/lib/data/services/api/auth_api_client.dart
Normal file
43
compass_app/app/lib/data/services/api/auth_api_client.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import 'model/login_request/login_request.dart';
|
||||
import 'model/login_response/login_response.dart';
|
||||
|
||||
class AuthApiClient {
|
||||
AuthApiClient({
|
||||
String? host,
|
||||
int? port,
|
||||
HttpClient Function()? clientFactory,
|
||||
}) : _host = host ?? 'localhost',
|
||||
_port = port ?? 8080,
|
||||
_clientFactory = clientFactory ?? (() => HttpClient());
|
||||
|
||||
final String _host;
|
||||
final int _port;
|
||||
final HttpClient Function() _clientFactory;
|
||||
|
||||
Future<Result<LoginResponse>> login(LoginRequest loginRequest) async {
|
||||
final client = _clientFactory();
|
||||
try {
|
||||
final request = await client.post(_host, _port, '/login');
|
||||
request.write(jsonEncode(loginRequest));
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
return Result.ok(LoginResponse.fromJson(jsonDecode(stringData)));
|
||||
} else {
|
||||
return Result.error(const HttpException("Login error"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'booking_api_model.freezed.dart';
|
||||
part 'booking_api_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class BookingApiModel with _$BookingApiModel {
|
||||
const factory BookingApiModel({
|
||||
/// Booking ID. Generated when stored in server.
|
||||
int? id,
|
||||
|
||||
/// Start date of the trip
|
||||
required DateTime startDate,
|
||||
|
||||
/// End date of the trip
|
||||
required DateTime endDate,
|
||||
|
||||
/// Booking name
|
||||
/// Should be "Destination, Continent"
|
||||
required String name,
|
||||
|
||||
/// Destination of the trip
|
||||
required String destinationRef,
|
||||
|
||||
/// List of chosen activities
|
||||
required List<String> activitiesRef,
|
||||
}) = _BookingApiModel;
|
||||
|
||||
factory BookingApiModel.fromJson(Map<String, Object?> json) =>
|
||||
_$BookingApiModelFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
// 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_api_model.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');
|
||||
|
||||
BookingApiModel _$BookingApiModelFromJson(Map<String, dynamic> json) {
|
||||
return _BookingApiModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$BookingApiModel {
|
||||
/// Booking ID. Generated when stored in server.
|
||||
int? get id => throw _privateConstructorUsedError;
|
||||
|
||||
/// Start date of the trip
|
||||
DateTime get startDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// End date of the trip
|
||||
DateTime get endDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// Booking name
|
||||
/// Should be "Destination, Continent"
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// Destination of the trip
|
||||
String get destinationRef => throw _privateConstructorUsedError;
|
||||
|
||||
/// List of chosen activities
|
||||
List<String> get activitiesRef => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this BookingApiModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of BookingApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$BookingApiModelCopyWith<BookingApiModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $BookingApiModelCopyWith<$Res> {
|
||||
factory $BookingApiModelCopyWith(
|
||||
BookingApiModel value, $Res Function(BookingApiModel) then) =
|
||||
_$BookingApiModelCopyWithImpl<$Res, BookingApiModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int? id,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
String name,
|
||||
String destinationRef,
|
||||
List<String> activitiesRef});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$BookingApiModelCopyWithImpl<$Res, $Val extends BookingApiModel>
|
||||
implements $BookingApiModelCopyWith<$Res> {
|
||||
_$BookingApiModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of BookingApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = freezed,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? name = null,
|
||||
Object? destinationRef = null,
|
||||
Object? activitiesRef = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: freezed == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
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,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
destinationRef: null == destinationRef
|
||||
? _value.destinationRef
|
||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
activitiesRef: null == activitiesRef
|
||||
? _value.activitiesRef
|
||||
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$BookingApiModelImplCopyWith<$Res>
|
||||
implements $BookingApiModelCopyWith<$Res> {
|
||||
factory _$$BookingApiModelImplCopyWith(_$BookingApiModelImpl value,
|
||||
$Res Function(_$BookingApiModelImpl) then) =
|
||||
__$$BookingApiModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int? id,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
String name,
|
||||
String destinationRef,
|
||||
List<String> activitiesRef});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$BookingApiModelImplCopyWithImpl<$Res>
|
||||
extends _$BookingApiModelCopyWithImpl<$Res, _$BookingApiModelImpl>
|
||||
implements _$$BookingApiModelImplCopyWith<$Res> {
|
||||
__$$BookingApiModelImplCopyWithImpl(
|
||||
_$BookingApiModelImpl _value, $Res Function(_$BookingApiModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of BookingApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = freezed,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? name = null,
|
||||
Object? destinationRef = null,
|
||||
Object? activitiesRef = null,
|
||||
}) {
|
||||
return _then(_$BookingApiModelImpl(
|
||||
id: freezed == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
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,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
destinationRef: null == destinationRef
|
||||
? _value.destinationRef
|
||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
activitiesRef: null == activitiesRef
|
||||
? _value._activitiesRef
|
||||
: activitiesRef // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$BookingApiModelImpl implements _BookingApiModel {
|
||||
const _$BookingApiModelImpl(
|
||||
{this.id,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.name,
|
||||
required this.destinationRef,
|
||||
required final List<String> activitiesRef})
|
||||
: _activitiesRef = activitiesRef;
|
||||
|
||||
factory _$BookingApiModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$BookingApiModelImplFromJson(json);
|
||||
|
||||
/// Booking ID. Generated when stored in server.
|
||||
@override
|
||||
final int? id;
|
||||
|
||||
/// Start date of the trip
|
||||
@override
|
||||
final DateTime startDate;
|
||||
|
||||
/// End date of the trip
|
||||
@override
|
||||
final DateTime endDate;
|
||||
|
||||
/// Booking name
|
||||
/// Should be "Destination, Continent"
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// Destination of the trip
|
||||
@override
|
||||
final String destinationRef;
|
||||
|
||||
/// List of chosen activities
|
||||
final List<String> _activitiesRef;
|
||||
|
||||
/// List of chosen activities
|
||||
@override
|
||||
List<String> get activitiesRef {
|
||||
if (_activitiesRef is EqualUnmodifiableListView) return _activitiesRef;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_activitiesRef);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BookingApiModel(id: $id, startDate: $startDate, endDate: $endDate, name: $name, destinationRef: $destinationRef, activitiesRef: $activitiesRef)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$BookingApiModelImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.destinationRef, destinationRef) ||
|
||||
other.destinationRef == destinationRef) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._activitiesRef, _activitiesRef));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, startDate, endDate, name,
|
||||
destinationRef, const DeepCollectionEquality().hash(_activitiesRef));
|
||||
|
||||
/// Create a copy of BookingApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith =>
|
||||
__$$BookingApiModelImplCopyWithImpl<_$BookingApiModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$BookingApiModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _BookingApiModel implements BookingApiModel {
|
||||
const factory _BookingApiModel(
|
||||
{final int? id,
|
||||
required final DateTime startDate,
|
||||
required final DateTime endDate,
|
||||
required final String name,
|
||||
required final String destinationRef,
|
||||
required final List<String> activitiesRef}) = _$BookingApiModelImpl;
|
||||
|
||||
factory _BookingApiModel.fromJson(Map<String, dynamic> json) =
|
||||
_$BookingApiModelImpl.fromJson;
|
||||
|
||||
/// Booking ID. Generated when stored in server.
|
||||
@override
|
||||
int? get id;
|
||||
|
||||
/// Start date of the trip
|
||||
@override
|
||||
DateTime get startDate;
|
||||
|
||||
/// End date of the trip
|
||||
@override
|
||||
DateTime get endDate;
|
||||
|
||||
/// Booking name
|
||||
/// Should be "Destination, Continent"
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// Destination of the trip
|
||||
@override
|
||||
String get destinationRef;
|
||||
|
||||
/// List of chosen activities
|
||||
@override
|
||||
List<String> get activitiesRef;
|
||||
|
||||
/// Create a copy of BookingApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'booking_api_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$BookingApiModelImpl _$$BookingApiModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$BookingApiModelImpl(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
startDate: DateTime.parse(json['startDate'] as String),
|
||||
endDate: DateTime.parse(json['endDate'] as String),
|
||||
name: json['name'] as String,
|
||||
destinationRef: json['destinationRef'] as String,
|
||||
activitiesRef: (json['activitiesRef'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$BookingApiModelImplToJson(
|
||||
_$BookingApiModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'startDate': instance.startDate.toIso8601String(),
|
||||
'endDate': instance.endDate.toIso8601String(),
|
||||
'name': instance.name,
|
||||
'destinationRef': instance.destinationRef,
|
||||
'activitiesRef': instance.activitiesRef,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'login_request.freezed.dart';
|
||||
|
||||
part 'login_request.g.dart';
|
||||
|
||||
/// Simple data class to hold login request data.
|
||||
@freezed
|
||||
class LoginRequest with _$LoginRequest {
|
||||
const factory LoginRequest({
|
||||
/// Email address.
|
||||
required String email,
|
||||
|
||||
/// Plain text password.
|
||||
required String password,
|
||||
}) = _LoginRequest;
|
||||
|
||||
factory LoginRequest.fromJson(Map<String, Object?> json) =>
|
||||
_$LoginRequestFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// 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 'login_request.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');
|
||||
|
||||
LoginRequest _$LoginRequestFromJson(Map<String, dynamic> json) {
|
||||
return _LoginRequest.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LoginRequest {
|
||||
/// Email address.
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
|
||||
/// Plain text password.
|
||||
String get password => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this LoginRequest to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of LoginRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$LoginRequestCopyWith<LoginRequest> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LoginRequestCopyWith<$Res> {
|
||||
factory $LoginRequestCopyWith(
|
||||
LoginRequest value, $Res Function(LoginRequest) then) =
|
||||
_$LoginRequestCopyWithImpl<$Res, LoginRequest>;
|
||||
@useResult
|
||||
$Res call({String email, String password});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest>
|
||||
implements $LoginRequestCopyWith<$Res> {
|
||||
_$LoginRequestCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of LoginRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? email = null,
|
||||
Object? password = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LoginRequestImplCopyWith<$Res>
|
||||
implements $LoginRequestCopyWith<$Res> {
|
||||
factory _$$LoginRequestImplCopyWith(
|
||||
_$LoginRequestImpl value, $Res Function(_$LoginRequestImpl) then) =
|
||||
__$$LoginRequestImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String email, String password});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LoginRequestImplCopyWithImpl<$Res>
|
||||
extends _$LoginRequestCopyWithImpl<$Res, _$LoginRequestImpl>
|
||||
implements _$$LoginRequestImplCopyWith<$Res> {
|
||||
__$$LoginRequestImplCopyWithImpl(
|
||||
_$LoginRequestImpl _value, $Res Function(_$LoginRequestImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of LoginRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? email = null,
|
||||
Object? password = null,
|
||||
}) {
|
||||
return _then(_$LoginRequestImpl(
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$LoginRequestImpl implements _LoginRequest {
|
||||
const _$LoginRequestImpl({required this.email, required this.password});
|
||||
|
||||
factory _$LoginRequestImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$LoginRequestImplFromJson(json);
|
||||
|
||||
/// Email address.
|
||||
@override
|
||||
final String email;
|
||||
|
||||
/// Plain text password.
|
||||
@override
|
||||
final String password;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoginRequest(email: $email, password: $password)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LoginRequestImpl &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.password, password) ||
|
||||
other.password == password));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, email, password);
|
||||
|
||||
/// Create a copy of LoginRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
|
||||
__$$LoginRequestImplCopyWithImpl<_$LoginRequestImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$LoginRequestImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _LoginRequest implements LoginRequest {
|
||||
const factory _LoginRequest(
|
||||
{required final String email,
|
||||
required final String password}) = _$LoginRequestImpl;
|
||||
|
||||
factory _LoginRequest.fromJson(Map<String, dynamic> json) =
|
||||
_$LoginRequestImpl.fromJson;
|
||||
|
||||
/// Email address.
|
||||
@override
|
||||
String get email;
|
||||
|
||||
/// Plain text password.
|
||||
@override
|
||||
String get password;
|
||||
|
||||
/// Create a copy of LoginRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'login_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$LoginRequestImpl _$$LoginRequestImplFromJson(Map<String, dynamic> json) =>
|
||||
_$LoginRequestImpl(
|
||||
email: json['email'] as String,
|
||||
password: json['password'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$LoginRequestImplToJson(_$LoginRequestImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'email': instance.email,
|
||||
'password': instance.password,
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'login_response.freezed.dart';
|
||||
|
||||
part 'login_response.g.dart';
|
||||
|
||||
/// LoginResponse model.
|
||||
@freezed
|
||||
class LoginResponse with _$LoginResponse {
|
||||
const factory LoginResponse({
|
||||
/// The token to be used for authentication.
|
||||
required String token,
|
||||
|
||||
/// The user id.
|
||||
required String userId,
|
||||
}) = _LoginResponse;
|
||||
|
||||
factory LoginResponse.fromJson(Map<String, Object?> json) =>
|
||||
_$LoginResponseFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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 'login_response.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');
|
||||
|
||||
LoginResponse _$LoginResponseFromJson(Map<String, dynamic> json) {
|
||||
return _LoginResponse.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LoginResponse {
|
||||
/// The token to be used for authentication.
|
||||
String get token => throw _privateConstructorUsedError;
|
||||
|
||||
/// The user id.
|
||||
String get userId => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this LoginResponse to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of LoginResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$LoginResponseCopyWith<LoginResponse> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LoginResponseCopyWith<$Res> {
|
||||
factory $LoginResponseCopyWith(
|
||||
LoginResponse value, $Res Function(LoginResponse) then) =
|
||||
_$LoginResponseCopyWithImpl<$Res, LoginResponse>;
|
||||
@useResult
|
||||
$Res call({String token, String userId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LoginResponseCopyWithImpl<$Res, $Val extends LoginResponse>
|
||||
implements $LoginResponseCopyWith<$Res> {
|
||||
_$LoginResponseCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of LoginResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? token = null,
|
||||
Object? userId = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
token: null == token
|
||||
? _value.token
|
||||
: token // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LoginResponseImplCopyWith<$Res>
|
||||
implements $LoginResponseCopyWith<$Res> {
|
||||
factory _$$LoginResponseImplCopyWith(
|
||||
_$LoginResponseImpl value, $Res Function(_$LoginResponseImpl) then) =
|
||||
__$$LoginResponseImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String token, String userId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LoginResponseImplCopyWithImpl<$Res>
|
||||
extends _$LoginResponseCopyWithImpl<$Res, _$LoginResponseImpl>
|
||||
implements _$$LoginResponseImplCopyWith<$Res> {
|
||||
__$$LoginResponseImplCopyWithImpl(
|
||||
_$LoginResponseImpl _value, $Res Function(_$LoginResponseImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of LoginResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? token = null,
|
||||
Object? userId = null,
|
||||
}) {
|
||||
return _then(_$LoginResponseImpl(
|
||||
token: null == token
|
||||
? _value.token
|
||||
: token // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$LoginResponseImpl implements _LoginResponse {
|
||||
const _$LoginResponseImpl({required this.token, required this.userId});
|
||||
|
||||
factory _$LoginResponseImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$LoginResponseImplFromJson(json);
|
||||
|
||||
/// The token to be used for authentication.
|
||||
@override
|
||||
final String token;
|
||||
|
||||
/// The user id.
|
||||
@override
|
||||
final String userId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoginResponse(token: $token, userId: $userId)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LoginResponseImpl &&
|
||||
(identical(other.token, token) || other.token == token) &&
|
||||
(identical(other.userId, userId) || other.userId == userId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, token, userId);
|
||||
|
||||
/// Create a copy of LoginResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith =>
|
||||
__$$LoginResponseImplCopyWithImpl<_$LoginResponseImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$LoginResponseImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _LoginResponse implements LoginResponse {
|
||||
const factory _LoginResponse(
|
||||
{required final String token,
|
||||
required final String userId}) = _$LoginResponseImpl;
|
||||
|
||||
factory _LoginResponse.fromJson(Map<String, dynamic> json) =
|
||||
_$LoginResponseImpl.fromJson;
|
||||
|
||||
/// The token to be used for authentication.
|
||||
@override
|
||||
String get token;
|
||||
|
||||
/// The user id.
|
||||
@override
|
||||
String get userId;
|
||||
|
||||
/// Create a copy of LoginResponse
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'login_response.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$LoginResponseImpl _$$LoginResponseImplFromJson(Map<String, dynamic> json) =>
|
||||
_$LoginResponseImpl(
|
||||
token: json['token'] as String,
|
||||
userId: json['userId'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$LoginResponseImplToJson(_$LoginResponseImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'token': instance.token,
|
||||
'userId': instance.userId,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'user_api_model.freezed.dart';
|
||||
part 'user_api_model.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class UserApiModel with _$UserApiModel {
|
||||
const factory UserApiModel({
|
||||
/// The user's ID.
|
||||
required String id,
|
||||
|
||||
/// The user's name.
|
||||
required String name,
|
||||
|
||||
/// The user's email.
|
||||
required String email,
|
||||
|
||||
/// The user's picture URL.
|
||||
required String picture,
|
||||
}) = _UserApiModel;
|
||||
|
||||
factory UserApiModel.fromJson(Map<String, Object?> json) =>
|
||||
_$UserApiModelFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// 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 'user_api_model.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');
|
||||
|
||||
UserApiModel _$UserApiModelFromJson(Map<String, dynamic> json) {
|
||||
return _UserApiModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserApiModel {
|
||||
/// The user's ID.
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
|
||||
/// The user's name.
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// The user's email.
|
||||
String get email => throw _privateConstructorUsedError;
|
||||
|
||||
/// The user's picture URL.
|
||||
String get picture => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this UserApiModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of UserApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$UserApiModelCopyWith<UserApiModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserApiModelCopyWith<$Res> {
|
||||
factory $UserApiModelCopyWith(
|
||||
UserApiModel value, $Res Function(UserApiModel) then) =
|
||||
_$UserApiModelCopyWithImpl<$Res, UserApiModel>;
|
||||
@useResult
|
||||
$Res call({String id, String name, String email, String picture});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserApiModelCopyWithImpl<$Res, $Val extends UserApiModel>
|
||||
implements $UserApiModelCopyWith<$Res> {
|
||||
_$UserApiModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of UserApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? email = null,
|
||||
Object? picture = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
picture: null == picture
|
||||
? _value.picture
|
||||
: picture // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserApiModelImplCopyWith<$Res>
|
||||
implements $UserApiModelCopyWith<$Res> {
|
||||
factory _$$UserApiModelImplCopyWith(
|
||||
_$UserApiModelImpl value, $Res Function(_$UserApiModelImpl) then) =
|
||||
__$$UserApiModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String id, String name, String email, String picture});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserApiModelImplCopyWithImpl<$Res>
|
||||
extends _$UserApiModelCopyWithImpl<$Res, _$UserApiModelImpl>
|
||||
implements _$$UserApiModelImplCopyWith<$Res> {
|
||||
__$$UserApiModelImplCopyWithImpl(
|
||||
_$UserApiModelImpl _value, $Res Function(_$UserApiModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of UserApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? email = null,
|
||||
Object? picture = null,
|
||||
}) {
|
||||
return _then(_$UserApiModelImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
email: null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
picture: null == picture
|
||||
? _value.picture
|
||||
: picture // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserApiModelImpl implements _UserApiModel {
|
||||
const _$UserApiModelImpl(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
required this.picture});
|
||||
|
||||
factory _$UserApiModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserApiModelImplFromJson(json);
|
||||
|
||||
/// The user's ID.
|
||||
@override
|
||||
final String id;
|
||||
|
||||
/// The user's name.
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// The user's email.
|
||||
@override
|
||||
final String email;
|
||||
|
||||
/// The user's picture URL.
|
||||
@override
|
||||
final String picture;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserApiModel(id: $id, name: $name, email: $email, picture: $picture)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserApiModelImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.picture, picture) || other.picture == picture));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, name, email, picture);
|
||||
|
||||
/// Create a copy of UserApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserApiModelImplCopyWith<_$UserApiModelImpl> get copyWith =>
|
||||
__$$UserApiModelImplCopyWithImpl<_$UserApiModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserApiModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserApiModel implements UserApiModel {
|
||||
const factory _UserApiModel(
|
||||
{required final String id,
|
||||
required final String name,
|
||||
required final String email,
|
||||
required final String picture}) = _$UserApiModelImpl;
|
||||
|
||||
factory _UserApiModel.fromJson(Map<String, dynamic> json) =
|
||||
_$UserApiModelImpl.fromJson;
|
||||
|
||||
/// The user's ID.
|
||||
@override
|
||||
String get id;
|
||||
|
||||
/// The user's name.
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// The user's email.
|
||||
@override
|
||||
String get email;
|
||||
|
||||
/// The user's picture URL.
|
||||
@override
|
||||
String get picture;
|
||||
|
||||
/// Create a copy of UserApiModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$UserApiModelImplCopyWith<_$UserApiModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_api_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$UserApiModelImpl _$$UserApiModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserApiModelImpl(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String,
|
||||
picture: json['picture'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserApiModelImplToJson(_$UserApiModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'email': instance.email,
|
||||
'picture': instance.picture,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../../config/assets.dart';
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../domain/models/continent/continent.dart';
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../domain/models/user/user.dart';
|
||||
|
||||
class LocalDataService {
|
||||
List<Continent> getContinents() {
|
||||
return [
|
||||
const Continent(
|
||||
name: 'Europe',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT',
|
||||
),
|
||||
const Continent(
|
||||
name: 'Asia',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1',
|
||||
),
|
||||
const Continent(
|
||||
name: 'South America',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e',
|
||||
),
|
||||
const Continent(
|
||||
name: 'Africa',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF',
|
||||
),
|
||||
const Continent(
|
||||
name: 'North America',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE',
|
||||
),
|
||||
const Continent(
|
||||
name: 'Oceania',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL',
|
||||
),
|
||||
const Continent(
|
||||
name: 'Australia',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<Activity>> getActivities() async {
|
||||
final json = await _loadStringAsset(Assets.activities);
|
||||
return json.map<Activity>((json) => Activity.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
Future<List<Destination>> getDestinations() async {
|
||||
final json = await _loadStringAsset(Assets.destinations);
|
||||
return json.map<Destination>((json) => Destination.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> _loadStringAsset(String asset) async {
|
||||
final localData = await rootBundle.loadString(asset);
|
||||
return (jsonDecode(localData) as List).cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
User getUser() {
|
||||
return const User(
|
||||
name: 'Sofie',
|
||||
// For demo purposes we use a local asset
|
||||
picture: 'assets/user.jpg',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../utils/result.dart';
|
||||
|
||||
class SharedPreferencesService {
|
||||
static const _tokenKey = 'TOKEN';
|
||||
final _log = Logger('SharedPreferencesService');
|
||||
|
||||
Future<Result<String?>> fetchToken() async {
|
||||
try {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
_log.finer('Got token from SharedPreferences');
|
||||
return Result.ok(sharedPreferences.getString(_tokenKey));
|
||||
} on Exception catch (e) {
|
||||
_log.warning('Failed to get token', e);
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void>> saveToken(String? token) async {
|
||||
try {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
if (token == null) {
|
||||
_log.finer('Removed token');
|
||||
await sharedPreferences.remove(_tokenKey);
|
||||
} else {
|
||||
_log.finer('Replaced token');
|
||||
await sharedPreferences.setString(_tokenKey, token);
|
||||
}
|
||||
return Result.ok(null);
|
||||
} on Exception catch (e) {
|
||||
_log.warning('Failed to set token', e);
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
compass_app/app/lib/domain/models/activity/activity.dart
Normal file
56
compass_app/app/lib/domain/models/activity/activity.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'activity.freezed.dart';
|
||||
|
||||
part 'activity.g.dart';
|
||||
|
||||
enum TimeOfDay {
|
||||
any,
|
||||
morning,
|
||||
afternoon,
|
||||
evening,
|
||||
night,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Activity with _$Activity {
|
||||
const factory Activity({
|
||||
/// e.g. 'Glacier Trekking and Ice Climbing'
|
||||
required String name,
|
||||
|
||||
/// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.'
|
||||
required String description,
|
||||
|
||||
/// e.g. 'Matanuska Glacier or Mendenhall Glacier'
|
||||
required String locationName,
|
||||
|
||||
/// Duration in days.
|
||||
/// e.g. 8
|
||||
required int duration,
|
||||
|
||||
/// e.g. 'morning'
|
||||
required TimeOfDay timeOfDay,
|
||||
|
||||
/// e.g. false
|
||||
required bool familyFriendly,
|
||||
|
||||
/// e.g. 4
|
||||
required int price,
|
||||
|
||||
/// e.g. 'alaska'
|
||||
required String destinationRef,
|
||||
|
||||
/// e.g. 'glacier-trekking-and-ice-climbing'
|
||||
required String ref,
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg'
|
||||
required String imageUrl,
|
||||
}) = _Activity;
|
||||
|
||||
factory Activity.fromJson(Map<String, Object?> json) =>
|
||||
_$ActivityFromJson(json);
|
||||
}
|
||||
425
compass_app/app/lib/domain/models/activity/activity.freezed.dart
Normal file
425
compass_app/app/lib/domain/models/activity/activity.freezed.dart
Normal file
@@ -0,0 +1,425 @@
|
||||
// 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 'activity.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');
|
||||
|
||||
Activity _$ActivityFromJson(Map<String, dynamic> json) {
|
||||
return _Activity.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Activity {
|
||||
/// e.g. 'Glacier Trekking and Ice Climbing'
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.'
|
||||
String get description => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Matanuska Glacier or Mendenhall Glacier'
|
||||
String get locationName => throw _privateConstructorUsedError;
|
||||
|
||||
/// Duration in days.
|
||||
/// e.g. 8
|
||||
int get duration => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'morning'
|
||||
TimeOfDay get timeOfDay => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. false
|
||||
bool get familyFriendly => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 4
|
||||
int get price => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'alaska'
|
||||
String get destinationRef => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'glacier-trekking-and-ice-climbing'
|
||||
String get ref => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg'
|
||||
String get imageUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Activity to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Activity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ActivityCopyWith<Activity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ActivityCopyWith<$Res> {
|
||||
factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) =
|
||||
_$ActivityCopyWithImpl<$Res, Activity>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
String description,
|
||||
String locationName,
|
||||
int duration,
|
||||
TimeOfDay timeOfDay,
|
||||
bool familyFriendly,
|
||||
int price,
|
||||
String destinationRef,
|
||||
String ref,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ActivityCopyWithImpl<$Res, $Val extends Activity>
|
||||
implements $ActivityCopyWith<$Res> {
|
||||
_$ActivityCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Activity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? description = null,
|
||||
Object? locationName = null,
|
||||
Object? duration = null,
|
||||
Object? timeOfDay = null,
|
||||
Object? familyFriendly = null,
|
||||
Object? price = null,
|
||||
Object? destinationRef = null,
|
||||
Object? ref = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
description: null == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
locationName: null == locationName
|
||||
? _value.locationName
|
||||
: locationName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
duration: null == duration
|
||||
? _value.duration
|
||||
: duration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
timeOfDay: null == timeOfDay
|
||||
? _value.timeOfDay
|
||||
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
||||
as TimeOfDay,
|
||||
familyFriendly: null == familyFriendly
|
||||
? _value.familyFriendly
|
||||
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
price: null == price
|
||||
? _value.price
|
||||
: price // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
destinationRef: null == destinationRef
|
||||
? _value.destinationRef
|
||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ActivityImplCopyWith<$Res>
|
||||
implements $ActivityCopyWith<$Res> {
|
||||
factory _$$ActivityImplCopyWith(
|
||||
_$ActivityImpl value, $Res Function(_$ActivityImpl) then) =
|
||||
__$$ActivityImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
String description,
|
||||
String locationName,
|
||||
int duration,
|
||||
TimeOfDay timeOfDay,
|
||||
bool familyFriendly,
|
||||
int price,
|
||||
String destinationRef,
|
||||
String ref,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ActivityImplCopyWithImpl<$Res>
|
||||
extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl>
|
||||
implements _$$ActivityImplCopyWith<$Res> {
|
||||
__$$ActivityImplCopyWithImpl(
|
||||
_$ActivityImpl _value, $Res Function(_$ActivityImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Activity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? description = null,
|
||||
Object? locationName = null,
|
||||
Object? duration = null,
|
||||
Object? timeOfDay = null,
|
||||
Object? familyFriendly = null,
|
||||
Object? price = null,
|
||||
Object? destinationRef = null,
|
||||
Object? ref = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_$ActivityImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
description: null == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
locationName: null == locationName
|
||||
? _value.locationName
|
||||
: locationName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
duration: null == duration
|
||||
? _value.duration
|
||||
: duration // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
timeOfDay: null == timeOfDay
|
||||
? _value.timeOfDay
|
||||
: timeOfDay // ignore: cast_nullable_to_non_nullable
|
||||
as TimeOfDay,
|
||||
familyFriendly: null == familyFriendly
|
||||
? _value.familyFriendly
|
||||
: familyFriendly // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
price: null == price
|
||||
? _value.price
|
||||
: price // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
destinationRef: null == destinationRef
|
||||
? _value.destinationRef
|
||||
: destinationRef // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ActivityImpl implements _Activity {
|
||||
const _$ActivityImpl(
|
||||
{required this.name,
|
||||
required this.description,
|
||||
required this.locationName,
|
||||
required this.duration,
|
||||
required this.timeOfDay,
|
||||
required this.familyFriendly,
|
||||
required this.price,
|
||||
required this.destinationRef,
|
||||
required this.ref,
|
||||
required this.imageUrl});
|
||||
|
||||
factory _$ActivityImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ActivityImplFromJson(json);
|
||||
|
||||
/// e.g. 'Glacier Trekking and Ice Climbing'
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.'
|
||||
@override
|
||||
final String description;
|
||||
|
||||
/// e.g. 'Matanuska Glacier or Mendenhall Glacier'
|
||||
@override
|
||||
final String locationName;
|
||||
|
||||
/// Duration in days.
|
||||
/// e.g. 8
|
||||
@override
|
||||
final int duration;
|
||||
|
||||
/// e.g. 'morning'
|
||||
@override
|
||||
final TimeOfDay timeOfDay;
|
||||
|
||||
/// e.g. false
|
||||
@override
|
||||
final bool familyFriendly;
|
||||
|
||||
/// e.g. 4
|
||||
@override
|
||||
final int price;
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
final String destinationRef;
|
||||
|
||||
/// e.g. 'glacier-trekking-and-ice-climbing'
|
||||
@override
|
||||
final String ref;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg'
|
||||
@override
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Activity(name: $name, description: $description, locationName: $locationName, duration: $duration, timeOfDay: $timeOfDay, familyFriendly: $familyFriendly, price: $price, destinationRef: $destinationRef, ref: $ref, imageUrl: $imageUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ActivityImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.locationName, locationName) ||
|
||||
other.locationName == locationName) &&
|
||||
(identical(other.duration, duration) ||
|
||||
other.duration == duration) &&
|
||||
(identical(other.timeOfDay, timeOfDay) ||
|
||||
other.timeOfDay == timeOfDay) &&
|
||||
(identical(other.familyFriendly, familyFriendly) ||
|
||||
other.familyFriendly == familyFriendly) &&
|
||||
(identical(other.price, price) || other.price == price) &&
|
||||
(identical(other.destinationRef, destinationRef) ||
|
||||
other.destinationRef == destinationRef) &&
|
||||
(identical(other.ref, ref) || other.ref == ref) &&
|
||||
(identical(other.imageUrl, imageUrl) ||
|
||||
other.imageUrl == imageUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
name,
|
||||
description,
|
||||
locationName,
|
||||
duration,
|
||||
timeOfDay,
|
||||
familyFriendly,
|
||||
price,
|
||||
destinationRef,
|
||||
ref,
|
||||
imageUrl);
|
||||
|
||||
/// Create a copy of Activity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ActivityImplCopyWith<_$ActivityImpl> get copyWith =>
|
||||
__$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ActivityImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Activity implements Activity {
|
||||
const factory _Activity(
|
||||
{required final String name,
|
||||
required final String description,
|
||||
required final String locationName,
|
||||
required final int duration,
|
||||
required final TimeOfDay timeOfDay,
|
||||
required final bool familyFriendly,
|
||||
required final int price,
|
||||
required final String destinationRef,
|
||||
required final String ref,
|
||||
required final String imageUrl}) = _$ActivityImpl;
|
||||
|
||||
factory _Activity.fromJson(Map<String, dynamic> json) =
|
||||
_$ActivityImpl.fromJson;
|
||||
|
||||
/// e.g. 'Glacier Trekking and Ice Climbing'
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.'
|
||||
@override
|
||||
String get description;
|
||||
|
||||
/// e.g. 'Matanuska Glacier or Mendenhall Glacier'
|
||||
@override
|
||||
String get locationName;
|
||||
|
||||
/// Duration in days.
|
||||
/// e.g. 8
|
||||
@override
|
||||
int get duration;
|
||||
|
||||
/// e.g. 'morning'
|
||||
@override
|
||||
TimeOfDay get timeOfDay;
|
||||
|
||||
/// e.g. false
|
||||
@override
|
||||
bool get familyFriendly;
|
||||
|
||||
/// e.g. 4
|
||||
@override
|
||||
int get price;
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
String get destinationRef;
|
||||
|
||||
/// e.g. 'glacier-trekking-and-ice-climbing'
|
||||
@override
|
||||
String get ref;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg'
|
||||
@override
|
||||
String get imageUrl;
|
||||
|
||||
/// Create a copy of Activity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ActivityImplCopyWith<_$ActivityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
43
compass_app/app/lib/domain/models/activity/activity.g.dart
Normal file
43
compass_app/app/lib/domain/models/activity/activity.g.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'activity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ActivityImpl _$$ActivityImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ActivityImpl(
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
locationName: json['locationName'] as String,
|
||||
duration: (json['duration'] as num).toInt(),
|
||||
timeOfDay: $enumDecode(_$TimeOfDayEnumMap, json['timeOfDay']),
|
||||
familyFriendly: json['familyFriendly'] as bool,
|
||||
price: (json['price'] as num).toInt(),
|
||||
destinationRef: json['destinationRef'] as String,
|
||||
ref: json['ref'] as String,
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ActivityImplToJson(_$ActivityImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'locationName': instance.locationName,
|
||||
'duration': instance.duration,
|
||||
'timeOfDay': _$TimeOfDayEnumMap[instance.timeOfDay]!,
|
||||
'familyFriendly': instance.familyFriendly,
|
||||
'price': instance.price,
|
||||
'destinationRef': instance.destinationRef,
|
||||
'ref': instance.ref,
|
||||
'imageUrl': instance.imageUrl,
|
||||
};
|
||||
|
||||
const _$TimeOfDayEnumMap = {
|
||||
TimeOfDay.any: 'any',
|
||||
TimeOfDay.morning: 'morning',
|
||||
TimeOfDay.afternoon: 'afternoon',
|
||||
TimeOfDay.evening: 'evening',
|
||||
TimeOfDay.night: 'night',
|
||||
};
|
||||
35
compass_app/app/lib/domain/models/booking/booking.dart
Normal file
35
compass_app/app/lib/domain/models/booking/booking.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../activity/activity.dart';
|
||||
import '../destination/destination.dart';
|
||||
|
||||
part 'booking.freezed.dart';
|
||||
part 'booking.g.dart';
|
||||
|
||||
@freezed
|
||||
class Booking with _$Booking {
|
||||
const factory Booking({
|
||||
/// Optional ID of the booking.
|
||||
/// May be null if the booking is not yet stored.
|
||||
int? id,
|
||||
|
||||
/// 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);
|
||||
}
|
||||
300
compass_app/app/lib/domain/models/booking/booking.freezed.dart
Normal file
300
compass_app/app/lib/domain/models/booking/booking.freezed.dart
Normal file
@@ -0,0 +1,300 @@
|
||||
// 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 {
|
||||
/// Optional ID of the booking.
|
||||
/// May be null if the booking is not yet stored.
|
||||
int? get id => throw _privateConstructorUsedError;
|
||||
|
||||
/// 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(
|
||||
{int? id,
|
||||
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? id = freezed,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? destination = null,
|
||||
Object? activity = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: freezed == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
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(
|
||||
{int? id,
|
||||
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? id = freezed,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
Object? destination = null,
|
||||
Object? activity = null,
|
||||
}) {
|
||||
return _then(_$BookingImpl(
|
||||
id: freezed == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
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(
|
||||
{this.id,
|
||||
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);
|
||||
|
||||
/// Optional ID of the booking.
|
||||
/// May be null if the booking is not yet stored.
|
||||
@override
|
||||
final int? id;
|
||||
|
||||
/// 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(id: $id, 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.id, id) || other.id == id) &&
|
||||
(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, id, 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(
|
||||
{final int? id,
|
||||
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;
|
||||
|
||||
/// Optional ID of the booking.
|
||||
/// May be null if the booking is not yet stored.
|
||||
@override
|
||||
int? get id;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
28
compass_app/app/lib/domain/models/booking/booking.g.dart
Normal file
28
compass_app/app/lib/domain/models/booking/booking.g.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'booking.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$BookingImpl _$$BookingImplFromJson(Map<String, dynamic> json) =>
|
||||
_$BookingImpl(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
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>{
|
||||
'id': instance.id,
|
||||
'startDate': instance.startDate.toIso8601String(),
|
||||
'endDate': instance.endDate.toIso8601String(),
|
||||
'destination': instance.destination,
|
||||
'activity': instance.activity,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'booking_summary.freezed.dart';
|
||||
part 'booking_summary.g.dart';
|
||||
|
||||
/// BookingSummary contains the necessary data to display a booking
|
||||
/// in the user home screen, but lacks the rest of the booking data
|
||||
/// like activitities or destination.
|
||||
///
|
||||
/// Use the [BookingRepository] to obtain a full [Booking]
|
||||
/// using the [BookingSummary.id].
|
||||
@freezed
|
||||
class BookingSummary with _$BookingSummary {
|
||||
const factory BookingSummary({
|
||||
/// Booking id
|
||||
required int id,
|
||||
|
||||
/// Name to be displayed
|
||||
required String name,
|
||||
|
||||
/// Start date of the booking
|
||||
required DateTime startDate,
|
||||
|
||||
/// End date of the booking
|
||||
required DateTime endDate,
|
||||
}) = _BookingSummary;
|
||||
|
||||
factory BookingSummary.fromJson(Map<String, Object?> json) =>
|
||||
_$BookingSummaryFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
// 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_summary.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');
|
||||
|
||||
BookingSummary _$BookingSummaryFromJson(Map<String, dynamic> json) {
|
||||
return _BookingSummary.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$BookingSummary {
|
||||
/// Booking id
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
|
||||
/// Name to be displayed
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// Start date of the booking
|
||||
DateTime get startDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// End date of the booking
|
||||
DateTime get endDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this BookingSummary to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of BookingSummary
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$BookingSummaryCopyWith<BookingSummary> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $BookingSummaryCopyWith<$Res> {
|
||||
factory $BookingSummaryCopyWith(
|
||||
BookingSummary value, $Res Function(BookingSummary) then) =
|
||||
_$BookingSummaryCopyWithImpl<$Res, BookingSummary>;
|
||||
@useResult
|
||||
$Res call({int id, String name, DateTime startDate, DateTime endDate});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$BookingSummaryCopyWithImpl<$Res, $Val extends BookingSummary>
|
||||
implements $BookingSummaryCopyWith<$Res> {
|
||||
_$BookingSummaryCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of BookingSummary
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
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,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$BookingSummaryImplCopyWith<$Res>
|
||||
implements $BookingSummaryCopyWith<$Res> {
|
||||
factory _$$BookingSummaryImplCopyWith(_$BookingSummaryImpl value,
|
||||
$Res Function(_$BookingSummaryImpl) then) =
|
||||
__$$BookingSummaryImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int id, String name, DateTime startDate, DateTime endDate});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$BookingSummaryImplCopyWithImpl<$Res>
|
||||
extends _$BookingSummaryCopyWithImpl<$Res, _$BookingSummaryImpl>
|
||||
implements _$$BookingSummaryImplCopyWith<$Res> {
|
||||
__$$BookingSummaryImplCopyWithImpl(
|
||||
_$BookingSummaryImpl _value, $Res Function(_$BookingSummaryImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of BookingSummary
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? startDate = null,
|
||||
Object? endDate = null,
|
||||
}) {
|
||||
return _then(_$BookingSummaryImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$BookingSummaryImpl implements _BookingSummary {
|
||||
const _$BookingSummaryImpl(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.startDate,
|
||||
required this.endDate});
|
||||
|
||||
factory _$BookingSummaryImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$BookingSummaryImplFromJson(json);
|
||||
|
||||
/// Booking id
|
||||
@override
|
||||
final int id;
|
||||
|
||||
/// Name to be displayed
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// Start date of the booking
|
||||
@override
|
||||
final DateTime startDate;
|
||||
|
||||
/// End date of the booking
|
||||
@override
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BookingSummary(id: $id, name: $name, startDate: $startDate, endDate: $endDate)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$BookingSummaryImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, name, startDate, endDate);
|
||||
|
||||
/// Create a copy of BookingSummary
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$BookingSummaryImplCopyWith<_$BookingSummaryImpl> get copyWith =>
|
||||
__$$BookingSummaryImplCopyWithImpl<_$BookingSummaryImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$BookingSummaryImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _BookingSummary implements BookingSummary {
|
||||
const factory _BookingSummary(
|
||||
{required final int id,
|
||||
required final String name,
|
||||
required final DateTime startDate,
|
||||
required final DateTime endDate}) = _$BookingSummaryImpl;
|
||||
|
||||
factory _BookingSummary.fromJson(Map<String, dynamic> json) =
|
||||
_$BookingSummaryImpl.fromJson;
|
||||
|
||||
/// Booking id
|
||||
@override
|
||||
int get id;
|
||||
|
||||
/// Name to be displayed
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// Start date of the booking
|
||||
@override
|
||||
DateTime get startDate;
|
||||
|
||||
/// End date of the booking
|
||||
@override
|
||||
DateTime get endDate;
|
||||
|
||||
/// Create a copy of BookingSummary
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$BookingSummaryImplCopyWith<_$BookingSummaryImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'booking_summary.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$BookingSummaryImpl _$$BookingSummaryImplFromJson(Map<String, dynamic> json) =>
|
||||
_$BookingSummaryImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
startDate: DateTime.parse(json['startDate'] as String),
|
||||
endDate: DateTime.parse(json['endDate'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$BookingSummaryImplToJson(
|
||||
_$BookingSummaryImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'startDate': instance.startDate.toIso8601String(),
|
||||
'endDate': instance.endDate.toIso8601String(),
|
||||
};
|
||||
23
compass_app/app/lib/domain/models/continent/continent.dart
Normal file
23
compass_app/app/lib/domain/models/continent/continent.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'continent.freezed.dart';
|
||||
|
||||
part 'continent.g.dart';
|
||||
|
||||
@freezed
|
||||
class Continent with _$Continent {
|
||||
const factory Continent({
|
||||
/// e.g. 'Europe'
|
||||
required String name,
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
required String imageUrl,
|
||||
}) = _Continent;
|
||||
|
||||
factory Continent.fromJson(Map<String, Object?> json) =>
|
||||
_$ContinentFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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 'continent.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');
|
||||
|
||||
Continent _$ContinentFromJson(Map<String, dynamic> json) {
|
||||
return _Continent.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Continent {
|
||||
/// e.g. 'Europe'
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
String get imageUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Continent to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ContinentCopyWith<Continent> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ContinentCopyWith<$Res> {
|
||||
factory $ContinentCopyWith(Continent value, $Res Function(Continent) then) =
|
||||
_$ContinentCopyWithImpl<$Res, Continent>;
|
||||
@useResult
|
||||
$Res call({String name, String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ContinentCopyWithImpl<$Res, $Val extends Continent>
|
||||
implements $ContinentCopyWith<$Res> {
|
||||
_$ContinentCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ContinentImplCopyWith<$Res>
|
||||
implements $ContinentCopyWith<$Res> {
|
||||
factory _$$ContinentImplCopyWith(
|
||||
_$ContinentImpl value, $Res Function(_$ContinentImpl) then) =
|
||||
__$$ContinentImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ContinentImplCopyWithImpl<$Res>
|
||||
extends _$ContinentCopyWithImpl<$Res, _$ContinentImpl>
|
||||
implements _$$ContinentImplCopyWith<$Res> {
|
||||
__$$ContinentImplCopyWithImpl(
|
||||
_$ContinentImpl _value, $Res Function(_$ContinentImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_$ContinentImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ContinentImpl implements _Continent {
|
||||
const _$ContinentImpl({required this.name, required this.imageUrl});
|
||||
|
||||
factory _$ContinentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ContinentImplFromJson(json);
|
||||
|
||||
/// e.g. 'Europe'
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
@override
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Continent(name: $name, imageUrl: $imageUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ContinentImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.imageUrl, imageUrl) ||
|
||||
other.imageUrl == imageUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, imageUrl);
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ContinentImplCopyWith<_$ContinentImpl> get copyWith =>
|
||||
__$$ContinentImplCopyWithImpl<_$ContinentImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ContinentImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Continent implements Continent {
|
||||
const factory _Continent(
|
||||
{required final String name,
|
||||
required final String imageUrl}) = _$ContinentImpl;
|
||||
|
||||
factory _Continent.fromJson(Map<String, dynamic> json) =
|
||||
_$ContinentImpl.fromJson;
|
||||
|
||||
/// e.g. 'Europe'
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
@override
|
||||
String get imageUrl;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ContinentImplCopyWith<_$ContinentImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
19
compass_app/app/lib/domain/models/continent/continent.g.dart
Normal file
19
compass_app/app/lib/domain/models/continent/continent.g.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'continent.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ContinentImpl _$$ContinentImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ContinentImpl(
|
||||
name: json['name'] as String,
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ContinentImplToJson(_$ContinentImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'imageUrl': instance.imageUrl,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'destination.freezed.dart';
|
||||
|
||||
part 'destination.g.dart';
|
||||
|
||||
@freezed
|
||||
class Destination with _$Destination {
|
||||
const factory Destination({
|
||||
/// e.g. 'alaska'
|
||||
required String ref,
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
required String name,
|
||||
|
||||
/// e.g. 'United States'
|
||||
required String country,
|
||||
|
||||
/// e.g. 'North America'
|
||||
required String continent,
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
required String knownFor,
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
required List<String> tags,
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
required String imageUrl,
|
||||
}) = _Destination;
|
||||
|
||||
factory Destination.fromJson(Map<String, Object?> json) =>
|
||||
_$DestinationFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
// 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 'destination.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');
|
||||
|
||||
Destination _$DestinationFromJson(Map<String, dynamic> json) {
|
||||
return _Destination.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Destination {
|
||||
/// e.g. 'alaska'
|
||||
String get ref => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'United States'
|
||||
String get country => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'North America'
|
||||
String get continent => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
String get knownFor => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
List<String> get tags => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
String get imageUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Destination to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$DestinationCopyWith<Destination> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $DestinationCopyWith<$Res> {
|
||||
factory $DestinationCopyWith(
|
||||
Destination value, $Res Function(Destination) then) =
|
||||
_$DestinationCopyWithImpl<$Res, Destination>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String ref,
|
||||
String name,
|
||||
String country,
|
||||
String continent,
|
||||
String knownFor,
|
||||
List<String> tags,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$DestinationCopyWithImpl<$Res, $Val extends Destination>
|
||||
implements $DestinationCopyWith<$Res> {
|
||||
_$DestinationCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? ref = null,
|
||||
Object? name = null,
|
||||
Object? country = null,
|
||||
Object? continent = null,
|
||||
Object? knownFor = null,
|
||||
Object? tags = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
country: null == country
|
||||
? _value.country
|
||||
: country // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
continent: null == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
knownFor: null == knownFor
|
||||
? _value.knownFor
|
||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tags: null == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$DestinationImplCopyWith<$Res>
|
||||
implements $DestinationCopyWith<$Res> {
|
||||
factory _$$DestinationImplCopyWith(
|
||||
_$DestinationImpl value, $Res Function(_$DestinationImpl) then) =
|
||||
__$$DestinationImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String ref,
|
||||
String name,
|
||||
String country,
|
||||
String continent,
|
||||
String knownFor,
|
||||
List<String> tags,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$DestinationImplCopyWithImpl<$Res>
|
||||
extends _$DestinationCopyWithImpl<$Res, _$DestinationImpl>
|
||||
implements _$$DestinationImplCopyWith<$Res> {
|
||||
__$$DestinationImplCopyWithImpl(
|
||||
_$DestinationImpl _value, $Res Function(_$DestinationImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? ref = null,
|
||||
Object? name = null,
|
||||
Object? country = null,
|
||||
Object? continent = null,
|
||||
Object? knownFor = null,
|
||||
Object? tags = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_$DestinationImpl(
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
country: null == country
|
||||
? _value.country
|
||||
: country // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
continent: null == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
knownFor: null == knownFor
|
||||
? _value.knownFor
|
||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tags: null == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$DestinationImpl implements _Destination {
|
||||
const _$DestinationImpl(
|
||||
{required this.ref,
|
||||
required this.name,
|
||||
required this.country,
|
||||
required this.continent,
|
||||
required this.knownFor,
|
||||
required final List<String> tags,
|
||||
required this.imageUrl})
|
||||
: _tags = tags;
|
||||
|
||||
factory _$DestinationImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$DestinationImplFromJson(json);
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
final String ref;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// e.g. 'United States'
|
||||
@override
|
||||
final String country;
|
||||
|
||||
/// e.g. 'North America'
|
||||
@override
|
||||
final String continent;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
@override
|
||||
final String knownFor;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
final List<String> _tags;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
@override
|
||||
List<String> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
@override
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Destination(ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$DestinationImpl &&
|
||||
(identical(other.ref, ref) || other.ref == ref) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.country, country) || other.country == country) &&
|
||||
(identical(other.continent, continent) ||
|
||||
other.continent == continent) &&
|
||||
(identical(other.knownFor, knownFor) ||
|
||||
other.knownFor == knownFor) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||
(identical(other.imageUrl, imageUrl) ||
|
||||
other.imageUrl == imageUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, ref, name, country, continent,
|
||||
knownFor, const DeepCollectionEquality().hash(_tags), imageUrl);
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$DestinationImplCopyWith<_$DestinationImpl> get copyWith =>
|
||||
__$$DestinationImplCopyWithImpl<_$DestinationImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$DestinationImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Destination implements Destination {
|
||||
const factory _Destination(
|
||||
{required final String ref,
|
||||
required final String name,
|
||||
required final String country,
|
||||
required final String continent,
|
||||
required final String knownFor,
|
||||
required final List<String> tags,
|
||||
required final String imageUrl}) = _$DestinationImpl;
|
||||
|
||||
factory _Destination.fromJson(Map<String, dynamic> json) =
|
||||
_$DestinationImpl.fromJson;
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
String get ref;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// e.g. 'United States'
|
||||
@override
|
||||
String get country;
|
||||
|
||||
/// e.g. 'North America'
|
||||
@override
|
||||
String get continent;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
@override
|
||||
String get knownFor;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
@override
|
||||
List<String> get tags;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
@override
|
||||
String get imageUrl;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$DestinationImplCopyWith<_$DestinationImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'destination.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$DestinationImpl _$$DestinationImplFromJson(Map<String, dynamic> json) =>
|
||||
_$DestinationImpl(
|
||||
ref: json['ref'] as String,
|
||||
name: json['name'] as String,
|
||||
country: json['country'] as String,
|
||||
continent: json['continent'] as String,
|
||||
knownFor: json['knownFor'] as String,
|
||||
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DestinationImplToJson(_$DestinationImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'ref': instance.ref,
|
||||
'name': instance.name,
|
||||
'country': instance.country,
|
||||
'continent': instance.continent,
|
||||
'knownFor': instance.knownFor,
|
||||
'tags': instance.tags,
|
||||
'imageUrl': instance.imageUrl,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'itinerary_config.freezed.dart';
|
||||
|
||||
part 'itinerary_config.g.dart';
|
||||
|
||||
@freezed
|
||||
class ItineraryConfig with _$ItineraryConfig {
|
||||
const factory ItineraryConfig({
|
||||
/// [Continent] name
|
||||
String? continent,
|
||||
|
||||
/// Start date (check in) of itinerary
|
||||
DateTime? startDate,
|
||||
|
||||
/// End date (check out) of itinerary
|
||||
DateTime? endDate,
|
||||
|
||||
/// Number of guests
|
||||
int? guests,
|
||||
|
||||
/// Selected [Destination] reference
|
||||
String? destination,
|
||||
|
||||
/// Selected [Activity] references
|
||||
@Default([]) List<String> activities,
|
||||
}) = _ItineraryConfig;
|
||||
|
||||
factory ItineraryConfig.fromJson(Map<String, Object?> json) =>
|
||||
_$ItineraryConfigFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// 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 'itinerary_config.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');
|
||||
|
||||
ItineraryConfig _$ItineraryConfigFromJson(Map<String, dynamic> json) {
|
||||
return _ItineraryConfig.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ItineraryConfig {
|
||||
/// [Continent] name
|
||||
String? get continent => throw _privateConstructorUsedError;
|
||||
|
||||
/// Start date (check in) of itinerary
|
||||
DateTime? get startDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// End date (check out) of itinerary
|
||||
DateTime? get endDate => throw _privateConstructorUsedError;
|
||||
|
||||
/// Number of guests
|
||||
int? get guests => throw _privateConstructorUsedError;
|
||||
|
||||
/// Selected [Destination] reference
|
||||
String? get destination => throw _privateConstructorUsedError;
|
||||
|
||||
/// Selected [Activity] references
|
||||
List<String> get activities => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ItineraryConfig to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ItineraryConfig
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ItineraryConfigCopyWith<ItineraryConfig> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ItineraryConfigCopyWith<$Res> {
|
||||
factory $ItineraryConfigCopyWith(
|
||||
ItineraryConfig value, $Res Function(ItineraryConfig) then) =
|
||||
_$ItineraryConfigCopyWithImpl<$Res, ItineraryConfig>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? continent,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
int? guests,
|
||||
String? destination,
|
||||
List<String> activities});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ItineraryConfigCopyWithImpl<$Res, $Val extends ItineraryConfig>
|
||||
implements $ItineraryConfigCopyWith<$Res> {
|
||||
_$ItineraryConfigCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ItineraryConfig
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? continent = freezed,
|
||||
Object? startDate = freezed,
|
||||
Object? endDate = freezed,
|
||||
Object? guests = freezed,
|
||||
Object? destination = freezed,
|
||||
Object? activities = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
continent: freezed == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
startDate: freezed == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
endDate: freezed == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
guests: freezed == guests
|
||||
? _value.guests
|
||||
: guests // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
destination: freezed == destination
|
||||
? _value.destination
|
||||
: destination // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
activities: null == activities
|
||||
? _value.activities
|
||||
: activities // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ItineraryConfigImplCopyWith<$Res>
|
||||
implements $ItineraryConfigCopyWith<$Res> {
|
||||
factory _$$ItineraryConfigImplCopyWith(_$ItineraryConfigImpl value,
|
||||
$Res Function(_$ItineraryConfigImpl) then) =
|
||||
__$$ItineraryConfigImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? continent,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
int? guests,
|
||||
String? destination,
|
||||
List<String> activities});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ItineraryConfigImplCopyWithImpl<$Res>
|
||||
extends _$ItineraryConfigCopyWithImpl<$Res, _$ItineraryConfigImpl>
|
||||
implements _$$ItineraryConfigImplCopyWith<$Res> {
|
||||
__$$ItineraryConfigImplCopyWithImpl(
|
||||
_$ItineraryConfigImpl _value, $Res Function(_$ItineraryConfigImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ItineraryConfig
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? continent = freezed,
|
||||
Object? startDate = freezed,
|
||||
Object? endDate = freezed,
|
||||
Object? guests = freezed,
|
||||
Object? destination = freezed,
|
||||
Object? activities = null,
|
||||
}) {
|
||||
return _then(_$ItineraryConfigImpl(
|
||||
continent: freezed == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
startDate: freezed == startDate
|
||||
? _value.startDate
|
||||
: startDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
endDate: freezed == endDate
|
||||
? _value.endDate
|
||||
: endDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
guests: freezed == guests
|
||||
? _value.guests
|
||||
: guests // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
destination: freezed == destination
|
||||
? _value.destination
|
||||
: destination // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
activities: null == activities
|
||||
? _value._activities
|
||||
: activities // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ItineraryConfigImpl implements _ItineraryConfig {
|
||||
const _$ItineraryConfigImpl(
|
||||
{this.continent,
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.guests,
|
||||
this.destination,
|
||||
final List<String> activities = const []})
|
||||
: _activities = activities;
|
||||
|
||||
factory _$ItineraryConfigImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ItineraryConfigImplFromJson(json);
|
||||
|
||||
/// [Continent] name
|
||||
@override
|
||||
final String? continent;
|
||||
|
||||
/// Start date (check in) of itinerary
|
||||
@override
|
||||
final DateTime? startDate;
|
||||
|
||||
/// End date (check out) of itinerary
|
||||
@override
|
||||
final DateTime? endDate;
|
||||
|
||||
/// Number of guests
|
||||
@override
|
||||
final int? guests;
|
||||
|
||||
/// Selected [Destination] reference
|
||||
@override
|
||||
final String? destination;
|
||||
|
||||
/// Selected [Activity] references
|
||||
final List<String> _activities;
|
||||
|
||||
/// Selected [Activity] references
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get activities {
|
||||
if (_activities is EqualUnmodifiableListView) return _activities;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_activities);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ItineraryConfig(continent: $continent, startDate: $startDate, endDate: $endDate, guests: $guests, destination: $destination, activities: $activities)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ItineraryConfigImpl &&
|
||||
(identical(other.continent, continent) ||
|
||||
other.continent == continent) &&
|
||||
(identical(other.startDate, startDate) ||
|
||||
other.startDate == startDate) &&
|
||||
(identical(other.endDate, endDate) || other.endDate == endDate) &&
|
||||
(identical(other.guests, guests) || other.guests == guests) &&
|
||||
(identical(other.destination, destination) ||
|
||||
other.destination == destination) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._activities, _activities));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, continent, startDate, endDate,
|
||||
guests, destination, const DeepCollectionEquality().hash(_activities));
|
||||
|
||||
/// Create a copy of ItineraryConfig
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ItineraryConfigImplCopyWith<_$ItineraryConfigImpl> get copyWith =>
|
||||
__$$ItineraryConfigImplCopyWithImpl<_$ItineraryConfigImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ItineraryConfigImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ItineraryConfig implements ItineraryConfig {
|
||||
const factory _ItineraryConfig(
|
||||
{final String? continent,
|
||||
final DateTime? startDate,
|
||||
final DateTime? endDate,
|
||||
final int? guests,
|
||||
final String? destination,
|
||||
final List<String> activities}) = _$ItineraryConfigImpl;
|
||||
|
||||
factory _ItineraryConfig.fromJson(Map<String, dynamic> json) =
|
||||
_$ItineraryConfigImpl.fromJson;
|
||||
|
||||
/// [Continent] name
|
||||
@override
|
||||
String? get continent;
|
||||
|
||||
/// Start date (check in) of itinerary
|
||||
@override
|
||||
DateTime? get startDate;
|
||||
|
||||
/// End date (check out) of itinerary
|
||||
@override
|
||||
DateTime? get endDate;
|
||||
|
||||
/// Number of guests
|
||||
@override
|
||||
int? get guests;
|
||||
|
||||
/// Selected [Destination] reference
|
||||
@override
|
||||
String? get destination;
|
||||
|
||||
/// Selected [Activity] references
|
||||
@override
|
||||
List<String> get activities;
|
||||
|
||||
/// Create a copy of ItineraryConfig
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ItineraryConfigImplCopyWith<_$ItineraryConfigImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'itinerary_config.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ItineraryConfigImpl _$$ItineraryConfigImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ItineraryConfigImpl(
|
||||
continent: json['continent'] as String?,
|
||||
startDate: json['startDate'] == null
|
||||
? null
|
||||
: DateTime.parse(json['startDate'] as String),
|
||||
endDate: json['endDate'] == null
|
||||
? null
|
||||
: DateTime.parse(json['endDate'] as String),
|
||||
guests: (json['guests'] as num?)?.toInt(),
|
||||
destination: json['destination'] as String?,
|
||||
activities: (json['activities'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ItineraryConfigImplToJson(
|
||||
_$ItineraryConfigImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'continent': instance.continent,
|
||||
'startDate': instance.startDate?.toIso8601String(),
|
||||
'endDate': instance.endDate?.toIso8601String(),
|
||||
'guests': instance.guests,
|
||||
'destination': instance.destination,
|
||||
'activities': instance.activities,
|
||||
};
|
||||
21
compass_app/app/lib/domain/models/user/user.dart
Normal file
21
compass_app/app/lib/domain/models/user/user.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'user.freezed.dart';
|
||||
part 'user.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class User with _$User {
|
||||
const factory User({
|
||||
/// The user's name.
|
||||
required String name,
|
||||
|
||||
/// The user's picture URL.
|
||||
required String picture,
|
||||
}) = _User;
|
||||
|
||||
factory User.fromJson(Map<String, Object?> json) => _$UserFromJson(json);
|
||||
}
|
||||
185
compass_app/app/lib/domain/models/user/user.freezed.dart
Normal file
185
compass_app/app/lib/domain/models/user/user.freezed.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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 'user.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');
|
||||
|
||||
User _$UserFromJson(Map<String, dynamic> json) {
|
||||
return _User.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$User {
|
||||
/// The user's name.
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// The user's picture URL.
|
||||
String get picture => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this User to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of User
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$UserCopyWith<User> get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserCopyWith<$Res> {
|
||||
factory $UserCopyWith(User value, $Res Function(User) then) =
|
||||
_$UserCopyWithImpl<$Res, User>;
|
||||
@useResult
|
||||
$Res call({String name, String picture});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserCopyWithImpl<$Res, $Val extends User>
|
||||
implements $UserCopyWith<$Res> {
|
||||
_$UserCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of User
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? picture = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
picture: null == picture
|
||||
? _value.picture
|
||||
: picture // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
|
||||
factory _$$UserImplCopyWith(
|
||||
_$UserImpl value, $Res Function(_$UserImpl) then) =
|
||||
__$$UserImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String picture});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserImplCopyWithImpl<$Res>
|
||||
extends _$UserCopyWithImpl<$Res, _$UserImpl>
|
||||
implements _$$UserImplCopyWith<$Res> {
|
||||
__$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of User
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? picture = null,
|
||||
}) {
|
||||
return _then(_$UserImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
picture: null == picture
|
||||
? _value.picture
|
||||
: picture // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserImpl implements _User {
|
||||
const _$UserImpl({required this.name, required this.picture});
|
||||
|
||||
factory _$UserImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserImplFromJson(json);
|
||||
|
||||
/// The user's name.
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// The user's picture URL.
|
||||
@override
|
||||
final String picture;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'User(name: $name, picture: $picture)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.picture, picture) || other.picture == picture));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, picture);
|
||||
|
||||
/// Create a copy of User
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserImplCopyWith<_$UserImpl> get copyWith =>
|
||||
__$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _User implements User {
|
||||
const factory _User(
|
||||
{required final String name, required final String picture}) = _$UserImpl;
|
||||
|
||||
factory _User.fromJson(Map<String, dynamic> json) = _$UserImpl.fromJson;
|
||||
|
||||
/// The user's name.
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// The user's picture URL.
|
||||
@override
|
||||
String get picture;
|
||||
|
||||
/// Create a copy of User
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$UserImplCopyWith<_$UserImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
18
compass_app/app/lib/domain/models/user/user.g.dart
Normal file
18
compass_app/app/lib/domain/models/user/user.g.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$UserImpl _$$UserImplFromJson(Map<String, dynamic> json) => _$UserImpl(
|
||||
name: json['name'] as String,
|
||||
picture: json['picture'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserImplToJson(_$UserImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'picture': instance.picture,
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/activity/activity_repository.dart';
|
||||
import '../../../data/repositories/booking/booking_repository.dart';
|
||||
import '../../../data/repositories/destination/destination_repository.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../models/activity/activity.dart';
|
||||
import '../../models/booking/booking.dart';
|
||||
import '../../models/destination/destination.dart';
|
||||
import '../../models/itinerary_config/itinerary_config.dart';
|
||||
|
||||
/// UseCase for creating [Booking] objects from [ItineraryConfig].
|
||||
///
|
||||
/// Fetches [Destination] and [Activity] objects from repositories,
|
||||
/// checks if dates are set and creates a [Booking] object.
|
||||
class BookingCreateUseCase {
|
||||
BookingCreateUseCase({
|
||||
required DestinationRepository destinationRepository,
|
||||
required ActivityRepository activityRepository,
|
||||
required BookingRepository bookingRepository,
|
||||
}) : _destinationRepository = destinationRepository,
|
||||
_activityRepository = activityRepository,
|
||||
_bookingRepository = bookingRepository;
|
||||
|
||||
final DestinationRepository _destinationRepository;
|
||||
final ActivityRepository _activityRepository;
|
||||
final BookingRepository _bookingRepository;
|
||||
final _log = Logger('BookingCreateUseCase');
|
||||
|
||||
/// 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'));
|
||||
}
|
||||
|
||||
final booking = Booking(
|
||||
startDate: itineraryConfig.startDate!,
|
||||
endDate: itineraryConfig.endDate!,
|
||||
destination: destinationResult.asOk.value,
|
||||
activity: activities,
|
||||
);
|
||||
|
||||
final saveBookingResult = await _bookingRepository.createBooking(booking);
|
||||
switch (saveBookingResult) {
|
||||
case Ok<void>():
|
||||
_log.fine('Booking saved successfully');
|
||||
break;
|
||||
case Error<void>():
|
||||
_log.warning('Failed to save booking', saveBookingResult.error);
|
||||
return Result.error(saveBookingResult.error);
|
||||
}
|
||||
|
||||
// Create Booking object
|
||||
return Result.ok(booking);
|
||||
}
|
||||
|
||||
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,46 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import '../../../ui/core/ui/date_format_start_end.dart';
|
||||
import '../../models/booking/booking.dart';
|
||||
|
||||
typedef ShareFunction = Future<void> Function(String text);
|
||||
|
||||
/// UseCase for sharing a booking.
|
||||
class BookingShareUseCase {
|
||||
BookingShareUseCase._(this._share);
|
||||
|
||||
/// Create a [BookingShareUseCase] that uses `share_plus` package.
|
||||
factory BookingShareUseCase.withSharePlus() =>
|
||||
BookingShareUseCase._(Share.share);
|
||||
|
||||
/// Create a [BookingShareUseCase] with a custom share function.
|
||||
factory BookingShareUseCase.custom(ShareFunction share) =>
|
||||
BookingShareUseCase._(share);
|
||||
|
||||
final ShareFunction _share;
|
||||
final _log = Logger('BookingShareUseCase');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
compass_app/app/lib/main.dart
Normal file
40
compass_app/app/lib/main.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'ui/core/localization/applocalization.dart';
|
||||
import 'ui/core/themes/theme.dart';
|
||||
import 'routing/router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'ui/core/ui/scroll_behavior.dart';
|
||||
import 'main_development.dart' as development;
|
||||
|
||||
/// Default main method
|
||||
void main() {
|
||||
// Launch development config by default
|
||||
development.main();
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
localizationsDelegates: [
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
AppLocalizationDelegate(),
|
||||
],
|
||||
scrollBehavior: AppCustomScrollBehavior(),
|
||||
theme: AppTheme.lightTheme,
|
||||
darkTheme: AppTheme.darkTheme,
|
||||
themeMode: ThemeMode.system,
|
||||
routerConfig: router(context.read()),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
compass_app/app/lib/main_development.dart
Normal file
24
compass_app/app/lib/main_development.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'config/dependencies.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// Development config entry point.
|
||||
/// Launch with `flutter run --target lib/main_development.dart`.
|
||||
/// Uses local data.
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: providersLocal,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
24
compass_app/app/lib/main_staging.dart
Normal file
24
compass_app/app/lib/main_staging.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'config/dependencies.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// Staging config entry point.
|
||||
/// Launch with `flutter run --target lib/main_staging.dart`.
|
||||
/// Uses remote data from a server.
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: providersRemote,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
154
compass_app/app/lib/routing/router.dart
Normal file
154
compass_app/app/lib/routing/router.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../data/repositories/auth/auth_repository.dart';
|
||||
import '../ui/activities/view_models/activities_viewmodel.dart';
|
||||
import '../ui/activities/widgets/activities_screen.dart';
|
||||
import '../ui/auth/login/view_models/login_viewmodel.dart';
|
||||
import '../ui/auth/login/widgets/login_screen.dart';
|
||||
import '../ui/booking/widgets/booking_screen.dart';
|
||||
import '../ui/booking/view_models/booking_viewmodel.dart';
|
||||
import '../ui/home/view_models/home_viewmodel.dart';
|
||||
import '../ui/home/widgets/home_screen.dart';
|
||||
import '../ui/results/view_models/results_viewmodel.dart';
|
||||
import '../ui/results/widgets/results_screen.dart';
|
||||
import '../ui/search_form/view_models/search_form_viewmodel.dart';
|
||||
import '../ui/search_form/widgets/search_form_screen.dart';
|
||||
import 'routes.dart';
|
||||
|
||||
/// Top go_router entry point.
|
||||
///
|
||||
/// Listens to changes in [AuthTokenRepository] to redirect the user
|
||||
/// to /login when the user logs out.
|
||||
GoRouter router(
|
||||
AuthRepository authRepository,
|
||||
) =>
|
||||
GoRouter(
|
||||
initialLocation: Routes.home,
|
||||
debugLogDiagnostics: true,
|
||||
redirect: _redirect,
|
||||
refreshListenable: authRepository,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.login,
|
||||
builder: (context, state) {
|
||||
return LoginScreen(
|
||||
viewModel: LoginViewModel(
|
||||
authRepository: context.read(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.home,
|
||||
builder: (context, state) {
|
||||
final viewModel = HomeViewModel(
|
||||
bookingRepository: context.read(),
|
||||
userRepository: context.read(),
|
||||
);
|
||||
return HomeScreen(viewModel: viewModel);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.searchRelative,
|
||||
builder: (context, state) {
|
||||
final viewModel = SearchFormViewModel(
|
||||
continentRepository: context.read(),
|
||||
itineraryConfigRepository: context.read(),
|
||||
);
|
||||
return SearchFormScreen(viewModel: viewModel);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.resultsRelative,
|
||||
builder: (context, state) {
|
||||
final viewModel = ResultsViewModel(
|
||||
destinationRepository: context.read(),
|
||||
itineraryConfigRepository: context.read(),
|
||||
);
|
||||
return ResultsScreen(
|
||||
viewModel: viewModel,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.activitiesRelative,
|
||||
builder: (context, state) {
|
||||
final viewModel = ActivitiesViewModel(
|
||||
activityRepository: context.read(),
|
||||
itineraryConfigRepository: context.read(),
|
||||
);
|
||||
return ActivitiesScreen(
|
||||
viewModel: viewModel,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.bookingRelative,
|
||||
builder: (context, state) {
|
||||
final viewModel = BookingViewModel(
|
||||
itineraryConfigRepository: context.read(),
|
||||
createBookingUseCase: context.read(),
|
||||
shareBookingUseCase: context.read(),
|
||||
bookingRepository: context.read(),
|
||||
);
|
||||
|
||||
// When opening the booking screen directly
|
||||
// create a new booking from the stored ItineraryConfig.
|
||||
viewModel.createBooking.execute();
|
||||
|
||||
return BookingScreen(
|
||||
viewModel: viewModel,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':id',
|
||||
builder: (context, state) {
|
||||
final id = int.parse(state.pathParameters['id']!);
|
||||
final viewModel = BookingViewModel(
|
||||
itineraryConfigRepository: context.read(),
|
||||
createBookingUseCase: context.read(),
|
||||
shareBookingUseCase: context.read(),
|
||||
bookingRepository: context.read(),
|
||||
);
|
||||
|
||||
// When opening the booking screen with an existing id
|
||||
// load and display that booking.
|
||||
viewModel.loadBooking.execute(id);
|
||||
|
||||
return BookingScreen(
|
||||
viewModel: viewModel,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// From https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart
|
||||
Future<String?> _redirect(BuildContext context, GoRouterState state) async {
|
||||
// if the user is not logged in, they need to login
|
||||
final bool loggedIn = await context.read<AuthRepository>().isAuthenticated;
|
||||
final bool loggingIn = state.matchedLocation == Routes.login;
|
||||
if (!loggedIn) {
|
||||
return Routes.login;
|
||||
}
|
||||
|
||||
// if the user is logged in but still on the login page, send them to
|
||||
// the home page
|
||||
if (loggingIn) {
|
||||
return Routes.home;
|
||||
}
|
||||
|
||||
// no need to redirect at all
|
||||
return null;
|
||||
}
|
||||
17
compass_app/app/lib/routing/routes.dart
Normal file
17
compass_app/app/lib/routing/routes.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
class Routes {
|
||||
static const home = '/';
|
||||
static const login = '/login';
|
||||
static const search = '/$searchRelative';
|
||||
static const searchRelative = 'search';
|
||||
static const results = '/$resultsRelative';
|
||||
static const resultsRelative = 'results';
|
||||
static const activities = '/$activitiesRelative';
|
||||
static const activitiesRelative = 'activities';
|
||||
static const booking = '/$bookingRelative';
|
||||
static const bookingRelative = 'booking';
|
||||
static String bookingWithId(int id) => '$booking/$id';
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/activity/activity_repository.dart';
|
||||
import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/command.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
class ActivitiesViewModel extends ChangeNotifier {
|
||||
ActivitiesViewModel({
|
||||
required ActivityRepository activityRepository,
|
||||
required ItineraryConfigRepository itineraryConfigRepository,
|
||||
}) : _activityRepository = activityRepository,
|
||||
_itineraryConfigRepository = itineraryConfigRepository {
|
||||
loadActivities = Command0(_loadActivities)..execute();
|
||||
saveActivities = Command0(_saveActivities);
|
||||
}
|
||||
|
||||
final _log = Logger('ActivitiesViewModel');
|
||||
final ActivityRepository _activityRepository;
|
||||
final ItineraryConfigRepository _itineraryConfigRepository;
|
||||
List<Activity> _daytimeActivities = <Activity>[];
|
||||
List<Activity> _eveningActivities = <Activity>[];
|
||||
final Set<String> _selectedActivities = <String>{};
|
||||
|
||||
/// List of daytime [Activity] per destination.
|
||||
List<Activity> get daytimeActivities => _daytimeActivities;
|
||||
|
||||
/// List of evening [Activity] per destination.
|
||||
List<Activity> get eveningActivities => _eveningActivities;
|
||||
|
||||
/// Selected [Activity] by ref.
|
||||
Set<String> get selectedActivities => _selectedActivities;
|
||||
|
||||
/// Load list of [Activity] for a [Destination] by ref.
|
||||
late final Command0 loadActivities;
|
||||
|
||||
/// Save list [selectedActivities] into itinerary configuration.
|
||||
late final Command0 saveActivities;
|
||||
|
||||
Future<Result<void>> _loadActivities() async {
|
||||
final result = await _itineraryConfigRepository.getItineraryConfig();
|
||||
if (result is Error) {
|
||||
_log.warning(
|
||||
'Failed to load stored ItineraryConfig',
|
||||
result.asError.error,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
final destinationRef = result.asOk.value.destination;
|
||||
if (destinationRef == null) {
|
||||
_log.severe('Destination missing in ItineraryConfig');
|
||||
return Result.error(Exception('Destination not found'));
|
||||
}
|
||||
|
||||
_selectedActivities.addAll(result.asOk.value.activities);
|
||||
|
||||
final resultActivities =
|
||||
await _activityRepository.getByDestination(destinationRef);
|
||||
switch (resultActivities) {
|
||||
case Ok():
|
||||
{
|
||||
_daytimeActivities = resultActivities.value
|
||||
.where((activity) => [
|
||||
TimeOfDay.any,
|
||||
TimeOfDay.morning,
|
||||
TimeOfDay.afternoon,
|
||||
].contains(activity.timeOfDay))
|
||||
.toList();
|
||||
|
||||
_eveningActivities = resultActivities.value
|
||||
.where((activity) => [
|
||||
TimeOfDay.evening,
|
||||
TimeOfDay.night,
|
||||
].contains(activity.timeOfDay))
|
||||
.toList();
|
||||
|
||||
_log.fine('Activities (daytime: ${_daytimeActivities.length}, '
|
||||
'evening: ${_eveningActivities.length}) loaded');
|
||||
}
|
||||
case Error():
|
||||
{
|
||||
_log.warning('Failed to load activities', resultActivities.error);
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return resultActivities;
|
||||
}
|
||||
|
||||
/// Add [Activity] to selected list.
|
||||
void addActivity(String activityRef) {
|
||||
assert(
|
||||
(_daytimeActivities + _eveningActivities)
|
||||
.any((activity) => activity.ref == activityRef),
|
||||
"Activity $activityRef not found",
|
||||
);
|
||||
_selectedActivities.add(activityRef);
|
||||
_log.finest('Activity $activityRef added');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Remove [Activity] from selected list.
|
||||
void removeActivity(String activityRef) {
|
||||
assert(
|
||||
(_daytimeActivities + _eveningActivities)
|
||||
.any((activity) => activity.ref == activityRef),
|
||||
"Activity $activityRef not found",
|
||||
);
|
||||
_selectedActivities.remove(activityRef);
|
||||
_log.finest('Activity $activityRef removed');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<Result<void>> _saveActivities() async {
|
||||
final resultConfig = await _itineraryConfigRepository.getItineraryConfig();
|
||||
if (resultConfig is Error) {
|
||||
_log.warning(
|
||||
'Failed to load stored ItineraryConfig',
|
||||
resultConfig.asError.error,
|
||||
);
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
final itineraryConfig = resultConfig.asOk.value;
|
||||
final result = await _itineraryConfigRepository.setItineraryConfig(
|
||||
itineraryConfig.copyWith(activities: _selectedActivities.toList()));
|
||||
if (result is Error) {
|
||||
_log.warning(
|
||||
'Failed to store ItineraryConfig',
|
||||
result.asError.error,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../routing/routes.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../../core/ui/back_button.dart';
|
||||
import '../../core/ui/home_button.dart';
|
||||
|
||||
class ActivitiesHeader extends StatelessWidget {
|
||||
const ActivitiesHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: true,
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: Dimens.of(context).paddingScreenHorizontal,
|
||||
right: Dimens.of(context).paddingScreenHorizontal,
|
||||
top: Dimens.of(context).paddingScreenVertical,
|
||||
bottom: Dimens.paddingVertical,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CustomBackButton(
|
||||
onTap: () {
|
||||
// Navigate to ResultsScreen and edit search
|
||||
context.go(Routes.results);
|
||||
},
|
||||
),
|
||||
Text(
|
||||
AppLocalization.of(context).activities,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const HomeButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../view_models/activities_viewmodel.dart';
|
||||
import 'activity_entry.dart';
|
||||
import 'activity_time_of_day.dart';
|
||||
|
||||
class ActivitiesList extends StatelessWidget {
|
||||
const ActivitiesList({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
required this.activityTimeOfDay,
|
||||
});
|
||||
|
||||
final ActivitiesViewModel viewModel;
|
||||
final ActivityTimeOfDay activityTimeOfDay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final list = switch (activityTimeOfDay) {
|
||||
ActivityTimeOfDay.daytime => viewModel.daytimeActivities,
|
||||
ActivityTimeOfDay.evening => viewModel.eveningActivities,
|
||||
};
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: Dimens.paddingVertical,
|
||||
left: Dimens.of(context).paddingScreenHorizontal,
|
||||
right: Dimens.of(context).paddingScreenHorizontal,
|
||||
bottom: Dimens.paddingVertical,
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final activity = list[index];
|
||||
return Padding(
|
||||
padding:
|
||||
EdgeInsets.only(bottom: index < list.length - 1 ? 20 : 0),
|
||||
child: ActivityEntry(
|
||||
key: ValueKey(activity.ref),
|
||||
activity: activity,
|
||||
selected: viewModel.selectedActivities.contains(activity.ref),
|
||||
onChanged: (value) {
|
||||
if (value!) {
|
||||
viewModel.addActivity(activity.ref);
|
||||
} else {
|
||||
viewModel.removeActivity(activity.ref);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
188
compass_app/app/lib/ui/activities/widgets/activities_screen.dart
Normal file
188
compass_app/app/lib/ui/activities/widgets/activities_screen.dart
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../routing/routes.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../../core/ui/error_indicator.dart';
|
||||
import '../view_models/activities_viewmodel.dart';
|
||||
import 'activities_header.dart';
|
||||
import 'activities_list.dart';
|
||||
import 'activities_title.dart';
|
||||
import 'activity_time_of_day.dart';
|
||||
|
||||
const String confirmButtonKey = 'confirm-button';
|
||||
|
||||
class ActivitiesScreen extends StatefulWidget {
|
||||
const ActivitiesScreen({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final ActivitiesViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<ActivitiesScreen> createState() => _ActivitiesScreenState();
|
||||
}
|
||||
|
||||
class _ActivitiesScreenState extends State<ActivitiesScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.saveActivities.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ActivitiesScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
oldWidget.viewModel.saveActivities.removeListener(_onResult);
|
||||
widget.viewModel.saveActivities.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.saveActivities.removeListener(_onResult);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, r) {
|
||||
if (!didPop) context.go(Routes.results);
|
||||
},
|
||||
child: Scaffold(
|
||||
body: ListenableBuilder(
|
||||
listenable: widget.viewModel.loadActivities,
|
||||
builder: (context, child) {
|
||||
if (widget.viewModel.loadActivities.completed) {
|
||||
return child!;
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
const ActivitiesHeader(),
|
||||
if (widget.viewModel.loadActivities.running)
|
||||
const Expanded(
|
||||
child: Center(child: CircularProgressIndicator())),
|
||||
if (widget.viewModel.loadActivities.error)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ErrorIndicator(
|
||||
title: AppLocalization.of(context)
|
||||
.errorWhileLoadingActivities,
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.loadActivities.execute,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, child) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: ActivitiesHeader(),
|
||||
),
|
||||
ActivitiesTitle(
|
||||
viewModel: widget.viewModel,
|
||||
activityTimeOfDay: ActivityTimeOfDay.daytime,
|
||||
),
|
||||
ActivitiesList(
|
||||
viewModel: widget.viewModel,
|
||||
activityTimeOfDay: ActivityTimeOfDay.daytime,
|
||||
),
|
||||
ActivitiesTitle(
|
||||
viewModel: widget.viewModel,
|
||||
activityTimeOfDay: ActivityTimeOfDay.evening,
|
||||
),
|
||||
ActivitiesList(
|
||||
viewModel: widget.viewModel,
|
||||
activityTimeOfDay: ActivityTimeOfDay.evening,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_BottomArea(viewModel: widget.viewModel),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onResult() {
|
||||
if (widget.viewModel.saveActivities.completed) {
|
||||
widget.viewModel.saveActivities.clearResult();
|
||||
context.go(Routes.booking);
|
||||
}
|
||||
|
||||
if (widget.viewModel.saveActivities.error) {
|
||||
widget.viewModel.saveActivities.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalization.of(context).errorWhileSavingActivities),
|
||||
action: SnackBarAction(
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.saveActivities.execute,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _BottomArea extends StatelessWidget {
|
||||
const _BottomArea({
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final ActivitiesViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
bottom: true,
|
||||
child: Material(
|
||||
elevation: 8,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: Dimens.of(context).paddingScreenHorizontal,
|
||||
right: Dimens.of(context).paddingScreenVertical,
|
||||
top: Dimens.paddingVertical,
|
||||
bottom: Dimens.of(context).paddingScreenVertical,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalization.of(context)
|
||||
.selected(viewModel.selectedActivities.length),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
FilledButton(
|
||||
key: const Key(confirmButtonKey),
|
||||
onPressed: viewModel.selectedActivities.isNotEmpty
|
||||
? viewModel.saveActivities.execute
|
||||
: null,
|
||||
child: Text(AppLocalization.of(context).confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../view_models/activities_viewmodel.dart';
|
||||
import 'activity_time_of_day.dart';
|
||||
|
||||
class ActivitiesTitle extends StatelessWidget {
|
||||
const ActivitiesTitle({
|
||||
super.key,
|
||||
required this.activityTimeOfDay,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final ActivitiesViewModel viewModel;
|
||||
final ActivityTimeOfDay activityTimeOfDay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final list = switch (activityTimeOfDay) {
|
||||
ActivityTimeOfDay.daytime => viewModel.daytimeActivities,
|
||||
ActivityTimeOfDay.evening => viewModel.eveningActivities,
|
||||
};
|
||||
if (list.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenHorizontal,
|
||||
child: Text(_label(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _label(BuildContext context) => switch (activityTimeOfDay) {
|
||||
ActivityTimeOfDay.daytime => AppLocalization.of(context).daytime,
|
||||
ActivityTimeOfDay.evening => AppLocalization.of(context).evening,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/image_error_listener.dart';
|
||||
import '../../core/ui/custom_checkbox.dart';
|
||||
|
||||
class ActivityEntry extends StatelessWidget {
|
||||
const ActivityEntry({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.selected,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final Activity activity;
|
||||
final bool selected;
|
||||
final ValueChanged<bool?> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 80,
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: activity.imageUrl,
|
||||
height: 80,
|
||||
width: 80,
|
||||
errorListener: imageErrorListener,
|
||||
),
|
||||
),
|
||||
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: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
CustomCheckbox(
|
||||
key: ValueKey('${activity.ref}-checkbox'),
|
||||
value: selected,
|
||||
onChanged: onChanged,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
enum ActivityTimeOfDay { daytime, evening }
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../../data/repositories/auth/auth_repository.dart';
|
||||
import '../../../../utils/command.dart';
|
||||
import '../../../../utils/result.dart';
|
||||
|
||||
class LoginViewModel {
|
||||
LoginViewModel({
|
||||
required AuthRepository authRepository,
|
||||
}) : _authRepository = authRepository {
|
||||
login = Command1<void, (String email, String password)>(_login);
|
||||
}
|
||||
|
||||
final AuthRepository _authRepository;
|
||||
final _log = Logger('LoginViewModel');
|
||||
|
||||
late Command1 login;
|
||||
|
||||
Future<Result<void>> _login((String, String) credentials) async {
|
||||
final (email, password) = credentials;
|
||||
final result = await _authRepository.login(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
if (result is Error<void>) {
|
||||
_log.warning('Login failed! ${result.error}');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
113
compass_app/app/lib/ui/auth/login/widgets/login_screen.dart
Normal file
113
compass_app/app/lib/ui/auth/login/widgets/login_screen.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../routing/routes.dart';
|
||||
import '../../../core/localization/applocalization.dart';
|
||||
import '../../../core/themes/dimens.dart';
|
||||
import '../view_models/login_viewmodel.dart';
|
||||
import 'tilted_cards.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final LoginViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final TextEditingController _email =
|
||||
TextEditingController(text: 'email@example.com');
|
||||
final TextEditingController _password =
|
||||
TextEditingController(text: 'password');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.login.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant LoginScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
oldWidget.viewModel.login.removeListener(_onResult);
|
||||
widget.viewModel.login.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.login.removeListener(_onResult);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const TiltedCards(),
|
||||
Padding(
|
||||
padding: Dimens.of(context).edgeInsetsScreenSymmetric,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _email,
|
||||
),
|
||||
const SizedBox(height: Dimens.paddingVertical),
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
),
|
||||
const SizedBox(height: Dimens.paddingVertical),
|
||||
ListenableBuilder(
|
||||
listenable: widget.viewModel.login,
|
||||
builder: (context, _) {
|
||||
return FilledButton(
|
||||
onPressed: () {
|
||||
widget.viewModel.login
|
||||
.execute((_email.value.text, _password.value.text));
|
||||
},
|
||||
child: Text(AppLocalization.of(context).login),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onResult() {
|
||||
if (widget.viewModel.login.completed) {
|
||||
widget.viewModel.login.clearResult();
|
||||
context.go(Routes.home);
|
||||
}
|
||||
|
||||
if (widget.viewModel.login.error) {
|
||||
widget.viewModel.login.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalization.of(context).errorWhileLogin),
|
||||
action: SnackBarAction(
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: () => widget.viewModel.login
|
||||
.execute((_email.value.text, _password.value.text)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
compass_app/app/lib/ui/auth/login/widgets/tilted_cards.dart
Normal file
97
compass_app/app/lib/ui/auth/login/widgets/tilted_cards.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../utils/image_error_listener.dart';
|
||||
|
||||
class TiltedCards extends StatelessWidget {
|
||||
const TiltedCards({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
child: const AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: _Card(
|
||||
imageUrl: 'https://rstr.in/google/tripedia/g2i0BsYPKW-',
|
||||
width: 200,
|
||||
height: 273,
|
||||
tilt: -3.83 / 360,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
child: _Card(
|
||||
imageUrl: 'https://rstr.in/google/tripedia/980sqNgaDRK',
|
||||
width: 180,
|
||||
height: 230,
|
||||
tilt: 3.46 / 360,
|
||||
),
|
||||
),
|
||||
_Card(
|
||||
imageUrl: 'https://rstr.in/google/tripedia/pHfPmf3o5NU',
|
||||
width: 225,
|
||||
height: 322,
|
||||
tilt: 0,
|
||||
showTitle: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Card extends StatelessWidget {
|
||||
const _Card({
|
||||
required this.imageUrl,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.tilt,
|
||||
this.showTitle = false,
|
||||
});
|
||||
|
||||
final double tilt;
|
||||
final double width;
|
||||
final double height;
|
||||
final String imageUrl;
|
||||
final bool showTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RotationTransition(
|
||||
turns: AlwaysStoppedAnimation(tilt),
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
color: showTitle ? Colors.black.withOpacity(0.5) : null,
|
||||
colorBlendMode: showTitle ? BlendMode.darken : null,
|
||||
errorListener: imageErrorListener,
|
||||
),
|
||||
if (showTitle) Center(child: SvgPicture.asset('assets/logo.svg')),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../../../data/repositories/auth/auth_repository.dart';
|
||||
import '../../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../../../../utils/command.dart';
|
||||
import '../../../../utils/result.dart';
|
||||
|
||||
class LogoutViewModel {
|
||||
LogoutViewModel({
|
||||
required AuthRepository authRepository,
|
||||
required ItineraryConfigRepository itineraryConfigRepository,
|
||||
}) : _authLogoutRepository = authRepository,
|
||||
_itineraryConfigRepository = itineraryConfigRepository {
|
||||
logout = Command0(_logout);
|
||||
}
|
||||
final AuthRepository _authLogoutRepository;
|
||||
final ItineraryConfigRepository _itineraryConfigRepository;
|
||||
late Command0 logout;
|
||||
|
||||
Future<Result> _logout() async {
|
||||
var result = await _authLogoutRepository.logout();
|
||||
switch (result) {
|
||||
case Ok<void>():
|
||||
// clear stored itinerary config
|
||||
return _itineraryConfigRepository
|
||||
.setItineraryConfig(const ItineraryConfig());
|
||||
case Error<void>():
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/localization/applocalization.dart';
|
||||
import '../../../core/themes/colors.dart';
|
||||
import '../view_models/logout_viewmodel.dart';
|
||||
|
||||
class LogoutButton extends StatefulWidget {
|
||||
const LogoutButton({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final LogoutViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<LogoutButton> createState() => _LogoutButtonState();
|
||||
}
|
||||
|
||||
class _LogoutButtonState extends State<LogoutButton> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.logout.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant LogoutButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
oldWidget.viewModel.logout.removeListener(_onResult);
|
||||
widget.viewModel.logout.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.logout.removeListener(_onResult);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: InkResponse(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
widget.viewModel.logout.execute();
|
||||
},
|
||||
child: Center(
|
||||
child: Icon(
|
||||
size: 24.0,
|
||||
Icons.logout,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onResult() {
|
||||
// We do not need to navigate to `/login` on logout,
|
||||
// it is done automatically by GoRouter.
|
||||
|
||||
if (widget.viewModel.logout.error) {
|
||||
widget.viewModel.logout.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalization.of(context).errorWhileLogout),
|
||||
action: SnackBarAction(
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.logout.execute,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/booking/booking_repository.dart';
|
||||
import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../../../domain/models/booking/booking.dart';
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../../../utils/command.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import '../../../domain/use_cases/booking/booking_create_use_case.dart';
|
||||
import '../../../domain/use_cases/booking/booking_share_use_case.dart';
|
||||
|
||||
class BookingViewModel extends ChangeNotifier {
|
||||
BookingViewModel({
|
||||
required BookingCreateUseCase createBookingUseCase,
|
||||
required BookingShareUseCase shareBookingUseCase,
|
||||
required ItineraryConfigRepository itineraryConfigRepository,
|
||||
required BookingRepository bookingRepository,
|
||||
}) : _createUseCase = createBookingUseCase,
|
||||
_shareUseCase = shareBookingUseCase,
|
||||
_itineraryConfigRepository = itineraryConfigRepository,
|
||||
_bookingRepository = bookingRepository {
|
||||
createBooking = Command0(_createBooking);
|
||||
shareBooking = Command0(() => _shareUseCase.shareBooking(_booking!));
|
||||
loadBooking = Command1(_load);
|
||||
}
|
||||
|
||||
final BookingCreateUseCase _createUseCase;
|
||||
final BookingShareUseCase _shareUseCase;
|
||||
final ItineraryConfigRepository _itineraryConfigRepository;
|
||||
final BookingRepository _bookingRepository;
|
||||
final _log = Logger('BookingViewModel');
|
||||
Booking? _booking;
|
||||
|
||||
Booking? get booking => _booking;
|
||||
|
||||
/// Creates a booking from the ItineraryConfig
|
||||
/// and saves it to the user bookins
|
||||
late final Command0 createBooking;
|
||||
|
||||
/// Loads booking by id
|
||||
late final Command1<void, int> loadBooking;
|
||||
|
||||
/// Share the current booking using the OS share dialog.
|
||||
late final Command0 shareBooking;
|
||||
|
||||
Future<Result<void>> _createBooking() async {
|
||||
_log.fine('Loading booking');
|
||||
final itineraryConfig =
|
||||
await _itineraryConfigRepository.getItineraryConfig();
|
||||
switch (itineraryConfig) {
|
||||
case Ok<ItineraryConfig>():
|
||||
_log.fine('Loaded stored ItineraryConfig');
|
||||
final result = await _createUseCase.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);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void>> _load(int id) async {
|
||||
final result = await _bookingRepository.getBooking(id);
|
||||
switch (result) {
|
||||
case Ok<Booking>():
|
||||
_log.fine('Loaded booking $id');
|
||||
_booking = result.value;
|
||||
notifyListeners();
|
||||
case Error<Booking>():
|
||||
_log.warning('Failed to load booking $id');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
104
compass_app/app/lib/ui/booking/widgets/booking_body.dart
Normal file
104
compass_app/app/lib/ui/booking/widgets/booking_body.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../domain/models/activity/activity.dart';
|
||||
import '../../../utils/image_error_listener.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,
|
||||
errorListener: imageErrorListener,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
191
compass_app/app/lib/ui/booking/widgets/booking_header.dart
Normal file
191
compass_app/app/lib/ui/booking/widgets/booking_header.dart
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../domain/models/booking/booking.dart';
|
||||
import '../../../utils/image_error_listener.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/colors.dart';
|
||||
import '../../core/themes/dimens.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(
|
||||
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,
|
||||
errorListener: imageErrorListener,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
115
compass_app/app/lib/ui/booking/widgets/booking_screen.dart
Normal file
115
compass_app/app/lib/ui/booking/widgets/booking_screen.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../routing/routes.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/ui/error_indicator.dart';
|
||||
import '../view_models/booking_viewmodel.dart';
|
||||
import 'booking_body.dart';
|
||||
|
||||
class BookingScreen extends StatefulWidget {
|
||||
const BookingScreen({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final BookingViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<BookingScreen> createState() => _BookingScreenState();
|
||||
}
|
||||
|
||||
class _BookingScreenState extends State<BookingScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.shareBooking.addListener(_listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.shareBooking.removeListener(_listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, r) {
|
||||
// Back navigation always goes to home
|
||||
if (!didPop) context.go(Routes.home);
|
||||
},
|
||||
child: Scaffold(
|
||||
floatingActionButton: ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, _) => FloatingActionButton.extended(
|
||||
// Workaround for https://github.com/flutter/flutter/issues/115358#issuecomment-2117157419
|
||||
heroTag: null,
|
||||
key: const ValueKey('share-button'),
|
||||
onPressed: widget.viewModel.booking != null
|
||||
? widget.viewModel.shareBooking.execute
|
||||
: null,
|
||||
label: Text(AppLocalization.of(context).shareTrip),
|
||||
icon: const Icon(Icons.share_outlined),
|
||||
),
|
||||
),
|
||||
body: ListenableBuilder(
|
||||
// Listen to changes in both commands
|
||||
listenable: Listenable.merge([
|
||||
widget.viewModel.createBooking,
|
||||
widget.viewModel.loadBooking,
|
||||
]),
|
||||
builder: (context, child) {
|
||||
// If either command is running, show progress indicator
|
||||
if (widget.viewModel.createBooking.running ||
|
||||
widget.viewModel.loadBooking.running) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
// If fails to create booking, tap to try again
|
||||
if (widget.viewModel.createBooking.error) {
|
||||
return Center(
|
||||
child: ErrorIndicator(
|
||||
title: AppLocalization.of(context).errorWhileLoadingBooking,
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.createBooking.execute,
|
||||
),
|
||||
);
|
||||
}
|
||||
// If existing booking fails to load, tap to go /home
|
||||
if (widget.viewModel.loadBooking.error) {
|
||||
return Center(
|
||||
child: ErrorIndicator(
|
||||
title: AppLocalization.of(context).errorWhileLoadingBooking,
|
||||
label: AppLocalization.of(context).close,
|
||||
onPressed: () => context.go(Routes.home),
|
||||
),
|
||||
);
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: BookingBody(viewModel: widget.viewModel),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _listener() {
|
||||
if (widget.viewModel.shareBooking.error) {
|
||||
widget.viewModel.shareBooking.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalization.of(context).errorWhileSharing),
|
||||
action: SnackBarAction(
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.shareBooking.execute,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
121
compass_app/app/lib/ui/core/localization/applocalization.dart
Normal file
121
compass_app/app/lib/ui/core/localization/applocalization.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Simple Localizations similar to
|
||||
/// https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#an-alternative-class-for-the-apps-localized-resources
|
||||
class AppLocalization {
|
||||
static AppLocalization of(BuildContext context) {
|
||||
return Localizations.of(context, AppLocalization);
|
||||
}
|
||||
|
||||
static const _strings = <String, String>{
|
||||
'activities': 'Activities',
|
||||
'addDates': 'Add Dates',
|
||||
'bookingDeleted': 'Booking deleted',
|
||||
'bookNewTrip': 'Book New Trip',
|
||||
'close': 'Close',
|
||||
'confirm': 'Confirm',
|
||||
'daytime': 'Daytime',
|
||||
'errorWhileDeletingBooking': 'Error while deleting booking',
|
||||
'errorWhileLoadingActivities': 'Error while loading activities',
|
||||
'errorWhileLoadingBooking': 'Error while loading booking',
|
||||
'errorWhileLoadingContinents': 'Error while loading continents',
|
||||
'errorWhileLoadingDestinations': 'Error while loading destinations',
|
||||
'errorWhileLoadingHome': 'Error while loading home',
|
||||
'errorWhileLogin': 'Error while trying to login',
|
||||
'errorWhileLogout': 'Error while trying to logout',
|
||||
'errorWhileSavingActivities': 'Error while saving activities',
|
||||
'errorWhileSavingItinerary': 'Error while saving itinerary',
|
||||
'errorWhileSharing': 'Error while sharing booking',
|
||||
'evening': 'Evening',
|
||||
'login': 'Login',
|
||||
'nameTrips': '{name}\'s Trips',
|
||||
'search': 'Search',
|
||||
'searchDestination': 'Search destination',
|
||||
'selected': '{1} selected',
|
||||
'shareTrip': 'Share Trip',
|
||||
'tryAgain': 'Try again',
|
||||
'yourChosenActivities': 'Your chosen activities',
|
||||
'when': 'When',
|
||||
};
|
||||
|
||||
// If string for "label" does not exist, will show "[LABEL]"
|
||||
static String _get(String label) =>
|
||||
_strings[label] ?? '[${label.toUpperCase()}]';
|
||||
|
||||
String get activities => _get('activities');
|
||||
|
||||
String get addDates => _get('addDates');
|
||||
|
||||
String get confirm => _get('confirm');
|
||||
|
||||
String get daytime => _get('daytime');
|
||||
|
||||
String get errorWhileLoadingActivities => _get('errorWhileLoadingActivities');
|
||||
|
||||
String get errorWhileLoadingBooking => _get('errorWhileLoadingBooking');
|
||||
|
||||
String get errorWhileLoadingContinents => _get('errorWhileLoadingContinents');
|
||||
|
||||
String get errorWhileLoadingDestinations =>
|
||||
_get('errorWhileLoadingDestinations');
|
||||
|
||||
String get errorWhileSavingActivities => _get('errorWhileSavingActivities');
|
||||
|
||||
String get errorWhileSavingItinerary => _get('errorWhileSavingItinerary');
|
||||
|
||||
String get evening => _get('evening');
|
||||
|
||||
String get search => _get('search');
|
||||
|
||||
String get searchDestination => _get('searchDestination');
|
||||
|
||||
String get shareTrip => _get('shareTrip');
|
||||
|
||||
String get tryAgain => _get('tryAgain');
|
||||
|
||||
String get yourChosenActivities => _get('yourChosenActivities');
|
||||
|
||||
String get when => _get('when');
|
||||
|
||||
String get errorWhileLogin => _get('errorWhileLogin');
|
||||
|
||||
String get login => _get('login');
|
||||
|
||||
String get errorWhileLogout => _get('errorWhileLogout');
|
||||
|
||||
String get close => _get('close');
|
||||
|
||||
String get errorWhileSharing => _get('errorWhileSharing');
|
||||
|
||||
String get bookNewTrip => _get('bookNewTrip');
|
||||
|
||||
String get errorWhileLoadingHome => _get('errorWhileLoadingHome');
|
||||
|
||||
String get bookingDeleted => _get('bookingDeleted');
|
||||
|
||||
String get errorWhileDeletingBooking => _get('errorWhileDeletingBooking');
|
||||
|
||||
String nameTrips(String name) => _get('nameTrips').replaceAll('{name}', name);
|
||||
|
||||
String selected(int value) =>
|
||||
_get('selected').replaceAll('{1}', value.toString());
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalization> {
|
||||
@override
|
||||
bool isSupported(Locale locale) => locale.languageCode == 'en';
|
||||
|
||||
@override
|
||||
Future<AppLocalization> load(Locale locale) {
|
||||
return SynchronousFuture(AppLocalization());
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(covariant LocalizationsDelegate<AppLocalization> old) =>
|
||||
false;
|
||||
}
|
||||
41
compass_app/app/lib/ui/core/themes/colors.dart
Normal file
41
compass_app/app/lib/ui/core/themes/colors.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppColors {
|
||||
static const black1 = Color(0xFF101010);
|
||||
static const white1 = Color(0xFFFFF7FA);
|
||||
static const grey1 = Color(0xFFF2F2F2);
|
||||
static const grey2 = Color(0xFF4D4D4D);
|
||||
static const grey3 = Color(0xFFA4A4A4);
|
||||
static const whiteTransparent =
|
||||
Color(0x4DFFFFFF); // Figma rgba(255, 255, 255, 0.3)
|
||||
static const blackTransparent = Color(0x4D000000);
|
||||
static const red1 = Color(0xFFE74C3C);
|
||||
|
||||
static const lightColorScheme = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: AppColors.black1,
|
||||
onPrimary: AppColors.white1,
|
||||
secondary: AppColors.black1,
|
||||
onSecondary: AppColors.white1,
|
||||
surface: Colors.white,
|
||||
onSurface: AppColors.black1,
|
||||
error: Colors.white,
|
||||
onError: Colors.red,
|
||||
);
|
||||
|
||||
static const darkColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: AppColors.white1,
|
||||
onPrimary: AppColors.black1,
|
||||
secondary: AppColors.white1,
|
||||
onSecondary: AppColors.black1,
|
||||
surface: AppColors.black1,
|
||||
onSurface: Colors.white,
|
||||
error: Colors.black,
|
||||
onError: AppColors.red1,
|
||||
);
|
||||
}
|
||||
65
compass_app/app/lib/ui/core/themes/dimens.dart
Normal file
65
compass_app/app/lib/ui/core/themes/dimens.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
sealed class Dimens {
|
||||
const Dimens();
|
||||
|
||||
/// General horizontal padding used to separate UI items
|
||||
static const paddingHorizontal = 20.0;
|
||||
|
||||
/// General vertical padding used to separate UI items
|
||||
static const paddingVertical = 24.0;
|
||||
|
||||
/// Horizontal padding for screen edges
|
||||
abstract final double paddingScreenHorizontal;
|
||||
|
||||
/// Vertical padding for screen edges
|
||||
abstract final double paddingScreenVertical;
|
||||
|
||||
/// Horizontal symmetric padding for screen edges
|
||||
EdgeInsets get edgeInsetsScreenHorizontal =>
|
||||
EdgeInsets.symmetric(horizontal: paddingScreenHorizontal);
|
||||
|
||||
/// Symmetric padding for screen edges
|
||||
EdgeInsets get edgeInsetsScreenSymmetric => EdgeInsets.symmetric(
|
||||
horizontal: paddingScreenHorizontal, vertical: paddingScreenVertical);
|
||||
|
||||
static final dimensDesktop = DimensDesktop();
|
||||
static final dimensMobile = DimensMobile();
|
||||
|
||||
/// Get dimensions definition based on screen size
|
||||
factory Dimens.of(BuildContext context) =>
|
||||
switch (MediaQuery.sizeOf(context).width) {
|
||||
> 600 => dimensDesktop,
|
||||
_ => dimensMobile,
|
||||
};
|
||||
|
||||
abstract final double profilePictureSize;
|
||||
}
|
||||
|
||||
/// Mobile dimensions
|
||||
class DimensMobile extends Dimens {
|
||||
@override
|
||||
double paddingScreenHorizontal = Dimens.paddingHorizontal;
|
||||
|
||||
@override
|
||||
double paddingScreenVertical = Dimens.paddingVertical;
|
||||
|
||||
@override
|
||||
double get profilePictureSize => 64.0;
|
||||
}
|
||||
|
||||
/// Desktop/Web dimensions
|
||||
class DimensDesktop extends Dimens {
|
||||
@override
|
||||
double paddingScreenHorizontal = 100.0;
|
||||
|
||||
@override
|
||||
double paddingScreenVertical = 64.0;
|
||||
|
||||
@override
|
||||
double get profilePictureSize => 128.0;
|
||||
}
|
||||
84
compass_app/app/lib/ui/core/themes/theme.dart
Normal file
84
compass_app/app/lib/ui/core/themes/theme.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'colors.dart';
|
||||
import '../ui/tag_chip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
static const _textTheme = TextTheme(
|
||||
headlineLarge: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey3,
|
||||
),
|
||||
);
|
||||
|
||||
static const _inputDecorationTheme = InputDecorationTheme(
|
||||
hintStyle: TextStyle(
|
||||
// grey3 works for both light and dark themes
|
||||
color: AppColors.grey3,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
);
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: AppColors.lightColorScheme,
|
||||
textTheme: _textTheme,
|
||||
inputDecorationTheme: _inputDecorationTheme,
|
||||
extensions: [
|
||||
TagChipTheme(
|
||||
chipColor: AppColors.whiteTransparent,
|
||||
onChipColor: Colors.white,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static ThemeData darkTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: AppColors.darkColorScheme,
|
||||
textTheme: _textTheme,
|
||||
inputDecorationTheme: _inputDecorationTheme,
|
||||
extensions: [
|
||||
TagChipTheme(
|
||||
chipColor: AppColors.blackTransparent,
|
||||
onChipColor: Colors.white,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
63
compass_app/app/lib/ui/core/ui/back_button.dart
Normal file
63
compass_app/app/lib/ui/core/ui/back_button.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
import 'blur_filter.dart';
|
||||
|
||||
/// Custom back button to pop navigation.
|
||||
class CustomBackButton extends StatelessWidget {
|
||||
const CustomBackButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.blur = false,
|
||||
});
|
||||
|
||||
final bool blur;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (blur)
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: kBlurFilter,
|
||||
child: const SizedBox(height: 40.0, width: 40.0),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: Icon(
|
||||
size: 24.0,
|
||||
Icons.arrow_back,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
compass_app/app/lib/ui/core/ui/blur_filter.dart
Normal file
7
compass_app/app/lib/ui/core/ui/blur_filter.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
final kBlurFilter = ImageFilter.blur(sigmaX: 2, sigmaY: 2);
|
||||
50
compass_app/app/lib/ui/core/ui/custom_checkbox.dart
Normal file
50
compass_app/app/lib/ui/core/ui/custom_checkbox.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
|
||||
class CustomCheckbox extends StatelessWidget {
|
||||
const CustomCheckbox({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final bool value;
|
||||
final ValueChanged<bool?> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkResponse(
|
||||
radius: 24,
|
||||
onTap: () => onChanged(!value),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: AppColors.grey3),
|
||||
),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: value
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Visibility(
|
||||
visible: value,
|
||||
child: Icon(
|
||||
Icons.check,
|
||||
size: 14,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
compass_app/app/lib/ui/core/ui/date_format_start_end.dart
Normal file
24
compass_app/app/lib/ui/core/ui/date_format_start_end.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
final _dateFormatDay = DateFormat('d');
|
||||
final _dateFormatDayMonth = DateFormat('d MMM');
|
||||
|
||||
String dateFormatStartEnd(DateTimeRange dateTimeRange) {
|
||||
final start = dateTimeRange.start;
|
||||
final end = dateTimeRange.end;
|
||||
|
||||
final dayMonthEnd = _dateFormatDayMonth.format(end);
|
||||
|
||||
if (start.month == end.month) {
|
||||
final dayStart = _dateFormatDay.format(start);
|
||||
return '$dayStart - $dayMonthEnd';
|
||||
}
|
||||
|
||||
final dayMonthStart = _dateFormatDayMonth.format(start);
|
||||
return '$dayMonthStart - $dayMonthEnd';
|
||||
}
|
||||
63
compass_app/app/lib/ui/core/ui/error_indicator.dart
Normal file
63
compass_app/app/lib/ui/core/ui/error_indicator.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
|
||||
class ErrorIndicator extends StatelessWidget {
|
||||
const ErrorIndicator({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IntrinsicWidth(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: onPressed,
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(AppColors.red1),
|
||||
foregroundColor: WidgetStatePropertyAll(Colors.white),
|
||||
),
|
||||
child: Text(label),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
60
compass_app/app/lib/ui/core/ui/home_button.dart
Normal file
60
compass_app/app/lib/ui/core/ui/home_button.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../routing/routes.dart';
|
||||
import '../themes/colors.dart';
|
||||
import 'blur_filter.dart';
|
||||
|
||||
/// Home button to navigate back to the '/' path.
|
||||
class HomeButton extends StatelessWidget {
|
||||
const HomeButton({
|
||||
super.key,
|
||||
this.blur = false,
|
||||
});
|
||||
|
||||
final bool blur;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (blur)
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: kBlurFilter,
|
||||
child: const SizedBox(height: 40.0, width: 40.0),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
onTap: () {
|
||||
context.go(Routes.home);
|
||||
},
|
||||
child: Center(
|
||||
child: Icon(
|
||||
size: 24.0,
|
||||
Icons.home_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
compass_app/app/lib/ui/core/ui/scroll_behavior.dart
Normal file
17
compass_app/app/lib/ui/core/ui/scroll_behavior.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Custom scroll behavior to allow dragging with mouse.
|
||||
/// Necessary to allow dragging with mouse on Continents carousel.
|
||||
class AppCustomScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
// Allow to drag with mouse on Regions carousel
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
111
compass_app/app/lib/ui/core/ui/search_bar.dart
Normal file
111
compass_app/app/lib/ui/core/ui/search_bar.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../localization/applocalization.dart';
|
||||
import '../themes/colors.dart';
|
||||
import '../themes/dimens.dart';
|
||||
import 'date_format_start_end.dart';
|
||||
import 'home_button.dart';
|
||||
|
||||
/// Application top search bar.
|
||||
///
|
||||
/// Displays a search bar with the current configuration.
|
||||
/// Includes [HomeButton] to navigate back to the '/' path.
|
||||
class AppSearchBar extends StatelessWidget {
|
||||
const AppSearchBar({
|
||||
super.key,
|
||||
this.config,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final ItineraryConfig? config;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey1),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Dimens.paddingHorizontal,
|
||||
),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: _QueryText(config: config),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const HomeButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QueryText extends StatelessWidget {
|
||||
const _QueryText({
|
||||
required this.config,
|
||||
});
|
||||
|
||||
final ItineraryConfig? config;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (config == null) {
|
||||
return const _EmptySearch();
|
||||
}
|
||||
|
||||
final ItineraryConfig(:continent, :startDate, :endDate, :guests) = config!;
|
||||
if (startDate == null ||
|
||||
endDate == null ||
|
||||
guests == null ||
|
||||
continent == null) {
|
||||
return const _EmptySearch();
|
||||
}
|
||||
|
||||
return Text(
|
||||
'$continent - ${dateFormatStartEnd(DateTimeRange(start: startDate, end: endDate))} - Guests: $guests',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptySearch extends StatelessWidget {
|
||||
const _EmptySearch();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.search),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalization.of(context).searchDestination,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).inputDecorationTheme.hintStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
144
compass_app/app/lib/ui/core/ui/tag_chip.dart
Normal file
144
compass_app/app/lib/ui/core/ui/tag_chip.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import '../themes/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class TagChip extends StatelessWidget {
|
||||
const TagChip({
|
||||
super.key,
|
||||
required this.tag,
|
||||
this.fontSize = 10,
|
||||
this.height = 20,
|
||||
this.chipColor,
|
||||
this.onChipColor,
|
||||
});
|
||||
|
||||
final String tag;
|
||||
|
||||
final double fontSize;
|
||||
final double height;
|
||||
final Color? chipColor;
|
||||
final Color? onChipColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(height / 2),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: chipColor ??
|
||||
Theme.of(context).extension<TagChipTheme>()?.chipColor ??
|
||||
AppColors.whiteTransparent,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_iconFrom(tag),
|
||||
color: onChipColor ??
|
||||
Theme.of(context)
|
||||
.extension<TagChipTheme>()
|
||||
?.onChipColor ??
|
||||
Colors.white,
|
||||
size: fontSize,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
tag,
|
||||
textAlign: TextAlign.center,
|
||||
style: _textStyle(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData? _iconFrom(String tag) {
|
||||
return switch (tag) {
|
||||
'Adventure sports' => Icons.kayaking_outlined,
|
||||
'Beach' => Icons.beach_access_outlined,
|
||||
'City' => Icons.location_city_outlined,
|
||||
'Cultural experiences' => Icons.museum_outlined,
|
||||
'Foodie' || 'Food tours' => Icons.restaurant,
|
||||
'Hiking' => Icons.hiking,
|
||||
'Historic' => Icons.menu_book_outlined,
|
||||
'Island' || 'Coastal' || 'Lake' || 'River' => Icons.water,
|
||||
'Luxury' => Icons.attach_money_outlined,
|
||||
'Mountain' || 'Wildlife watching' => Icons.landscape_outlined,
|
||||
'Nightlife' => Icons.local_bar_outlined,
|
||||
'Off-the-beaten-path' => Icons.do_not_step_outlined,
|
||||
'Romantic' => Icons.favorite_border_outlined,
|
||||
'Rural' => Icons.agriculture_outlined,
|
||||
'Secluded' => Icons.church_outlined,
|
||||
'Sightseeing' => Icons.attractions_outlined,
|
||||
'Skiing' => Icons.downhill_skiing_outlined,
|
||||
'Wine tasting' => Icons.wine_bar_outlined,
|
||||
'Winter destination' => Icons.ac_unit,
|
||||
_ => Icons.label_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
// Note: original Figma file uses Google Sans
|
||||
// which is not available on GoogleFonts
|
||||
_textStyle(BuildContext context) => GoogleFonts.openSans(
|
||||
textStyle: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: fontSize,
|
||||
color: onChipColor ??
|
||||
Theme.of(context).extension<TagChipTheme>()?.onChipColor ??
|
||||
Colors.white,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class TagChipTheme extends ThemeExtension<TagChipTheme> {
|
||||
final Color chipColor;
|
||||
final Color onChipColor;
|
||||
|
||||
TagChipTheme({
|
||||
required this.chipColor,
|
||||
required this.onChipColor,
|
||||
});
|
||||
|
||||
@override
|
||||
ThemeExtension<TagChipTheme> copyWith({
|
||||
Color? chipColor,
|
||||
Color? onChipColor,
|
||||
}) {
|
||||
return TagChipTheme(
|
||||
chipColor: chipColor ?? this.chipColor,
|
||||
onChipColor: onChipColor ?? this.onChipColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ThemeExtension<TagChipTheme> lerp(
|
||||
covariant ThemeExtension<TagChipTheme> other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! TagChipTheme) {
|
||||
return this;
|
||||
}
|
||||
return TagChipTheme(
|
||||
chipColor: Color.lerp(chipColor, other.chipColor, t) ?? chipColor,
|
||||
onChipColor: Color.lerp(onChipColor, other.onChipColor, t) ?? onChipColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
95
compass_app/app/lib/ui/home/view_models/home_viewmodel.dart
Normal file
95
compass_app/app/lib/ui/home/view_models/home_viewmodel.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/booking/booking_repository.dart';
|
||||
import '../../../data/repositories/user/user_repository.dart';
|
||||
import '../../../domain/models/booking/booking_summary.dart';
|
||||
import '../../../domain/models/user/user.dart';
|
||||
import '../../../utils/command.dart';
|
||||
import '../../../utils/result.dart';
|
||||
|
||||
class HomeViewModel extends ChangeNotifier {
|
||||
HomeViewModel({
|
||||
required BookingRepository bookingRepository,
|
||||
required UserRepository userRepository,
|
||||
}) : _bookingRepository = bookingRepository,
|
||||
_userRepository = userRepository {
|
||||
load = Command0(_load)..execute();
|
||||
deleteBooking = Command1(_deleteBooking);
|
||||
}
|
||||
|
||||
final BookingRepository _bookingRepository;
|
||||
final UserRepository _userRepository;
|
||||
final _log = Logger('HomeViewModel');
|
||||
List<BookingSummary> _bookings = [];
|
||||
User? _user;
|
||||
|
||||
late Command0 load;
|
||||
late Command1<void, int> deleteBooking;
|
||||
|
||||
List<BookingSummary> get bookings => _bookings;
|
||||
|
||||
User? get user => _user;
|
||||
|
||||
Future<Result> _load() async {
|
||||
try {
|
||||
final result = await _bookingRepository.getBookingsList();
|
||||
switch (result) {
|
||||
case Ok<List<BookingSummary>>():
|
||||
_bookings = result.value;
|
||||
_log.fine('Loaded bookings');
|
||||
case Error<List<BookingSummary>>():
|
||||
_log.warning('Failed to load bookings', result.error);
|
||||
return result;
|
||||
}
|
||||
|
||||
final userResult = await _userRepository.getUser();
|
||||
switch (userResult) {
|
||||
case Ok<User>():
|
||||
_user = userResult.value;
|
||||
_log.fine('Loaded user');
|
||||
case Error<User>():
|
||||
_log.warning('Failed to load user', userResult.error);
|
||||
}
|
||||
|
||||
return userResult;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<void>> _deleteBooking(int id) async {
|
||||
try {
|
||||
final resultDelete = await _bookingRepository.delete(id);
|
||||
switch (resultDelete) {
|
||||
case Ok<void>():
|
||||
_log.fine('Deleted booking $id');
|
||||
case Error<void>():
|
||||
_log.warning('Failed to delete booking $id', resultDelete.error);
|
||||
return resultDelete;
|
||||
}
|
||||
|
||||
// After deleting the booking, we need to reload the bookings list.
|
||||
// BookingRepository is the source of truth for bookings.
|
||||
final resultLoadBookings = await _bookingRepository.getBookingsList();
|
||||
switch (resultLoadBookings) {
|
||||
case Ok<List<BookingSummary>>():
|
||||
_bookings = resultLoadBookings.value;
|
||||
_log.fine('Loaded bookings');
|
||||
case Error<List<BookingSummary>>():
|
||||
_log.warning('Failed to load bookings', resultLoadBookings.error);
|
||||
return resultLoadBookings;
|
||||
}
|
||||
|
||||
return resultLoadBookings;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
198
compass_app/app/lib/ui/home/widgets/home_screen.dart
Normal file
198
compass_app/app/lib/ui/home/widgets/home_screen.dart
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../domain/models/booking/booking_summary.dart';
|
||||
import '../../../routing/routes.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../../core/ui/date_format_start_end.dart';
|
||||
import '../../core/ui/error_indicator.dart';
|
||||
import '../view_models/home_viewmodel.dart';
|
||||
import 'home_title.dart';
|
||||
|
||||
const String bookingButtonKey = 'booking-button';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final HomeViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.deleteBooking.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HomeScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
oldWidget.viewModel.deleteBooking.removeListener(_onResult);
|
||||
widget.viewModel.deleteBooking.addListener(_onResult);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.deleteBooking.removeListener(_onResult);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
// Workaround for https://github.com/flutter/flutter/issues/115358#issuecomment-2117157419
|
||||
heroTag: null,
|
||||
key: const ValueKey(bookingButtonKey),
|
||||
onPressed: () => context.go(Routes.search),
|
||||
label: Text(AppLocalization.of(context).bookNewTrip),
|
||||
icon: const Icon(Icons.add_location_outlined),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: true,
|
||||
bottom: true,
|
||||
child: ListenableBuilder(
|
||||
listenable: widget.viewModel.load,
|
||||
builder: (context, child) {
|
||||
if (widget.viewModel.load.running) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.viewModel.load.error) {
|
||||
return ErrorIndicator(
|
||||
title: AppLocalization.of(context).errorWhileLoadingHome,
|
||||
label: AppLocalization.of(context).tryAgain,
|
||||
onPressed: widget.viewModel.load.execute,
|
||||
);
|
||||
}
|
||||
|
||||
return child!;
|
||||
},
|
||||
child: ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, _) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: Dimens.of(context).paddingScreenVertical,
|
||||
horizontal: Dimens.of(context).paddingScreenHorizontal,
|
||||
),
|
||||
child: HomeHeader(viewModel: widget.viewModel),
|
||||
),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: widget.viewModel.bookings.length,
|
||||
itemBuilder: (_, index) => _Booking(
|
||||
key: ValueKey(widget.viewModel.bookings[index].id),
|
||||
booking: widget.viewModel.bookings[index],
|
||||
onTap: () => context.push(Routes.bookingWithId(
|
||||
widget.viewModel.bookings[index].id)),
|
||||
confirmDismiss: (_) async {
|
||||
// wait for command to complete
|
||||
await widget.viewModel.deleteBooking.execute(
|
||||
widget.viewModel.bookings[index].id,
|
||||
);
|
||||
// if command completed successfully, return true
|
||||
if (widget.viewModel.deleteBooking.completed) {
|
||||
// removes the dismissable from the list
|
||||
return true;
|
||||
} else {
|
||||
// the dismissable stays in the list
|
||||
return false;
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onResult() {
|
||||
if (widget.viewModel.deleteBooking.completed) {
|
||||
widget.viewModel.deleteBooking.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalization.of(context).bookingDeleted),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.viewModel.deleteBooking.error) {
|
||||
widget.viewModel.deleteBooking.clearResult();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalization.of(context).errorWhileDeletingBooking),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Booking extends StatelessWidget {
|
||||
const _Booking({
|
||||
super.key,
|
||||
required this.booking,
|
||||
required this.onTap,
|
||||
required this.confirmDismiss,
|
||||
});
|
||||
|
||||
final BookingSummary booking;
|
||||
final GestureTapCallback onTap;
|
||||
final ConfirmDismissCallback confirmDismiss;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dismissible(
|
||||
key: ValueKey(booking.id),
|
||||
direction: DismissDirection.endToStart,
|
||||
confirmDismiss: confirmDismiss,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Dimens.of(context).paddingScreenHorizontal,
|
||||
vertical: Dimens.paddingVertical,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
booking.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
dateFormatStartEnd(
|
||||
DateTimeRange(
|
||||
start: booking.startDate,
|
||||
end: booking.endDate,
|
||||
),
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
89
compass_app/app/lib/ui/home/widgets/home_title.dart
Normal file
89
compass_app/app/lib/ui/home/widgets/home_title.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../auth/logout/view_models/logout_viewmodel.dart';
|
||||
import '../../auth/logout/widgets/logout_button.dart';
|
||||
import '../../core/localization/applocalization.dart';
|
||||
import '../../core/themes/dimens.dart';
|
||||
import '../view_models/home_viewmodel.dart';
|
||||
|
||||
class HomeHeader extends StatelessWidget {
|
||||
const HomeHeader({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
});
|
||||
|
||||
final HomeViewModel viewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = viewModel.user;
|
||||
if (user == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Image.asset(
|
||||
user.picture,
|
||||
width: Dimens.of(context).profilePictureSize,
|
||||
height: Dimens.of(context).profilePictureSize,
|
||||
),
|
||||
),
|
||||
LogoutButton(
|
||||
viewModel: LogoutViewModel(
|
||||
authRepository: context.read(),
|
||||
itineraryConfigRepository: context.read(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: Dimens.paddingVertical),
|
||||
_Title(
|
||||
text: AppLocalization.of(context).nameTrips(user.name),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Title extends StatelessWidget {
|
||||
const _Title({
|
||||
required this.text,
|
||||
});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ShaderMask(
|
||||
blendMode: BlendMode.srcIn,
|
||||
shaderCallback: (bounds) => RadialGradient(
|
||||
center: Alignment.bottomLeft,
|
||||
radius: 2,
|
||||
colors: [
|
||||
Colors.purple.shade700,
|
||||
Colors.purple.shade400,
|
||||
],
|
||||
).createShader(
|
||||
Rect.fromLTWH(0, 0, bounds.width, bounds.height),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: GoogleFonts.rubik(
|
||||
textStyle: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../../data/repositories/destination/destination_repository.dart';
|
||||
import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart';
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../domain/models/itinerary_config/itinerary_config.dart';
|
||||
import '../../../utils/command.dart';
|
||||
import '../../../utils/result.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
/// Results screen view model
|
||||
/// Based on https://docs.flutter.dev/get-started/fwe/state-management#using-mvvm-for-your-applications-architecture
|
||||
class ResultsViewModel extends ChangeNotifier {
|
||||
ResultsViewModel({
|
||||
required DestinationRepository destinationRepository,
|
||||
required ItineraryConfigRepository itineraryConfigRepository,
|
||||
}) : _destinationRepository = destinationRepository,
|
||||
_itineraryConfigRepository = itineraryConfigRepository {
|
||||
updateItineraryConfig = Command1<void, String>(_updateItineraryConfig);
|
||||
search = Command0(_search)..execute();
|
||||
}
|
||||
|
||||
final _log = Logger('ResultsViewModel');
|
||||
|
||||
final DestinationRepository _destinationRepository;
|
||||
|
||||
final ItineraryConfigRepository _itineraryConfigRepository;
|
||||
|
||||
// Setters are private
|
||||
List<Destination> _destinations = [];
|
||||
|
||||
/// List of destinations, may be empty but never null
|
||||
List<Destination> get destinations => _destinations;
|
||||
|
||||
ItineraryConfig? _itineraryConfig;
|
||||
|
||||
/// Filter options to display on search bar
|
||||
ItineraryConfig get config => _itineraryConfig ?? const ItineraryConfig();
|
||||
|
||||
/// Perform search
|
||||
late final Command0 search;
|
||||
|
||||
/// Store ViewModel data into [ItineraryConfigRepository] before navigating.
|
||||
late final Command1<void, String> updateItineraryConfig;
|
||||
|
||||
Future<Result<void>> _search() async {
|
||||
// Load current itinerary config
|
||||
final resultConfig = await _itineraryConfigRepository.getItineraryConfig();
|
||||
if (resultConfig is Error) {
|
||||
_log.warning(
|
||||
'Failed to load stored ItineraryConfig',
|
||||
resultConfig.asError.error,
|
||||
);
|
||||
return resultConfig;
|
||||
}
|
||||
_itineraryConfig = resultConfig.asOk.value;
|
||||
notifyListeners();
|
||||
|
||||
final result = await _destinationRepository.getDestinations();
|
||||
switch (result) {
|
||||
case Ok():
|
||||
{
|
||||
// If the result is Ok, update the list of destinations
|
||||
_destinations = result.value
|
||||
.where((destination) =>
|
||||
destination.continent == _itineraryConfig!.continent)
|
||||
.toList();
|
||||
_log.fine('Destinations (${_destinations.length}) loaded');
|
||||
}
|
||||
case Error():
|
||||
{
|
||||
_log.warning('Failed to load destinations', result.error);
|
||||
}
|
||||
}
|
||||
|
||||
// After finish loading results, notify the view
|
||||
notifyListeners();
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Result<void>> _updateItineraryConfig(String destinationRef) async {
|
||||
assert(destinationRef.isNotEmpty, "destinationRef should not be empty");
|
||||
|
||||
final resultConfig = await _itineraryConfigRepository.getItineraryConfig();
|
||||
if (resultConfig is Error) {
|
||||
_log.warning(
|
||||
'Failed to load stored ItineraryConfig',
|
||||
resultConfig.asError.error,
|
||||
);
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
final itineraryConfig = resultConfig.asOk.value;
|
||||
final result = await _itineraryConfigRepository
|
||||
.setItineraryConfig(itineraryConfig.copyWith(
|
||||
destination: destinationRef,
|
||||
activities: [],
|
||||
));
|
||||
if (result is Error) {
|
||||
_log.warning(
|
||||
'Failed to store ItineraryConfig',
|
||||
result.asError.error,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
89
compass_app/app/lib/ui/results/widgets/result_card.dart
Normal file
89
compass_app/app/lib/ui/results/widgets/result_card.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../domain/models/destination/destination.dart';
|
||||
import '../../../utils/image_error_listener.dart';
|
||||
import '../../core/ui/tag_chip.dart';
|
||||
|
||||
class ResultCard extends StatelessWidget {
|
||||
const ResultCard({
|
||||
super.key,
|
||||
required this.destination,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final Destination destination;
|
||||
final GestureTapCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: destination.imageUrl,
|
||||
fit: BoxFit.fitHeight,
|
||||
errorWidget: (context, url, error) => const Icon(Icons.error),
|
||||
errorListener: imageErrorListener,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 12.0,
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
destination.name.toUpperCase(),
|
||||
style: _cardTitleStyle,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
direction: Axis.horizontal,
|
||||
children:
|
||||
destination.tags.map((e) => TagChip(tag: e)).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Handle taps
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final _cardTitleStyle = GoogleFonts.rubik(
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15.0,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1,
|
||||
shadows: [
|
||||
// Helps to read the text a bit better
|
||||
Shadow(
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user