1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00

Compass app (#2446)

This commit is contained in:
Eric Windmill
2024-09-27 18:49:27 -04:00
committed by GitHub
parent fcf2552cda
commit 46b5a26b26
326 changed files with 53272 additions and 0 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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]!);
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View 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 '../../../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);
}

View 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 '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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}

View File

@@ -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()));
}
}

View File

@@ -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!);
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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!);
}
}
}

View 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 '../../../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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View 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 '../../../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());
}
}

View File

@@ -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);
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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';
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);
}

View File

@@ -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;
}

View File

@@ -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,
};

View 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: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);
}

View File

@@ -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;
}

View File

@@ -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,
};

View 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: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);
}

View File

@@ -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;
}

View File

@@ -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,
};

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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,
};

View File

@@ -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',
);
}
}

View 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: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);
}
}
}