diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 7803aa9..9481cfc 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -47,6 +47,7 @@ "postCreateAccountNext": "What's next?", "postCreateAccountNext1": "Go to your email inbox and receive the account activation email.", "postCreateAccountNext2": "Log in to your account and start exploring the Solar Network!", + "postPlaceholder": "What's on your mind?", "publishersEmpty": "No publishers yet", "publishersEmptyDescription": "You can need to create a publisher to start publishing your posts." } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d5cb8fe..5923683 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -75,6 +75,8 @@ 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): @@ -128,6 +130,8 @@ 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) @@ -155,6 +159,7 @@ 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`) @@ -164,6 +169,7 @@ 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`) - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) @@ -203,6 +209,8 @@ 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: @@ -219,6 +227,8 @@ 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: @@ -246,6 +256,7 @@ 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 @@ -260,6 +271,7 @@ 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 46f6f27..8e5ef4d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ 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'; @@ -100,6 +101,7 @@ 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 e379811..1101782 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 String content, + required List<dynamic>? content, required int type, required Map<String, dynamic>? meta, required int viewsUnique, diff --git a/lib/models/post.freezed.dart b/lib/models/post.freezed.dart index ab2c4d2..ae3d95b 100644 --- a/lib/models/post.freezed.dart +++ b/lib/models/post.freezed.dart @@ -16,7 +16,7 @@ T _$identity<T>(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; String get content; int get type; Map<String, dynamic>? 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<SnCloudFile> get attachments; SnPublisher get publisher; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; bool get empty; DateTime get createdAt; DateTime get updatedAt; dynamic get deletedAt; + int get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; List<dynamic>? get content; int get type; Map<String, dynamic>? 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<SnCloudFile> get attachments; SnPublisher get publisher; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; bool get empty; DateTime get createdAt; DateTime get updatedAt; dynamic get deletedAt; /// Create a copy of SnPost /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -29,12 +29,12 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&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.empty, empty) || other.empty == empty)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(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)&&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.empty, empty) || other.empty == empty)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,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),empty,createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)]); +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),empty,createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)]); @override String toString() { @@ -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, String content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, bool empty, DateTime createdAt, DateTime updatedAt, dynamic deletedAt + int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, List<dynamic>? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, bool empty, DateTime createdAt, DateTime updatedAt, dynamic 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 = null,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? empty = 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? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? empty = 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 @@ -75,8 +75,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: null == 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,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as List<dynamic>?,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<String, dynamic>?,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 @@ -118,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 this.content, required this.type, required final Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final List<SnCloudFile> attachments, required this.publisher, required final List<dynamic> reactions, required final List<dynamic> tags, required final List<dynamic> categories, required final List<dynamic> collections, required this.empty, required this.createdAt, required this.updatedAt, required this.deletedAt}): _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 final List<dynamic>? content, required this.type, required final Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final List<SnCloudFile> attachments, required this.publisher, required final List<dynamic> reactions, required final List<dynamic> tags, required final List<dynamic> categories, required final List<dynamic> collections, required this.empty, required this.createdAt, required this.updatedAt, required this.deletedAt}): _content = content,_meta = meta,_attachments = attachments,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); @override final int id; @@ -128,7 +128,15 @@ class _SnPost implements SnPost { @override final DateTime? editedAt; @override final DateTime publishedAt; @override final int visibility; -@override final String content; + final List<dynamic>? _content; +@override List<dynamic>? 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 int type; final Map<String, dynamic>? _meta; @override Map<String, dynamic>? get meta { @@ -203,12 +211,12 @@ Map<String, dynamic> toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&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.empty, empty) || other.empty == empty)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(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)&&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.empty, empty) || other.empty == empty)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&const DeepCollectionEquality().equals(other.deletedAt, deletedAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,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),empty,createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)]); +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),empty,createdAt,updatedAt,const DeepCollectionEquality().hash(deletedAt)]); @override String toString() { @@ -223,7 +231,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, String content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, bool empty, DateTime createdAt, DateTime updatedAt, dynamic deletedAt + int id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, List<dynamic>? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, dynamic threadedPostId, dynamic threadedPost, dynamic repliedPostId, dynamic repliedPost, dynamic forwardedPostId, dynamic forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, bool empty, DateTime createdAt, DateTime updatedAt, dynamic deletedAt }); @@ -240,7 +248,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 = null,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? empty = 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? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? empty = 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 @@ -249,8 +257,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: null == 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,content: freezed == content ? _self._content : content // ignore: cast_nullable_to_non_nullable +as List<dynamic>?,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<String, dynamic>?,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 diff --git a/lib/models/post.g.dart b/lib/models/post.g.dart index c8206fe..8eb5afd 100644 --- a/lib/models/post.g.dart +++ b/lib/models/post.g.dart @@ -17,7 +17,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> 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 String, + content: json['content'] as List<dynamic>?, type: (json['type'] as num).toInt(), meta: json['meta'] as Map<String, dynamic>?, viewsUnique: (json['views_unique'] as num).toInt(), diff --git a/lib/screens/account.dart b/lib/screens/account.dart index c71deb3..868b474 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -7,7 +7,7 @@ import 'package:island/pods/userinfo.dart'; import 'package:island/route.gr.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @RoutePage() @@ -76,8 +76,8 @@ class AccountScreen extends HookConsumerWidget { const Gap(8), ListTile( minTileHeight: 48, - leading: const Icon(LucideIcons.bookOpen), - trailing: const Icon(LucideIcons.chevronRight), + leading: const Icon(Symbols.public), + trailing: const Icon(Symbols.chevron_right), contentPadding: EdgeInsets.symmetric(horizontal: 24), title: Text('managedPublisher').tr(), onTap: () { @@ -86,8 +86,8 @@ class AccountScreen extends HookConsumerWidget { ), ListTile( minTileHeight: 48, - leading: const Icon(LucideIcons.edit), - trailing: const Icon(LucideIcons.chevronRight), + leading: const Icon(Symbols.edit), + trailing: const Icon(Symbols.chevron_right), contentPadding: EdgeInsets.symmetric(horizontal: 24), title: Text('updateYourProfile').tr(), onTap: () { @@ -97,8 +97,8 @@ class AccountScreen extends HookConsumerWidget { const Divider(height: 1).padding(vertical: 4), ListTile( minTileHeight: 48, - leading: const Icon(LucideIcons.logOut), - trailing: const Icon(LucideIcons.chevronRight), + leading: const Icon(Symbols.logout), + trailing: const Icon(Symbols.chevron_right), contentPadding: EdgeInsets.symmetric(horizontal: 24), title: Text('logout').tr(), onTap: () { @@ -123,8 +123,8 @@ class _UnauthorizedAccountScreen extends StatelessWidget { body: Column( children: <Widget>[ ListTile( - leading: const Icon(LucideIcons.userPlus), - trailing: const Icon(LucideIcons.chevronRight), + leading: const Icon(Symbols.person_add), + trailing: const Icon(Symbols.chevron_right), title: Text('createAccount').tr(), subtitle: Text('New to here? We got you covered!'), contentPadding: EdgeInsets.symmetric(horizontal: 24), @@ -133,8 +133,8 @@ class _UnauthorizedAccountScreen extends StatelessWidget { }, ), ListTile( - leading: const Icon(LucideIcons.logIn), - trailing: const Icon(LucideIcons.chevronRight), + leading: const Icon(Symbols.login), + trailing: const Icon(Symbols.chevron_right), subtitle: Text('Existing user? We\'re welcome you back!'), contentPadding: EdgeInsets.symmetric(horizontal: 24), title: Text('login').tr(), diff --git a/lib/screens/account/me/publishers.dart b/lib/screens/account/me/publishers.dart index f1b4979..4c4d44f 100644 --- a/lib/screens/account/me/publishers.dart +++ b/lib/screens/account/me/publishers.dart @@ -15,7 +15,7 @@ import 'package:island/services/file.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -50,10 +50,10 @@ class ManagedPublisherScreen extends HookConsumerWidget { (value) => Column( children: [ ListTile( - leading: const Icon(LucideIcons.plus), + leading: const Icon(Symbols.add), title: Text('Create a publisher').tr(), subtitle: Text('To create posts, collections, etc.'), - trailing: const Icon(LucideIcons.chevronRight), + trailing: const Icon(Symbols.chevron_right), contentPadding: const EdgeInsets.symmetric(horizontal: 24), onTap: () { context.router.push(NewPublisherRoute()); @@ -79,7 +79,7 @@ class ManagedPublisherScreen extends HookConsumerWidget { IconButton( padding: EdgeInsets.zero, visualDensity: VisualDensity.compact, - icon: Icon(LucideIcons.trash, size: 16), + icon: Icon(Symbols.delete, size: 16), onPressed: () { showConfirmAlert( 'deletePublisherHint'.tr(), @@ -102,7 +102,7 @@ class ManagedPublisherScreen extends HookConsumerWidget { IconButton( padding: EdgeInsets.zero, visualDensity: VisualDensity.compact, - icon: Icon(LucideIcons.edit, size: 16), + icon: Icon(Symbols.edit, size: 16), onPressed: () { context.router .push( @@ -354,12 +354,12 @@ class EditPublisherScreen extends HookConsumerWidget { background.value = user.value!.profile.background; }, label: Text('syncPublisher'.tr()), - icon: const Icon(LucideIcons.refreshCcw), + icon: const Icon(Symbols.link), ), TextButton.icon( onPressed: submitting.value ? null : performAction, label: Text('saveChanges'.tr()), - icon: const Icon(LucideIcons.save), + icon: const Icon(Symbols.save), ), ], ), diff --git a/lib/screens/account/me/update.dart b/lib/screens/account/me/update.dart index 263055c..ed69a6d 100644 --- a/lib/screens/account/me/update.dart +++ b/lib/screens/account/me/update.dart @@ -11,7 +11,7 @@ import 'package:island/services/file.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @RoutePage() @@ -195,7 +195,7 @@ class UpdateProfileScreen extends HookConsumerWidget { child: TextButton.icon( onPressed: submitting.value ? null : updateBasicInfo, label: Text('saveChanges').tr(), - icon: const Icon(LucideIcons.save), + icon: const Icon(Symbols.save), ), ), ], @@ -225,7 +225,7 @@ class UpdateProfileScreen extends HookConsumerWidget { child: TextButton.icon( onPressed: submitting.value ? null : updateProfile, label: Text('saveChanges').tr(), - icon: const Icon(LucideIcons.save), + icon: const Icon(Symbols.save), ), ), ], diff --git a/lib/screens/auth/create_account.dart b/lib/screens/auth/create_account.dart index ddcea58..0c01967 100644 --- a/lib/screens/auth/create_account.dart +++ b/lib/screens/auth/create_account.dart @@ -9,7 +9,7 @@ import 'package:island/pods/network.dart'; import 'package:island/route.gr.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -84,7 +84,7 @@ class CreateAccountScreen extends HookConsumerWidget { alignment: Alignment.centerLeft, child: CircleAvatar( radius: 26, - child: const Icon(LucideIcons.userPlus, size: 28), + child: const Icon(Symbols.person_add, size: 28), ).padding(bottom: 8), ), Text( @@ -220,10 +220,7 @@ class CreateAccountScreen extends HookConsumerWidget { children: [ Text('termAcceptLink').tr(), const Gap(4), - const Icon( - LucideIcons.externalLink, - size: 14, - ), + const Icon(Symbols.launch, size: 14), ], ), onTap: () { @@ -248,7 +245,7 @@ class CreateAccountScreen extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Text("next").tr(), - const Icon(LucideIcons.chevronRight), + const Icon(Symbols.chevron_right), ], ), ), diff --git a/lib/screens/auth/login.dart b/lib/screens/auth/login.dart index fb6900c..57dfc4a 100644 --- a/lib/screens/auth/login.dart +++ b/lib/screens/auth/login.dart @@ -15,7 +15,7 @@ import 'package:island/pods/websocket.dart'; import 'package:island/services/notify.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -156,7 +156,7 @@ class _LoginCheckScreen extends HookConsumerWidget { alignment: Alignment.centerLeft, child: CircleAvatar( radius: 26, - child: const Icon(LucideIcons.squareAsterisk, size: 28), + child: const Icon(Symbols.asterisk, size: 28), ).padding(bottom: 8), ), Text( @@ -191,7 +191,7 @@ class _LoginCheckScreen extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Text('next').tr(), - const Icon(LucideIcons.chevronRight), + const Icon(Symbols.chevron_right), ], ), ), @@ -256,7 +256,7 @@ class _LoginPickerScreen extends HookConsumerWidget { alignment: Alignment.centerLeft, child: CircleAvatar( radius: 26, - child: const Icon(LucideIcons.lock, size: 28), + child: const Icon(Symbols.lock, size: 28), ).padding(bottom: 8), ), Text( @@ -274,7 +274,7 @@ class _LoginPickerScreen extends HookConsumerWidget { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), - secondary: const Icon(LucideIcons.shieldQuestion), + secondary: const Icon(Symbols.question_mark), title: Text('unknown').tr(), enabled: !ticket!.blacklistFactors.contains(x.id), value: factorPicked.value == x.id, @@ -304,7 +304,7 @@ class _LoginPickerScreen extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Text('next'.tr()), - const Icon(LucideIcons.chevronRight), + const Icon(Symbols.chevron_right), ], ), ), @@ -403,7 +403,7 @@ class _LoginLookupScreen extends HookConsumerWidget { alignment: Alignment.centerLeft, child: CircleAvatar( radius: 26, - child: const Icon(LucideIcons.logIn, size: 28), + child: const Icon(Symbols.login, size: 28), ).padding(bottom: 8), ), Text( @@ -439,7 +439,7 @@ class _LoginLookupScreen extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Text('next').tr(), - const Icon(LucideIcons.chevronRight), + const Icon(Symbols.chevron_right), ], ), ), @@ -471,7 +471,7 @@ class _LoginLookupScreen extends HookConsumerWidget { children: [ Text('termAcceptLink'.tr()), const Gap(4), - const Icon(LucideIcons.externalLink, size: 14), + const Icon(Symbols.launch, size: 14), ], ), onTap: () { diff --git a/lib/screens/auth/tabs.dart b/lib/screens/auth/tabs.dart index 77caa51..b0c3e59 100644 --- a/lib/screens/auth/tabs.dart +++ b/lib/screens/auth/tabs.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:island/route.gr.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; @RoutePage() class TabsScreen extends StatelessWidget { @@ -23,11 +23,11 @@ class TabsScreen extends StatelessWidget { destinations: [ NavigationDestination( label: 'Explore', - icon: const Icon(LucideIcons.compass), + icon: const Icon(Symbols.explore), ), NavigationDestination( label: 'Account', - icon: const Icon(LucideIcons.userCircle), + icon: const Icon(Symbols.account_circle), ), ], ), diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 22a642d..b7bae45 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -5,7 +5,7 @@ import 'package:island/route.gr.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/models/post.dart'; import 'package:island/widgets/post/post_item.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:dio/dio.dart'; import 'package:island/pods/network.dart'; @@ -28,7 +28,7 @@ class ExploreScreen extends ConsumerWidget { } }); }, - child: const Icon(LucideIcons.pencil), + child: const Icon(Symbols.edit), ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, body: postAsync.when( diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index b3ee465..bfc44cf 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -3,8 +3,10 @@ import 'dart:typed_data'; import 'package:auto_route/auto_route.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'; @@ -19,7 +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:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -82,13 +84,18 @@ class PostComposeScreen extends HookConsumerWidget { .toList() ?? [], ); - final contentController = useTextEditingController( - text: originalPost?.content, - ); final titleController = useTextEditingController(text: originalPost?.title); 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 submitting = useState(false); @@ -192,7 +199,7 @@ class PostComposeScreen extends HookConsumerWidget { await client.request( originalPost == null ? '/posts' : '/posts/${originalPost!.id}', data: { - 'content': contentController.text, + 'content': contentController.document.toDelta().toJson(), 'attachments': attachments.value .where((e) => e.isOnCloud) @@ -231,8 +238,8 @@ class PostComposeScreen extends HookConsumerWidget { ), ).center() : originalPost != null - ? const Icon(LucideIcons.edit) - : const Icon(LucideIcons.upload), + ? const Icon(Symbols.edit) + : const Icon(Symbols.upload), ), const Gap(8), ], @@ -269,7 +276,7 @@ class PostComposeScreen extends HookConsumerWidget { decoration: InputDecoration.collapsed( hintText: 'Title', ), - style: TextStyle(fontSize: 20), + style: TextStyle(fontSize: 16), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), @@ -279,21 +286,17 @@ class PostComposeScreen extends HookConsumerWidget { decoration: InputDecoration.collapsed( hintText: 'Description', ), - style: TextStyle(fontSize: 18), + style: TextStyle(fontSize: 16), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(12), - TextField( + QuillEditor.basic( controller: contentController, - decoration: InputDecoration.collapsed( - hintText: 'What\'s happened?!', + config: QuillEditorConfig( + placeholder: 'postPlaceholder'.tr(), ), - maxLines: null, - onTapOutside: - (_) => - FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(8), Column( @@ -334,17 +337,29 @@ class PostComposeScreen extends HookConsumerWidget { ), Material( elevation: 2, - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - IconButton( - onPressed: pickPhotoMedia, - icon: const Icon(LucideIcons.imagePlus), - color: Theme.of(context).colorScheme.primary, + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: QuillSimpleToolbar( + controller: contentController, + config: QuillSimpleToolbarConfig(showFontFamily: false), + ), ), - IconButton( - onPressed: pickVideoMedia, - icon: const Icon(LucideIcons.fileVideo2), - 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, + ), + ], ), ], ).padding( @@ -447,7 +462,7 @@ class _AttachmentPreview extends StatelessWidget { InkWell( borderRadius: BorderRadius.circular(8), child: const Icon( - LucideIcons.trash, + Symbols.delete, size: 14, color: Colors.white, ).padding(horizontal: 8, vertical: 6), @@ -466,7 +481,7 @@ class _AttachmentPreview extends StatelessWidget { InkWell( borderRadius: BorderRadius.circular(8), child: const Icon( - LucideIcons.arrowUp, + Symbols.keyboard_arrow_up, size: 14, color: Colors.white, ).padding(horizontal: 8, vertical: 6), @@ -477,7 +492,7 @@ class _AttachmentPreview extends StatelessWidget { InkWell( borderRadius: BorderRadius.circular(8), child: const Icon( - LucideIcons.arrowDown, + Symbols.keyboard_arrow_down, size: 14, color: Colors.white, ).padding(horizontal: 8, vertical: 6), @@ -510,7 +525,7 @@ class _AttachmentPreview extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - LucideIcons.cloud, + Symbols.cloud, size: 16, color: Colors.white, ), @@ -525,7 +540,7 @@ class _AttachmentPreview extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - LucideIcons.cloudOff, + Symbols.cloud_off, size: 16, color: Colors.white, ), diff --git a/lib/widgets/content/cloud_files.dart b/lib/widgets/content/cloud_files.dart index 45a8a74..e6e99ff 100644 --- a/lib/widgets/content/cloud_files.dart +++ b/lib/widgets/content/cloud_files.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/pods/config.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'image.dart'; @@ -56,7 +56,7 @@ class ProfilePictureWidget extends ConsumerWidget { color: Theme.of(context).colorScheme.primaryContainer, child: item == null - ? Icon(LucideIcons.userCircle, size: radius).center() + ? Icon(Symbols.account_circle, size: radius).center() : CloudFileWidget(item: item!), ), ); diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index a215f4c..93db2fa 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -3,6 +3,7 @@ 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'; @@ -11,8 +12,8 @@ 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/content/markdown.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:island/widgets/quill_content.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; @@ -47,7 +48,7 @@ class PostItem extends HookConsumerWidget { if (isAuthor) MenuAction( title: 'edit'.tr(), - image: MenuImage.icon(LucideIcons.edit), + image: MenuImage.icon(Symbols.edit), callback: () { context.router.push(PostEditRoute(id: item.id)).then((value) { if (value != null) { @@ -59,7 +60,7 @@ class PostItem extends HookConsumerWidget { if (isAuthor) MenuAction( title: 'delete'.tr(), - image: MenuImage.icon(LucideIcons.trash), + image: MenuImage.icon(Symbols.delete), callback: () { showConfirmAlert( 'deletePostHint'.tr(), @@ -83,7 +84,7 @@ class PostItem extends HookConsumerWidget { if (isAuthor) MenuSeparator(), MenuAction( title: 'copyLink'.tr(), - image: MenuImage.icon(LucideIcons.link), + image: MenuImage.icon(Symbols.link), callback: () { Clipboard.setData( ClipboardData(text: 'https://solsynth.dev/posts/${item.id}'), @@ -111,8 +112,10 @@ class PostItem extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.publisher.nick).bold(), - if (item.content.isNotEmpty) - MarkdownTextContent(content: item.content), + if (item.content?.isNotEmpty ?? false) + QuillContent( + document: Document.fromJson(item.content!), + ), ], ), onTap: () { diff --git a/lib/widgets/post/post_quick_reply.dart b/lib/widgets/post/post_quick_reply.dart index 43386a4..d7f6771 100644 --- a/lib/widgets/post/post_quick_reply.dart +++ b/lib/widgets/post/post_quick_reply.dart @@ -8,7 +8,7 @@ import 'package:island/screens/account/me/publishers.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/post/publishers_modal.dart'; -import 'package:lucide_icons/lucide_icons.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -106,7 +106,7 @@ class PostQuickReply extends HookConsumerWidget { height: 28, child: CircularProgressIndicator(strokeWidth: 3), ) - : Icon(LucideIcons.send, size: 20), + : Icon(Symbols.send, size: 20), color: Theme.of(context).colorScheme.primary, onPressed: submitting.value ? null : performAction, ), diff --git a/lib/widgets/quill_content.dart b/lib/widgets/quill_content.dart new file mode 100644 index 0000000..1a68abe --- /dev/null +++ b/lib/widgets/quill_content.dart @@ -0,0 +1,25 @@ +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 b0b8b04..150250d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -19,6 +19,7 @@ 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 super_native_extensions @@ -41,6 +42,7 @@ 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")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 5369118..33ee8e8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + chalkdart: + dependency: transitive + description: + name: chalkdart + sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" + url: "https://pub.dev" + source: hosted + version: "3.0.5" characters: dependency: transitive description: @@ -225,6 +233,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -297,6 +313,14 @@ 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: @@ -337,6 +361,14 @@ 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: @@ -369,6 +401,14 @@ 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: @@ -550,6 +590,14 @@ 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_highlight: dependency: "direct main" description: @@ -630,6 +678,46 @@ 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: @@ -683,6 +771,22 @@ 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: @@ -797,6 +901,14 @@ 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: @@ -997,14 +1109,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - lucide_icons: - dependency: "direct main" - description: - name: lucide_icons - sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4 - url: "https://pub.dev" - source: hosted - version: "0.257.0" markdown: dependency: "direct main" description: @@ -1029,6 +1133,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + material_symbols_icons: + dependency: "direct main" + description: + name: material_symbols_icons + sha256: d45b6c36c3effa8cb51b1afb8698107d5ff1f88fa4631428f34a8a01abc295d7 + url: "https://pub.dev" + source: hosted + version: "4.2815.0" media_kit: dependency: "direct main" description: @@ -1293,6 +1405,78 @@ 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" responsive_framework: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2f6926d..cdc428c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,7 +69,6 @@ dependencies: animations: ^2.0.11 package_info_plus: ^8.3.0 device_info_plus: ^11.4.0 - lucide_icons: ^0.257.0 tus_client_dart: git: https://github.com/LittleSheep2Code/tus_client.git cross_file: ^0.3.4+2 @@ -84,6 +83,8 @@ 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 dev_dependencies: flutter_test: