implemented RMV commute integration in the timetable, added Nominatim geocoding for home station lookup, created CommuteCubit for daily trip management with TTL caching, and introduced specialized timetable tiles, detail sheets, and settings for transit connections and walking buffers
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'nominatim_result.freezed.dart';
|
||||
part 'nominatim_result.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class NominatimResult with _$NominatimResult {
|
||||
const factory NominatimResult({
|
||||
required String displayName,
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) = _NominatimResult;
|
||||
|
||||
factory NominatimResult.fromJson(Map<String, Object?> json) =>
|
||||
_$NominatimResultFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// 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 'nominatim_result.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$NominatimResult {
|
||||
|
||||
String get displayName; double get lat; double get lon;
|
||||
/// Create a copy of NominatimResult
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$NominatimResultCopyWith<NominatimResult> get copyWith => _$NominatimResultCopyWithImpl<NominatimResult>(this as NominatimResult, _$identity);
|
||||
|
||||
/// Serializes this NominatimResult to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is NominatimResult&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.lat, lat) || other.lat == lat)&&(identical(other.lon, lon) || other.lon == lon));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,displayName,lat,lon);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NominatimResult(displayName: $displayName, lat: $lat, lon: $lon)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $NominatimResultCopyWith<$Res> {
|
||||
factory $NominatimResultCopyWith(NominatimResult value, $Res Function(NominatimResult) _then) = _$NominatimResultCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String displayName, double lat, double lon
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$NominatimResultCopyWithImpl<$Res>
|
||||
implements $NominatimResultCopyWith<$Res> {
|
||||
_$NominatimResultCopyWithImpl(this._self, this._then);
|
||||
|
||||
final NominatimResult _self;
|
||||
final $Res Function(NominatimResult) _then;
|
||||
|
||||
/// Create a copy of NominatimResult
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? displayName = null,Object? lat = null,Object? lon = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lat: null == lat ? _self.lat : lat // ignore: cast_nullable_to_non_nullable
|
||||
as double,lon: null == lon ? _self.lon : lon // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [NominatimResult].
|
||||
extension NominatimResultPatterns on NominatimResult {
|
||||
/// 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( _NominatimResult value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult() 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( _NominatimResult value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult():
|
||||
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( _NominatimResult value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult() 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 displayName, double lat, double lon)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult() when $default != null:
|
||||
return $default(_that.displayName,_that.lat,_that.lon);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 displayName, double lat, double lon) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult():
|
||||
return $default(_that.displayName,_that.lat,_that.lon);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 displayName, double lat, double lon)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _NominatimResult() when $default != null:
|
||||
return $default(_that.displayName,_that.lat,_that.lon);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _NominatimResult implements NominatimResult {
|
||||
const _NominatimResult({required this.displayName, required this.lat, required this.lon});
|
||||
factory _NominatimResult.fromJson(Map<String, dynamic> json) => _$NominatimResultFromJson(json);
|
||||
|
||||
@override final String displayName;
|
||||
@override final double lat;
|
||||
@override final double lon;
|
||||
|
||||
/// Create a copy of NominatimResult
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$NominatimResultCopyWith<_NominatimResult> get copyWith => __$NominatimResultCopyWithImpl<_NominatimResult>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$NominatimResultToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NominatimResult&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.lat, lat) || other.lat == lat)&&(identical(other.lon, lon) || other.lon == lon));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,displayName,lat,lon);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NominatimResult(displayName: $displayName, lat: $lat, lon: $lon)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$NominatimResultCopyWith<$Res> implements $NominatimResultCopyWith<$Res> {
|
||||
factory _$NominatimResultCopyWith(_NominatimResult value, $Res Function(_NominatimResult) _then) = __$NominatimResultCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String displayName, double lat, double lon
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$NominatimResultCopyWithImpl<$Res>
|
||||
implements _$NominatimResultCopyWith<$Res> {
|
||||
__$NominatimResultCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _NominatimResult _self;
|
||||
final $Res Function(_NominatimResult) _then;
|
||||
|
||||
/// Create a copy of NominatimResult
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? displayName = null,Object? lat = null,Object? lon = null,}) {
|
||||
return _then(_NominatimResult(
|
||||
displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||
as String,lat: null == lat ? _self.lat : lat // ignore: cast_nullable_to_non_nullable
|
||||
as double,lon: null == lon ? _self.lon : lon // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'nominatim_result.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_NominatimResult _$NominatimResultFromJson(Map<String, dynamic> json) =>
|
||||
_NominatimResult(
|
||||
displayName: json['displayName'] as String,
|
||||
lat: (json['lat'] as num).toDouble(),
|
||||
lon: (json['lon'] as num).toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$NominatimResultToJson(_NominatimResult instance) =>
|
||||
<String, dynamic>{
|
||||
'displayName': instance.displayName,
|
||||
'lat': instance.lat,
|
||||
'lon': instance.lon,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../errors/network_exception.dart';
|
||||
import '../errors/parse_exception.dart';
|
||||
import '../errors/server_exception.dart';
|
||||
import 'nominatim_result.dart';
|
||||
|
||||
/// Tiny wrapper around the public Nominatim geocoder. Only used in the
|
||||
/// commute-settings flow to look up a home address; not called from any
|
||||
/// hot path. The User-Agent header is **required** by the Nominatim usage
|
||||
/// policy — without it the service throttles/blocks the client.
|
||||
class NominatimSearch {
|
||||
static const _userAgent = 'MarianumMobile/1.0 (contact@elias-mueller.com)';
|
||||
static final Uri _base = Uri.parse('https://nominatim.openstreetmap.org/search');
|
||||
|
||||
/// Returns up to [limit] geocoded matches for the user-typed [query].
|
||||
Future<List<NominatimResult>> run(String query, {int limit = 5}) async {
|
||||
final uri = _base.replace(
|
||||
queryParameters: {
|
||||
'q': query,
|
||||
'format': 'json',
|
||||
'limit': limit.toString(),
|
||||
'addressdetails': '0',
|
||||
'accept-language': 'de',
|
||||
},
|
||||
);
|
||||
|
||||
final http.Response response;
|
||||
try {
|
||||
response = await http
|
||||
.get(uri, headers: {'User-Agent': _userAgent, 'Accept': 'application/json'})
|
||||
.timeout(const Duration(seconds: 15));
|
||||
} on SocketException catch (e) {
|
||||
throw NetworkException(technicalDetails: 'nominatim: ${e.message}');
|
||||
} on TimeoutException catch (e) {
|
||||
throw NetworkException.timeout(technicalDetails: 'nominatim: $e');
|
||||
} on http.ClientException catch (e) {
|
||||
throw NetworkException(technicalDetails: 'nominatim: ${e.message}');
|
||||
}
|
||||
|
||||
if (response.statusCode > 299) {
|
||||
throw ServerException(
|
||||
statusCode: response.statusCode,
|
||||
technicalDetails: 'nominatim HTTP ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final raw = jsonDecode(utf8.decode(response.bodyBytes)) as List;
|
||||
return raw
|
||||
.map((e) => _resultFromRaw(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
} catch (e) {
|
||||
throw ParseException(technicalDetails: 'nominatim assemble: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static NominatimResult _resultFromRaw(Map<String, dynamic> json) {
|
||||
// Nominatim returns lat/lon as strings, not numbers. Normalise here.
|
||||
final lat = double.parse(json['lat'].toString());
|
||||
final lon = double.parse(json['lon'].toString());
|
||||
return NominatimResult(
|
||||
displayName: json['display_name'] as String? ?? '?',
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user