chore(wip): upgrade dependencies, begin refactor

This commit is contained in:
2025-12-23 21:49:54 +01:00
parent 0070e57aec
commit 239b63ca81
82 changed files with 4028 additions and 195 deletions

View File

@@ -10,7 +10,8 @@ pkgs.mkShell {
# androidenv.androidPkgs.ndk-bundle
];
# Set up Android SDK paths if needed
# Setting up android build environments on nix is a bit of a pain - immutable paths, etc.
# I used a hacky workaround by manually downloading the SDK and NDK from the website and simply telling the shell where to find them
ANDROID_HOME = "/scratch/remy/android";
shellHook = ''

View File

@@ -26,7 +26,7 @@ To truly deploy a new version of the application, i.e. to the official app store
git tag -a v<name> -m "Release <name>"
git push origin v<name>
```
We adhere to the [Semantic Versioning](https://semver.org/) standard, so the tag should be of the form `v0.1.8` for example.
We adhere to the [Semantic Versioning](https://semver.org/) standard, so the tag should be of the form `v0.1.8` for example.
### Icons and logos
The application uses a custom launcher icon and splash screen. These are managed platform-independently using the `flutter_launcher_icons` package.
@@ -66,3 +66,10 @@ These are used by the CI/CD pipeline to deploy the application.
- `IOS_ASC_KEY_ID` as well
- `IOS_MATCH_PASSWORD` is used by fastlane match to download the certificates
- `IOS_MATCH_REPO_SSH_KEY_BASE64` is used to authenticate with the git repository where the certificates are stored
## Android SDK&NDK setup

View File

@@ -49,7 +49,8 @@ if (secretPropertiesFile.exists()) {
android {
namespace "com.anydev.anyway"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
ndkVersion = "27.0.12077973"
// TODO - set back to ndkVersion flutter.ndkVersion once https://github.com/flutter/flutter/issues/139427 is resolved
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -65,7 +66,7 @@ android {
}
defaultConfig {
applicationId "com.anydev.anyway"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
const String APP_NAME = 'AnyWay';
String API_URL_BASE = 'https://anyway.anydev.info';
String API_URL_DEBUG = 'https://anyway-stg.anydev.info';
String PRIVACY_URL = 'https://anydev.info/privacy';
const String MAP_ID = '41c21ac9b81dbfd8';
const Color GRADIENT_START = Color(0xFFF9B208);
const Color GRADIENT_END = Color(0xFFE72E77);
const Color PRIMARY_COLOR = Color(0xFFF38F1A);
const double TRIP_PANEL_MAX_HEIGHT = 0.8;
const double TRIP_PANEL_MIN_HEIGHT = 0.12;
ThemeData APP_THEME = ThemeData(
primaryColor: PRIMARY_COLOR,
scaffoldBackgroundColor: Colors.white,
cardColor: Colors.white,
useMaterial3: true,
colorScheme: const ColorScheme.light(
primary: PRIMARY_COLOR,
secondary: GRADIENT_END,
surface: Colors.white,
error: Colors.red,
onPrimary: Colors.white,
onSecondary: Color.fromARGB(255, 30, 22, 22),
onSurface: Colors.black,
onError: Colors.white,
brightness: Brightness.light,
),
textButtonTheme: const TextButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
side: WidgetStatePropertyAll(
BorderSide(
color: PRIMARY_COLOR,
width: 1,
),
),
)
),
elevatedButtonTheme: const ElevatedButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
)
),
outlinedButtonTheme: const OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(PRIMARY_COLOR),
)
),
sliderTheme: const SliderThemeData(
trackHeight: 15,
inactiveTrackColor: Colors.grey,
thumbColor: PRIMARY_COLOR,
activeTrackColor: GRADIENT_END
)
);
const Gradient APP_GRADIENT = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [GRADIENT_START, GRADIENT_END],
);
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

View File

@@ -0,0 +1,16 @@
import 'package:dio/dio.dart';
class DioClient {
final Dio dio;
DioClient({required String baseUrl}): dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 120),
// also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors
validateStatus: (status) => status! <= 500,
receiveDataWhenStatusError: true,
contentType: Headers.jsonContentType,
responseType: ResponseType.json,
));
}

View File

View File

@@ -0,0 +1,5 @@
import 'package:anyway/domain/entities/landmark.dart';
abstract class TripRemoteDataSource {
Future<List<Landmark>> fetchLandmarks();
}

View File

@@ -0,0 +1,30 @@
import 'package:anyway/domain/entities/landmark.dart';
import 'package:anyway/domain/entities/landmark_description.dart';
import 'package:anyway/domain/entities/landmark_type.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_model.freezed.dart';
part 'landmark_model.g.dart';
@freezed
abstract class LandmarkModel with _$LandmarkModel {
const factory LandmarkModel({
required String uuid,
required String name,
required List<double> location,
required String type,
required bool isSecondary,
required String description,
}) = _LandmarkModel;
factory LandmarkModel.fromJson(Map<String, dynamic> json) => _$LandmarkModelFromJson(json);
Landmark toEntity() => Landmark(
uuid: uuid,
name: name,
location: location,
type: LandmarkType(type: LandmarkTypeEnum.values.firstWhere((e) => e.value == type)),
isSecondary: isSecondary,
// TODO - try to set tags
description: LandmarkDescription(description: description, tags: [])
);
}

View File

@@ -0,0 +1,443 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'landmark_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkModel {
String get uuid;
String get name;
List<double> get location;
String get type;
bool get isSecondary;
String get description;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkModelCopyWith<LandmarkModel> get copyWith =>
_$LandmarkModelCopyWithImpl<LandmarkModel>(
this as LandmarkModel, _$identity);
/// Serializes this LandmarkModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is LandmarkModel &&
(identical(other.uuid, uuid) || other.uuid == uuid) &&
(identical(other.name, name) || other.name == name) &&
const DeepCollectionEquality().equals(other.location, location) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.isSecondary, isSecondary) ||
other.isSecondary == isSecondary) &&
(identical(other.description, description) ||
other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
uuid,
name,
const DeepCollectionEquality().hash(location),
type,
isSecondary,
description);
@override
String toString() {
return 'LandmarkModel(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class $LandmarkModelCopyWith<$Res> {
factory $LandmarkModelCopyWith(
LandmarkModel value, $Res Function(LandmarkModel) _then) =
_$LandmarkModelCopyWithImpl;
@useResult
$Res call(
{String uuid,
String name,
List<double> location,
String type,
bool isSecondary,
String description});
}
/// @nodoc
class _$LandmarkModelCopyWithImpl<$Res>
implements $LandmarkModelCopyWith<$Res> {
_$LandmarkModelCopyWithImpl(this._self, this._then);
final LandmarkModel _self;
final $Res Function(LandmarkModel) _then;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uuid = null,
Object? name = null,
Object? location = null,
Object? type = null,
Object? isSecondary = null,
Object? description = null,
}) {
return _then(_self.copyWith(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
location: null == location
? _self.location
: location // ignore: cast_nullable_to_non_nullable
as List<double>,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String,
isSecondary: null == isSecondary
? _self.isSecondary
: isSecondary // ignore: cast_nullable_to_non_nullable
as bool,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkModel].
extension LandmarkModelPatterns on LandmarkModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_LandmarkModel value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_LandmarkModel value) $default,
) {
final _that = this;
switch (_that) {
case _LandmarkModel():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_LandmarkModel value)? $default,
) {
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(String uuid, String name, List<double> location,
String type, bool isSecondary, String description)?
$default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(String uuid, String name, List<double> location,
String type, bool isSecondary, String description)
$default,
) {
final _that = this;
switch (_that) {
case _LandmarkModel():
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(String uuid, String name, List<double> location,
String type, bool isSecondary, String description)?
$default,
) {
final _that = this;
switch (_that) {
case _LandmarkModel() when $default != null:
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkModel implements LandmarkModel {
const _LandmarkModel(
{required this.uuid,
required this.name,
required final List<double> location,
required this.type,
required this.isSecondary,
required this.description})
: _location = location;
factory _LandmarkModel.fromJson(Map<String, dynamic> json) =>
_$LandmarkModelFromJson(json);
@override
final String uuid;
@override
final String name;
final List<double> _location;
@override
List<double> get location {
if (_location is EqualUnmodifiableListView) return _location;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_location);
}
@override
final String type;
@override
final bool isSecondary;
@override
final String description;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkModelCopyWith<_LandmarkModel> get copyWith =>
__$LandmarkModelCopyWithImpl<_LandmarkModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkModelToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _LandmarkModel &&
(identical(other.uuid, uuid) || other.uuid == uuid) &&
(identical(other.name, name) || other.name == name) &&
const DeepCollectionEquality().equals(other._location, _location) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.isSecondary, isSecondary) ||
other.isSecondary == isSecondary) &&
(identical(other.description, description) ||
other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
uuid,
name,
const DeepCollectionEquality().hash(_location),
type,
isSecondary,
description);
@override
String toString() {
return 'LandmarkModel(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class _$LandmarkModelCopyWith<$Res>
implements $LandmarkModelCopyWith<$Res> {
factory _$LandmarkModelCopyWith(
_LandmarkModel value, $Res Function(_LandmarkModel) _then) =
__$LandmarkModelCopyWithImpl;
@override
@useResult
$Res call(
{String uuid,
String name,
List<double> location,
String type,
bool isSecondary,
String description});
}
/// @nodoc
class __$LandmarkModelCopyWithImpl<$Res>
implements _$LandmarkModelCopyWith<$Res> {
__$LandmarkModelCopyWithImpl(this._self, this._then);
final _LandmarkModel _self;
final $Res Function(_LandmarkModel) _then;
/// Create a copy of LandmarkModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? uuid = null,
Object? name = null,
Object? location = null,
Object? type = null,
Object? isSecondary = null,
Object? description = null,
}) {
return _then(_LandmarkModel(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
location: null == location
? _self._location
: location // ignore: cast_nullable_to_non_nullable
as List<double>,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String,
isSecondary: null == isSecondary
? _self.isSecondary
: isSecondary // ignore: cast_nullable_to_non_nullable
as bool,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View File

@@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkModel _$LandmarkModelFromJson(Map<String, dynamic> json) =>
_LandmarkModel(
uuid: json['uuid'] as String,
name: json['name'] as String,
location: (json['location'] as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList(),
type: json['type'] as String,
isSecondary: json['isSecondary'] as bool,
description: json['description'] as String,
);
Map<String, dynamic> _$LandmarkModelToJson(_LandmarkModel instance) =>
<String, dynamic>{
'uuid': instance.uuid,
'name': instance.name,
'location': instance.location,
'type': instance.type,
'isSecondary': instance.isSecondary,
'description': instance.description,
};

View File

@@ -0,0 +1,119 @@
import 'dart:developer';
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/entities/trip.dart';
import 'package:anyway/domain/repositories/trip_repository.dart';
import 'package:dio/dio.dart';
// We can request a new trip from our backend API by passing it user preferences (which contain all the necessary data)
class BackendTripRepository implements TripRepository {
final Dio dio;
BackendTripRepository({required this.dio});
@override
Future<Trip> getTrip({Preferences? preferences, String? tripUUID}) async {
Map<String, dynamic> data = {
"preferences": preferences!.toJson(),
// "start": preferences!.startPoint.location,
};
late Response response;
try {
response = await dio.post(
"/trip/new",
data: data
);
} catch (e) {
trip.updateUUID("error");
// Format the error message to be more user friendly
String errorDescription;
if (e is DioException) {
errorDescription = e.message ?? "Unknown error";
} else if (e is SocketException) {
errorDescription = "No internet connection";
} else if (e is TimeoutException) {
errorDescription = "Request timed out";
} else {
errorDescription = "Unknown error";
}
String errorMessage = """
We're sorry, the following error was generated:
${errorDescription.trim()}
""".trim();
trip.updateError(errorMessage);
log(e.toString());
log(errorMessage);
return;
}
// handle more specific errors
if (response.statusCode != 200) {
trip.updateUUID("error");
String errorDescription;
if (response.data.runtimeType == String) {
errorDescription = response.data;
} else if (response.data.runtimeType == Map<String, dynamic>) {
errorDescription = response.data["detail"] ?? "Unknown error";
} else {
errorDescription = "Unknown error";
}
String errorMessage = """
We're sorry, our servers generated the following error:
${errorDescription.trim()}
Please try again.
""".trim();
trip.updateError(errorMessage);
log(errorMessage);
// Actualy no need to throw an exception, we can just log the error and let the user retry
// throw Exception(errorDetail);
} else {
// if the response data is not json, throw an error
if (response.data is! Map<String, dynamic>) {
log("${response.data.runtimeType}");
trip.updateUUID("error");
String errorMessage = """
We're sorry, our servers generated the following error:
${response.data.trim()}
Please try again.
""".trim();
trip.updateError(errorMessage);
log(errorMessage);
return;
}
Map<String, dynamic> json = response.data;
// only fill in the trip "meta" data for now
trip.loadFromJson(json);
// now fill the trip with landmarks
// we are going to recreate ALL the landmarks from the information given by the api
trip.landmarks.remove(trip.landmarks.first);
String? nextUUID = json["first_landmark_uuid"];
while (nextUUID != null) {
var (landmark, newUUID) = await fetchLandmark(nextUUID);
trip.addLandmark(landmark);
nextUUID = newUUID;
}
log(response.data.toString());
// // Also save the trip for the user's convenience
// savedTrips.addTrip(trip);
}
}
}

View File

@@ -0,0 +1,24 @@
# Domain layer
## `entities` - Model definition
Since we follow the repository structure convention, in this folder we purely define data models, without providing any logic. To reduce boilerplate, we use the `freezed` package. This requires some code generation, which means that all model definitions have the following structure:
```dart
import 'package:freezed_annotation/freezed_annotation.dart';
// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';
```
This is required boilerplate for all models. Then, we define the model itself using the `@freezed` annotation:
```dart
@freezed
...
```
The `*.frozen.dart` and `*.g.dart` are pure boilerplate and should not be touched.
Note that the description of the data will losely follow the capabilities of the backend but does not need to reflect it exactly. That is where the `data` part is for: translating api calls into flutter objects.

View File

@@ -0,0 +1,21 @@
import 'package:anyway/domain/entities/landmark_type.dart';
import 'package:anyway/domain/entities/landmark_description.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark.freezed.dart';
part 'landmark.g.dart';
@unfreezed
abstract class Landmark with _$Landmark {
factory Landmark({
required String uuid,
required String name,
required List<double> location,
required LandmarkType type,
required bool isSecondary,
required LandmarkDescription description,
}) = _Landmark;
factory Landmark.fromJson(Map<String, Object?> json) => _$LandmarkFromJson(json);
}

View File

@@ -0,0 +1,448 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'landmark.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Landmark {
String get uuid;
set uuid(String value);
String get name;
set name(String value);
List<double> get location;
set location(List<double> value);
LandmarkType get type;
set type(LandmarkType value);
bool get isSecondary;
set isSecondary(bool value);
LandmarkDescription get description;
set description(LandmarkDescription value);
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkCopyWith<Landmark> get copyWith =>
_$LandmarkCopyWithImpl<Landmark>(this as Landmark, _$identity);
/// Serializes this Landmark to a JSON map.
Map<String, dynamic> toJson();
@override
String toString() {
return 'Landmark(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class $LandmarkCopyWith<$Res> {
factory $LandmarkCopyWith(Landmark value, $Res Function(Landmark) _then) =
_$LandmarkCopyWithImpl;
@useResult
$Res call(
{String uuid,
String name,
List<double> location,
LandmarkType type,
bool isSecondary,
LandmarkDescription description});
$LandmarkTypeCopyWith<$Res> get type;
$LandmarkDescriptionCopyWith<$Res> get description;
}
/// @nodoc
class _$LandmarkCopyWithImpl<$Res> implements $LandmarkCopyWith<$Res> {
_$LandmarkCopyWithImpl(this._self, this._then);
final Landmark _self;
final $Res Function(Landmark) _then;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uuid = null,
Object? name = null,
Object? location = null,
Object? type = null,
Object? isSecondary = null,
Object? description = null,
}) {
return _then(_self.copyWith(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
location: null == location
? _self.location
: location // ignore: cast_nullable_to_non_nullable
as List<double>,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as LandmarkType,
isSecondary: null == isSecondary
? _self.isSecondary
: isSecondary // ignore: cast_nullable_to_non_nullable
as bool,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as LandmarkDescription,
));
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<$Res> get type {
return $LandmarkTypeCopyWith<$Res>(_self.type, (value) {
return _then(_self.copyWith(type: value));
});
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<$Res> get description {
return $LandmarkDescriptionCopyWith<$Res>(_self.description, (value) {
return _then(_self.copyWith(description: value));
});
}
}
/// Adds pattern-matching-related methods to [Landmark].
extension LandmarkPatterns on Landmark {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_Landmark value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_Landmark value) $default,
) {
final _that = this;
switch (_that) {
case _Landmark():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_Landmark value)? $default,
) {
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(
String uuid,
String name,
List<double> location,
LandmarkType type,
bool isSecondary,
LandmarkDescription description)?
$default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(
String uuid,
String name,
List<double> location,
LandmarkType type,
bool isSecondary,
LandmarkDescription description)
$default,
) {
final _that = this;
switch (_that) {
case _Landmark():
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(
String uuid,
String name,
List<double> location,
LandmarkType type,
bool isSecondary,
LandmarkDescription description)?
$default,
) {
final _that = this;
switch (_that) {
case _Landmark() when $default != null:
return $default(_that.uuid, _that.name, _that.location, _that.type,
_that.isSecondary, _that.description);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Landmark implements Landmark {
_Landmark(
{required this.uuid,
required this.name,
required this.location,
required this.type,
required this.isSecondary,
required this.description});
factory _Landmark.fromJson(Map<String, dynamic> json) =>
_$LandmarkFromJson(json);
@override
String uuid;
@override
String name;
@override
List<double> location;
@override
LandmarkType type;
@override
bool isSecondary;
@override
LandmarkDescription description;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkCopyWith<_Landmark> get copyWith =>
__$LandmarkCopyWithImpl<_Landmark>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkToJson(
this,
);
}
@override
String toString() {
return 'Landmark(uuid: $uuid, name: $name, location: $location, type: $type, isSecondary: $isSecondary, description: $description)';
}
}
/// @nodoc
abstract mixin class _$LandmarkCopyWith<$Res>
implements $LandmarkCopyWith<$Res> {
factory _$LandmarkCopyWith(_Landmark value, $Res Function(_Landmark) _then) =
__$LandmarkCopyWithImpl;
@override
@useResult
$Res call(
{String uuid,
String name,
List<double> location,
LandmarkType type,
bool isSecondary,
LandmarkDescription description});
@override
$LandmarkTypeCopyWith<$Res> get type;
@override
$LandmarkDescriptionCopyWith<$Res> get description;
}
/// @nodoc
class __$LandmarkCopyWithImpl<$Res> implements _$LandmarkCopyWith<$Res> {
__$LandmarkCopyWithImpl(this._self, this._then);
final _Landmark _self;
final $Res Function(_Landmark) _then;
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? uuid = null,
Object? name = null,
Object? location = null,
Object? type = null,
Object? isSecondary = null,
Object? description = null,
}) {
return _then(_Landmark(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
location: null == location
? _self.location
: location // ignore: cast_nullable_to_non_nullable
as List<double>,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as LandmarkType,
isSecondary: null == isSecondary
? _self.isSecondary
: isSecondary // ignore: cast_nullable_to_non_nullable
as bool,
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as LandmarkDescription,
));
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<$Res> get type {
return $LandmarkTypeCopyWith<$Res>(_self.type, (value) {
return _then(_self.copyWith(type: value));
});
}
/// Create a copy of Landmark
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<$Res> get description {
return $LandmarkDescriptionCopyWith<$Res>(_self.description, (value) {
return _then(_self.copyWith(description: value));
});
}
}
// dart format on

View File

@@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Landmark _$LandmarkFromJson(Map<String, dynamic> json) => _Landmark(
uuid: json['uuid'] as String,
name: json['name'] as String,
location: (json['location'] as List<dynamic>)
.map((e) => (e as num).toDouble())
.toList(),
type: LandmarkType.fromJson(json['type'] as Map<String, dynamic>),
isSecondary: json['isSecondary'] as bool,
description: LandmarkDescription.fromJson(
json['description'] as Map<String, dynamic>),
);
Map<String, dynamic> _$LandmarkToJson(_Landmark instance) => <String, dynamic>{
'uuid': instance.uuid,
'name': instance.name,
'location': instance.location,
'type': instance.type,
'isSecondary': instance.isSecondary,
'description': instance.description,
};

View File

@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_description.freezed.dart';
part 'landmark_description.g.dart';
@Freezed(makeCollectionsUnmodifiable: false)
abstract class LandmarkDescription with _$LandmarkDescription {
const factory LandmarkDescription({
required String description,
required List<String> tags,
}) = _LandmarkDescription;
factory LandmarkDescription.fromJson(Map<String, Object?> json) => _$LandmarkDescriptionFromJson(json);
}

View File

@@ -0,0 +1,336 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'landmark_description.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkDescription {
String get description;
List<String> get tags;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkDescriptionCopyWith<LandmarkDescription> get copyWith =>
_$LandmarkDescriptionCopyWithImpl<LandmarkDescription>(
this as LandmarkDescription, _$identity);
/// Serializes this LandmarkDescription to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is LandmarkDescription &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other.tags, tags));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, description, const DeepCollectionEquality().hash(tags));
@override
String toString() {
return 'LandmarkDescription(description: $description, tags: $tags)';
}
}
/// @nodoc
abstract mixin class $LandmarkDescriptionCopyWith<$Res> {
factory $LandmarkDescriptionCopyWith(
LandmarkDescription value, $Res Function(LandmarkDescription) _then) =
_$LandmarkDescriptionCopyWithImpl;
@useResult
$Res call({String description, List<String> tags});
}
/// @nodoc
class _$LandmarkDescriptionCopyWithImpl<$Res>
implements $LandmarkDescriptionCopyWith<$Res> {
_$LandmarkDescriptionCopyWithImpl(this._self, this._then);
final LandmarkDescription _self;
final $Res Function(LandmarkDescription) _then;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? description = null,
Object? tags = null,
}) {
return _then(_self.copyWith(
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
tags: null == tags
? _self.tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkDescription].
extension LandmarkDescriptionPatterns on LandmarkDescription {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_LandmarkDescription value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_LandmarkDescription value) $default,
) {
final _that = this;
switch (_that) {
case _LandmarkDescription():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_LandmarkDescription value)? $default,
) {
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(String description, List<String> tags)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that.description, _that.tags);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(String description, List<String> tags) $default,
) {
final _that = this;
switch (_that) {
case _LandmarkDescription():
return $default(_that.description, _that.tags);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(String description, List<String> tags)? $default,
) {
final _that = this;
switch (_that) {
case _LandmarkDescription() when $default != null:
return $default(_that.description, _that.tags);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkDescription implements LandmarkDescription {
const _LandmarkDescription({required this.description, required this.tags});
factory _LandmarkDescription.fromJson(Map<String, dynamic> json) =>
_$LandmarkDescriptionFromJson(json);
@override
final String description;
@override
final List<String> tags;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkDescriptionCopyWith<_LandmarkDescription> get copyWith =>
__$LandmarkDescriptionCopyWithImpl<_LandmarkDescription>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkDescriptionToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _LandmarkDescription &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other.tags, tags));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, description, const DeepCollectionEquality().hash(tags));
@override
String toString() {
return 'LandmarkDescription(description: $description, tags: $tags)';
}
}
/// @nodoc
abstract mixin class _$LandmarkDescriptionCopyWith<$Res>
implements $LandmarkDescriptionCopyWith<$Res> {
factory _$LandmarkDescriptionCopyWith(_LandmarkDescription value,
$Res Function(_LandmarkDescription) _then) =
__$LandmarkDescriptionCopyWithImpl;
@override
@useResult
$Res call({String description, List<String> tags});
}
/// @nodoc
class __$LandmarkDescriptionCopyWithImpl<$Res>
implements _$LandmarkDescriptionCopyWith<$Res> {
__$LandmarkDescriptionCopyWithImpl(this._self, this._then);
final _LandmarkDescription _self;
final $Res Function(_LandmarkDescription) _then;
/// Create a copy of LandmarkDescription
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? description = null,
Object? tags = null,
}) {
return _then(_LandmarkDescription(
description: null == description
? _self.description
: description // ignore: cast_nullable_to_non_nullable
as String,
tags: null == tags
? _self.tags
: tags // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
// dart format on

View File

@@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_description.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkDescription _$LandmarkDescriptionFromJson(Map<String, dynamic> json) =>
_LandmarkDescription(
description: json['description'] as String,
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$LandmarkDescriptionToJson(
_LandmarkDescription instance) =>
<String, dynamic>{
'description': instance.description,
'tags': instance.tags,
};

View File

@@ -0,0 +1,27 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'landmark_type.freezed.dart';
part 'landmark_type.g.dart';
@freezed
abstract class LandmarkType with _$LandmarkType {
const factory LandmarkType({
required LandmarkTypeEnum type,
}) = _LandmarkType;
factory LandmarkType.fromJson(Map<String, Object?> json) => _$LandmarkTypeFromJson(json);
}
@JsonEnum(alwaysCreate: true)
enum LandmarkTypeEnum {
@JsonValue('culture')
culture,
@JsonValue('nature')
nature,
@JsonValue('shopping')
shopping,
}
extension LandmarkTypeEnumExtension on LandmarkTypeEnum {
String get value => _$LandmarkTypeEnumEnumMap[this]!;
}

View File

@@ -0,0 +1,315 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'landmark_type.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LandmarkType {
LandmarkTypeEnum get type;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LandmarkTypeCopyWith<LandmarkType> get copyWith =>
_$LandmarkTypeCopyWithImpl<LandmarkType>(
this as LandmarkType, _$identity);
/// Serializes this LandmarkType to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is LandmarkType &&
(identical(other.type, type) || other.type == type));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, type);
@override
String toString() {
return 'LandmarkType(type: $type)';
}
}
/// @nodoc
abstract mixin class $LandmarkTypeCopyWith<$Res> {
factory $LandmarkTypeCopyWith(
LandmarkType value, $Res Function(LandmarkType) _then) =
_$LandmarkTypeCopyWithImpl;
@useResult
$Res call({LandmarkTypeEnum type});
}
/// @nodoc
class _$LandmarkTypeCopyWithImpl<$Res> implements $LandmarkTypeCopyWith<$Res> {
_$LandmarkTypeCopyWithImpl(this._self, this._then);
final LandmarkType _self;
final $Res Function(LandmarkType) _then;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
}) {
return _then(_self.copyWith(
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as LandmarkTypeEnum,
));
}
}
/// Adds pattern-matching-related methods to [LandmarkType].
extension LandmarkTypePatterns on LandmarkType {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_LandmarkType value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_LandmarkType value) $default,
) {
final _that = this;
switch (_that) {
case _LandmarkType():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_LandmarkType value)? $default,
) {
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(LandmarkTypeEnum type)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that.type);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(LandmarkTypeEnum type) $default,
) {
final _that = this;
switch (_that) {
case _LandmarkType():
return $default(_that.type);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(LandmarkTypeEnum type)? $default,
) {
final _that = this;
switch (_that) {
case _LandmarkType() when $default != null:
return $default(_that.type);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LandmarkType implements LandmarkType {
const _LandmarkType({required this.type});
factory _LandmarkType.fromJson(Map<String, dynamic> json) =>
_$LandmarkTypeFromJson(json);
@override
final LandmarkTypeEnum type;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LandmarkTypeCopyWith<_LandmarkType> get copyWith =>
__$LandmarkTypeCopyWithImpl<_LandmarkType>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LandmarkTypeToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _LandmarkType &&
(identical(other.type, type) || other.type == type));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, type);
@override
String toString() {
return 'LandmarkType(type: $type)';
}
}
/// @nodoc
abstract mixin class _$LandmarkTypeCopyWith<$Res>
implements $LandmarkTypeCopyWith<$Res> {
factory _$LandmarkTypeCopyWith(
_LandmarkType value, $Res Function(_LandmarkType) _then) =
__$LandmarkTypeCopyWithImpl;
@override
@useResult
$Res call({LandmarkTypeEnum type});
}
/// @nodoc
class __$LandmarkTypeCopyWithImpl<$Res>
implements _$LandmarkTypeCopyWith<$Res> {
__$LandmarkTypeCopyWithImpl(this._self, this._then);
final _LandmarkType _self;
final $Res Function(_LandmarkType) _then;
/// Create a copy of LandmarkType
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? type = null,
}) {
return _then(_LandmarkType(
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as LandmarkTypeEnum,
));
}
}
// dart format on

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'landmark_type.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LandmarkType _$LandmarkTypeFromJson(Map<String, dynamic> json) =>
_LandmarkType(
type: $enumDecode(_$LandmarkTypeEnumEnumMap, json['type']),
);
Map<String, dynamic> _$LandmarkTypeToJson(_LandmarkType instance) =>
<String, dynamic>{
'type': _$LandmarkTypeEnumEnumMap[instance.type]!,
};
const _$LandmarkTypeEnumEnumMap = {
LandmarkTypeEnum.culture: 'culture',
LandmarkTypeEnum.nature: 'nature',
LandmarkTypeEnum.shopping: 'shopping',
};

View File

@@ -0,0 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'preferences.freezed.dart';
part 'preferences.g.dart';
@freezed
abstract class Preferences with _$Preferences {
const factory Preferences({
required String test,
}) = _Preferences;
factory Preferences.fromJson(Map<String, Object?> json) => _$PreferencesFromJson(json);
}

View File

@@ -0,0 +1,313 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'preferences.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Preferences {
String get test;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PreferencesCopyWith<Preferences> get copyWith =>
_$PreferencesCopyWithImpl<Preferences>(this as Preferences, _$identity);
/// Serializes this Preferences to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is Preferences &&
(identical(other.test, test) || other.test == test));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, test);
@override
String toString() {
return 'Preferences(test: $test)';
}
}
/// @nodoc
abstract mixin class $PreferencesCopyWith<$Res> {
factory $PreferencesCopyWith(
Preferences value, $Res Function(Preferences) _then) =
_$PreferencesCopyWithImpl;
@useResult
$Res call({String test});
}
/// @nodoc
class _$PreferencesCopyWithImpl<$Res> implements $PreferencesCopyWith<$Res> {
_$PreferencesCopyWithImpl(this._self, this._then);
final Preferences _self;
final $Res Function(Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? test = null,
}) {
return _then(_self.copyWith(
test: null == test
? _self.test
: test // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [Preferences].
extension PreferencesPatterns on Preferences {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_Preferences value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_Preferences value) $default,
) {
final _that = this;
switch (_that) {
case _Preferences():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_Preferences value)? $default,
) {
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(String test)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.test);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(String test) $default,
) {
final _that = this;
switch (_that) {
case _Preferences():
return $default(_that.test);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(String test)? $default,
) {
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.test);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Preferences implements Preferences {
const _Preferences({required this.test});
factory _Preferences.fromJson(Map<String, dynamic> json) =>
_$PreferencesFromJson(json);
@override
final String test;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PreferencesCopyWith<_Preferences> get copyWith =>
__$PreferencesCopyWithImpl<_Preferences>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PreferencesToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _Preferences &&
(identical(other.test, test) || other.test == test));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, test);
@override
String toString() {
return 'Preferences(test: $test)';
}
}
/// @nodoc
abstract mixin class _$PreferencesCopyWith<$Res>
implements $PreferencesCopyWith<$Res> {
factory _$PreferencesCopyWith(
_Preferences value, $Res Function(_Preferences) _then) =
__$PreferencesCopyWithImpl;
@override
@useResult
$Res call({String test});
}
/// @nodoc
class __$PreferencesCopyWithImpl<$Res> implements _$PreferencesCopyWith<$Res> {
__$PreferencesCopyWithImpl(this._self, this._then);
final _Preferences _self;
final $Res Function(_Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? test = null,
}) {
return _then(_Preferences(
test: null == test
? _self.test
: test // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View File

@@ -0,0 +1,16 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'preferences.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Preferences _$PreferencesFromJson(Map<String, dynamic> json) => _Preferences(
test: json['test'] as String,
);
Map<String, dynamic> _$PreferencesToJson(_Preferences instance) =>
<String, dynamic>{
'test': instance.test,
};

View File

@@ -0,0 +1,19 @@
import 'package:anyway/domain/entities/landmark.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'trip.freezed.dart';
part 'trip.g.dart';
@Freezed(makeCollectionsUnmodifiable: false)
abstract class Trip with _$Trip {
const factory Trip({
required String uuid,
// Duration totalTime,
required List<Landmark> landmarks,
}) = _Trip;
factory Trip.fromJson(Map<String, Object?> json) => _$TripFromJson(json);
}

View File

@@ -0,0 +1,327 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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 'trip.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Trip {
String get uuid; // Duration totalTime,
List<Landmark> get landmarks;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TripCopyWith<Trip> get copyWith =>
_$TripCopyWithImpl<Trip>(this as Trip, _$identity);
/// Serializes this Trip to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is Trip &&
(identical(other.uuid, uuid) || other.uuid == uuid) &&
const DeepCollectionEquality().equals(other.landmarks, landmarks));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, uuid, const DeepCollectionEquality().hash(landmarks));
@override
String toString() {
return 'Trip(uuid: $uuid, landmarks: $landmarks)';
}
}
/// @nodoc
abstract mixin class $TripCopyWith<$Res> {
factory $TripCopyWith(Trip value, $Res Function(Trip) _then) =
_$TripCopyWithImpl;
@useResult
$Res call({String uuid, List<Landmark> landmarks});
}
/// @nodoc
class _$TripCopyWithImpl<$Res> implements $TripCopyWith<$Res> {
_$TripCopyWithImpl(this._self, this._then);
final Trip _self;
final $Res Function(Trip) _then;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uuid = null,
Object? landmarks = null,
}) {
return _then(_self.copyWith(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
landmarks: null == landmarks
? _self.landmarks
: landmarks // ignore: cast_nullable_to_non_nullable
as List<Landmark>,
));
}
}
/// Adds pattern-matching-related methods to [Trip].
extension TripPatterns on Trip {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_Trip value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_Trip value) $default,
) {
final _that = this;
switch (_that) {
case _Trip():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_Trip value)? $default,
) {
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(String uuid, List<Landmark> landmarks)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that.uuid, _that.landmarks);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(String uuid, List<Landmark> landmarks) $default,
) {
final _that = this;
switch (_that) {
case _Trip():
return $default(_that.uuid, _that.landmarks);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(String uuid, List<Landmark> landmarks)? $default,
) {
final _that = this;
switch (_that) {
case _Trip() when $default != null:
return $default(_that.uuid, _that.landmarks);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Trip implements Trip {
const _Trip({required this.uuid, required this.landmarks});
factory _Trip.fromJson(Map<String, dynamic> json) => _$TripFromJson(json);
@override
final String uuid;
// Duration totalTime,
@override
final List<Landmark> landmarks;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TripCopyWith<_Trip> get copyWith =>
__$TripCopyWithImpl<_Trip>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$TripToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _Trip &&
(identical(other.uuid, uuid) || other.uuid == uuid) &&
const DeepCollectionEquality().equals(other.landmarks, landmarks));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, uuid, const DeepCollectionEquality().hash(landmarks));
@override
String toString() {
return 'Trip(uuid: $uuid, landmarks: $landmarks)';
}
}
/// @nodoc
abstract mixin class _$TripCopyWith<$Res> implements $TripCopyWith<$Res> {
factory _$TripCopyWith(_Trip value, $Res Function(_Trip) _then) =
__$TripCopyWithImpl;
@override
@useResult
$Res call({String uuid, List<Landmark> landmarks});
}
/// @nodoc
class __$TripCopyWithImpl<$Res> implements _$TripCopyWith<$Res> {
__$TripCopyWithImpl(this._self, this._then);
final _Trip _self;
final $Res Function(_Trip) _then;
/// Create a copy of Trip
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? uuid = null,
Object? landmarks = null,
}) {
return _then(_Trip(
uuid: null == uuid
? _self.uuid
: uuid // ignore: cast_nullable_to_non_nullable
as String,
landmarks: null == landmarks
? _self.landmarks
: landmarks // ignore: cast_nullable_to_non_nullable
as List<Landmark>,
));
}
}
// dart format on

View File

@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'trip.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Trip _$TripFromJson(Map<String, dynamic> json) => _Trip(
uuid: json['uuid'] as String,
landmarks: (json['landmarks'] as List<dynamic>)
.map((e) => Landmark.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$TripToJson(_Trip instance) => <String, dynamic>{
'uuid': instance.uuid,
'landmarks': instance.landmarks,
};

View File

@@ -0,0 +1,5 @@
import 'package:anyway/domain/entities/preferences.dart';
abstract class PreferencesRepository {
Future<Preferences> getPreferences();
}

View File

@@ -0,0 +1,6 @@
import 'package:anyway/domain/entities/preferences.dart';
import 'package:anyway/domain/entities/trip.dart';
abstract class TripRepository {
Future<Trip> getTrip({Preferences? preferences, String? tripUUID});
}

View File

@@ -1,26 +1,39 @@
import 'package:anyway/presentation/pages/start.dart';
import 'package:flutter/material.dart';
import 'package:anyway/constants.dart';
import 'package:anyway/utils/get_first_page.dart';
import 'package:anyway/utils/load_trips.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:anyway/core/constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(const App());
/// The app entry point.
/// Initializes persistence, sets up dependency injection via ProviderScope,
/// and determines which screen (login or main app) to show based on auth state.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Some global variables
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final SavedTrips savedTrips = SavedTrips();
// the list of saved trips is then populated implicitly by getFirstPage()
// initialize local persistence (shared preferences)
SharedPreferences prefs = await SharedPreferences.getInstance();
// the app wrapped in ProviderScope
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: APP_NAME,
home: getFirstPage(),
theme: APP_THEME,
scaffoldMessengerKey: rootScaffoldMessengerKey
);
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: APP_NAME,
theme: APP_THEME,
scaffoldMessengerKey: rootScaffoldMessengerKey,
home: const StartPage()
// TODO - set up routing
// onGenerateRoute: AppRouter.onGenerateRoute,
);
}
}

View File

@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
class NewTripPage extends StatefulWidget {
const NewTripPage({super.key});
@override
_NewTripPageState createState() => _NewTripPageState();
}
class _NewTripPageState extends State<NewTripPage> {
int _currentStep = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Create New Trip'),
),
body: Stepper(
currentStep: _currentStep,
onStepContinue: () {
if (_currentStep < 1) {
setState(() {
_currentStep += 1;
});
}
},
onStepCancel: () {
if (_currentStep > 0) {
setState(() {
_currentStep -= 1;
});
}
},
steps: [
Step(
title: const Text('Select Location'),
content: const MapWithSearchField(), // Replace with your map module
isActive: _currentStep >= 0,
),
Step(
title: const Text('Choose Options'),
content: const OptionsList(), // Replace with your options module
isActive: _currentStep >= 1,
),
],
),
);
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class LoginPage extends StatelessWidget {
const LoginPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Login Page')),
);
}
}

View File

@@ -1,12 +1,11 @@
import 'dart:ui';
import 'package:anyway/constants.dart';
import 'package:anyway/modules/onbarding_agreement_card.dart';
import 'package:anyway/modules/onboarding_card.dart';
import 'package:anyway/pages/new_trip_location.dart';
import 'package:anyway/structs/agreement.dart';
import 'package:anyway/core/constants.dart';
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
import 'package:anyway/presentation/widgets/onbarding_agreement_card.dart';
import 'package:anyway/presentation/widgets/onboarding_card.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
List<Widget> onboardingCards = [
@@ -33,14 +32,14 @@ List<Widget> onboardingCards = [
];
class OnboardingPage extends StatefulWidget {
class OnboardingPage extends ConsumerStatefulWidget {
const OnboardingPage({super.key});
@override
State<OnboardingPage> createState() => _OnboardingPageState();
ConsumerState<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
class _OnboardingPageState extends ConsumerState<OnboardingPage> {
final PageController _controller = PageController();
late List<Widget> fullCards;
@@ -121,41 +120,53 @@ class _OnboardingPageState extends State<OnboardingPage> {
label: const Icon(Icons.arrow_forward)
);
} else {
// only allow the user to proceed if they have agreed to the terms and conditions
Future<bool> hasAgreed = getAgreement().then((agreement) => agreement.agreed);
// only allow the user to proceed if they have agreed to the terms and conditions
// the information is accessible through ref.watch(onboardingStateProvider)
return FutureBuilder(
future: hasAgreed,
builder: (context, snapshot){
if (snapshot.hasData && snapshot.data!) {
return FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const NewTripPage()
)
);
},
label: const Row(
children: [
Text("Start planning!"),
Padding(padding: EdgeInsets.only(right: 8.0)),
Icon(Icons.map_outlined)
],
)
);
} else {
return Container();
}
}
);
Widget disabledWidget = FloatingActionButton.extended(
onPressed: null,
label: const Row(
children: [
Text("Start planning!"),
Padding(padding: EdgeInsets.only(right: 8.0)),
Icon(Icons.map_outlined)
],
)
);
return ref.watch(onboardingStateProvider).when(
data: (isOnboarded) {
if (isOnboarded) {
return FloatingActionButton.extended(
onPressed: () {
// proceed to the next page - pop the onboarding page so the StartPage can decide what to show next
Navigator.of(context).pop();
},
label: const Row(
children: [
Text("Start planning!"),
Padding(padding: EdgeInsets.only(right: 8.0)),
Icon(Icons.map_outlined)
],
)
);
} else {
return disabledWidget;
}
},
loading: () => disabledWidget,
error: (error, stack) => disabledWidget,
);
}
}
);
void onAgreementChanged(bool value) async {
saveAgreement(value);
// Update the state of the OnboardingPage
setState(() {});
await ref.read(onboardingControllerProvider).setOnboarded(value);
setState(() {
// rebuild to show the next button
});
}
}

View File

@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
import 'package:anyway/presentation/pages/login.dart';
import 'package:anyway/presentation/pages/onboarding.dart';
// Example providers (replace these with your actual providers)
// final onboardingStateProvider = Provider<bool>((ref) => true); // Replace with actual onboarding state logic
final authStateProvider = FutureProvider<bool>((ref) async => true); // Replace with actual auth state logic
final tripsAvailableProvider = FutureProvider<bool>((ref) async => false); // Replace with actual trips availability logic
class StartPage extends ConsumerWidget {
const StartPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// the home page is dependent on the state of the providers:
// - if the user is not onboarded, show the onboarding flow
// - if the user is not logged in, show the login page
// - if there are no trips available, show the trip creation page
// - else: show the overview page that shows the last trip
final onboardingState = ref.watch(onboardingStateProvider);
final authState = ref.watch(authStateProvider);
final tripsAvailable = ref.watch(tripsAvailableProvider);
return onboardingState.when(
data: (isOnboarded) {
if (!isOnboarded) {
return const OnboardingPage();
}
return authState.when(
data: (isLoggedIn) {
if (!isLoggedIn) {
return const LoginPage();
}
return tripsAvailable.when(
data: (hasTrips) {
if (!hasTrips) {
return const TripCreationPage();
}
return const OverviewPage();
},
loading: () => const Scaffold(
body: Center(child: CircularProgressIndicator()),
),
error: (error, stack) => Scaffold(
body: Center(child: Text('Error: $error')),
),
);
},
loading: () => const Scaffold(
body: Center(child: CircularProgressIndicator()),
),
error: (error, stack) => Scaffold(
body: Center(child: Text('Error: $error')),
),
);
},
loading: () => const Scaffold(
body: Center(child: CircularProgressIndicator()),
),
error: (error, stack) => Scaffold(
body: Center(child: Text('Error: $error')
),
),
);
}
}
class TripCreationPage extends StatelessWidget {
const TripCreationPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Trip Creation Page')),
);
}
}
class OverviewPage extends StatelessWidget {
const OverviewPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Overview Page')),
);
}
}

View File

@@ -0,0 +1,24 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final onboardingStateProvider = FutureProvider<bool>((ref) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('onboardingCompleted') ?? false;
});
final onboardingControllerProvider = Provider<OnboardingController>((ref) {
return OnboardingController(ref);
});
class OnboardingController {
final Ref ref;
OnboardingController(this.ref);
Future<void> setOnboarded(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('onboardingCompleted', value);
// refresh the read provider so UI updates
ref.invalidate(onboardingStateProvider);
}
}

View File

@@ -0,0 +1,6 @@
// import 'package:anyway/data/repositories/trip_repository.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// final weatherRepositoryProvider = Provider<TripRepository>((ref) {
// return TripRepositoryImpl(???);
// });

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:your_project/models/trip_request.dart';
import 'package:your_project/presentation/widgets/new_trip_map.dart';
import 'package:your_project/presentation/widgets/new_trip_location_search.dart';
class CreateTripLocation extends StatelessWidget {
final TripRequest tripRequest;
const CreateTripLocation(this.tripRequest, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
NewTripMap(tripRequest),
Padding(
padding: const EdgeInsets.all(15),
child: NewTripLocationSearch(tripRequest),
),
],
);
}
}

View File

@@ -0,0 +1,105 @@
// A map that allows the user to select a location for a new trip.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:widget_to_marker/widget_to_marker.dart';
import 'package:anyway/constants.dart';
import 'package:anyway/structs/trip.dart';
import 'package:anyway/structs/landmark.dart';
import 'package:anyway/modules/landmark_map_marker.dart';
class NewTripMap extends StatefulWidget {
@override
State<NewTripMap> createState() => _NewTripMapState();
}
class _NewTripMapState extends State<NewTripMap> {
final CameraPosition _cameraPosition = const CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 11.0,
);
GoogleMapController? _mapController;
final Set<Marker> _markers = <Marker>{};
_onLongPress(LatLng location) {
// widget.trip.landmarks.clear();
// widget.trip.addLandmark(
// Landmark(
// uuid: 'pending',
// name: 'start',
// location: [location.latitude, location.longitude],
// type: typeStart
// )
// );
}
updateTripDetails() async {
_markers.clear();
if (widget.trip.landmarks.isNotEmpty) {
Landmark landmark = widget.trip.landmarks.first;
_markers.add(
Marker(
markerId: MarkerId(landmark.uuid),
position: LatLng(landmark.location[0], landmark.location[1]),
icon: await ThemedMarker(landmark: landmark, position: 0).toBitmapDescriptor(
logicalSize: const Size(150, 150),
imageSize: const Size(150, 150)
),
)
);
// check if the controller is ready
if (_mapController != null) {
_mapController!.animateCamera(
CameraUpdate.newLatLng(
LatLng(landmark.location[0], landmark.location[1])
)
);
}
setState(() {});
}
}
void _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
}
@override
Widget build(BuildContext context) {
widget.trip.addListener(updateTripDetails);
Future<SharedPreferences> preferences = SharedPreferences.getInstance();
return FutureBuilder(
future: preferences,
builder: (context, snapshot) {
if (snapshot.hasData) {
SharedPreferences prefs = snapshot.data as SharedPreferences;
bool useLocation = prefs.getBool('useLocation') ?? true;
return _buildMap(useLocation);
} else {
return const CircularProgressIndicator();
}
}
);
}
Widget _buildMap(bool useLocation) {
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: _cameraPosition,
onLongPress: _onLongPress,
markers: _markers,
cloudMapId: MAP_ID,
mapToolbarEnabled: false,
zoomControlsEnabled: false,
myLocationButtonEnabled: false,
myLocationEnabled: useLocation,
);
}
}

View File

@@ -0,0 +1,124 @@
// A search bar that allow the user to enter a city name
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:shared_preferences/shared_preferences.dart';
const Map<String, List> debugLocations = {
'paris': [48.8575, 2.3514],
'london': [51.5074, -0.1278],
'new york': [40.7128, -74.0060],
'tokyo': [35.6895, 139.6917],
};
class NewTripLocationSearch extends StatefulWidget {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
Trip trip;
NewTripLocationSearch(
this.trip,
);
@override
State<NewTripLocationSearch> createState() => _NewTripLocationSearchState();
}
class _NewTripLocationSearchState extends State<NewTripLocationSearch> {
final TextEditingController _controller = TextEditingController();
setTripLocation (String query) async {
List<Location> locations = [];
Location startLocation;
log('Searching for: $query');
if (GeocodingPlatform.instance != null) {
locations.addAll(await locationFromAddress(query));
}
if (locations.isNotEmpty) {
startLocation = locations.first;
} else {
log('No results found for: $query. Is geocoding available?');
log('Setting Fallback location');
List coordinates = debugLocations[query.toLowerCase()] ?? [48.8575, 2.3514];
startLocation = Location(
latitude: coordinates[0],
longitude: coordinates[1],
timestamp: DateTime.now(),
);
}
widget.trip.landmarks.clear();
widget.trip.addLandmark(
Landmark(
uuid: 'pending',
name: query,
location: [startLocation.latitude, startLocation.longitude],
type: typeStart
)
);
}
late Widget locationSearchBar = SearchBar(
hintText: 'Enter a city name or long press on the map.',
onSubmitted: setTripLocation,
controller: _controller,
leading: const Icon(Icons.search),
trailing: [
ElevatedButton(
onPressed: () {
setTripLocation(_controller.text);
},
child: const Text('Search'),
)
]
);
late Widget useCurrentLocationButton = ElevatedButton(
onPressed: () async {
// this widget is only shown if the user has already granted location permissions
Position position = await Geolocator.getCurrentPosition();
widget.trip.landmarks.clear();
widget.trip.addLandmark(
Landmark(
uuid: 'pending',
name: 'start',
location: [position.latitude, position.longitude],
type: typeStart
)
);
},
child: const Text('Use current location'),
);
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.prefs,
builder: (context, snapshot) {
if (snapshot.hasData) {
final useLocation = snapshot.data!.getBool('useLocation') ?? false;
if (useLocation) {
return Column(
children: [
locationSearchBar,
useCurrentLocationButton,
],
);
} else {
return locationSearchBar;
}
} else {
return locationSearchBar;
}
},
);
}
}

View File

@@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:anyway/presentation/providers/onboarding_state_provider.dart';
import 'package:anyway/presentation/widgets/onboarding_card.dart';
class OnboardingAgreementCard extends ConsumerStatefulWidget {
final String title;
final String description;
final String imagePath;
final String agreementTextPath;
final ValueChanged<bool> onAgreementChanged;
const OnboardingAgreementCard({
super.key,
required this.title,
required this.description,
required this.imagePath,
required this.agreementTextPath,
required this.onAgreementChanged
});
@override
ConsumerState<OnboardingAgreementCard> createState() => _OnboardingAgreementCardState();
}
class _OnboardingAgreementCardState extends ConsumerState<OnboardingAgreementCard> {
// @override
// void initState() {
// super.initState();
// // You can use ref here if needed in initState
// }
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OnboardingCard(title: widget.title, description: widget.description, imagePath: widget.imagePath),
const Padding(padding: EdgeInsets.only(top: 20)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// The checkbox of the agreement
FutureBuilder(
future: ref.read(onboardingStateProvider.future),
builder: (context, snapshot) {
bool agreed = false;
if (snapshot.hasData) {
agreed = snapshot.data!;
}
return Checkbox(
value: agreed,
onChanged: (bool? value) {
if (value != null) {
widget.onAgreementChanged(value);
}
},
activeColor: Colors.white,
checkColor: Colors.black,
);
},
),
// The text of the agreement
Text(
"I agree to the ",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Colors.white,
),
),
// The clickable text of the agreement that shows the agreement text
GestureDetector(
onTap: () {
// show a dialog with the agreement text
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
content: FutureBuilder(
future: DefaultAssetBundle.of(context).loadString(widget.agreementTextPath),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MarkdownBody(
data: snapshot.data.toString(),
);
} else {
return const CircularProgressIndicator();
}
},
)
);
}
);
},
child: Text(
"Terms of Service (click to view)",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class OnboardingCard extends StatelessWidget {
final String title;
final String description;
final String imagePath;
const OnboardingCard({super.key,
required this.title,
required this.description,
required this.imagePath,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
color: Colors.white,
),
),
const Padding(padding: EdgeInsets.only(top: 20)),
SvgPicture.asset(
imagePath,
height: 200,
),
const Padding(padding: EdgeInsets.only(top: 20)),
Text(
description,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Colors.white,
),
),
]
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.3.4 <4.0.0'
flutter: '3.29.1'
sdk: ^3.8.0
flutter: '3.32.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@@ -32,7 +32,6 @@ dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
@@ -42,18 +41,20 @@ dependencies:
dio: ^5.5.0+1
google_maps_flutter: ^2.7.0
cached_network_image: ^3.4.0
geocoding: ^3.0.0
geocoding: ^4.0.0
widget_to_marker: ^1.0.6
provider: ^6.1.2
auto_size_text: ^3.0.0
map_launcher: ^3.3.1
# map_launcher: ^4.4.2
flutter_svg: ^2.0.10+1
url_launcher: ^6.3.0
flutter_launcher_icons: ^0.13.1
permission_handler: ^11.3.1
geolocator: ^13.0.1
fuzzywuzzy: ^1.2.0
flutter_markdown: ^0.7.6+2
flutter_launcher_icons: ^0.14.4
permission_handler: ^12.0.1
geolocator: ^14.0.2
freezed_annotation: ^3.1.0
json_annotation: ^4.9.0
uuid: ^4.5.1
flutter_riverpod: ^3.0.3
flutter_markdown_plus: ^1.0.5
dev_dependencies:
flutter_test:
@@ -64,7 +65,12 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_lints: ^6.0.0
build_runner:
freezed: ^3.2.3
json_serializable: ^6.11.1
custom_lint:
riverpod_lint: ^3.0.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec