From e4e562918cecf4709c2e25db00611a674c10fb2c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 5 May 2025 13:13:42 +0800 Subject: [PATCH] :sparkles: Post reactions --- assets/i18n/en-US.json | 11 +- ios/Podfile.lock | 12 - lib/main.dart | 2 - lib/models/post.dart | 23 +- lib/models/post.freezed.dart | 183 ++++++++++++-- lib/models/post.g.dart | 8 +- lib/screens/auth/create_account.dart | 84 +++---- lib/screens/chat/chat.dart | 179 +++++++------- lib/screens/chat/room_detail.dart | 193 +++++++-------- lib/screens/explore.dart | 89 ++++--- lib/screens/posts/compose.dart | 153 ++++++------ lib/screens/realm/detail.dart | 193 +++++++-------- lib/screens/realm/realms.dart | 174 ++++++------- lib/widgets/account/account_picker.dart | 94 ++++--- lib/widgets/post/post_item.dart | 233 +++++++++++++++++- lib/widgets/post/post_quick_reply.dart | 3 +- lib/widgets/post/publishers_modal.dart | 110 ++++----- lib/widgets/quill_content.dart | 25 -- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 200 +++------------ pubspec.yaml | 2 +- 21 files changed, 1054 insertions(+), 919 deletions(-) delete mode 100644 lib/widgets/quill_content.dart diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 3d02698..f22e9c7 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -94,5 +94,14 @@ "edited": "Edited", "addVideo": "Add video", "addPhoto": "Add photo", - "createDirectMessage": "New direct message" + "createDirectMessage": "New direct message", + "react": "React", + "reactions": { + "zero": "Reactions", + "one": "{} reaction", + "other": "{} reactions" + }, + "reactionPositive": "Postive", + "reactionNegative": "Negative", + "reactionNeutral": "Neutral" } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3d847f0..79c8167 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -75,8 +75,6 @@ PODS: - flutter_inappwebview_ios/Core (0.0.1): - Flutter - OrderedSet (~> 6.0.3) - - flutter_keyboard_visibility_temp_fork (0.0.1): - - Flutter - flutter_platform_alert (0.0.1): - Flutter - flutter_udid (0.0.1): @@ -130,8 +128,6 @@ PODS: - Flutter - FlutterMacOS - PromisesObjC (2.4.0) - - quill_native_bridge_ios (0.0.1): - - Flutter - SAMKeychain (1.5.3) - SDWebImage (5.21.0): - SDWebImage/Core (= 5.21.0) @@ -181,7 +177,6 @@ DEPENDENCIES: - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - - flutter_keyboard_visibility_temp_fork (from `.symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios`) - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -191,7 +186,6 @@ DEPENDENCIES: - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - quill_native_bridge_ios (from `.symlinks/plugins/quill_native_bridge_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) @@ -233,8 +227,6 @@ EXTERNAL SOURCES: :path: Flutter flutter_inappwebview_ios: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" - flutter_keyboard_visibility_temp_fork: - :path: ".symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios" flutter_platform_alert: :path: ".symlinks/plugins/flutter_platform_alert/ios" flutter_udid: @@ -251,8 +243,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" - quill_native_bridge_ios: - :path: ".symlinks/plugins/quill_native_bridge_ios/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite_darwin: @@ -282,7 +272,6 @@ SPEC CHECKSUMS: FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3 flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 @@ -297,7 +286,6 @@ SPEC CHECKSUMS: package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - quill_native_bridge_ios: f47af4b14e7757968486641656c5d23250cee521 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 diff --git a/lib/main.dart b/lib/main.dart index dab4be9..9b40f87 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_quill/flutter_quill.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker_android/image_picker_android.dart'; import 'package:island/firebase_options.dart'; @@ -101,7 +100,6 @@ class IslandApp extends HookConsumerWidget { supportedLocales: context.supportedLocales, localizationsDelegates: [ ...context.localizationDelegates, - FlutterQuillLocalizations.delegate, ], // this contains the cupertino one locale: context.locale, builder: (context, child) { diff --git a/lib/models/post.dart b/lib/models/post.dart index ec10366..d8d1d47 100644 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -14,7 +14,7 @@ abstract class SnPost with _$SnPost { required DateTime? editedAt, required DateTime publishedAt, required int visibility, - required List? content, + required String? content, required int type, required Map? meta, required int viewsUnique, @@ -29,6 +29,7 @@ abstract class SnPost with _$SnPost { required dynamic forwardedPost, required List attachments, required SnPublisher publisher, + @Default({}) Map reactionsCount, required List reactions, required List tags, required List categories, @@ -62,3 +63,23 @@ abstract class SnPublisher with _$SnPublisher { factory SnPublisher.fromJson(Map json) => _$SnPublisherFromJson(json); } + +@freezed +abstract class ReactInfo with _$ReactInfo { + const factory ReactInfo({required String icon, required int attitude}) = + _ReactInfo; +} + +const Map kReactionTemplates = { + 'thumb_up': ReactInfo(icon: '๐Ÿ‘', attitude: 0), + 'thumb_down': ReactInfo(icon: '๐Ÿ‘Ž', attitude: 2), + 'just_okay': ReactInfo(icon: '๐Ÿ˜…', attitude: 1), + 'cry': ReactInfo(icon: '๐Ÿ˜ญ', attitude: 1), + 'confuse': ReactInfo(icon: '๐Ÿง', attitude: 1), + 'clap': ReactInfo(icon: '๐Ÿ‘', attitude: 0), + 'laugh': ReactInfo(icon: '๐Ÿ˜‚', attitude: 0), + 'angry': ReactInfo(icon: '๐Ÿ˜ก', attitude: 2), + 'party': ReactInfo(icon: '๐ŸŽ‰', attitude: 0), + 'pray': ReactInfo(icon: '๐Ÿ™', attitude: 0), + 'heart': ReactInfo(icon: 'โค๏ธ', attitude: 0), +}; diff --git a/lib/models/post.freezed.dart b/lib/models/post.freezed.dart index 4ab2eb8..ae720fb 100644 --- a/lib/models/post.freezed.dart +++ b/lib/models/post.freezed.dart @@ -16,7 +16,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$SnPost { - int get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; List? get content; int get type; Map? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; dynamic get threadedPostId; dynamic get threadedPost; dynamic get repliedPostId; dynamic get repliedPost; dynamic get forwardedPostId; dynamic get forwardedPost; List get attachments; SnPublisher get publisher; List get reactions; List get tags; List get categories; List get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; + int 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? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; dynamic get threadedPostId; dynamic get threadedPost; dynamic get repliedPostId; dynamic get repliedPost; dynamic get forwardedPostId; dynamic get forwardedPost; List get attachments; SnPublisher get publisher; Map get reactionsCount; List get reactions; List get tags; List get categories; List 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 get copyWith => _$SnPostCopyWithImpl(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)&&const DeepCollectionEquality().equals(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)&&const DeepCollectionEquality().equals(other.threadedPostId, threadedPostId)&&const DeepCollectionEquality().equals(other.threadedPost, threadedPost)&&const DeepCollectionEquality().equals(other.repliedPostId, repliedPostId)&&const DeepCollectionEquality().equals(other.repliedPost, repliedPost)&&const DeepCollectionEquality().equals(other.forwardedPostId, forwardedPostId)&&const DeepCollectionEquality().equals(other.forwardedPost, forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&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)&&const DeepCollectionEquality().equals(other.threadedPostId, threadedPostId)&&const DeepCollectionEquality().equals(other.threadedPost, threadedPost)&&const DeepCollectionEquality().equals(other.repliedPostId, repliedPostId)&&const DeepCollectionEquality().equals(other.repliedPost, repliedPost)&&const DeepCollectionEquality().equals(other.forwardedPostId, forwardedPostId)&&const DeepCollectionEquality().equals(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,const DeepCollectionEquality().hash(content),type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,const DeepCollectionEquality().hash(threadedPostId),const DeepCollectionEquality().hash(threadedPost),const DeepCollectionEquality().hash(repliedPostId),const DeepCollectionEquality().hash(repliedPost),const DeepCollectionEquality().hash(forwardedPostId),const DeepCollectionEquality().hash(forwardedPost),const DeepCollectionEquality().hash(attachments),publisher,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,const DeepCollectionEquality().hash(threadedPostId),const DeepCollectionEquality().hash(threadedPost),const DeepCollectionEquality().hash(repliedPostId),const DeepCollectionEquality().hash(repliedPost),const DeepCollectionEquality().hash(forwardedPostId),const DeepCollectionEquality().hash(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, 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, 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({ - int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, List? content, int type, Map? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List attachments, SnPublisher publisher, List reactions, List tags, List categories, List collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List attachments, SnPublisher publisher, Map reactionsCount, List reactions, List tags, List categories, List 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? 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? 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 int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable @@ -76,7 +76,7 @@ as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: as DateTime?,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable as DateTime,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable -as List?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable as Map?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable @@ -90,7 +90,8 @@ as dynamic,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : as dynamic,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable as dynamic,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable as List,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable -as SnPublisher,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable +as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable +as Map,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable as List,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable as List,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable as List,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable @@ -117,7 +118,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 final List? content, required this.type, required final Map? 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 attachments, required this.publisher, required final List reactions, required final List tags, required final List categories, required final List collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _content = content,_meta = meta,_attachments = attachments,_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? 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 attachments, required this.publisher, final Map reactionsCount = const {}, required final List reactions, required final List tags, required final List categories, required final List 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 json) => _$SnPostFromJson(json); @override final int id; @@ -127,15 +128,7 @@ class _SnPost implements SnPost { @override final DateTime? editedAt; @override final DateTime publishedAt; @override final int visibility; - final List? _content; -@override List? get content { - final value = _content; - if (value == null) return null; - if (_content is EqualUnmodifiableListView) return _content; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); -} - +@override final String? content; @override final int type; final Map? _meta; @override Map? get meta { @@ -164,6 +157,13 @@ class _SnPost implements SnPost { } @override final SnPublisher publisher; + final Map _reactionsCount; +@override@JsonKey() Map get reactionsCount { + if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_reactionsCount); +} + final List _reactions; @override List get reactions { if (_reactions is EqualUnmodifiableListView) return _reactions; @@ -209,16 +209,16 @@ Map 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)&&const DeepCollectionEquality().equals(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)&&const DeepCollectionEquality().equals(other.threadedPostId, threadedPostId)&&const DeepCollectionEquality().equals(other.threadedPost, threadedPost)&&const DeepCollectionEquality().equals(other.repliedPostId, repliedPostId)&&const DeepCollectionEquality().equals(other.repliedPost, repliedPost)&&const DeepCollectionEquality().equals(other.forwardedPostId, forwardedPostId)&&const DeepCollectionEquality().equals(other.forwardedPost, forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&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)&&const DeepCollectionEquality().equals(other.threadedPostId, threadedPostId)&&const DeepCollectionEquality().equals(other.threadedPost, threadedPost)&&const DeepCollectionEquality().equals(other.repliedPostId, repliedPostId)&&const DeepCollectionEquality().equals(other.repliedPost, repliedPost)&&const DeepCollectionEquality().equals(other.forwardedPostId, forwardedPostId)&&const DeepCollectionEquality().equals(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,const DeepCollectionEquality().hash(_content),type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,const DeepCollectionEquality().hash(threadedPostId),const DeepCollectionEquality().hash(threadedPost),const DeepCollectionEquality().hash(repliedPostId),const DeepCollectionEquality().hash(repliedPost),const DeepCollectionEquality().hash(forwardedPostId),const DeepCollectionEquality().hash(forwardedPost),const DeepCollectionEquality().hash(_attachments),publisher,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,const DeepCollectionEquality().hash(threadedPostId),const DeepCollectionEquality().hash(threadedPost),const DeepCollectionEquality().hash(repliedPostId),const DeepCollectionEquality().hash(repliedPost),const DeepCollectionEquality().hash(forwardedPostId),const DeepCollectionEquality().hash(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, 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, 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)'; } @@ -229,7 +229,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> { factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; @override @useResult $Res call({ - int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, List? content, int type, Map? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List attachments, SnPublisher publisher, List reactions, List tags, List categories, List collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List attachments, SnPublisher publisher, Map reactionsCount, List reactions, List tags, List categories, List collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); @@ -246,7 +246,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? 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? 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 int,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable @@ -255,8 +255,8 @@ as String?,language: freezed == language ? _self.language : language // ignore: as String?,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable as DateTime?,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable as DateTime,visibility: null == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable -as int,content: freezed == content ? _self._content : content // ignore: cast_nullable_to_non_nullable -as List?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable as Map?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable @@ -270,7 +270,8 @@ as dynamic,forwardedPostId: freezed == forwardedPostId ? _self.forwardedPostId : as dynamic,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwardedPost // ignore: cast_nullable_to_non_nullable as dynamic,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable as List,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable -as SnPublisher,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable +as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable +as Map,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable as List,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable as List,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable as List,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable @@ -510,4 +511,134 @@ $SnCloudFileCopyWith<$Res>? get background { } } +/// @nodoc +mixin _$ReactInfo { + + String get icon; int get attitude; +/// Create a copy of ReactInfo +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ReactInfoCopyWith get copyWith => _$ReactInfoCopyWithImpl(this as ReactInfo, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ReactInfo&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.attitude, attitude) || other.attitude == attitude)); +} + + +@override +int get hashCode => Object.hash(runtimeType,icon,attitude); + +@override +String toString() { + return 'ReactInfo(icon: $icon, attitude: $attitude)'; +} + + +} + +/// @nodoc +abstract mixin class $ReactInfoCopyWith<$Res> { + factory $ReactInfoCopyWith(ReactInfo value, $Res Function(ReactInfo) _then) = _$ReactInfoCopyWithImpl; +@useResult +$Res call({ + String icon, int attitude +}); + + + + +} +/// @nodoc +class _$ReactInfoCopyWithImpl<$Res> + implements $ReactInfoCopyWith<$Res> { + _$ReactInfoCopyWithImpl(this._self, this._then); + + final ReactInfo _self; + final $Res Function(ReactInfo) _then; + +/// Create a copy of ReactInfo +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? icon = null,Object? attitude = null,}) { + return _then(_self.copyWith( +icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// @nodoc + + +class _ReactInfo implements ReactInfo { + const _ReactInfo({required this.icon, required this.attitude}); + + +@override final String icon; +@override final int attitude; + +/// Create a copy of ReactInfo +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ReactInfoCopyWith<_ReactInfo> get copyWith => __$ReactInfoCopyWithImpl<_ReactInfo>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ReactInfo&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.attitude, attitude) || other.attitude == attitude)); +} + + +@override +int get hashCode => Object.hash(runtimeType,icon,attitude); + +@override +String toString() { + return 'ReactInfo(icon: $icon, attitude: $attitude)'; +} + + +} + +/// @nodoc +abstract mixin class _$ReactInfoCopyWith<$Res> implements $ReactInfoCopyWith<$Res> { + factory _$ReactInfoCopyWith(_ReactInfo value, $Res Function(_ReactInfo) _then) = __$ReactInfoCopyWithImpl; +@override @useResult +$Res call({ + String icon, int attitude +}); + + + + +} +/// @nodoc +class __$ReactInfoCopyWithImpl<$Res> + implements _$ReactInfoCopyWith<$Res> { + __$ReactInfoCopyWithImpl(this._self, this._then); + + final _ReactInfo _self; + final $Res Function(_ReactInfo) _then; + +/// Create a copy of ReactInfo +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? icon = null,Object? attitude = null,}) { + return _then(_ReactInfo( +icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable +as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + // dart format on diff --git a/lib/models/post.g.dart b/lib/models/post.g.dart index 46eb161..490a3a6 100644 --- a/lib/models/post.g.dart +++ b/lib/models/post.g.dart @@ -17,7 +17,7 @@ _SnPost _$SnPostFromJson(Map json) => _SnPost( : DateTime.parse(json['edited_at'] as String), publishedAt: DateTime.parse(json['published_at'] as String), visibility: (json['visibility'] as num).toInt(), - content: json['content'] as List?, + content: json['content'] as String?, type: (json['type'] as num).toInt(), meta: json['meta'] as Map?, viewsUnique: (json['views_unique'] as num).toInt(), @@ -35,6 +35,11 @@ _SnPost _$SnPostFromJson(Map json) => _SnPost( .map((e) => SnCloudFile.fromJson(e as Map)) .toList(), publisher: SnPublisher.fromJson(json['publisher'] as Map), + reactionsCount: + (json['reactions_count'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) ?? + const {}, reactions: json['reactions'] as List, tags: json['tags'] as List, categories: json['categories'] as List, @@ -70,6 +75,7 @@ Map _$SnPostToJson(_SnPost instance) => { 'forwarded_post': instance.forwardedPost, 'attachments': instance.attachments.map((e) => e.toJson()).toList(), 'publisher': instance.publisher.toJson(), + 'reactions_count': instance.reactionsCount, 'reactions': instance.reactions, 'tags': instance.tags, 'categories': instance.categories, diff --git a/lib/screens/auth/create_account.dart b/lib/screens/auth/create_account.dart index 0c01967..89c11dd 100644 --- a/lib/screens/auth/create_account.dart +++ b/lib/screens/auth/create_account.dart @@ -30,7 +30,8 @@ class CreateAccountScreen extends HookConsumerWidget { final passwordController = useTextEditingController(); void showPostCreateModal() { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => _PostCreateModal(), ); @@ -265,48 +266,45 @@ class _PostCreateModal extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Center( - child: Material( - color: Colors.transparent, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 280), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('๐ŸŽ‰').fontSize(32), - Text( - 'postCreateAccountTitle'.tr(), - textAlign: TextAlign.center, - ).fontSize(17), - const Gap(18), - Text('postCreateAccountNext').tr().fontSize(19).bold(), - const Gap(4), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 6, - children: [ - Text('\u2022'), - Expanded(child: Text('postCreateAccountNext1').tr()), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 6, - children: [ - Text('\u2022'), - Expanded(child: Text('postCreateAccountNext2').tr()), - ], - ), - const Gap(6), - TextButton( - onPressed: () { - Navigator.pop(context); - context.router.replace(LoginRoute()); - }, - child: Text('login'.tr()), - ), - ], - ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('๐ŸŽ‰').fontSize(32), + Text( + 'postCreateAccountTitle'.tr(), + textAlign: TextAlign.center, + ).fontSize(17), + const Gap(18), + Text('postCreateAccountNext').tr().fontSize(19).bold(), + const Gap(4), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 6, + children: [ + Text('\u2022'), + Expanded(child: Text('postCreateAccountNext1').tr()), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 6, + children: [ + Text('\u2022'), + Expanded(child: Text('postCreateAccountNext2').tr()), + ], + ), + const Gap(6), + TextButton( + onPressed: () { + Navigator.pop(context); + context.router.replace(LoginRoute()); + }, + child: Text('login'.tr()), + ), + ], ), ), ); diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 70d7211..6dd0386 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -45,7 +45,7 @@ class ChatListScreen extends HookConsumerWidget { final fabKey = useMemoized(() => GlobalKey(), []); Future createDirectMessage() async { - final result = await showCupertinoModalBottomSheet( + final result = await showModalBottomSheet( context: context, builder: (context) => AccountPickerSheet(), ); @@ -66,7 +66,8 @@ class ChatListScreen extends HookConsumerWidget { IconButton( icon: const Icon(Symbols.email), onPressed: () { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => _ChatInvitesSheet(), ); @@ -436,102 +437,90 @@ class _ChatInvitesSheet extends HookConsumerWidget { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), - child: Material( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only( - top: 16, - left: 20, - right: 16, - bottom: 12, - ), - child: Row( - children: [ - Text( - 'invites'.tr(), - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), + child: Row( + children: [ + Text( + 'invites'.tr(), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, ), - const Spacer(), - IconButton( - icon: const Icon(Symbols.refresh), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - onPressed: () { - ref.invalidate(chatroomInvitesProvider); - }, - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - ], - ), + ), + const Spacer(), + IconButton( + icon: const Icon(Symbols.refresh), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + onPressed: () { + ref.invalidate(chatroomInvitesProvider); + }, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], ), - const Divider(height: 1), - Expanded( - child: invites.when( - data: - (items) => - items.isEmpty - ? Center( - child: - Text( - 'invitesEmpty', - textAlign: TextAlign.center, - ).tr(), - ) - : ListView.builder( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (context, index) { - final invite = items[index]; - return ListTile( - leading: ProfilePictureWidget( - fileId: invite.chatRoom!.pictureId, - radius: 24, - fallbackIcon: Symbols.group, - ), - title: Text(invite.chatRoom!.name), - subtitle: - Text( - invite.role >= 100 - ? 'permissionOwner' - : invite.role >= 50 - ? 'permissionModerator' - : 'permissionMember', - ).tr(), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Symbols.check), - onPressed: () => acceptInvite(invite), - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => declineInvite(invite), - ), - ], - ), - ); - }, - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => Center(child: Text('Error: $error')), - ), + ), + const Divider(height: 1), + Expanded( + child: invites.when( + data: + (items) => + items.isEmpty + ? Center( + child: + Text( + 'invitesEmpty', + textAlign: TextAlign.center, + ).tr(), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final invite = items[index]; + return ListTile( + leading: ProfilePictureWidget( + fileId: invite.chatRoom!.pictureId, + radius: 24, + fallbackIcon: Symbols.group, + ), + title: Text(invite.chatRoom!.name), + subtitle: + Text( + invite.role >= 100 + ? 'permissionOwner' + : invite.role >= 50 + ? 'permissionModerator' + : 'permissionMember', + ).tr(), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Symbols.check), + onPressed: () => acceptInvite(invite), + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => declineInvite(invite), + ), + ], + ), + ); + }, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), ), - ], - ), + ), + ], ), ); } diff --git a/lib/screens/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index 8159c9e..6631503 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -95,7 +95,8 @@ class ChatDetailScreen extends HookConsumerWidget { IconButton( icon: const Icon(Icons.people, shadows: [iconShadow]), onPressed: () { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => _ChatMemberListSheet(roomId: id), @@ -263,7 +264,8 @@ class _ChatMemberListSheet extends HookConsumerWidget { }, []); Future invitePerson() async { - final result = await showCupertinoModalBottomSheet( + final result = await showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => const AccountPickerSheet(), ); @@ -285,111 +287,96 @@ class _ChatMemberListSheet extends HookConsumerWidget { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), - child: Material( - color: Colors.transparent, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 16, - left: 20, - right: 16, - bottom: 12, - ), - child: Row( - children: [ - Text( - 'members'.plural(memberState.total), - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), + child: Row( + children: [ + Text( + 'members'.plural(memberState.total), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, ), - const Spacer(), - IconButton( - icon: const Icon(Symbols.person_add), - onPressed: invitePerson, - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - IconButton( - icon: const Icon(Symbols.refresh), - onPressed: () { - memberNotifier.reset(); - memberNotifier.loadMore(); - }, - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - ], - ), + ), + const Spacer(), + IconButton( + icon: const Icon(Symbols.person_add), + onPressed: invitePerson, + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + IconButton( + icon: const Icon(Symbols.refresh), + onPressed: () { + memberNotifier.reset(); + memberNotifier.loadMore(); + }, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], ), - const Divider(height: 1), - Expanded( - child: - memberState.error != null - ? Center(child: Text(memberState.error!)) - : ListView.builder( - itemCount: memberState.members.length + 1, - itemBuilder: (context, index) { - if (index == memberState.members.length) { - if (memberState.isLoading) { - return const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ); - } - if (memberState.members.length < - memberState.total) { - memberNotifier.loadMore( - offset: memberState.members.length, - ); - } - return const SizedBox.shrink(); + ), + const Divider(height: 1), + Expanded( + child: + memberState.error != null + ? Center(child: Text(memberState.error!)) + : ListView.builder( + itemCount: memberState.members.length + 1, + itemBuilder: (context, index) { + if (index == memberState.members.length) { + if (memberState.isLoading) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); } + if (memberState.members.length < memberState.total) { + memberNotifier.loadMore( + offset: memberState.members.length, + ); + } + return const SizedBox.shrink(); + } - final member = memberState.members[index]; - return ListTile( - leading: ProfilePictureWidget( - fileId: member.account.profile.pictureId, - ), - title: Row( - spacing: 6, - children: [ - Flexible(child: Text(member.account.nick)), - if (member.joinedAt == null) - const Icon(Symbols.pending_actions, size: 20), - ], - ), - subtitle: Row( - children: [ - Text( - member.role >= 100 - ? 'permissionOwner' - : member.role >= 50 - ? 'permissionModerator' - : 'permissionMember', - ).tr(), - Text('ยท').bold().padding(horizontal: 6), - Expanded( - child: Text("@${member.account.name}"), - ), - ], - ), - ); - }, - ), - ), - ], - ), + final member = memberState.members[index]; + return ListTile( + leading: ProfilePictureWidget( + fileId: member.account.profile.pictureId, + ), + title: Row( + spacing: 6, + children: [ + Flexible(child: Text(member.account.nick)), + if (member.joinedAt == null) + const Icon(Symbols.pending_actions, size: 20), + ], + ), + subtitle: Row( + children: [ + Text( + member.role >= 100 + ? 'permissionOwner' + : member.role >= 50 + ? 'permissionModerator' + : 'permissionMember', + ).tr(), + Text('ยท').bold().padding(horizontal: 6), + Expanded(child: Text("@${member.account.name}")), + ], + ), + ); + }, + ), + ), + ], ), ); } diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 10aec43..4e2ccc1 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -16,7 +16,8 @@ class ExploreScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final postAsync = ref.watch(postListProvider); + final posts = ref.watch(postListProvider); + final postsNotifier = ref.watch(postListProvider.notifier); return AppScaffold( appBar: AppBar(title: const Text('Explore')), @@ -32,60 +33,48 @@ class ExploreScreen extends ConsumerWidget { child: const Icon(Symbols.edit), ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, - body: postAsync.when( - data: - (controller) => RefreshIndicator( - onRefresh: - () => Future.sync((() { - ref.invalidate(postListProvider); - })), - child: InfiniteList( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), - itemCount: controller.posts.length, - isLoading: controller.isLoading, - hasReachedMax: controller.hasReachedMax, - onFetchData: controller.fetchMore, - itemBuilder: (context, index) { - final post = controller.posts[index]; - return PostItem( - item: post, - onRefresh: (_) { - ref.invalidate(postListProvider); - }, - ); - }, - separatorBuilder: (_, __) => const Divider(height: 1), - ), - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: - (e, _) => GestureDetector( - child: Center( - child: Text('Error: $e', textAlign: TextAlign.center), - ), - onTap: () { + body: RefreshIndicator( + onRefresh: + () => Future.sync((() { + ref.invalidate(postListProvider); + })), + child: InfiniteList( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), + itemCount: posts.length, + isLoading: postsNotifier.isLoading, + hasReachedMax: postsNotifier.hasReachedMax, + onFetchData: postsNotifier.fetchMore, + itemBuilder: (context, index) { + final post = posts[index]; + return PostItem( + item: post, + onRefresh: (_) { ref.invalidate(postListProvider); }, - ), + onUpdate: (post) { + postsNotifier.updateOne(index, post); + }, + ); + }, + separatorBuilder: (_, __) => const Divider(height: 1), + ), ), ); } } -final postListProvider = FutureProvider<_PostListController>((ref) async { - final client = ref.watch(apiClientProvider); - final controller = _PostListController(client); - await controller.fetchMore(); - return controller; -}); +final postListProvider = + StateNotifierProvider<_PostListController, List>((ref) { + final client = ref.watch(apiClientProvider); + return _PostListController(client); + }); -class _PostListController { - _PostListController(this._dio); +class _PostListController extends StateNotifier> { + _PostListController(this._dio) : super([]); final Dio _dio; - final List posts = []; bool isLoading = false; bool hasReachedMax = false; int offset = 0; @@ -109,10 +98,16 @@ class _PostListController { final headerTotal = int.tryParse(response.headers['x-total']?.first ?? ''); if (headerTotal != null) total = headerTotal; - posts.addAll(fetched); + state = [...state, ...fetched]; offset += fetched.length; - if (posts.length >= total) hasReachedMax = true; + if (state.length >= total) hasReachedMax = true; isLoading = false; } + + void updateOne(int index, SnPost post) { + final updatedPosts = [...state]; + updatedPosts[index] = post; + state = updatedPosts; + } } diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index 9de0aaf..d828504 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -2,11 +2,11 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_quill/flutter_quill.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; @@ -21,6 +21,7 @@ import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/post/publishers_modal.dart'; +import 'package:markdown_editor_plus/widgets/markdown_auto_preview.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -88,14 +89,9 @@ class PostComposeScreen extends HookConsumerWidget { final descriptionController = useTextEditingController( text: originalPost?.description, ); - final contentController = useMemoized(() => QuillController.basic()); - - useEffect(() { - if (originalPost?.content != null) { - contentController.document = Document.fromJson(originalPost!.content!); - } - return null; - }, [originalPost]); + final contentController = useTextEditingController( + text: originalPost?.content, + ); final submitting = useState(false); @@ -192,14 +188,14 @@ class PostComposeScreen extends HookConsumerWidget { await Future.wait( attachments.value .where((e) => e.isOnDevice) - .map((e) => uploadAttachment(e.data)), + .mapIndexed((idx, e) => uploadAttachment(idx)), ); final client = ref.watch(apiClientProvider); await client.request( originalPost == null ? '/posts' : '/posts/${originalPost!.id}', data: { - 'content': contentController.document.toDelta().toJson(), + 'content': contentController.text, 'attachments': attachments.value .where((e) => e.isOnCloud) @@ -255,10 +251,15 @@ class PostComposeScreen extends HookConsumerWidget { GestureDetector( child: ProfilePictureWidget( fileId: currentPublisher.value?.pictureId, - radius: 24, + radius: 20, + fallbackIcon: + currentPublisher.value == null + ? Symbols.question_mark + : null, ), onTap: () { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => PublisherModal(), ).then((value) { @@ -292,11 +293,18 @@ class PostComposeScreen extends HookConsumerWidget { FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(12), - QuillEditor.basic( - controller: contentController, - config: QuillEditorConfig( - placeholder: 'postPlaceholder'.tr(), + TapRegion( + child: MarkdownAutoPreview( + controller: contentController, + emojiConvert: true, + hintText: 'postPlaceholder'.tr(), + decoration: InputDecoration( + border: InputBorder.none, + ), ), + onTapOutside: + (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(8), Column( @@ -337,29 +345,17 @@ class PostComposeScreen extends HookConsumerWidget { ), Material( elevation: 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: QuillSimpleToolbar( - controller: contentController, - config: QuillSimpleToolbarConfig(showFontFamily: false), - ), + IconButton( + onPressed: pickPhotoMedia, + icon: const Icon(Symbols.add_a_photo), + color: Theme.of(context).colorScheme.primary, ), - Row( - children: [ - IconButton( - onPressed: pickPhotoMedia, - icon: const Icon(Symbols.add_a_photo), - color: Theme.of(context).colorScheme.primary, - ), - IconButton( - onPressed: pickVideoMedia, - icon: const Icon(Symbols.videocam), - color: Theme.of(context).colorScheme.primary, - ), - ], + IconButton( + onPressed: pickVideoMedia, + icon: const Icon(Symbols.videocam), + color: Theme.of(context).colorScheme.primary, ), ], ).padding( @@ -510,49 +506,46 @@ class AttachmentPreview extends StatelessWidget { Positioned( top: 8, right: 8, - child: Material( - color: Colors.transparent, - child: InkWell( + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => onRequestUpload?.call(), + child: ClipRRect( borderRadius: BorderRadius.circular(8), - onTap: () => onRequestUpload?.call(), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Container( - color: Colors.black.withOpacity(0.5), - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: - (item.isOnCloud) - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Symbols.cloud, - size: 16, - color: Colors.white, - ), - const Gap(8), - Text( - 'On-cloud', - style: TextStyle(color: Colors.white), - ), - ], - ) - : Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Symbols.cloud_off, - size: 16, - color: Colors.white, - ), - const Gap(8), - Text( - 'On-device', - style: TextStyle(color: Colors.white), - ), - ], - ), - ), + child: Container( + color: Colors.black.withOpacity(0.5), + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: + (item.isOnCloud) + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Symbols.cloud, + size: 16, + color: Colors.white, + ), + const Gap(8), + Text( + 'On-cloud', + style: TextStyle(color: Colors.white), + ), + ], + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Symbols.cloud_off, + size: 16, + color: Colors.white, + ), + const Gap(8), + Text( + 'On-device', + style: TextStyle(color: Colors.white), + ), + ], + ), ), ), ), diff --git a/lib/screens/realm/detail.dart b/lib/screens/realm/detail.dart index 56dcc00..1f3b725 100644 --- a/lib/screens/realm/detail.dart +++ b/lib/screens/realm/detail.dart @@ -80,7 +80,8 @@ class RealmDetailScreen extends HookConsumerWidget { IconButton( icon: const Icon(Icons.people, shadows: [iconShadow]), onPressed: () { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => @@ -236,7 +237,8 @@ class _RealmMemberListSheet extends HookConsumerWidget { }, []); Future invitePerson() async { - final result = await showCupertinoModalBottomSheet( + final result = await showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => const AccountPickerSheet(), ); @@ -258,111 +260,96 @@ class _RealmMemberListSheet extends HookConsumerWidget { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), - child: Material( - color: Colors.transparent, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: 16, - left: 20, - right: 16, - bottom: 12, - ), - child: Row( - children: [ - Text( - 'members'.plural(memberState.total), - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), + child: Row( + children: [ + Text( + 'members'.plural(memberState.total), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, ), - const Spacer(), - IconButton( - icon: const Icon(Symbols.person_add), - onPressed: invitePerson, - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - IconButton( - icon: const Icon(Symbols.refresh), - onPressed: () { - memberNotifier.reset(); - memberNotifier.loadMore(); - }, - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - ], - ), + ), + const Spacer(), + IconButton( + icon: const Icon(Symbols.person_add), + onPressed: invitePerson, + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + IconButton( + icon: const Icon(Symbols.refresh), + onPressed: () { + memberNotifier.reset(); + memberNotifier.loadMore(); + }, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], ), - const Divider(height: 1), - Expanded( - child: - memberState.error != null - ? Center(child: Text(memberState.error!)) - : ListView.builder( - itemCount: memberState.members.length + 1, - itemBuilder: (context, index) { - if (index == memberState.members.length) { - if (memberState.isLoading) { - return const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ); - } - if (memberState.members.length < - memberState.total) { - memberNotifier.loadMore( - offset: memberState.members.length, - ); - } - return const SizedBox.shrink(); + ), + const Divider(height: 1), + Expanded( + child: + memberState.error != null + ? Center(child: Text(memberState.error!)) + : ListView.builder( + itemCount: memberState.members.length + 1, + itemBuilder: (context, index) { + if (index == memberState.members.length) { + if (memberState.isLoading) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); } + if (memberState.members.length < memberState.total) { + memberNotifier.loadMore( + offset: memberState.members.length, + ); + } + return const SizedBox.shrink(); + } - final member = memberState.members[index]; - return ListTile( - leading: ProfilePictureWidget( - fileId: member.account!.profile.pictureId, - ), - title: Row( - spacing: 6, - children: [ - Flexible(child: Text(member.account!.nick)), - if (member.joinedAt == null) - const Icon(Symbols.pending_actions, size: 20), - ], - ), - subtitle: Row( - children: [ - Text( - member.role >= 100 - ? 'permissionOwner' - : member.role >= 50 - ? 'permissionModerator' - : 'permissionMember', - ).tr(), - Text('ยท').bold().padding(horizontal: 6), - Expanded( - child: Text("@${member.account!.name}"), - ), - ], - ), - ); - }, - ), - ), - ], - ), + final member = memberState.members[index]; + return ListTile( + leading: ProfilePictureWidget( + fileId: member.account!.profile.pictureId, + ), + title: Row( + spacing: 6, + children: [ + Flexible(child: Text(member.account!.nick)), + if (member.joinedAt == null) + const Icon(Symbols.pending_actions, size: 20), + ], + ), + subtitle: Row( + children: [ + Text( + member.role >= 100 + ? 'permissionOwner' + : member.role >= 50 + ? 'permissionModerator' + : 'permissionMember', + ).tr(), + Text('ยท').bold().padding(horizontal: 6), + Expanded(child: Text("@${member.account!.name}")), + ], + ), + ); + }, + ), + ), + ], ), ); } diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart index df2b518..2e05ae4 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/screens/realm/realms.dart @@ -359,102 +359,90 @@ class _RealmInviteSheet extends HookConsumerWidget { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), - child: Material( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only( - top: 16, - left: 20, - right: 16, - bottom: 12, - ), - child: Row( - children: [ - Text( - 'invites'.tr(), - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - letterSpacing: -0.5, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), + child: Row( + children: [ + Text( + 'invites'.tr(), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, ), - const Spacer(), - IconButton( - icon: const Icon(Symbols.refresh), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - onPressed: () { - ref.invalidate(realmInvitesProvider); - }, - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => Navigator.pop(context), - style: IconButton.styleFrom( - minimumSize: const Size(36, 36), - ), - ), - ], - ), + ), + const Spacer(), + IconButton( + icon: const Icon(Symbols.refresh), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + onPressed: () { + ref.invalidate(realmInvitesProvider); + }, + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], ), - const Divider(height: 1), - Expanded( - child: invites.when( - data: - (items) => - items.isEmpty - ? Center( - child: - Text( - 'invitesEmpty', - textAlign: TextAlign.center, - ).tr(), - ) - : ListView.builder( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (context, index) { - final invite = items[index]; - return ListTile( - leading: ProfilePictureWidget( - fileId: invite.realm!.pictureId, - radius: 24, - fallbackIcon: Symbols.group, - ), - title: Text(invite.realm!.name), - subtitle: - Text( - invite.role >= 100 - ? 'permissionOwner' - : invite.role >= 50 - ? 'permissionModerator' - : 'permissionMember', - ).tr(), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Symbols.check), - onPressed: () => acceptInvite(invite), - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => declineInvite(invite), - ), - ], - ), - ); - }, - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => Center(child: Text('Error: $error')), - ), + ), + const Divider(height: 1), + Expanded( + child: invites.when( + data: + (items) => + items.isEmpty + ? Center( + child: + Text( + 'invitesEmpty', + textAlign: TextAlign.center, + ).tr(), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final invite = items[index]; + return ListTile( + leading: ProfilePictureWidget( + fileId: invite.realm!.pictureId, + radius: 24, + fallbackIcon: Symbols.group, + ), + title: Text(invite.realm!.name), + subtitle: + Text( + invite.role >= 100 + ? 'permissionOwner' + : invite.role >= 50 + ? 'permissionModerator' + : 'permissionMember', + ).tr(), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Symbols.check), + onPressed: () => acceptInvite(invite), + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => declineInvite(invite), + ), + ], + ), + ); + }, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), ), - ], - ), + ), + ], ), ); } diff --git a/lib/widgets/account/account_picker.dart b/lib/widgets/account/account_picker.dart index 1c34567..6c24180 100644 --- a/lib/widgets/account/account_picker.dart +++ b/lib/widgets/account/account_picker.dart @@ -47,60 +47,56 @@ class AccountPickerSheet extends HookConsumerWidget { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.4, ), - child: Material( - color: Colors.transparent, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 4), - child: TextField( - controller: searchController, - onChanged: onSearchChanged, - decoration: const InputDecoration( - hintText: 'Search accounts...', - contentPadding: EdgeInsets.symmetric( - horizontal: 18, - vertical: 16, - ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 4), + child: TextField( + controller: searchController, + onChanged: onSearchChanged, + decoration: const InputDecoration( + hintText: 'Search accounts...', + contentPadding: EdgeInsets.symmetric( + horizontal: 18, + vertical: 16, ), - autofocus: true, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), ), + autofocus: true, + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), ), - Expanded( - child: Consumer( - builder: (context, ref, child) { - final searchResult = ref.watch( - searchAccountsProvider(query: searchController.text), - ); + ), + Expanded( + child: Consumer( + builder: (context, ref, child) { + final searchResult = ref.watch( + searchAccountsProvider(query: searchController.text), + ); - return searchResult.when( - data: - (accounts) => ListView.builder( - itemCount: accounts.length, - itemBuilder: (context, index) { - final account = accounts[index]; - return ListTile( - leading: ProfilePictureWidget( - fileId: account.profile.pictureId, - ), - title: Text(account.nick), - subtitle: Text('@${account.name}'), - onTap: () => Navigator.of(context).pop(account), - ); - }, - ), - loading: - () => const Center(child: CircularProgressIndicator()), - error: - (error, stack) => Center(child: Text('Error: $error')), - ); - }, - ), + return searchResult.when( + data: + (accounts) => ListView.builder( + itemCount: accounts.length, + itemBuilder: (context, index) { + final account = accounts[index]; + return ListTile( + leading: ProfilePictureWidget( + fileId: account.profile.pictureId, + ), + title: Text(account.nick), + subtitle: Text('@${account.name}'), + onTap: () => Navigator.of(context).pop(account), + ); + }, + ), + loading: + () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), + ); + }, ), - ], - ), + ), + ], ), ); } diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 517714c..1f845ca 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_quill/flutter_quill.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post.dart'; import 'package:island/pods/network.dart'; @@ -12,7 +11,7 @@ import 'package:island/route.gr.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_file_collection.dart'; import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/quill_content.dart'; +import 'package:island/widgets/content/markdown.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; @@ -22,12 +21,14 @@ class PostItem extends HookConsumerWidget { final EdgeInsets? padding; final bool isOpenable; final Function? onRefresh; + final Function(SnPost)? onUpdate; const PostItem({ super.key, required this.item, this.padding, this.isOpenable = true, this.onRefresh, + this.onUpdate, }); @override @@ -113,9 +114,7 @@ class PostItem extends HookConsumerWidget { children: [ Text(item.publisher.nick).bold(), if (item.content?.isNotEmpty ?? false) - QuillContent( - document: Document.fromJson(item.content!), - ), + MarkdownTextContent(content: item.content!), ], ), onTap: () { @@ -129,6 +128,19 @@ class PostItem extends HookConsumerWidget { ), if (item.attachments.isNotEmpty) CloudFileList(files: item.attachments), + PostReactionList( + parentId: item.id, + reactions: item.reactionsCount, + padding: EdgeInsets.only(left: 48), + onReact: (symbol, attitude, delta) { + final reactionsCount = Map.from( + item.reactionsCount, + ); + reactionsCount[symbol] = + (reactionsCount[symbol] ?? 0) + delta; + onUpdate?.call(item.copyWith(reactionsCount: reactionsCount)); + }, + ), ], ), ), @@ -136,3 +148,214 @@ class PostItem extends HookConsumerWidget { ); } } + +class PostReactionList extends HookConsumerWidget { + final int parentId; + final Map reactions; + final Function(String symbol, int attitude, int delta) onReact; + final EdgeInsets? padding; + const PostReactionList({ + super.key, + required this.parentId, + required this.reactions, + this.padding, + required this.onReact, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final submitting = useState(false); + + Future reactPost(String symbol, int attitude) async { + final client = ref.watch(apiClientProvider); + submitting.value = true; + await client + .post( + '/posts/$parentId/reactions', + data: {'symbol': symbol, 'attitude': attitude}, + ) + .catchError((err) { + showErrorAlert(err); + return err; + }) + .then((resp) { + var isRemoving = resp.statusCode == 204; + onReact(symbol, attitude, isRemoving ? -1 : 1); + HapticFeedback.heavyImpact(); + }); + submitting.value = false; + } + + return SizedBox( + height: 28, + child: ListView( + scrollDirection: Axis.horizontal, + padding: padding ?? EdgeInsets.zero, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: ActionChip( + avatar: Icon(Symbols.add_reaction), + label: Text('react').tr(), + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + onPressed: + submitting.value + ? null + : () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return _PostReactionSheet( + reactionsCount: reactions, + onReact: (symbol, attitude) { + reactPost(symbol, attitude); + }, + ); + }, + ); + }, + ), + ), + for (final symbol in reactions.keys) + Padding( + padding: const EdgeInsets.only(right: 8), + child: ActionChip( + avatar: Text(kReactionTemplates[symbol]?.icon ?? '?'), + label: Row( + spacing: 4, + children: [ + Text(symbol), + Text('x${reactions[symbol]}').bold(), + ], + ), + onPressed: + submitting.value + ? null + : () { + reactPost( + symbol, + kReactionTemplates[symbol]?.attitude ?? 0, + ); + }, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + ), + ), + ], + ), + ); + } +} + +class _PostReactionSheet extends StatelessWidget { + final Map reactionsCount; + final Function(String symbol, int attitude) onReact; + const _PostReactionSheet({ + required this.reactionsCount, + required this.onReact, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), + child: Row( + children: [ + Text( + 'reactions'.plural( + reactionsCount.isNotEmpty + ? reactionsCount.values.reduce((a, b) => a + b) + : 0, + ), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.5, + ), + ), + const Spacer(), + + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => Navigator.pop(context), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + ), + ], + ), + ), + const Divider(height: 1), + Expanded( + child: ListView( + children: [ + _buildReactionSection(context, 'Positive Reactions', 0), + _buildReactionSection(context, 'Neutral Reactions', 1), + _buildReactionSection(context, 'Negative Reactions', 2), + ], + ), + ), + ], + ); + } + + Widget _buildReactionSection( + BuildContext context, + String title, + int attitude, + ) { + final allReactions = + kReactionTemplates.entries + .where((entry) => entry.value.attitude == attitude) + .map((entry) => entry.key) + .toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title).fontSize(20).bold().padding(horizontal: 20, vertical: 12), + SizedBox( + height: 84, + child: GridView.builder( + scrollDirection: Axis.horizontal, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 1, + mainAxisExtent: 100, + mainAxisSpacing: 8.0, + crossAxisSpacing: 8.0, + childAspectRatio: 2.0, + ), + itemCount: allReactions.length, + itemBuilder: (context, index) { + final symbol = allReactions[index]; + final count = reactionsCount[symbol] ?? 0; + return InkWell( + onTap: () { + onReact(symbol, attitude); + Navigator.pop(context); + }, + child: GridTile( + header: Text( + kReactionTemplates[symbol]?.icon ?? '', + textAlign: TextAlign.center, + ).fontSize(24), + footer: Text( + count > 0 ? 'x$count' : '', + textAlign: TextAlign.center, + ).bold().padding(bottom: 12), + child: Center( + child: Text(symbol, textAlign: TextAlign.center), + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/post/post_quick_reply.dart b/lib/widgets/post/post_quick_reply.dart index 9b473d4..c7fbf2f 100644 --- a/lib/widgets/post/post_quick_reply.dart +++ b/lib/widgets/post/post_quick_reply.dart @@ -70,7 +70,8 @@ class PostQuickReply extends HookConsumerWidget { radius: 16, ), onTap: () { - showCupertinoModalBottomSheet( + showModalBottomSheet( + isScrollControlled: true, context: context, builder: (context) => PublisherModal(), ).then((value) { diff --git a/lib/widgets/post/publishers_modal.dart b/lib/widgets/post/publishers_modal.dart index 11ad7ae..692553e 100644 --- a/lib/widgets/post/publishers_modal.dart +++ b/lib/widgets/post/publishers_modal.dart @@ -22,66 +22,62 @@ class PublisherModal extends HookConsumerWidget { child: Column( children: [ Expanded( - child: Material( - color: Colors.transparent, - child: publishers.when( - data: - (value) => - value.isEmpty - ? ConstrainedBox( - constraints: BoxConstraints(maxWidth: 280), - child: - Column( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'publishersEmpty', - textAlign: TextAlign.center, - ).tr().fontSize(17).bold(), - Text( - 'publishersEmptyDescription', - textAlign: TextAlign.center, - ).tr(), - const Gap(12), - ElevatedButton( - onPressed: () { - context.router - .push(NewPublisherRoute()) - .then((value) { - if (value != null) { - ref.invalidate( - publishersManagedProvider, - ); - } - }); - }, - child: Text('createPublisher').tr(), - ), - ], - ).center(), - ) - : SingleChildScrollView( - child: Column( - children: [ - for (final publisher in value) - ListTile( - leading: ProfilePictureWidget( - fileId: publisher.picture?.id, - ), - title: Text(publisher.nick), - subtitle: Text('@${publisher.name}'), - onTap: () { - Navigator.pop(context, publisher); + child: publishers.when( + data: + (value) => + value.isEmpty + ? ConstrainedBox( + constraints: BoxConstraints(maxWidth: 280), + child: + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'publishersEmpty', + textAlign: TextAlign.center, + ).tr().fontSize(17).bold(), + Text( + 'publishersEmptyDescription', + textAlign: TextAlign.center, + ).tr(), + const Gap(12), + ElevatedButton( + onPressed: () { + context.router + .push(NewPublisherRoute()) + .then((value) { + if (value != null) { + ref.invalidate( + publishersManagedProvider, + ); + } + }); }, + child: Text('createPublisher').tr(), ), - ], - ), + ], + ).center(), + ) + : SingleChildScrollView( + child: Column( + children: [ + for (final publisher in value) + ListTile( + leading: ProfilePictureWidget( + fileId: publisher.picture?.id, + ), + title: Text(publisher.nick), + subtitle: Text('@${publisher.name}'), + onTap: () { + Navigator.pop(context, publisher); + }, + ), + ], ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (e, _) => Text('Error: $e'), - ), + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (e, _) => Text('Error: $e'), ), ), ], diff --git a/lib/widgets/quill_content.dart b/lib/widgets/quill_content.dart deleted file mode 100644 index 1a68abe..0000000 --- a/lib/widgets/quill_content.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -class QuillContent extends HookConsumerWidget { - final Document document; - const QuillContent({super.key, required this.document}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final controller = useMemoized(() => QuillController.basic()); - - useEffect(() { - controller.document = document; - controller.readOnly = true; - return null; - }, [document]); - - return QuillEditor.basic( - controller: controller, - config: const QuillEditorConfig(), - ); - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7f9cef8..960c824 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -19,7 +19,6 @@ import media_kit_libs_macos_video import media_kit_video import package_info_plus import path_provider_foundation -import quill_native_bridge_macos import shared_preferences_foundation import sqflite_darwin import sqlite3_flutter_libs @@ -43,7 +42,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 556d41d..0cced39 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -313,14 +313,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -361,14 +353,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0+7.3.0" - dart_quill_delta: - dependency: transitive - description: - name: dart_quill_delta - sha256: bddb0b2948bd5b5a328f1651764486d162c59a8ccffd4c63e8b2c5e44be1dac4 - url: "https://pub.dev" - source: hosted - version: "10.8.3" dart_style: dependency: transitive description: @@ -401,14 +385,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.2" - diff_match_patch: - dependency: transitive - description: - name: diff_match_patch - sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" - url: "https://pub.dev" - source: hosted - version: "0.4.1" dio: dependency: "direct main" description: @@ -429,18 +405,18 @@ packages: dependency: "direct main" description: name: drift - sha256: "14a61af39d4584faf1d73b5b35e4b758a43008cf4c0fdb0576ec8e7032c0d9a5" + sha256: b584ddeb2b74436735dd2cf746d2d021e19a9a6770f409212fd5cbc2814ada85 url: "https://pub.dev" source: hosted - version: "2.26.0" + version: "2.26.1" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588" + sha256: "54dc207c6e4662741f60e5752678df183957ab907754ffab0372a7082f6d2816" url: "https://pub.dev" source: hosted - version: "2.26.0" + version: "2.26.1" drift_flutter: dependency: "direct main" description: @@ -473,6 +449,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + expandable: + dependency: transitive + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" fake_async: dependency: transitive description: @@ -614,14 +598,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" - flutter_colorpicker: - dependency: transitive - description: - name: flutter_colorpicker - sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" - url: "https://pub.dev" - source: hosted - version: "1.1.0" flutter_expandable_fab: dependency: "direct main" description: @@ -710,46 +686,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_temp_fork: - dependency: transitive - description: - name: flutter_keyboard_visibility_temp_fork - sha256: e3d02900640fbc1129245540db16944a0898b8be81694f4bf04b6c985bed9048 - url: "https://pub.dev" - source: hosted - version: "0.1.5" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_lints: dependency: "direct dev" description: @@ -803,22 +739,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.28" - flutter_quill: - dependency: "direct main" - description: - name: flutter_quill - sha256: de019f6160023d36ad3e89343da6d740ab66ae7839875c89871fbeabcb9e8f9b - url: "https://pub.dev" - source: hosted - version: "11.4.0" - flutter_quill_delta_from_html: - dependency: transitive - description: - name: flutter_quill_delta_from_html - sha256: "4597bd0853a704696837aa6b05cffd851f587b176204c234edddfed1c1862a09" - url: "https://pub.dev" - source: hosted - version: "1.5.2" flutter_riverpod: dependency: "direct main" description: @@ -853,6 +773,14 @@ packages: description: flutter source: sdk version: "0.0.0" + font_awesome_flutter: + dependency: transitive + description: + name: font_awesome_flutter + sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a + url: "https://pub.dev" + source: hosted + version: "10.8.0" freezed: dependency: "direct dev" description: @@ -933,14 +861,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.0" - html: - dependency: transitive - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" - source: hosted - version: "0.15.6" http: dependency: transitive description: @@ -1149,6 +1069,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.3.0" + markdown_editor_plus: + dependency: "direct main" + description: + name: markdown_editor_plus + sha256: "8a4749e5b80330674dc20c65281fb2171ff583136a5ffb269802bd8fe818a7ce" + url: "https://pub.dev" + source: hosted + version: "0.2.15" matcher: dependency: transitive description: @@ -1437,78 +1365,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" - quill_native_bridge: - dependency: transitive - description: - name: quill_native_bridge - sha256: "00752aca7d67cbd3254709a47558be78427750cb81aa42cfbed354d4a079bcfa" - url: "https://pub.dev" - source: hosted - version: "11.0.1" - quill_native_bridge_android: - dependency: transitive - description: - name: quill_native_bridge_android - sha256: b75c7e6ede362a7007f545118e756b1f19053994144ec9eda932ce5e54a57569 - url: "https://pub.dev" - source: hosted - version: "0.0.1+2" - quill_native_bridge_ios: - dependency: transitive - description: - name: quill_native_bridge_ios - sha256: d23de3cd7724d482fe2b514617f8eedc8f296e120fb297368917ac3b59d8099f - url: "https://pub.dev" - source: hosted - version: "0.0.1" - quill_native_bridge_linux: - dependency: transitive - description: - name: quill_native_bridge_linux - sha256: "5fcc60cab2ab9079e0746941f05c5ca5fec85cc050b738c8c8b9da7c09da17eb" - url: "https://pub.dev" - source: hosted - version: "0.0.1" - quill_native_bridge_macos: - dependency: transitive - description: - name: quill_native_bridge_macos - sha256: "1c0631bd1e2eee765a8b06017c5286a4e829778f4585736e048eb67c97af8a77" - url: "https://pub.dev" - source: hosted - version: "0.0.1" - quill_native_bridge_platform_interface: - dependency: transitive - description: - name: quill_native_bridge_platform_interface - sha256: "8264a2bdb8a294c31377a27b46c0f8717fa9f968cf113f7dc52d332ed9c84526" - url: "https://pub.dev" - source: hosted - version: "0.0.2+1" - quill_native_bridge_web: - dependency: transitive - description: - name: quill_native_bridge_web - sha256: "7c723f6824b0250d7f33e8b6c23f2f8eb0103fe48ee7ebf47ab6786b64d5c05d" - url: "https://pub.dev" - source: hosted - version: "0.0.2" - quill_native_bridge_windows: - dependency: transitive - description: - name: quill_native_bridge_windows - sha256: "60e50d74238f22ceb43113d9a42b6627451dab9fc27f527b979a32051cf1da45" - url: "https://pub.dev" - source: hosted - version: "0.0.1" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" recase: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 07c8795..acf8695 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,13 +83,13 @@ dependencies: flutter_udid: ^4.0.0 firebase_core: ^3.13.0 web_socket_channel: ^3.0.3 - flutter_quill: ^11.4.0 material_symbols_icons: ^4.2815.0 drift: ^2.26.0 drift_flutter: ^0.2.4 path: ^1.9.1 collection: ^1.19.1 flutter_expandable_fab: ^2.5.0 + markdown_editor_plus: ^0.2.15 dev_dependencies: flutter_test: