1// Comment data models for Coves 2// 3// These models match the backend response structure from: 4// /xrpc/social.coves.community.comment.getComments 5 6import 'post.dart'; 7 8class CommentsResponse { 9 CommentsResponse({required this.post, this.cursor, required this.comments}); 10 11 factory CommentsResponse.fromJson(Map<String, dynamic> json) { 12 // Handle null comments array from backend 13 final commentsData = json['comments']; 14 final List<ThreadViewComment> commentsList; 15 16 if (commentsData == null) { 17 // Backend returned null, use empty list 18 commentsList = []; 19 } else { 20 // Parse comment items 21 commentsList = 22 (commentsData as List<dynamic>) 23 .map( 24 (item) => 25 ThreadViewComment.fromJson(item as Map<String, dynamic>), 26 ) 27 .toList(); 28 } 29 30 return CommentsResponse( 31 post: json['post'], 32 cursor: json['cursor'] as String?, 33 comments: commentsList, 34 ); 35 } 36 37 final dynamic post; 38 final String? cursor; 39 final List<ThreadViewComment> comments; 40} 41 42class ThreadViewComment { 43 ThreadViewComment({ 44 required this.comment, 45 this.replies, 46 this.hasMore = false, 47 }); 48 49 factory ThreadViewComment.fromJson(Map<String, dynamic> json) { 50 return ThreadViewComment( 51 comment: CommentView.fromJson(json['comment'] as Map<String, dynamic>), 52 replies: 53 json['replies'] != null 54 ? (json['replies'] as List<dynamic>) 55 .map( 56 (item) => ThreadViewComment.fromJson( 57 item as Map<String, dynamic>, 58 ), 59 ) 60 .toList() 61 : null, 62 hasMore: json['hasMore'] as bool? ?? false, 63 ); 64 } 65 66 final CommentView comment; 67 final List<ThreadViewComment>? replies; 68 final bool hasMore; 69} 70 71class CommentView { 72 CommentView({ 73 required this.uri, 74 required this.cid, 75 required this.content, 76 this.contentFacets, 77 required this.createdAt, 78 required this.indexedAt, 79 required this.author, 80 required this.post, 81 this.parent, 82 required this.stats, 83 this.viewer, 84 this.embed, 85 }); 86 87 factory CommentView.fromJson(Map<String, dynamic> json) { 88 return CommentView( 89 uri: json['uri'] as String, 90 cid: json['cid'] as String, 91 content: json['content'] as String, 92 contentFacets: 93 json['contentFacets'] != null 94 ? (json['contentFacets'] as List<dynamic>) 95 .map((f) => PostFacet.fromJson(f as Map<String, dynamic>)) 96 .toList() 97 : null, 98 createdAt: DateTime.parse(json['createdAt'] as String), 99 indexedAt: DateTime.parse(json['indexedAt'] as String), 100 author: AuthorView.fromJson(json['author'] as Map<String, dynamic>), 101 post: CommentRef.fromJson(json['post'] as Map<String, dynamic>), 102 parent: 103 json['parent'] != null 104 ? CommentRef.fromJson(json['parent'] as Map<String, dynamic>) 105 : null, 106 stats: CommentStats.fromJson(json['stats'] as Map<String, dynamic>), 107 viewer: 108 json['viewer'] != null 109 ? CommentViewerState.fromJson( 110 json['viewer'] as Map<String, dynamic>, 111 ) 112 : null, 113 embed: json['embed'], 114 ); 115 } 116 117 final String uri; 118 final String cid; 119 final String content; 120 final List<PostFacet>? contentFacets; 121 final DateTime createdAt; 122 final DateTime indexedAt; 123 final AuthorView author; 124 final CommentRef post; 125 final CommentRef? parent; 126 final CommentStats stats; 127 final CommentViewerState? viewer; 128 final dynamic embed; 129} 130 131class CommentRef { 132 CommentRef({required this.uri, required this.cid}); 133 134 factory CommentRef.fromJson(Map<String, dynamic> json) { 135 return CommentRef(uri: json['uri'] as String, cid: json['cid'] as String); 136 } 137 138 final String uri; 139 final String cid; 140} 141 142class CommentStats { 143 CommentStats({this.upvotes = 0, this.downvotes = 0, this.score = 0}); 144 145 factory CommentStats.fromJson(Map<String, dynamic> json) { 146 return CommentStats( 147 upvotes: json['upvotes'] as int? ?? 0, 148 downvotes: json['downvotes'] as int? ?? 0, 149 score: json['score'] as int? ?? 0, 150 ); 151 } 152 153 final int upvotes; 154 final int downvotes; 155 final int score; 156} 157 158class CommentViewerState { 159 CommentViewerState({this.vote, this.voteUri}); 160 161 factory CommentViewerState.fromJson(Map<String, dynamic> json) { 162 return CommentViewerState( 163 vote: json['vote'] as String?, 164 voteUri: json['voteUri'] as String?, 165 ); 166 } 167 168 /// Vote direction: "up", "down", or null if not voted 169 final String? vote; 170 171 /// AT-URI of the vote record (if backend provides it) 172 final String? voteUri; 173}