import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/foundation.dart'; import 'club_models.dart'; DateTime? _parseDateTime(dynamic value) { if (value == null) return null; if (value is Timestamp) return value.toDate(); if (value is DateTime) return value; if (value is String) return DateTime.tryParse(value); if (value is int) return DateTime.fromMillisecondsSinceEpoch(value); return null; } dynamic _dateTimeToFirestore(DateTime? dt) { if (dt == null) return null; return Timestamp.fromDate(dt); } /// Comment on a Club Wall post. @immutable class ClubWallComment { final String id; final String postId; final String authorUid; final String authorDisplayName; final String? authorHandle; final String? authorAvatarUrl; final String? authorDisplayRank; final ClubRole authorRole; final String content; final DateTime createdAt; const ClubWallComment({ required this.id, required this.postId, required this.authorUid, required this.authorDisplayName, this.authorHandle, this.authorAvatarUrl, this.authorDisplayRank, this.authorRole = ClubRole.member, required this.content, required this.createdAt, }); Map toJson() => { 'id': id, 'postId': postId, 'authorUid': authorUid, 'authorDisplayName': authorDisplayName, if (authorHandle != null) 'authorHandle': authorHandle, if (authorAvatarUrl != null) 'authorAvatarUrl': authorAvatarUrl, if (authorDisplayRank != null) 'authorDisplayRank': authorDisplayRank, 'authorRole': authorRole.name, 'content': content, 'createdAt': createdAt.toIso8601String(), }; factory ClubWallComment.fromJson(Map json) { return ClubWallComment( id: json['id'] as String? ?? '', postId: json['postId'] as String? ?? '', authorUid: json['authorUid'] as String? ?? '', authorDisplayName: json['authorDisplayName'] as String? ?? 'Rider', authorHandle: json['authorHandle'] as String?, authorAvatarUrl: json['authorAvatarUrl'] as String?, authorDisplayRank: json['authorDisplayRank'] as String?, authorRole: ClubRole.fromString(json['authorRole'] as String?), content: json['content'] as String? ?? '', createdAt: _parseDateTime(json['createdAt']) ?? DateTime.now(), ); } } /// Post on a Club Wall discussion board. @immutable class ClubWallPost { final String id; final String clubId; final String authorUid; final String authorDisplayName; final String? authorHandle; final String? authorAvatarUrl; final String? authorDisplayRank; final ClubRole authorRole; final String content; final DateTime createdAt; final bool isPinned; final int fistBumpCount; final List fistBumpUserIds; final List comments; const ClubWallPost({ required this.id, required this.clubId, required this.authorUid, required this.authorDisplayName, this.authorHandle, this.authorAvatarUrl, this.authorDisplayRank, this.authorRole = ClubRole.member, required this.content, required this.createdAt, this.isPinned = false, this.fistBumpCount = 0, this.fistBumpUserIds = const [], this.comments = const [], }); bool isFistBumpedBy(String? uid) { if (uid == null) return false; return fistBumpUserIds.contains(uid); } ClubWallPost copyWith({ String? id, String? clubId, String? authorUid, String? authorDisplayName, String? authorHandle, String? authorAvatarUrl, String? authorDisplayRank, ClubRole? authorRole, String? content, DateTime? createdAt, bool? isPinned, int? fistBumpCount, List? fistBumpUserIds, List? comments, }) { return ClubWallPost( id: id ?? this.id, clubId: clubId ?? this.clubId, authorUid: authorUid ?? this.authorUid, authorDisplayName: authorDisplayName ?? this.authorDisplayName, authorHandle: authorHandle ?? this.authorHandle, authorAvatarUrl: authorAvatarUrl ?? this.authorAvatarUrl, authorDisplayRank: authorDisplayRank ?? this.authorDisplayRank, authorRole: authorRole ?? this.authorRole, content: content ?? this.content, createdAt: createdAt ?? this.createdAt, isPinned: isPinned ?? this.isPinned, fistBumpCount: fistBumpCount ?? this.fistBumpCount, fistBumpUserIds: fistBumpUserIds ?? this.fistBumpUserIds, comments: comments ?? this.comments, ); } Map toFirestore() => { 'id': id, 'clubId': clubId, 'authorUid': authorUid, 'authorDisplayName': authorDisplayName, if (authorHandle != null) 'authorHandle': authorHandle, if (authorAvatarUrl != null) 'authorAvatarUrl': authorAvatarUrl, if (authorDisplayRank != null) 'authorDisplayRank': authorDisplayRank, 'authorRole': authorRole.name, 'content': content, 'createdAt': _dateTimeToFirestore(createdAt), 'isPinned': isPinned, 'fistBumpCount': fistBumpCount, 'fistBumpUserIds': fistBumpUserIds, 'comments': comments.map((c) => c.toJson()).toList(), }; Map toJson() => { 'id': id, 'clubId': clubId, 'authorUid': authorUid, 'authorDisplayName': authorDisplayName, if (authorHandle != null) 'authorHandle': authorHandle, if (authorAvatarUrl != null) 'authorAvatarUrl': authorAvatarUrl, if (authorDisplayRank != null) 'authorDisplayRank': authorDisplayRank, 'authorRole': authorRole.name, 'content': content, 'createdAt': createdAt.toIso8601String(), 'isPinned': isPinned, 'fistBumpCount': fistBumpCount, 'fistBumpUserIds': fistBumpUserIds, 'comments': comments.map((c) => c.toJson()).toList(), }; factory ClubWallPost.fromFirestore(DocumentSnapshot doc) { final data = doc.data() as Map? ?? {}; return ClubWallPost.fromJson(data, idOverride: doc.id); } factory ClubWallPost.fromJson(Map json, {String? idOverride}) { final rawComments = json['comments'] as List? ?? []; final commentsList = rawComments .map((c) => ClubWallComment.fromJson(Map.from(c as Map))) .toList(); final rawFistBumps = json['fistBumpUserIds'] as List? ?? []; final fistBumpList = rawFistBumps.map((e) => e.toString()).toList(); return ClubWallPost( id: idOverride ?? json['id'] as String? ?? '', clubId: json['clubId'] as String? ?? '', authorUid: json['authorUid'] as String? ?? '', authorDisplayName: json['authorDisplayName'] as String? ?? 'Rider', authorHandle: json['authorHandle'] as String?, authorAvatarUrl: json['authorAvatarUrl'] as String?, authorDisplayRank: json['authorDisplayRank'] as String?, authorRole: ClubRole.fromString(json['authorRole'] as String?), content: json['content'] as String? ?? '', createdAt: _parseDateTime(json['createdAt']) ?? DateTime.now(), isPinned: json['isPinned'] as bool? ?? false, fistBumpCount: (json['fistBumpCount'] as num?)?.toInt() ?? fistBumpList.length, fistBumpUserIds: fistBumpList, comments: commentsList, ); } @override bool operator ==(Object other) => identical(this, other) || other is ClubWallPost && runtimeType == other.runtimeType && id == other.id && clubId == other.clubId && content == other.content && isPinned == other.isPinned && fistBumpCount == other.fistBumpCount; @override int get hashCode => id.hashCode ^ clubId.hashCode ^ content.hashCode ^ isPinned.hashCode ^ fistBumpCount.hashCode; }