MotorSignal-App/lib/services/social/rider_profile_models.dart

414 lines
12 KiB
Dart

import 'package:flutter/foundation.dart';
/// Preset avatar options available for all riders without external image hosting.
class PresetAvatar {
final String id;
final String label;
final int primaryColorHex;
final int secondaryColorHex;
const PresetAvatar({
required this.id,
required this.label,
required this.primaryColorHex,
required this.secondaryColorHex,
});
static const List<PresetAvatar> availablePresets = [
PresetAvatar(
id: 'helmet_cyan',
label: 'Cyan Apex',
primaryColorHex: 0xFF00E5FF,
secondaryColorHex: 0xFF003840,
),
PresetAvatar(
id: 'helmet_amber',
label: 'Amber Sector',
primaryColorHex: 0xFFFFB300,
secondaryColorHex: 0xFF4A3200,
),
PresetAvatar(
id: 'tach_neon',
label: 'Neon Tach',
primaryColorHex: 0xFF00E676,
secondaryColorHex: 0xFF00381B,
),
PresetAvatar(
id: 'speed_violet',
label: 'Violet Velocity',
primaryColorHex: 0xFFD500F9,
secondaryColorHex: 0xFF3E004A,
),
PresetAvatar(
id: 'cruiser_red',
label: 'Crimson Cruiser',
primaryColorHex: 0xFFFF1744,
secondaryColorHex: 0xFF4A0513,
),
PresetAvatar(
id: 'adv_blue',
label: 'Adventure Azure',
primaryColorHex: 0xFF2979FF,
secondaryColorHex: 0xFF0A224A,
),
PresetAvatar(
id: 'cafe_gold',
label: 'Cafe Gold',
primaryColorHex: 0xFFFFD700,
secondaryColorHex: 0xFF4A3E00,
),
PresetAvatar(
id: 'touring_slate',
label: 'Touring Slate',
primaryColorHex: 0xFF90A4AE,
secondaryColorHex: 0xFF263238,
),
];
static PresetAvatar byId(String? id) {
if (id == null) return availablePresets.first;
return availablePresets.firstWhere(
(p) => p.id == id,
orElse: () => availablePresets.first,
);
}
}
/// Privacy controls for a rider profile.
@immutable
class ProfilePrivacySettings {
final bool isSearchableByHandle;
final bool isProfilePublic;
final bool allowFriendRequests;
const ProfilePrivacySettings({
this.isSearchableByHandle = true,
this.isProfilePublic = true,
this.allowFriendRequests = true,
});
Map<String, dynamic> toJson() => {
'isSearchableByHandle': isSearchableByHandle,
'isProfilePublic': isProfilePublic,
'allowFriendRequests': allowFriendRequests,
};
factory ProfilePrivacySettings.fromJson(Map<String, dynamic>? json) {
if (json == null) return const ProfilePrivacySettings();
return ProfilePrivacySettings(
isSearchableByHandle: json['isSearchableByHandle'] as bool? ?? true,
isProfilePublic: json['isProfilePublic'] as bool? ?? true,
allowFriendRequests: json['allowFriendRequests'] as bool? ?? true,
);
}
ProfilePrivacySettings copyWith({
bool? isSearchableByHandle,
bool? isProfilePublic,
bool? allowFriendRequests,
}) {
return ProfilePrivacySettings(
isSearchableByHandle: isSearchableByHandle ?? this.isSearchableByHandle,
isProfilePublic: isProfilePublic ?? this.isProfilePublic,
allowFriendRequests: allowFriendRequests ?? this.allowFriendRequests,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ProfilePrivacySettings &&
runtimeType == other.runtimeType &&
isSearchableByHandle == other.isSearchableByHandle &&
isProfilePublic == other.isProfilePublic &&
allowFriendRequests == other.allowFriendRequests;
@override
int get hashCode =>
isSearchableByHandle.hashCode ^
isProfilePublic.hashCode ^
allowFriendRequests.hashCode;
}
/// Public rider profile decoupled from authentication credentials and cloud ride ownership.
@immutable
class RiderProfile {
final String uid;
final String handle;
final String displayName;
final String bio;
final String region;
final String? avatarPreset;
final String? avatarUrl;
final ProfilePrivacySettings privacySettings;
final DateTime createdAt;
final DateTime updatedAt;
const RiderProfile({
required this.uid,
required this.handle,
required this.displayName,
this.bio = '',
this.region = '',
this.avatarPreset,
this.avatarUrl,
this.privacySettings = const ProfilePrivacySettings(),
required this.createdAt,
required this.updatedAt,
});
String get displayHandle => '@$handle';
PresetAvatar get preset => PresetAvatar.byId(avatarPreset);
Map<String, dynamic> toJson() => {
'id': uid,
'username': handle,
'displayName': displayName,
'bio': bio,
'location': region,
'profilePhotoUrl': avatarUrl,
'avatarPreset': avatarPreset,
'privacySettings': privacySettings.toJson(),
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
factory RiderProfile.fromJson(Map<String, dynamic> json) {
final created = json['createdAt'] != null
? DateTime.tryParse(json['createdAt'] as String) ?? DateTime.now()
: DateTime.now();
final updated = json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'] as String) ?? created
: created;
return RiderProfile(
uid: json['id'] as String? ?? '',
handle: (json['username'] as String? ?? '').toLowerCase().trim(),
displayName: json['displayName'] as String? ?? '',
bio: json['bio'] as String? ?? '',
region: json['location'] as String? ?? '',
avatarPreset: json['avatarPreset'] as String?,
avatarUrl: json['profilePhotoUrl'] as String?,
privacySettings: ProfilePrivacySettings.fromJson(
json['privacySettings'] as Map<String, dynamic>?,
),
createdAt: created,
updatedAt: updated,
);
}
RiderProfile copyWith({
String? uid,
String? handle,
String? displayName,
String? bio,
String? region,
String? avatarPreset,
String? avatarUrl,
ProfilePrivacySettings? privacySettings,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return RiderProfile(
uid: uid ?? this.uid,
handle: handle ?? this.handle,
displayName: displayName ?? this.displayName,
bio: bio ?? this.bio,
region: region ?? this.region,
avatarPreset: avatarPreset ?? this.avatarPreset,
avatarUrl: avatarUrl ?? this.avatarUrl,
privacySettings: privacySettings ?? this.privacySettings,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RiderProfile &&
runtimeType == other.runtimeType &&
uid == other.uid &&
handle == other.handle &&
displayName == other.displayName &&
bio == other.bio &&
region == other.region &&
avatarPreset == other.avatarPreset &&
avatarUrl == other.avatarUrl &&
privacySettings == other.privacySettings;
@override
int get hashCode =>
uid.hashCode ^
handle.hashCode ^
displayName.hashCode ^
bio.hashCode ^
region.hashCode ^
avatarPreset.hashCode ^
avatarUrl.hashCode ^
privacySettings.hashCode;
}
/// Relationship status between the active rider and another rider.
enum FriendshipStatus {
self,
none,
friend,
requestIncoming,
requestOutgoing,
blocked,
}
/// Mutual friend summary.
@immutable
class FriendSummary {
final String uid;
final String handle;
final String displayName;
final String? avatarPreset;
final String? avatarUrl;
final DateTime friendedAt;
const FriendSummary({
required this.uid,
required this.handle,
required this.displayName,
this.avatarPreset,
this.avatarUrl,
required this.friendedAt,
});
String get displayHandle => '@$handle';
Map<String, dynamic> toJson() => {
'uid': uid,
'handle': handle,
'displayName': displayName,
'avatarPreset': avatarPreset,
'avatarUrl': avatarUrl,
'friendedAt': friendedAt.toIso8601String(),
};
factory FriendSummary.fromJson(Map<String, dynamic> json) => FriendSummary(
uid: json['uid'] as String? ?? '',
handle: json['handle'] as String? ?? '',
displayName: json['displayName'] as String? ?? '',
avatarPreset: json['avatarPreset'] as String?,
avatarUrl: json['avatarUrl'] as String?,
friendedAt: json['friendedAt'] != null
? DateTime.tryParse(json['friendedAt'] as String) ?? DateTime.now()
: DateTime.now(),
);
}
/// Incoming or outgoing friend request.
@immutable
class FriendRequest {
final String requestId;
final String senderUid;
final String senderHandle;
final String senderDisplayName;
final String? senderAvatarPreset;
final String? senderAvatarUrl;
final String receiverUid;
final DateTime sentAt;
const FriendRequest({
required this.requestId,
required this.senderUid,
required this.senderHandle,
required this.senderDisplayName,
this.senderAvatarPreset,
this.senderAvatarUrl,
required this.receiverUid,
required this.sentAt,
});
Map<String, dynamic> toJson() => {
'requestId': requestId,
'senderUid': senderUid,
'senderHandle': senderHandle,
'senderDisplayName': senderDisplayName,
'senderAvatarPreset': senderAvatarPreset,
'senderAvatarUrl': senderAvatarUrl,
'receiverUid': receiverUid,
'sentAt': sentAt.toIso8601String(),
};
factory FriendRequest.fromJson(Map<String, dynamic> json) => FriendRequest(
requestId: json['requestId'] as String? ?? '',
senderUid: json['senderUid'] as String? ?? '',
senderHandle: json['senderHandle'] as String? ?? '',
senderDisplayName: json['senderDisplayName'] as String? ?? '',
senderAvatarPreset: json['senderAvatarPreset'] as String?,
senderAvatarUrl: json['senderAvatarUrl'] as String?,
receiverUid: json['receiverUid'] as String? ?? '',
sentAt: json['sentAt'] != null
? DateTime.tryParse(json['sentAt'] as String) ?? DateTime.now()
: DateTime.now(),
);
}
/// Blocked rider record.
@immutable
class BlockedRider {
final String blockedUid;
final String? blockedHandle;
final String? blockedDisplayName;
final DateTime blockedAt;
const BlockedRider({
required this.blockedUid,
this.blockedHandle,
this.blockedDisplayName,
required this.blockedAt,
});
Map<String, dynamic> toJson() => {
'blockedUid': blockedUid,
'blockedHandle': blockedHandle,
'blockedDisplayName': blockedDisplayName,
'blockedAt': blockedAt.toIso8601String(),
};
factory BlockedRider.fromJson(Map<String, dynamic> json) => BlockedRider(
blockedUid: json['blockedUid'] as String? ?? '',
blockedHandle: json['blockedHandle'] as String?,
blockedDisplayName: json['blockedDisplayName'] as String?,
blockedAt: json['blockedAt'] != null
? DateTime.tryParse(json['blockedAt'] as String) ?? DateTime.now()
: DateTime.now(),
);
}
/// Search result summary for public rider lookup.
@immutable
class RiderSearchResult {
final String uid;
final String handle;
final String displayName;
final String bio;
final String region;
final String? avatarPreset;
final String? avatarUrl;
final bool isProfilePublic;
final bool allowFriendRequests;
final FriendshipStatus relationship;
const RiderSearchResult({
required this.uid,
required this.handle,
required this.displayName,
this.bio = '',
this.region = '',
this.avatarPreset,
this.avatarUrl,
required this.isProfilePublic,
required this.allowFriendRequests,
required this.relationship,
});
String get displayHandle => '@$handle';
}