mirror of https://github.com/flutter/samples.git
Merge branch 'compass-app' of https://github.com/flutter/samples into compass-app
commit
3c7ee30631
After Width: | Height: | Size: 41 KiB |
@ -1,11 +1,8 @@
|
|||||||
import 'package:compass_model/model.dart';
|
import '../../../domain/models/destination/destination.dart';
|
||||||
|
|
||||||
import '../../../utils/result.dart';
|
import '../../../utils/result.dart';
|
||||||
|
|
||||||
/// Data source with all possible destinations
|
/// Data source with all possible destinations
|
||||||
abstract class DestinationRepository {
|
abstract class DestinationRepository {
|
||||||
/// Get complete list of destinations
|
/// Get complete list of destinations
|
||||||
Future<Result<List<Destination>>> getDestinations();
|
Future<Result<List<Destination>>> getDestinations();
|
||||||
|
|
||||||
// TODO: Consider creating getByContinent instead of filtering in ViewModel
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
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,17 @@
|
|||||||
|
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,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
};
|
@ -1,12 +1,18 @@
|
|||||||
import 'package:compass_model/model.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../activity/activity.dart';
|
||||||
|
import '../destination/destination.dart';
|
||||||
|
|
||||||
part 'booking.freezed.dart';
|
part 'booking.freezed.dart';
|
||||||
part 'booking.g.dart';
|
part 'booking.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class Booking with _$Booking {
|
class Booking with _$Booking {
|
||||||
const factory 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
|
/// Start date of the trip
|
||||||
required DateTime startDate,
|
required DateTime startDate,
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
@ -1,24 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
|
|
||||||
// TODO: Maybe the text styles here should be moved to the respective widgets
|
|
||||||
class TextStyles {
|
|
||||||
// Note: original Figma file uses Nikkei Maru
|
|
||||||
// which is not available on GoogleFonts
|
|
||||||
// Note: Card title theme doesn't change based on light/dark mode
|
|
||||||
static 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,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,85 @@
|
|||||||
|
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,79 @@
|
|||||||
|
import 'package:compass_app/data/services/api/api_client.dart';
|
||||||
|
import 'package:compass_app/domain/models/continent/continent.dart';
|
||||||
|
import 'package:compass_app/utils/result.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../../../../testing/mocks.dart';
|
||||||
|
import '../../../../testing/models/activity.dart';
|
||||||
|
import '../../../../testing/models/booking.dart';
|
||||||
|
import '../../../../testing/models/destination.dart';
|
||||||
|
import '../../../../testing/models/user.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ApiClient', () {
|
||||||
|
late MockHttpClient mockHttpClient;
|
||||||
|
late ApiClient apiClient;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockHttpClient = MockHttpClient();
|
||||||
|
apiClient = ApiClient(clientFactory: () => mockHttpClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get continents', () async {
|
||||||
|
final continents = [const Continent(name: 'NAME', imageUrl: 'URL')];
|
||||||
|
mockHttpClient.mockGet('/continent', continents);
|
||||||
|
final result = await apiClient.getContinents();
|
||||||
|
expect(result.asOk.value, continents);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get activities by destination', () async {
|
||||||
|
final activites = [kActivity];
|
||||||
|
mockHttpClient.mockGet(
|
||||||
|
'/destination/${kDestination1.ref}/activity',
|
||||||
|
activites,
|
||||||
|
);
|
||||||
|
final result =
|
||||||
|
await apiClient.getActivityByDestination(kDestination1.ref);
|
||||||
|
expect(result.asOk.value, activites);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get booking', () async {
|
||||||
|
mockHttpClient.mockGet(
|
||||||
|
'/booking/${kBookingApiModel.id}',
|
||||||
|
kBookingApiModel,
|
||||||
|
);
|
||||||
|
final result = await apiClient.getBooking(kBookingApiModel.id!);
|
||||||
|
expect(result.asOk.value, kBookingApiModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get bookings', () async {
|
||||||
|
mockHttpClient.mockGet('/booking', [kBookingApiModel]);
|
||||||
|
final result = await apiClient.getBookings();
|
||||||
|
expect(result.asOk.value, [kBookingApiModel]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get destinations', () async {
|
||||||
|
mockHttpClient.mockGet('/destination', [kDestination1]);
|
||||||
|
final result = await apiClient.getDestinations();
|
||||||
|
expect(result.asOk.value, [kDestination1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get user', () async {
|
||||||
|
mockHttpClient.mockGet('/user', userApiModel);
|
||||||
|
final result = await apiClient.getUser();
|
||||||
|
expect(result.asOk.value, userApiModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should post booking', () async {
|
||||||
|
mockHttpClient.mockPost('/booking', kBookingApiModel);
|
||||||
|
final result = await apiClient.postBooking(kBookingApiModel);
|
||||||
|
expect(result.asOk.value, kBookingApiModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should delete booking', () async {
|
||||||
|
mockHttpClient.mockDelete('/booking/0');
|
||||||
|
final result = await apiClient.deleteBooking(0);
|
||||||
|
expect(result, isA<Ok<void>>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:compass_app/data/services/api/auth_api_client.dart';
|
||||||
|
import 'package:compass_app/data/services/api/model/login_request/login_request.dart';
|
||||||
|
import 'package:compass_app/data/services/api/model/login_response/login_response.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../../../../testing/mocks.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('AuthApiClient', () {
|
||||||
|
late MockHttpClient mockHttpClient;
|
||||||
|
late AuthApiClient apiClient;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockHttpClient = MockHttpClient();
|
||||||
|
apiClient = AuthApiClient(clientFactory: () => mockHttpClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should post login', () async {
|
||||||
|
const loginResponse = LoginResponse(
|
||||||
|
token: 'TOKEN',
|
||||||
|
userId: '123',
|
||||||
|
);
|
||||||
|
mockHttpClient.mockPost(
|
||||||
|
'/login',
|
||||||
|
loginResponse,
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
final result = await apiClient.login(
|
||||||
|
const LoginRequest(
|
||||||
|
email: 'EMAIL',
|
||||||
|
password: 'PASSWORD',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.asOk.value, loginResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:compass_app/data/repositories/user/user_repository.dart';
|
||||||
|
import 'package:compass_app/domain/models/user/user.dart';
|
||||||
|
import 'package:compass_app/utils/result.dart';
|
||||||
|
|
||||||
|
import '../../models/user.dart';
|
||||||
|
|
||||||
|
class FakeUserRepository implements UserRepository {
|
||||||
|
@override
|
||||||
|
Future<Result<User>> getUser() async {
|
||||||
|
return Result.ok(user);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,54 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockGoRouter extends Mock implements GoRouter {}
|
class MockGoRouter extends Mock implements GoRouter {}
|
||||||
|
|
||||||
|
class MockHttpClient extends Mock implements HttpClient {}
|
||||||
|
|
||||||
|
class MockHttpHeaders extends Mock implements HttpHeaders {}
|
||||||
|
|
||||||
|
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
|
||||||
|
|
||||||
|
class MockHttpClientResponse extends Mock implements HttpClientResponse {}
|
||||||
|
|
||||||
|
extension HttpMethodMocks on MockHttpClient {
|
||||||
|
void mockGet(String path, Object object) {
|
||||||
|
when(() => get(any(), any(), path)).thenAnswer((invocation) {
|
||||||
|
final request = MockHttpClientRequest();
|
||||||
|
final response = MockHttpClientResponse();
|
||||||
|
when(() => request.close()).thenAnswer((_) => Future.value(response));
|
||||||
|
when(() => request.headers).thenReturn(MockHttpHeaders());
|
||||||
|
when(() => response.statusCode).thenReturn(200);
|
||||||
|
when(() => response.transform(utf8.decoder))
|
||||||
|
.thenAnswer((_) => Stream.value(jsonEncode(object)));
|
||||||
|
return Future.value(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void mockPost(String path, Object object, [int statusCode = 201]) {
|
||||||
|
when(() => post(any(), any(), path)).thenAnswer((invocation) {
|
||||||
|
final request = MockHttpClientRequest();
|
||||||
|
final response = MockHttpClientResponse();
|
||||||
|
when(() => request.close()).thenAnswer((_) => Future.value(response));
|
||||||
|
when(() => request.headers).thenReturn(MockHttpHeaders());
|
||||||
|
when(() => response.statusCode).thenReturn(statusCode);
|
||||||
|
when(() => response.transform(utf8.decoder))
|
||||||
|
.thenAnswer((_) => Stream.value(jsonEncode(object)));
|
||||||
|
return Future.value(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void mockDelete(String path) {
|
||||||
|
when(() => delete(any(), any(), path)).thenAnswer((invocation) {
|
||||||
|
final request = MockHttpClientRequest();
|
||||||
|
final response = MockHttpClientResponse();
|
||||||
|
when(() => request.close()).thenAnswer((_) => Future.value(response));
|
||||||
|
when(() => request.headers).thenReturn(MockHttpHeaders());
|
||||||
|
when(() => response.statusCode).thenReturn(204);
|
||||||
|
return Future.value(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:compass_app/data/services/api/model/user/user_api_model.dart';
|
||||||
|
import 'package:compass_app/domain/models/user/user.dart';
|
||||||
|
|
||||||
|
const userApiModel = UserApiModel(
|
||||||
|
id: 'ID',
|
||||||
|
name: 'NAME',
|
||||||
|
email: 'EMAIL',
|
||||||
|
picture: 'assets/user.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = User(
|
||||||
|
name: 'NAME',
|
||||||
|
picture: 'assets/user.jpg',
|
||||||
|
);
|
@ -1,7 +0,0 @@
|
|||||||
# https://dart.dev/guides/libraries/private-files
|
|
||||||
# Created by `dart pub`
|
|
||||||
.dart_tool/
|
|
||||||
|
|
||||||
# Avoid committing pubspec.lock for library packages; see
|
|
||||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
|
||||||
pubspec.lock
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue