Add new friends system, and sharing legs support
All checks were successful
Release / meta (push) Successful in 9s
Release / linux-build (push) Successful in 6m37s
Release / web-build (push) Successful in 5m29s
Release / android-build (push) Successful in 15m58s
Release / release-master (push) Successful in 20s
Release / release-dev (push) Successful in 26s
All checks were successful
Release / meta (push) Successful in 9s
Release / linux-build (push) Successful in 6m37s
Release / web-build (push) Successful in 5m29s
Release / android-build (push) Successful in 15m58s
Release / release-master (push) Successful in 20s
Release / release-dev (push) Successful in 26s
This commit is contained in:
@@ -164,6 +164,187 @@ class UserSummary extends UserData {
|
||||
);
|
||||
}
|
||||
|
||||
class Friendship {
|
||||
final String? id;
|
||||
final String status;
|
||||
final String requesterId;
|
||||
final String addresseeId;
|
||||
final UserSummary? requester;
|
||||
final UserSummary? addressee;
|
||||
final DateTime? requestedAt;
|
||||
final DateTime? respondedAt;
|
||||
|
||||
const Friendship({
|
||||
required this.id,
|
||||
required this.status,
|
||||
required this.requesterId,
|
||||
required this.addresseeId,
|
||||
this.requester,
|
||||
this.addressee,
|
||||
this.requestedAt,
|
||||
this.respondedAt,
|
||||
});
|
||||
|
||||
String get _statusLower => status.toLowerCase();
|
||||
bool get isNone => _statusLower == 'none';
|
||||
bool get isPending => _statusLower == 'pending';
|
||||
bool get isAccepted => _statusLower == 'accepted';
|
||||
bool get isBlocked => _statusLower == 'blocked';
|
||||
bool get isDeclined =>
|
||||
_statusLower == 'declined' || _statusLower == 'rejected';
|
||||
|
||||
factory Friendship.fromJson(Map<String, dynamic> json) {
|
||||
DateTime? parseDate(dynamic value) {
|
||||
if (value is DateTime) return value;
|
||||
return DateTime.tryParse(value?.toString() ?? '');
|
||||
}
|
||||
|
||||
String pickId(Map<String, dynamic> map, List<String> keys) {
|
||||
for (final key in keys) {
|
||||
final value = map[key];
|
||||
if (value == null) continue;
|
||||
final str = value.toString();
|
||||
if (str.isNotEmpty) return str;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
UserSummary? parseUser(dynamic raw, {String? usernameKey, String? fullKey, String? idKey}) {
|
||||
if (raw is Map) {
|
||||
return UserSummary.fromJson(
|
||||
raw.map((key, value) => MapEntry(key.toString(), value)),
|
||||
);
|
||||
}
|
||||
final username = usernameKey != null ? _asString(json[usernameKey]) : '';
|
||||
final fullName = fullKey != null ? _asString(json[fullKey]) : '';
|
||||
final id = idKey != null ? _asString(json[idKey]) : '';
|
||||
if (username.isEmpty && fullName.isEmpty && id.isEmpty) return null;
|
||||
return UserSummary(
|
||||
username: username,
|
||||
fullName: fullName,
|
||||
userId: id,
|
||||
email: '',
|
||||
);
|
||||
}
|
||||
|
||||
final requesterJson = json['requester'] ??
|
||||
json['requestor'] ??
|
||||
json['sender'] ??
|
||||
json['from_user'];
|
||||
final addresseeJson = json['addressee'] ??
|
||||
json['receiver'] ??
|
||||
json['target_user'] ??
|
||||
json['to_user'];
|
||||
|
||||
final requester = parseUser(
|
||||
requesterJson,
|
||||
usernameKey: 'requester_username',
|
||||
fullKey: 'requester_full_name',
|
||||
idKey: 'requester_id',
|
||||
);
|
||||
final addressee = parseUser(
|
||||
addresseeJson,
|
||||
usernameKey: 'addressee_username',
|
||||
fullKey: 'addressee_full_name',
|
||||
idKey: 'addressee_id',
|
||||
);
|
||||
|
||||
final requesterUsername = _asString(json['requester_username']);
|
||||
final requesterFullName = _asString(json['requester_full_name']);
|
||||
final addresseeUsername = _asString(json['addressee_username']);
|
||||
final addresseeFullName = _asString(json['addressee_full_name']);
|
||||
|
||||
final requesterId = requester?.userId.isNotEmpty == true
|
||||
? requester!.userId
|
||||
: pickId(json, [
|
||||
'requester_id',
|
||||
'requestor_id',
|
||||
'requestorId',
|
||||
'sender_id',
|
||||
'from_user_id',
|
||||
'from_id',
|
||||
'user_id',
|
||||
]);
|
||||
final addresseeId = addressee?.userId.isNotEmpty == true
|
||||
? addressee!.userId
|
||||
: pickId(json, [
|
||||
'addressee_id',
|
||||
'addresseeId',
|
||||
'receiver_id',
|
||||
'target_user_id',
|
||||
'to_user_id',
|
||||
'to_id',
|
||||
]);
|
||||
|
||||
final normalizedStatus = _asString(json['status'], 'none').trim();
|
||||
|
||||
return Friendship(
|
||||
id: pickId(json, ['friendship_id', 'friendshipId', 'id']),
|
||||
status: normalizedStatus.isNotEmpty ? normalizedStatus : 'none',
|
||||
requesterId: requesterId,
|
||||
addresseeId: addresseeId,
|
||||
requester: requester ??
|
||||
(requesterUsername.isNotEmpty || requesterFullName.isNotEmpty
|
||||
? UserSummary(
|
||||
username: requesterUsername,
|
||||
fullName: requesterFullName,
|
||||
userId: requesterId,
|
||||
email: '',
|
||||
)
|
||||
: null),
|
||||
addressee: addressee ??
|
||||
(addresseeUsername.isNotEmpty || addresseeFullName.isNotEmpty
|
||||
? UserSummary(
|
||||
username: addresseeUsername,
|
||||
fullName: addresseeFullName,
|
||||
userId: addresseeId,
|
||||
email: '',
|
||||
)
|
||||
: null),
|
||||
requestedAt: parseDate(json['requested_at'] ?? json['requestedAt']),
|
||||
respondedAt: parseDate(json['responded_at'] ?? json['respondedAt']),
|
||||
);
|
||||
}
|
||||
|
||||
factory Friendship.fromStatusJson(
|
||||
Map<String, dynamic> json, {
|
||||
String? targetUserId,
|
||||
}) {
|
||||
final statusVal = _asString(json['status'], 'none').trim();
|
||||
if (statusVal.toLowerCase() == 'none') {
|
||||
return Friendship(
|
||||
id: null,
|
||||
status: 'none',
|
||||
requesterId: '',
|
||||
addresseeId: targetUserId ?? '',
|
||||
);
|
||||
}
|
||||
return Friendship.fromJson(json);
|
||||
}
|
||||
|
||||
Friendship copyWith({
|
||||
String? id,
|
||||
String? status,
|
||||
String? requesterId,
|
||||
String? addresseeId,
|
||||
UserSummary? requester,
|
||||
UserSummary? addressee,
|
||||
DateTime? requestedAt,
|
||||
DateTime? respondedAt,
|
||||
}) {
|
||||
return Friendship(
|
||||
id: id ?? this.id,
|
||||
status: status ?? this.status,
|
||||
requesterId: requesterId ?? this.requesterId,
|
||||
addresseeId: addresseeId ?? this.addresseeId,
|
||||
requester: requester ?? this.requester,
|
||||
addressee: addressee ?? this.addressee,
|
||||
requestedAt: requestedAt ?? this.requestedAt,
|
||||
respondedAt: respondedAt ?? this.respondedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomepageStats {
|
||||
final double totalMileage;
|
||||
final List<YearlyMileage> yearlyMileage;
|
||||
@@ -441,6 +622,7 @@ class Loco {
|
||||
final String type, number, locoClass;
|
||||
final String? name, operator, notes, evn;
|
||||
final bool powering;
|
||||
final int allocPos;
|
||||
|
||||
Loco({
|
||||
required this.id,
|
||||
@@ -452,6 +634,7 @@ class Loco {
|
||||
this.notes,
|
||||
this.evn,
|
||||
this.powering = true,
|
||||
this.allocPos = 0,
|
||||
});
|
||||
|
||||
factory Loco.fromJson(Map<String, dynamic> json) => Loco(
|
||||
@@ -464,6 +647,7 @@ class Loco {
|
||||
notes: json['notes'],
|
||||
evn: json['evn'],
|
||||
powering: _asBool(json['alloc_powering'] ?? json['powering'], true),
|
||||
allocPos: _asInt(json['alloc_pos'], 0),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -497,6 +681,7 @@ class LocoSummary extends Loco {
|
||||
this.location,
|
||||
Map<String, dynamic>? extra,
|
||||
super.powering = true,
|
||||
super.allocPos = 0,
|
||||
}) : extra = extra ?? const {},
|
||||
super(
|
||||
id: locoId,
|
||||
@@ -532,6 +717,7 @@ class LocoSummary extends Loco {
|
||||
location: json['location'],
|
||||
extra: Map<String, dynamic>.from(json),
|
||||
powering: _asBool(json['alloc_powering'] ?? json['powering'], true),
|
||||
allocPos: _asInt(json['alloc_pos'], 0),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -874,6 +1060,7 @@ class Leg {
|
||||
final String start, end, network, notes, headcode, user;
|
||||
final String origin, destination;
|
||||
final List<String> route;
|
||||
final String? legShareId;
|
||||
final DateTime beginTime;
|
||||
final DateTime? endTime;
|
||||
final DateTime? originTime;
|
||||
@@ -904,6 +1091,7 @@ class Leg {
|
||||
this.endDelayMinutes,
|
||||
this.origin = '',
|
||||
this.destination = '',
|
||||
this.legShareId,
|
||||
});
|
||||
|
||||
factory Leg.fromJson(Map<String, dynamic> json) {
|
||||
@@ -944,6 +1132,93 @@ class Leg {
|
||||
: _asInt(json['leg_end_delay']),
|
||||
origin: _asString(json['leg_origin']),
|
||||
destination: _asString(json['leg_destination']),
|
||||
legShareId: _asString(json['leg_share_id']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LegShareData {
|
||||
final String id;
|
||||
final Leg entry;
|
||||
final Map<String, dynamic> metadata;
|
||||
final UserSummary? requester;
|
||||
final UserSummary? addressee;
|
||||
final DateTime? sharedAt;
|
||||
final String status;
|
||||
final int? notificationId;
|
||||
|
||||
LegShareData({
|
||||
required this.id,
|
||||
required this.entry,
|
||||
required this.metadata,
|
||||
this.requester,
|
||||
this.addressee,
|
||||
this.sharedAt,
|
||||
this.status = 'pending',
|
||||
this.notificationId,
|
||||
});
|
||||
|
||||
LegShareData copyWith({int? notificationId}) {
|
||||
return LegShareData(
|
||||
id: id,
|
||||
entry: entry,
|
||||
metadata: metadata,
|
||||
requester: requester,
|
||||
addressee: addressee,
|
||||
sharedAt: sharedAt,
|
||||
status: status,
|
||||
notificationId: notificationId ?? this.notificationId,
|
||||
);
|
||||
}
|
||||
|
||||
String get sharedFromName =>
|
||||
requester?.displayName.isNotEmpty == true ? requester!.displayName : '';
|
||||
|
||||
factory LegShareData.fromJson(Map<String, dynamic> json) {
|
||||
final metadataRaw = json['metadata'];
|
||||
final entryRaw = json['entry'];
|
||||
final metadata = metadataRaw is Map
|
||||
? metadataRaw.map((k, v) => MapEntry(k.toString(), v))
|
||||
: <String, dynamic>{};
|
||||
final shareId = _asString(
|
||||
metadata['leg_share_id'] ?? metadata['share_id'] ?? json['leg_share_id'],
|
||||
);
|
||||
final requester = UserSummary.fromJson({
|
||||
"user_id": metadata['requester_id'] ?? metadata['from_user_id'] ?? '',
|
||||
"username": metadata['requester_username'] ?? '',
|
||||
"full_name": metadata['requester_full_name'] ?? '',
|
||||
"email": '',
|
||||
});
|
||||
final addressee = UserSummary.fromJson({
|
||||
"user_id": metadata['addressee_id'] ?? metadata['target_user_id'] ?? '',
|
||||
"username": metadata['addressee_username'] ?? '',
|
||||
"full_name": metadata['addressee_full_name'] ?? '',
|
||||
"email": '',
|
||||
});
|
||||
DateTime? sharedAt;
|
||||
for (final key in ['requested_at', 'created_at', 'shared_at']) {
|
||||
final value = metadata[key];
|
||||
if (value != null) {
|
||||
sharedAt = DateTime.tryParse(value.toString());
|
||||
if (sharedAt != null) break;
|
||||
}
|
||||
}
|
||||
final entryMap = entryRaw is Map
|
||||
? entryRaw.map((k, v) => MapEntry(k.toString(), v))
|
||||
: json.map((k, v) => MapEntry(k.toString(), v));
|
||||
final entryObj = Leg.fromJson(entryMap);
|
||||
final resolvedId = shareId.isNotEmpty
|
||||
? shareId
|
||||
: entryObj.legShareId ?? _asString(json['leg_share_id']);
|
||||
|
||||
return LegShareData(
|
||||
id: resolvedId,
|
||||
entry: entryObj,
|
||||
metadata: metadata,
|
||||
requester: requester.userId.isEmpty ? null : requester,
|
||||
addressee: addressee.userId.isEmpty ? null : addressee,
|
||||
sharedAt: sharedAt,
|
||||
status: _asString(metadata['status'], 'pending'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1223,6 +1498,7 @@ class UserNotification {
|
||||
final String title;
|
||||
final String body;
|
||||
final String channel;
|
||||
final String type;
|
||||
final DateTime? createdAt;
|
||||
final bool dismissed;
|
||||
|
||||
@@ -1231,6 +1507,7 @@ class UserNotification {
|
||||
required this.title,
|
||||
required this.body,
|
||||
required this.channel,
|
||||
required this.type,
|
||||
required this.createdAt,
|
||||
required this.dismissed,
|
||||
});
|
||||
@@ -1248,6 +1525,7 @@ class UserNotification {
|
||||
title: _asString(json['title']),
|
||||
body: _asString(json['body']),
|
||||
channel: _asString(json['channel']),
|
||||
type: _asString(json['type'] ?? json['notification_type']),
|
||||
createdAt: createdAt,
|
||||
dismissed: _asBool(json['dismissed'] ?? false, false),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user