implemented RMV public transit module including trip search, station departures, and nearby stop lookup, added "Marianum Connect" API integration with bearer token authentication and auto-refresh logic, integrated geolocator for location-based station search, added persistent storage for favorite stations and recent trip queries, and implemented comprehensive UI for journey details, trip results, and disruption alerts
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import 'login_request.dart';
|
||||
import 'login_response.dart';
|
||||
|
||||
class Login extends ConnectApi<LoginResponse> {
|
||||
final LoginRequest payload;
|
||||
|
||||
Login(this.payload) : super('auth/login');
|
||||
|
||||
@override
|
||||
bool get requiresAuth => false;
|
||||
|
||||
@override
|
||||
ConnectHttpMethod get method => ConnectHttpMethod.post;
|
||||
|
||||
@override
|
||||
Object? get body => payload.toJson();
|
||||
|
||||
@override
|
||||
LoginResponse assemble(String raw) =>
|
||||
LoginResponse.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'login_request.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class LoginRequest {
|
||||
final String username;
|
||||
final String password;
|
||||
final String tokenName;
|
||||
|
||||
LoginRequest({
|
||||
required this.username,
|
||||
required this.password,
|
||||
required this.tokenName,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'login_request.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
LoginRequest _$LoginRequestFromJson(Map<String, dynamic> json) => LoginRequest(
|
||||
username: json['username'] as String,
|
||||
password: json['password'] as String,
|
||||
tokenName: json['tokenName'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$LoginRequestToJson(LoginRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'username': instance.username,
|
||||
'password': instance.password,
|
||||
'tokenName': instance.tokenName,
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/// Hand-rolled to be tolerant of the actual server payload: only [token] is
|
||||
/// load-bearing. `expiresAt` may be `null` (server-issued tokens without an
|
||||
/// explicit expiry); every other field shape is also tolerated so a stray
|
||||
/// rename on the backend does not break login for everyone.
|
||||
class LoginResponse {
|
||||
final String token;
|
||||
final String? tokenId;
|
||||
|
||||
/// `null` when the backend did not provide an expiry. In that case the
|
||||
/// token is treated as long-lived; callers should refresh on 401.
|
||||
final DateTime? expiresAt;
|
||||
final ConnectUserDto? user;
|
||||
|
||||
LoginResponse({
|
||||
required this.token,
|
||||
required this.tokenId,
|
||||
required this.expiresAt,
|
||||
required this.user,
|
||||
});
|
||||
|
||||
factory LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
final token = json['token'];
|
||||
if (token is! String || token.isEmpty) {
|
||||
throw const FormatException('login response missing "token" string');
|
||||
}
|
||||
final expiresRaw = json['expiresAt'];
|
||||
final expires = expiresRaw is String ? DateTime.tryParse(expiresRaw) : null;
|
||||
final userJson = json['user'];
|
||||
return LoginResponse(
|
||||
token: token,
|
||||
tokenId: json['tokenId']?.toString(),
|
||||
expiresAt: expires,
|
||||
user: userJson is Map<String, dynamic>
|
||||
? ConnectUserDto.fromJson(userJson)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectUserDto {
|
||||
final String? id;
|
||||
final String? username;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? userType;
|
||||
final String? className;
|
||||
|
||||
ConnectUserDto({
|
||||
this.id,
|
||||
this.username,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.userType,
|
||||
this.className,
|
||||
});
|
||||
|
||||
factory ConnectUserDto.fromJson(Map<String, dynamic> json) => ConnectUserDto(
|
||||
id: json['id']?.toString(),
|
||||
username: json['username']?.toString(),
|
||||
firstName: json['firstName']?.toString(),
|
||||
lastName: json['lastName']?.toString(),
|
||||
userType: json['userType']?.toString(),
|
||||
className: json['className']?.toString(),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user