at main 6.1 kB view raw
1// Community data models for Coves 2// 3// These models match the backend API structure from: 4// GET /xrpc/social.coves.community.list 5// POST /xrpc/social.coves.community.post.create 6 7/// Response from GET /xrpc/social.coves.community.list 8class CommunitiesResponse { 9 CommunitiesResponse({required this.communities, this.cursor}); 10 11 factory CommunitiesResponse.fromJson(Map<String, dynamic> json) { 12 // Handle null communities array from backend 13 final communitiesData = json['communities']; 14 final List<CommunityView> communitiesList; 15 16 if (communitiesData == null) { 17 // Backend returned null, use empty list 18 communitiesList = []; 19 } else { 20 // Parse community items 21 communitiesList = (communitiesData as List<dynamic>) 22 .map( 23 (item) => CommunityView.fromJson(item as Map<String, dynamic>), 24 ) 25 .toList(); 26 } 27 28 return CommunitiesResponse( 29 communities: communitiesList, 30 cursor: json['cursor'] as String?, 31 ); 32 } 33 34 final List<CommunityView> communities; 35 final String? cursor; 36} 37 38/// Full community view data 39class CommunityView { 40 CommunityView({ 41 required this.did, 42 required this.name, 43 this.handle, 44 this.displayName, 45 this.description, 46 this.avatar, 47 this.visibility, 48 this.subscriberCount, 49 this.memberCount, 50 this.postCount, 51 this.viewer, 52 }); 53 54 factory CommunityView.fromJson(Map<String, dynamic> json) { 55 return CommunityView( 56 did: json['did'] as String, 57 name: json['name'] as String, 58 handle: json['handle'] as String?, 59 displayName: json['displayName'] as String?, 60 description: json['description'] as String?, 61 avatar: json['avatar'] as String?, 62 visibility: json['visibility'] as String?, 63 subscriberCount: json['subscriberCount'] as int?, 64 memberCount: json['memberCount'] as int?, 65 postCount: json['postCount'] as int?, 66 viewer: json['viewer'] != null 67 ? CommunityViewerState.fromJson( 68 json['viewer'] as Map<String, dynamic>, 69 ) 70 : null, 71 ); 72 } 73 74 /// Community DID (decentralized identifier) 75 final String did; 76 77 /// Community name (unique identifier) 78 final String name; 79 80 /// Community handle 81 final String? handle; 82 83 /// Display name for UI 84 final String? displayName; 85 86 /// Community description 87 final String? description; 88 89 /// Avatar URL 90 final String? avatar; 91 92 /// Visibility setting (e.g., "public", "private") 93 final String? visibility; 94 95 /// Number of subscribers 96 final int? subscriberCount; 97 98 /// Number of members 99 final int? memberCount; 100 101 /// Number of posts 102 final int? postCount; 103 104 /// Current user's relationship with this community 105 final CommunityViewerState? viewer; 106} 107 108/// Current user's relationship with a community 109class CommunityViewerState { 110 CommunityViewerState({this.subscribed, this.member}); 111 112 factory CommunityViewerState.fromJson(Map<String, dynamic> json) { 113 return CommunityViewerState( 114 subscribed: json['subscribed'] as bool?, 115 member: json['member'] as bool?, 116 ); 117 } 118 119 /// Whether the user is subscribed to this community 120 final bool? subscribed; 121 122 /// Whether the user is a member of this community 123 final bool? member; 124} 125 126/// Request body for POST /xrpc/social.coves.community.post.create 127class CreatePostRequest { 128 CreatePostRequest({ 129 required this.community, 130 this.title, 131 this.content, 132 this.embed, 133 this.langs, 134 this.labels, 135 }); 136 137 Map<String, dynamic> toJson() { 138 final json = <String, dynamic>{ 139 'community': community, 140 }; 141 142 if (title != null) { 143 json['title'] = title; 144 } 145 if (content != null) { 146 json['content'] = content; 147 } 148 if (embed != null) { 149 json['embed'] = embed!.toJson(); 150 } 151 if (langs != null && langs!.isNotEmpty) { 152 json['langs'] = langs; 153 } 154 if (labels != null) { 155 json['labels'] = labels!.toJson(); 156 } 157 158 return json; 159 } 160 161 /// Community DID or handle 162 final String community; 163 164 /// Post title 165 final String? title; 166 167 /// Post content/text 168 final String? content; 169 170 /// External link embed 171 final ExternalEmbedInput? embed; 172 173 /// Language codes (e.g., ["en", "es"]) 174 final List<String>? langs; 175 176 /// Self-applied content labels 177 final SelfLabels? labels; 178} 179 180/// Response from POST /xrpc/social.coves.community.post.create 181class CreatePostResponse { 182 const CreatePostResponse({required this.uri, required this.cid}); 183 184 factory CreatePostResponse.fromJson(Map<String, dynamic> json) { 185 return CreatePostResponse( 186 uri: json['uri'] as String, 187 cid: json['cid'] as String, 188 ); 189 } 190 191 /// AT-URI of the created post 192 final String uri; 193 194 /// Content identifier (CID) of the created post 195 final String cid; 196} 197 198/// External link embed input for creating posts 199class ExternalEmbedInput { 200 const ExternalEmbedInput({ 201 required this.uri, 202 this.title, 203 this.description, 204 this.thumb, 205 }); 206 207 Map<String, dynamic> toJson() { 208 final json = <String, dynamic>{ 209 'uri': uri, 210 }; 211 212 if (title != null) { 213 json['title'] = title; 214 } 215 if (description != null) { 216 json['description'] = description; 217 } 218 if (thumb != null) { 219 json['thumb'] = thumb; 220 } 221 222 return json; 223 } 224 225 /// URL of the external link 226 final String uri; 227 228 /// Title of the linked content 229 final String? title; 230 231 /// Description of the linked content 232 final String? description; 233 234 /// Thumbnail URL 235 final String? thumb; 236} 237 238/// Self-applied content labels 239class SelfLabels { 240 const SelfLabels({required this.values}); 241 242 Map<String, dynamic> toJson() { 243 return { 244 'values': values.map((label) => label.toJson()).toList(), 245 }; 246 } 247 248 /// List of self-applied labels 249 final List<SelfLabel> values; 250} 251 252/// Individual self-applied label 253class SelfLabel { 254 const SelfLabel({required this.val}); 255 256 Map<String, dynamic> toJson() { 257 return { 258 'val': val, 259 }; 260 } 261 262 /// Label value (e.g., "nsfw", "spoiler") 263 final String val; 264}