From d2c33dde5ea4ef7f1d0157513023b1cc32bd5882 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 2 May 2025 12:33:35 +0800 Subject: [PATCH] :recycle: Replace lucide icons with material symbols --- assets/i18n/en-US.json | 1 + ios/Podfile.lock | 12 ++ lib/main.dart | 2 + lib/models/post.dart | 2 +- lib/models/post.freezed.dart | 38 ++-- lib/models/post.g.dart | 2 +- lib/screens/account.dart | 22 +- lib/screens/account/me/publishers.dart | 14 +- lib/screens/account/me/update.dart | 6 +- lib/screens/auth/create_account.dart | 11 +- lib/screens/auth/login.dart | 18 +- lib/screens/auth/tabs.dart | 6 +- lib/screens/explore.dart | 4 +- lib/screens/posts/compose.dart | 75 ++++--- lib/widgets/content/cloud_files.dart | 4 +- lib/widgets/post/post_item.dart | 17 +- lib/widgets/post/post_quick_reply.dart | 4 +- lib/widgets/quill_content.dart | 25 +++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 200 +++++++++++++++++- pubspec.yaml | 3 +- 21 files changed, 359 insertions(+), 109 deletions(-) create mode 100644 lib/widgets/quill_content.dart 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? content, required int type, required Map? 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 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? 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; 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? 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; 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 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)&&(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? 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, bool empty, DateTime createdAt, DateTime updatedAt, dynamic deletedAt + 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, 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?,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 @@ -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? 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.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? 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.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 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? _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 int type; final Map? _meta; @override Map? get meta { @@ -203,12 +211,12 @@ 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)&&(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? 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, bool empty, DateTime createdAt, DateTime updatedAt, dynamic deletedAt + 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, 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?,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 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 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?, type: (json['type'] as num).toInt(), meta: json['meta'] as Map?, 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: [ 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: