Compare commits
	
		
			4 Commits
		
	
	
		
			89fd80bcb8
			...
			3.0.0+106
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 91c5a2e1b6 | |||
| cb991d1574 | |||
| 75097ab6fc | |||
| 8d855867c1 | 
| @@ -100,6 +100,11 @@ | ||||
|   "permissionModerator": "Moderator", | ||||
|   "permissionMember": "Member", | ||||
|   "reply": "Reply", | ||||
|   "repliesCount": { | ||||
|     "zero": "No reply", | ||||
|     "one": "{} reply", | ||||
|     "other": "{} replies" | ||||
|   }, | ||||
|   "forward": "Forward", | ||||
|   "repliedTo": "Replied to", | ||||
|   "forwarded": "Forwarded", | ||||
|   | ||||
| @@ -22,6 +22,7 @@ sealed class SnPost with _$SnPost { | ||||
|     required int viewsTotal, | ||||
|     required int upvotes, | ||||
|     required int downvotes, | ||||
|     required int repliesCount, | ||||
|     required String? threadedPostId, | ||||
|     required SnPost? threadedPost, | ||||
|     required String? repliedPostId, | ||||
|   | ||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$SnPost { | ||||
|  | ||||
|  String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
|  String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -29,16 +29,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt]); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res>  { | ||||
|   factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -66,7 +66,7 @@ class _$SnPostCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_self.copyWith( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||
| @@ -82,6 +82,7 @@ as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : | ||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable | ||||
| as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable | ||||
| @@ -154,7 +155,7 @@ $SnPublisherCopyWith<$Res> get publisher { | ||||
| @JsonSerializable() | ||||
|  | ||||
| class _SnPost implements SnPost { | ||||
|   const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final  Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final  List<SnCloudFile> attachments, required this.publisher, final  Map<String, int> reactionsCount = const {}, required final  List<dynamic> reactions, required final  List<dynamic> tags, required final  List<dynamic> categories, required final  List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||
|   const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final  Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.repliesCount, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final  List<SnCloudFile> attachments, required this.publisher, final  Map<String, int> reactionsCount = const {}, required final  List<dynamic> reactions, required final  List<dynamic> tags, required final  List<dynamic> categories, required final  List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||
|   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); | ||||
|  | ||||
| @override final  String id; | ||||
| @@ -179,6 +180,7 @@ class _SnPost implements SnPost { | ||||
| @override final  int viewsTotal; | ||||
| @override final  int upvotes; | ||||
| @override final  int downvotes; | ||||
| @override final  int repliesCount; | ||||
| @override final  String? threadedPostId; | ||||
| @override final  SnPost? threadedPost; | ||||
| @override final  String? repliedPostId; | ||||
| @@ -245,16 +247,16 @@ Map<String, dynamic> toJson() { | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||
| } | ||||
|  | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @override | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt]); | ||||
| int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt]); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
|   return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -265,7 +267,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> { | ||||
|   factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
|  String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -282,7 +284,7 @@ class __$SnPostCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of SnPost | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||
|   return _then(_SnPost( | ||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||
| as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||
| @@ -298,6 +300,7 @@ as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : | ||||
| as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable | ||||
| as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable | ||||
| as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable | ||||
| as int,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable | ||||
| as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable | ||||
| as SnPost?,repliedPostId: freezed == repliedPostId ? _self.repliedPostId : repliedPostId // ignore: cast_nullable_to_non_nullable | ||||
|   | ||||
| @@ -24,6 +24,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | ||||
|   viewsTotal: (json['views_total'] as num).toInt(), | ||||
|   upvotes: (json['upvotes'] as num).toInt(), | ||||
|   downvotes: (json['downvotes'] as num).toInt(), | ||||
|   repliesCount: (json['replies_count'] as num).toInt(), | ||||
|   threadedPostId: json['threaded_post_id'] as String?, | ||||
|   threadedPost: | ||||
|       json['threaded_post'] == null | ||||
| @@ -76,6 +77,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | ||||
|   'views_total': instance.viewsTotal, | ||||
|   'upvotes': instance.upvotes, | ||||
|   'downvotes': instance.downvotes, | ||||
|   'replies_count': instance.repliesCount, | ||||
|   'threaded_post_id': instance.threadedPostId, | ||||
|   'threaded_post': instance.threadedPost?.toJson(), | ||||
|   'replied_post_id': instance.repliedPostId, | ||||
|   | ||||
| @@ -16,27 +16,42 @@ import 'config.dart'; | ||||
| final imagePickerProvider = Provider((ref) => ImagePicker()); | ||||
|  | ||||
| final userAgentProvider = FutureProvider<String>((ref) async { | ||||
|   // Helper function to sanitize strings for HTTP headers | ||||
|   String sanitizeForHeader(String input) { | ||||
|     // Remove or replace characters that are not allowed in HTTP headers | ||||
|     // Keep only ASCII printable characters (32-126) and replace others with underscore | ||||
|     return input.runes.map((rune) { | ||||
|       if (rune >= 32 && rune <= 126) { | ||||
|         return String.fromCharCode(rune); | ||||
|       } else { | ||||
|         return '_'; | ||||
|       } | ||||
|     }).join(); | ||||
|   } | ||||
|  | ||||
|   final String platformInfo; | ||||
|   if (kIsWeb) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().webBrowserInfo; | ||||
|     platformInfo = 'Web; ${deviceInfo.vendor}'; | ||||
|     platformInfo = 'Web; ${sanitizeForHeader(deviceInfo.vendor ?? 'Unknown')}'; | ||||
|   } else if (Platform.isAndroid) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().androidInfo; | ||||
|     platformInfo = | ||||
|         'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}'; | ||||
|         'Android; ${sanitizeForHeader(deviceInfo.brand)} ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.id)}'; | ||||
|   } else if (Platform.isIOS) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().iosInfo; | ||||
|     platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}'; | ||||
|     platformInfo = | ||||
|         'iOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.name)}'; | ||||
|   } else if (Platform.isMacOS) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().macOsInfo; | ||||
|     platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}'; | ||||
|     platformInfo = | ||||
|         'MacOS; ${sanitizeForHeader(deviceInfo.model)}; ${sanitizeForHeader(deviceInfo.hostName)}'; | ||||
|   } else if (Platform.isWindows) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().windowsInfo; | ||||
|     platformInfo = | ||||
|         'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}'; | ||||
|         'Windows NT; ${sanitizeForHeader(deviceInfo.productName)}; ${sanitizeForHeader(deviceInfo.computerName)}'; | ||||
|   } else if (Platform.isLinux) { | ||||
|     final deviceInfo = await DeviceInfoPlugin().linuxInfo; | ||||
|     platformInfo = 'Linux; ${deviceInfo.prettyName}'; | ||||
|     platformInfo = 'Linux; ${sanitizeForHeader(deviceInfo.prettyName)}'; | ||||
|   } else { | ||||
|     platformInfo = 'Unknown'; | ||||
|   } | ||||
|   | ||||
| @@ -182,17 +182,10 @@ class ArticleComposeRoute extends _i29.PageRouteInfo<ArticleComposeRouteArgs> { | ||||
|   ArticleComposeRoute({ | ||||
|     _i30.Key? key, | ||||
|     _i32.SnPost? originalPost, | ||||
|     _i32.SnPost? repliedPost, | ||||
|     _i32.SnPost? forwardedPost, | ||||
|     List<_i29.PageRouteInfo>? children, | ||||
|   }) : super( | ||||
|          ArticleComposeRoute.name, | ||||
|          args: ArticleComposeRouteArgs( | ||||
|            key: key, | ||||
|            originalPost: originalPost, | ||||
|            repliedPost: repliedPost, | ||||
|            forwardedPost: forwardedPost, | ||||
|          ), | ||||
|          args: ArticleComposeRouteArgs(key: key, originalPost: originalPost), | ||||
|          initialChildren: children, | ||||
|        ); | ||||
|  | ||||
| @@ -213,42 +206,26 @@ class ArticleComposeRoute extends _i29.PageRouteInfo<ArticleComposeRouteArgs> { | ||||
| } | ||||
|  | ||||
| class ArticleComposeRouteArgs { | ||||
|   const ArticleComposeRouteArgs({ | ||||
|     this.key, | ||||
|     this.originalPost, | ||||
|     this.repliedPost, | ||||
|     this.forwardedPost, | ||||
|   }); | ||||
|   const ArticleComposeRouteArgs({this.key, this.originalPost}); | ||||
|  | ||||
|   final _i30.Key? key; | ||||
|  | ||||
|   final _i32.SnPost? originalPost; | ||||
|  | ||||
|   final _i32.SnPost? repliedPost; | ||||
|  | ||||
|   final _i32.SnPost? forwardedPost; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'ArticleComposeRouteArgs{key: $key, originalPost: $originalPost, repliedPost: $repliedPost, forwardedPost: $forwardedPost}'; | ||||
|     return 'ArticleComposeRouteArgs{key: $key, originalPost: $originalPost}'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     if (identical(this, other)) return true; | ||||
|     if (other is! ArticleComposeRouteArgs) return false; | ||||
|     return key == other.key && | ||||
|         originalPost == other.originalPost && | ||||
|         repliedPost == other.repliedPost && | ||||
|         forwardedPost == other.forwardedPost; | ||||
|     return key == other.key && originalPost == other.originalPost; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       key.hashCode ^ | ||||
|       originalPost.hashCode ^ | ||||
|       repliedPost.hashCode ^ | ||||
|       forwardedPost.hashCode; | ||||
|   int get hashCode => key.hashCode ^ originalPost.hashCode; | ||||
| } | ||||
|  | ||||
| /// generated route for | ||||
|   | ||||
| @@ -47,8 +47,9 @@ class NotificationUnreadCountNotifier | ||||
|   void _subscribeToWebSocket() { | ||||
|     final webSocketService = ref.read(websocketProvider); | ||||
|     _subscription = webSocketService.dataStream.listen((packet) { | ||||
|       if (packet.type == 'notifications.new') { | ||||
|         _incrementCounter(); | ||||
|       if (packet.type == 'notifications.new' && packet.data != null) { | ||||
|         final notification = SnNotification.fromJson(packet.data!); | ||||
|         if (notification.topic != 'messages.new') _incrementCounter(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -1,236 +0,0 @@ | ||||
| // ignore_for_file: implementation_imports, invalid_use_of_internal_member | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||
| import 'package:riverpod_paging_utils/src/paging_data.dart'; | ||||
| import 'package:riverpod_paging_utils/src/paging_helper_view_theme.dart'; | ||||
| import 'package:riverpod_paging_utils/src/paging_notifier_mixin.dart'; | ||||
| import 'package:visibility_detector/visibility_detector.dart'; | ||||
|  | ||||
| /// A generic widget for pagination. | ||||
| /// | ||||
| /// Main features: | ||||
| /// 1. Displays the widget created by [contentBuilder] when data is available. | ||||
| /// 2. Shows a CircularProgressIndicator while loading the first page. | ||||
| /// 3. Displays an error widget when there is an error on the first page. | ||||
| /// 4. Shows error messages using a SnackBar. | ||||
| /// 5. Loads the next page when the last item is displayed. | ||||
| /// 6. Supports pull-to-refresh functionality. | ||||
| /// | ||||
| /// You can customize the appearance of the loading view, error view, and endItemView using [PagingHelperViewTheme]. | ||||
| final class PagingHelperSliverView<D extends PagingData<I>, I> | ||||
|     extends ConsumerWidget { | ||||
|   const PagingHelperSliverView({ | ||||
|     required this.provider, | ||||
|     required this.futureRefreshable, | ||||
|     required this.notifierRefreshable, | ||||
|     required this.contentBuilder, | ||||
|     this.showSecondPageError = true, | ||||
|     super.key, | ||||
|   }); | ||||
|  | ||||
|   final ProviderListenable<AsyncValue<D>> provider; | ||||
|   final Refreshable<Future<D>> futureRefreshable; | ||||
|   final Refreshable<PagingNotifierMixin<D, I>> notifierRefreshable; | ||||
|  | ||||
|   /// Specifies a function that returns a widget to display when data is available. | ||||
|   /// endItemView is a widget to detect when the last displayed item is visible. | ||||
|   /// If endItemView is non-null, it is displayed at the end of the list. | ||||
|   final Widget Function(D data, int widgetCount, Widget endItemView) | ||||
|   contentBuilder; | ||||
|  | ||||
|   final bool showSecondPageError; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final theme = Theme.of(context).extension<PagingHelperViewTheme>(); | ||||
|  | ||||
|     final loadingBuilder = | ||||
|         theme?.loadingViewBuilder ?? | ||||
|         (context) => SliverFillRemaining( | ||||
|           child: const Center(child: CircularProgressIndicator()), | ||||
|         ); | ||||
|     final errorBuilder = | ||||
|         theme?.errorViewBuilder ?? | ||||
|         (context, e, st, onPressed) => SliverFillRemaining( | ||||
|           child: Center( | ||||
|             child: Column( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               children: [ | ||||
|                 IconButton( | ||||
|                   onPressed: onPressed, | ||||
|                   icon: const Icon(Icons.refresh), | ||||
|                 ), | ||||
|                 Text(e.toString()), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|  | ||||
|     return ref | ||||
|         .watch(provider) | ||||
|         .whenIgnorableError( | ||||
|           data: ( | ||||
|             data, { | ||||
|             required hasError, | ||||
|             required isLoading, | ||||
|             required error, | ||||
|           }) { | ||||
|             final content = contentBuilder( | ||||
|               data, | ||||
|               // Add 1 to the length to include the endItemView | ||||
|               data.items.length + 1, | ||||
|               switch ((data.hasMore, hasError, isLoading)) { | ||||
|                 // Display a widget to detect when the last element is reached | ||||
|                 // if there are more pages and no errors | ||||
|                 (true, false, _) => _EndVDLoadingItemView( | ||||
|                   onScrollEnd: | ||||
|                       () async => ref.read(notifierRefreshable).loadNext(), | ||||
|                 ), | ||||
|                 (true, true, false) when showSecondPageError => | ||||
|                   _EndErrorItemView( | ||||
|                     error: error, | ||||
|                     onRetryButtonPressed: | ||||
|                         () async => ref.read(notifierRefreshable).loadNext(), | ||||
|                   ), | ||||
|                 (true, true, true) => const _EndLoadingItemView(), | ||||
|                 _ => const SizedBox.shrink(), | ||||
|               }, | ||||
|             ); | ||||
|  | ||||
|             return content; | ||||
|           }, | ||||
|           // Loading state for the first page | ||||
|           loading: () => loadingBuilder(context), | ||||
|           // Error state for the first page | ||||
|           error: | ||||
|               (e, st) => errorBuilder( | ||||
|                 context, | ||||
|                 e, | ||||
|                 st, | ||||
|                 () => ref.read(notifierRefreshable).forceRefresh(), | ||||
|               ), | ||||
|           // Prioritize data for errors on the second page and beyond | ||||
|           skipErrorOnHasValue: true, | ||||
|         ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final class _EndLoadingItemView extends StatelessWidget { | ||||
|   const _EndLoadingItemView(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final theme = Theme.of(context).extension<PagingHelperViewTheme>(); | ||||
|     final childBuilder = | ||||
|         theme?.endLoadingViewBuilder ?? | ||||
|         (context) => const Center( | ||||
|           child: Padding( | ||||
|             padding: EdgeInsets.all(16), | ||||
|             child: CircularProgressIndicator(), | ||||
|           ), | ||||
|         ); | ||||
|  | ||||
|     return childBuilder(context); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final class _EndVDLoadingItemView extends StatelessWidget { | ||||
|   const _EndVDLoadingItemView({required this.onScrollEnd}); | ||||
|   final VoidCallback onScrollEnd; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return VisibilityDetector( | ||||
|       key: key ?? const Key('EndItem'), | ||||
|       onVisibilityChanged: (info) { | ||||
|         if (info.visibleFraction > 0.1) { | ||||
|           onScrollEnd(); | ||||
|         } | ||||
|       }, | ||||
|       child: const _EndLoadingItemView(), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final class _EndErrorItemView extends StatelessWidget { | ||||
|   const _EndErrorItemView({ | ||||
|     required this.error, | ||||
|     required this.onRetryButtonPressed, | ||||
|   }); | ||||
|   final Object? error; | ||||
|   final VoidCallback onRetryButtonPressed; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final theme = Theme.of(context).extension<PagingHelperViewTheme>(); | ||||
|     final childBuilder = | ||||
|         theme?.endErrorViewBuilder ?? | ||||
|         (context, e, onPressed) => Center( | ||||
|           child: Padding( | ||||
|             padding: const EdgeInsets.all(16), | ||||
|             child: Column( | ||||
|               children: [ | ||||
|                 IconButton( | ||||
|                   onPressed: onPressed, | ||||
|                   icon: const Icon(Icons.refresh), | ||||
|                 ), | ||||
|                 Text(error.toString()), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|  | ||||
|     return childBuilder(context, error, onRetryButtonPressed); | ||||
|   } | ||||
| } | ||||
|  | ||||
| extension _AsyncValueX<T> on AsyncValue<T> { | ||||
|   /// Extends the [when] method to handle async data states more effectively, | ||||
|   /// especially when maintaining data integrity despite errors. | ||||
|   /// | ||||
|   /// Use `skipErrorOnHasValue` to retain and display existing data | ||||
|   /// even if subsequent fetch attempts result in errors, | ||||
|   /// ideal for maintaining a seamless user experience. | ||||
|   R whenIgnorableError<R>({ | ||||
|     required R Function( | ||||
|       T data, { | ||||
|       required bool hasError, | ||||
|       required bool isLoading, | ||||
|       required Object? error, | ||||
|     }) | ||||
|     data, | ||||
|     required R Function(Object error, StackTrace stackTrace) error, | ||||
|     required R Function() loading, | ||||
|     bool skipLoadingOnReload = false, | ||||
|     bool skipLoadingOnRefresh = true, | ||||
|     bool skipError = false, | ||||
|     bool skipErrorOnHasValue = false, | ||||
|   }) { | ||||
|     if (skipErrorOnHasValue) { | ||||
|       if (hasValue && hasError) { | ||||
|         return data( | ||||
|           requireValue, | ||||
|           hasError: true, | ||||
|           isLoading: isLoading, | ||||
|           error: this.error, | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return when( | ||||
|       skipLoadingOnReload: skipLoadingOnReload, | ||||
|       skipLoadingOnRefresh: skipLoadingOnRefresh, | ||||
|       skipError: skipError, | ||||
|       data: | ||||
|           (d) => data( | ||||
|             d, | ||||
|             hasError: hasError, | ||||
|             isLoading: isLoading, | ||||
|             error: this.error, | ||||
|           ), | ||||
|       error: error, | ||||
|       loading: loading, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -213,7 +213,7 @@ class ComposeLogic { | ||||
|       // Prepare API request | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|       final isNewPost = originalPost == null; | ||||
|       final endpoint = isNewPost ? '/posts' : '/posts/${originalPost!.id}'; | ||||
|       final endpoint = isNewPost ? '/posts' : '/posts/${originalPost.id}'; | ||||
|  | ||||
|       // Create request payload | ||||
|       final payload = { | ||||
|   | ||||
| @@ -19,6 +19,7 @@ import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/content/cloud_file_collection.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/markdown.dart'; | ||||
| import 'package:island/widgets/post/post_replies_sheet.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:super_context_menu/super_context_menu.dart'; | ||||
| @@ -235,18 +236,52 @@ class PostItem extends HookConsumerWidget { | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|               PostReactionList( | ||||
|                 parentId: item.id, | ||||
|                 reactions: item.reactionsCount, | ||||
|                 padding: EdgeInsets.only(left: 48), | ||||
|                 onReact: (symbol, attitude, delta) { | ||||
|                   final reactionsCount = Map<String, int>.from( | ||||
|                     item.reactionsCount, | ||||
|                   ); | ||||
|                   reactionsCount[symbol] = | ||||
|                       (reactionsCount[symbol] ?? 0) + delta; | ||||
|                   onUpdate?.call(item.copyWith(reactionsCount: reactionsCount)); | ||||
|                 }, | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   // Replies count button | ||||
|                   Padding( | ||||
|                     padding: const EdgeInsets.only(left: 48, right: 12), | ||||
|                     child: ActionChip( | ||||
|                       avatar: Icon(Symbols.reply, size: 16), | ||||
|                       label: Text( | ||||
|                         (item.repliesCount > 0) | ||||
|                             ? 'repliesCount'.plural(item.repliesCount) | ||||
|                             : 'reply'.tr(), | ||||
|                       ), | ||||
|                       visualDensity: const VisualDensity( | ||||
|                         horizontal: VisualDensity.minimumDensity, | ||||
|                         vertical: VisualDensity.minimumDensity, | ||||
|                       ), | ||||
|                       onPressed: () { | ||||
|                          if (isOpenable) { | ||||
|                            showModalBottomSheet( | ||||
|                              context: context, | ||||
|                              isScrollControlled: true, | ||||
|                              builder: (context) => PostRepliesSheet(post: item), | ||||
|                            ); | ||||
|                          } | ||||
|                        }, | ||||
|                     ), | ||||
|                   ), | ||||
|                   // Reactions list | ||||
|                   Expanded( | ||||
|                     child: PostReactionList( | ||||
|                       parentId: item.id, | ||||
|                       reactions: item.reactionsCount, | ||||
|                       padding: EdgeInsets.zero, | ||||
|                       onReact: (symbol, attitude, delta) { | ||||
|                         final reactionsCount = Map<String, int>.from( | ||||
|                           item.reactionsCount, | ||||
|                         ); | ||||
|                         reactionsCount[symbol] = | ||||
|                             (reactionsCount[symbol] ?? 0) + delta; | ||||
|                         onUpdate?.call( | ||||
|                           item.copyWith(reactionsCount: reactionsCount), | ||||
|                         ); | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/widgets/content/paging_helper_ext.dart'; | ||||
| import 'package:island/widgets/post/post_item.dart'; | ||||
| import 'package:island/widgets/post/post_item_creator.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/content/paging_helper_ext.dart'; | ||||
| import 'package:island/widgets/post/post_item.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||
| @@ -57,7 +56,8 @@ class PostRepliesNotifier extends _$PostRepliesNotifier | ||||
|  | ||||
| class PostRepliesList extends HookConsumerWidget { | ||||
|   final String postId; | ||||
|   const PostRepliesList({super.key, required this.postId}); | ||||
|   final Color? backgroundColor; | ||||
|   const PostRepliesList({super.key, required this.postId, this.backgroundColor}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -93,7 +93,7 @@ class PostRepliesList extends HookConsumerWidget { | ||||
|               children: [ | ||||
|                 PostItem( | ||||
|                   item: data.items[index], | ||||
|                   backgroundColor: isWide ? Colors.transparent : null, | ||||
|                   backgroundColor: backgroundColor ?? (isWide ? Colors.transparent : null), | ||||
|                   showReferencePost: false, | ||||
|                 ), | ||||
|                 const Divider(height: 1), | ||||
|   | ||||
							
								
								
									
										48
									
								
								lib/widgets/post/post_replies_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								lib/widgets/post/post_replies_sheet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:island/widgets/post/post_replies.dart'; | ||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| class PostRepliesSheet extends HookConsumerWidget { | ||||
|   final SnPost post; | ||||
|  | ||||
|   const PostRepliesSheet({super.key, required this.post}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     return SheetScaffold( | ||||
|       titleText: 'repliesCount'.plural(post.repliesCount), | ||||
|       child: Column( | ||||
|         children: [ | ||||
|           // Replies list | ||||
|           Expanded( | ||||
|             child: CustomScrollView( | ||||
|               slivers: [PostRepliesList( | ||||
|                 postId: post.id.toString(), | ||||
|                 backgroundColor: Colors.transparent, | ||||
|               )], | ||||
|             ), | ||||
|           ), | ||||
|           // Quick reply section | ||||
|           Material( | ||||
|             elevation: 2, | ||||
|             child: PostQuickReply( | ||||
|               parent: post, | ||||
|               onPosted: () { | ||||
|                 ref.invalidate(postRepliesNotifierProvider(post.id)); | ||||
|               }, | ||||
|             ).padding( | ||||
|               bottom: MediaQuery.of(context).padding.bottom + 16, | ||||
|               top: 16, | ||||
|               horizontal: 16, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,14 +1,4 @@ | ||||
| PODS: | ||||
|   - AppAuth (1.7.6): | ||||
|     - AppAuth/Core (= 1.7.6) | ||||
|     - AppAuth/ExternalUserAgent (= 1.7.6) | ||||
|   - AppAuth/Core (1.7.6) | ||||
|   - AppAuth/ExternalUserAgent (1.7.6): | ||||
|     - AppAuth/Core | ||||
|   - AppCheckCore (11.2.0): | ||||
|     - GoogleUtilities/Environment (~> 8.0) | ||||
|     - GoogleUtilities/UserDefaults (~> 8.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
|   - bitsdojo_window_macos (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - connectivity_plus (0.0.1): | ||||
| @@ -71,20 +61,9 @@ PODS: | ||||
|   - gal (1.0.0): | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|   - google_sign_in_ios (0.0.1): | ||||
|     - AppAuth (>= 1.7.4) | ||||
|     - Flutter | ||||
|     - FlutterMacOS | ||||
|     - GoogleSignIn (~> 8.0) | ||||
|     - GTMSessionFetcher (>= 3.4.0) | ||||
|   - GoogleDataTransport (10.1.0): | ||||
|     - nanopb (~> 3.30910.0) | ||||
|     - PromisesObjC (~> 2.4) | ||||
|   - GoogleSignIn (8.0.0): | ||||
|     - AppAuth (< 2.0, >= 1.7.3) | ||||
|     - AppCheckCore (~> 11.0) | ||||
|     - GTMAppAuth (< 5.0, >= 4.1.1) | ||||
|     - GTMSessionFetcher/Core (~> 3.3) | ||||
|   - GoogleUtilities/AppDelegateSwizzler (8.1.0): | ||||
|     - GoogleUtilities/Environment | ||||
|     - GoogleUtilities/Logger | ||||
| @@ -109,14 +88,6 @@ PODS: | ||||
|   - GoogleUtilities/UserDefaults (8.1.0): | ||||
|     - GoogleUtilities/Logger | ||||
|     - GoogleUtilities/Privacy | ||||
|   - GTMAppAuth (4.1.1): | ||||
|     - AppAuth/Core (~> 1.7) | ||||
|     - GTMSessionFetcher/Core (< 4.0, >= 3.3) | ||||
|   - GTMSessionFetcher (3.5.0): | ||||
|     - GTMSessionFetcher/Full (= 3.5.0) | ||||
|   - GTMSessionFetcher/Core (3.5.0) | ||||
|   - GTMSessionFetcher/Full (3.5.0): | ||||
|     - GTMSessionFetcher/Core | ||||
|   - irondash_engine_context (0.0.1): | ||||
|     - FlutterMacOS | ||||
|   - livekit_client (2.4.8): | ||||
| @@ -200,7 +171,6 @@ DEPENDENCIES: | ||||
|   - flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`) | ||||
|   - FlutterMacOS (from `Flutter/ephemeral`) | ||||
|   - gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) | ||||
|   - google_sign_in_ios (from `Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin`) | ||||
|   - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) | ||||
|   - livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`) | ||||
|   - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) | ||||
| @@ -220,18 +190,13 @@ DEPENDENCIES: | ||||
|  | ||||
| SPEC REPOS: | ||||
|   trunk: | ||||
|     - AppAuth | ||||
|     - AppCheckCore | ||||
|     - Firebase | ||||
|     - FirebaseCore | ||||
|     - FirebaseCoreInternal | ||||
|     - FirebaseInstallations | ||||
|     - FirebaseMessaging | ||||
|     - GoogleDataTransport | ||||
|     - GoogleSignIn | ||||
|     - GoogleUtilities | ||||
|     - GTMAppAuth | ||||
|     - GTMSessionFetcher | ||||
|     - nanopb | ||||
|     - OrderedSet | ||||
|     - PromisesObjC | ||||
| @@ -270,8 +235,6 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral | ||||
|   gal: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/gal/darwin | ||||
|   google_sign_in_ios: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin | ||||
|   irondash_engine_context: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos | ||||
|   livekit_client: | ||||
| @@ -306,8 +269,6 @@ EXTERNAL SOURCES: | ||||
|     :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos | ||||
|  | ||||
| SPEC CHECKSUMS: | ||||
|   AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 | ||||
|   AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f | ||||
|   bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9 | ||||
|   connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e | ||||
|   croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43 | ||||
| @@ -328,12 +289,8 @@ SPEC CHECKSUMS: | ||||
|   flutter_webrtc: a7eeb54859e672228c28f4b48b1fb61561976ea3 | ||||
|   FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | ||||
|   gal: baecd024ebfd13c441269ca7404792a7152fde89 | ||||
|   google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9 | ||||
|   GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 | ||||
|   GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4 | ||||
|   GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 | ||||
|   GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de | ||||
|   GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 | ||||
|   irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba | ||||
|   livekit_client: 6a35243df3da61750c98e266e02dedcf5d25c888 | ||||
|   media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|             <BuildableReference | ||||
|                BuildableIdentifier = "primary" | ||||
|                BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||
|                BuildableName = "island.app" | ||||
|                BuildableName = "Solian.app" | ||||
|                BlueprintName = "Runner" | ||||
|                ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|             </BuildableReference> | ||||
| @@ -31,7 +31,7 @@ | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||
|             BuildableName = "island.app" | ||||
|             BuildableName = "Solian.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
| @@ -66,7 +66,7 @@ | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||
|             BuildableName = "island.app" | ||||
|             BuildableName = "Solian.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
| @@ -83,7 +83,7 @@ | ||||
|          <BuildableReference | ||||
|             BuildableIdentifier = "primary" | ||||
|             BlueprintIdentifier = "33CC10EC2044A3C60003C045" | ||||
|             BuildableName = "island.app" | ||||
|             BuildableName = "Solian.app" | ||||
|             BlueprintName = "Runner" | ||||
|             ReferencedContainer = "container:Runner.xcodeproj"> | ||||
|          </BuildableReference> | ||||
|   | ||||
							
								
								
									
										38
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -149,10 +149,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: build | ||||
|       sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 | ||||
|       sha256: "7cf79af8eb6023bee797a77b067fb6e63ac5650f3789546e023958098feb776e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.2" | ||||
|     version: "2.5.2" | ||||
|   build_config: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -173,26 +173,26 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: build_resolvers | ||||
|       sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 | ||||
|       sha256: "7a507e6026abe52074836d51a945bfad456daa7493eb7a6cac565e490e7d5b54" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.4" | ||||
|     version: "2.5.2" | ||||
|   build_runner: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
|       name: build_runner | ||||
|       sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" | ||||
|       sha256: "1ce1e5063b564f26c27bda54c82a3d38339df69ec58f90e0017f447de77e4839" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.15" | ||||
|     version: "2.5.2" | ||||
|   build_runner_core: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: build_runner_core | ||||
|       sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" | ||||
|       sha256: "564230f3fd9363df7870058fef11ec5502ee620aec3b1ee8106b943be5c63a76" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.0.0" | ||||
|     version: "9.1.0" | ||||
|   built_collection: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -453,18 +453,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: device_info_plus | ||||
|       sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53" | ||||
|       sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.4.0" | ||||
|     version: "11.5.0" | ||||
|   device_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: device_info_plus_platform_interface | ||||
|       sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" | ||||
|       sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.0.2" | ||||
|     version: "7.0.3" | ||||
|   dio: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -895,10 +895,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_svg | ||||
|       sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 | ||||
|       sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.0" | ||||
|     version: "2.2.0" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @@ -1217,10 +1217,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: lean_builder | ||||
|       sha256: ac129cd2173aa4e53e1327bcee2233d738d68ee446f3c797135633deafe6ca8a | ||||
|       sha256: dca2165cfe681c69ae903a0880cab90ee93d730777605a0f44c9dd08cec7e1b9 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.1.0-alpha.12" | ||||
|     version: "0.1.0-alpha.13" | ||||
|   lint: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1769,10 +1769,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: riverpod_paging_utils | ||||
|       sha256: "18f59960807835b1d3cb993e825442d7b09928d0f55ad50bda65c002b5893bdc" | ||||
|       sha256: a3eb7cc87d53d90dac9bf0b0d695ecdc049aae5dd6debd7d2d62ab3682cf5841 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.0" | ||||
|     version: "0.8.1" | ||||
|   rxdart: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2509,4 +2509,4 @@ packages: | ||||
|     version: "3.1.3" | ||||
| sdks: | ||||
|   dart: ">=3.8.0 <4.0.0" | ||||
|   flutter: ">=3.27.4" | ||||
|   flutter: ">=3.29.0" | ||||
|   | ||||
| @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev | ||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||
| # In Windows, build-name is used as the major, minor, and patch parts | ||||
| # of the product and file versions while build-number is used as the build suffix. | ||||
| version: 3.0.0+104 | ||||
| version: 3.0.0+106 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.7.2 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user