diff --git a/lib/screens/posts/compose.dart b/lib/screens/posts/compose.dart index a7b9612..4fde655 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/screens/posts/compose.dart @@ -103,30 +103,32 @@ class PostComposeScreen extends HookConsumerWidget { originalPost: originalPost, forwardedPost: effectiveForwardedPost, repliedPost: effectiveRepliedPost, + postType: 0, // Regular post type ), [originalPost, effectiveForwardedPost, effectiveRepliedPost], ); // Add a listener to the entire state to trigger rebuilds final stateNotifier = useMemoized( - () => Listenable.merge([ - state.titleController, - state.descriptionController, - state.contentController, - state.visibility, - state.attachments, - state.attachmentProgress, - state.currentPublisher, - state.submitting, - ]), - [state]); + () => Listenable.merge([ + state.titleController, + state.descriptionController, + state.contentController, + state.visibility, + state.attachments, + state.attachmentProgress, + state.currentPublisher, + state.submitting, + ]), + [state], + ); useListenable(stateNotifier); // Start auto-save when component mounts useEffect(() { if (originalPost == null) { // Only auto-save for new posts, not edits - state.startAutoSave(ref, postType: 0); + state.startAutoSave(ref); } return () => state.stopAutoSave(); }, [state]); @@ -165,13 +167,18 @@ class PostComposeScreen extends HookConsumerWidget { final drafts = ref.read(composeStorageNotifierProvider); if (drafts.isNotEmpty) { final mostRecentDraft = drafts.values.reduce( - (a, b) => (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) ? a : b, + (a, b) => + (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) + ? a + : b, ); // Only load if the draft has meaningful content - if (mostRecentDraft.content?.isNotEmpty == true || mostRecentDraft.title?.isNotEmpty == true) { + if (mostRecentDraft.content?.isNotEmpty == true || + mostRecentDraft.title?.isNotEmpty == true) { state.titleController.text = mostRecentDraft.title ?? ''; - state.descriptionController.text = mostRecentDraft.description ?? ''; + state.descriptionController.text = + mostRecentDraft.description ?? ''; state.contentController.text = mostRecentDraft.content ?? ''; state.visibility.value = mostRecentDraft.visibility; } @@ -298,7 +305,8 @@ class PostComposeScreen extends HookConsumerWidget { state.titleController.text = draft.title ?? ''; state.descriptionController.text = draft.description ?? ''; - state.contentController.text = draft.content ?? ''; + state.contentController.text = + draft.content ?? ''; state.visibility.value = draft.visibility; } }, @@ -322,14 +330,13 @@ class PostComposeScreen extends HookConsumerWidget { state.submitting.value ? null : () => ComposeLogic.performAction( - ref, - state, - context, - originalPost: originalPost, - repliedPost: repliedPost, - forwardedPost: forwardedPost, - postType: 0, // Regular post type - ), + ref, + state, + context, + originalPost: originalPost, + repliedPost: repliedPost, + forwardedPost: forwardedPost, + ), icon: state.submitting.value ? SizedBox( @@ -341,9 +348,7 @@ class PostComposeScreen extends HookConsumerWidget { ), ).center() : Icon( - originalPost != null - ? Symbols.edit - : Symbols.upload, + originalPost != null ? Symbols.edit : Symbols.upload, ), ), const Gap(8), @@ -405,7 +410,6 @@ class PostComposeScreen extends HookConsumerWidget { originalPost: originalPost, repliedPost: repliedPost, forwardedPost: forwardedPost, - postType: 0, // Regular post type ), child: TextField( controller: state.contentController, diff --git a/lib/screens/posts/compose_article.dart b/lib/screens/posts/compose_article.dart index e8f18d3..bbc199d 100644 --- a/lib/screens/posts/compose_article.dart +++ b/lib/screens/posts/compose_article.dart @@ -60,7 +60,10 @@ class ArticleComposeScreen extends HookConsumerWidget { final publishers = ref.watch(publishersManagedProvider); final state = useMemoized( - () => ComposeLogic.createState(originalPost: originalPost), + () => ComposeLogic.createState( + originalPost: originalPost, + postType: 1, // Article type + ), [originalPost], ); @@ -70,7 +73,7 @@ class ArticleComposeScreen extends HookConsumerWidget { if (originalPost == null) { // Only auto-save for new articles, not edits autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) { - ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); + ComposeLogic.saveDraftWithoutUpload(ref, state); }); } return () { @@ -78,7 +81,7 @@ class ArticleComposeScreen extends HookConsumerWidget { state.stopAutoSave(); // Save final draft before disposing if (originalPost == null) { - ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); + ComposeLogic.saveDraftWithoutUpload(ref, state); } ComposeLogic.dispose(state); autoSaveTimer?.cancel(); @@ -362,7 +365,7 @@ class ArticleComposeScreen extends HookConsumerWidget { return PopScope( onPopInvoked: (_) { if (originalPost == null) { - ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1); + ComposeLogic.saveDraftWithoutUpload(ref, state); } }, child: AppScaffold( @@ -410,7 +413,7 @@ class ArticleComposeScreen extends HookConsumerWidget { ), IconButton( icon: const Icon(Symbols.save), - onPressed: () => ComposeLogic.saveDraft(ref, state, postType: 1), + onPressed: () => ComposeLogic.saveDraft(ref, state), tooltip: 'saveDraft'.tr(), ), IconButton( @@ -437,7 +440,6 @@ class ArticleComposeScreen extends HookConsumerWidget { state, context, originalPost: originalPost, - postType: 1, // Article type ), icon: submitting @@ -530,18 +532,17 @@ class ArticleComposeScreen extends HookConsumerWidget { if (isPaste && isModifierPressed) { ComposeLogic.handlePaste(state); } else if (isSave && isModifierPressed) { - ComposeLogic.saveDraft(ref, state, postType: 1); + ComposeLogic.saveDraft(ref, state); + ComposeLogic.saveDraft(ref, state); } else if (isSubmit && isModifierPressed && !state.submitting.value) { ComposeLogic.performAction( ref, state, context, originalPost: originalPost, - postType: 1, // Article type ); } } // Helper method to save article draft - } diff --git a/lib/widgets/post/compose_settings_sheet.dart b/lib/widgets/post/compose_settings_sheet.dart index d3c3ed9..be7c3ab 100644 --- a/lib/widgets/post/compose_settings_sheet.dart +++ b/lib/widgets/post/compose_settings_sheet.dart @@ -27,6 +27,7 @@ class ChipTagInputField extends StatelessWidget { decoration: InputDecoration( label: Text(labelText).tr(), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: const EdgeInsets.all(16), hintText: inputFieldValues.tags.isNotEmpty ? '' : hintText.tr(), errorText: inputFieldValues.error, prefixIconConstraints: BoxConstraints( @@ -51,9 +52,7 @@ class ChipTagInputField extends StatelessWidget { ), color: Theme.of(context).colorScheme.primary, ), - margin: const EdgeInsets.symmetric( - horizontal: 5.0, - ), + margin: const EdgeInsets.only(left: 5), padding: const EdgeInsets.symmetric( horizontal: 10.0, vertical: 5.0, @@ -72,11 +71,8 @@ class ChipTagInputField extends StatelessWidget { ).colorScheme.onPrimary, ), ), - onTap: () { - //print("$tag selected"); - }, ), - const SizedBox(width: 4.0), + const Gap(4), InkWell( child: const Icon( Icons.cancel, diff --git a/lib/widgets/post/compose_shared.dart b/lib/widgets/post/compose_shared.dart index 5c142b6..fb9bc17 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/widgets/post/compose_shared.dart @@ -27,9 +27,10 @@ class ComposeState { final ValueNotifier> attachmentProgress; final ValueNotifier currentPublisher; final ValueNotifier submitting; - final StringTagController tagsController; - final StringTagController categoriesController; + StringTagController tagsController; + StringTagController categoriesController; final String draftId; + int postType; Timer? _autoSaveTimer; ComposeState({ @@ -44,12 +45,13 @@ class ComposeState { required this.tagsController, required this.categoriesController, required this.draftId, + this.postType = 0, }); - void startAutoSave(WidgetRef ref, {int postType = 0}) { + void startAutoSave(WidgetRef ref) { _autoSaveTimer?.cancel(); _autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) { - ComposeLogic.saveDraftWithoutUpload(ref, this, postType: postType); + ComposeLogic.saveDraftWithoutUpload(ref, this); }); } @@ -65,6 +67,7 @@ class ComposeLogic { SnPost? forwardedPost, SnPost? repliedPost, String? draftId, + int postType = 0, }) { final id = draftId ?? DateTime.now().millisecondsSinceEpoch.toString(); final tagsController = StringTagController(); @@ -110,10 +113,11 @@ class ComposeLogic { tagsController: tagsController, categoriesController: categoriesController, draftId: id, + postType: postType, ); } - static ComposeState createStateFromDraft(SnPost draft) { + static ComposeState createStateFromDraft(SnPost draft, {int postType = 0}) { final tagsController = StringTagController(); final categoriesController = StringTagController(); for (var x in draft.tags) { @@ -136,14 +140,11 @@ class ComposeLogic { tagsController: tagsController, categoriesController: categoriesController, draftId: draft.id, + postType: postType, ); } - static Future saveDraft( - WidgetRef ref, - ComposeState state, { - int postType = 0, - }) async { + static Future saveDraft(WidgetRef ref, ComposeState state) async { final hasContent = state.titleController.text.trim().isNotEmpty || state.descriptionController.text.trim().isNotEmpty || @@ -175,7 +176,7 @@ class ComposeLogic { baseUrl: baseUrl, filename: attachment.data.name ?? - (postType == 1 ? 'Article media' : 'Post media'), + (state.postType == 1 ? 'Article media' : 'Post media'), mimetype: attachment.data.mimeType ?? ComposeLogic.getMimeTypeFromFileType(attachment.type), @@ -202,7 +203,7 @@ class ComposeLogic { publishedAt: DateTime.now(), visibility: state.visibility.value, content: state.contentController.text, - type: postType, + type: state.postType, meta: null, viewsUnique: 0, viewsTotal: 0, @@ -252,9 +253,8 @@ class ComposeLogic { static Future saveDraftWithoutUpload( WidgetRef ref, - ComposeState state, { - int postType = 0, - }) async { + ComposeState state, + ) async { final hasContent = state.titleController.text.trim().isNotEmpty || state.descriptionController.text.trim().isNotEmpty || @@ -279,7 +279,7 @@ class ComposeLogic { publishedAt: DateTime.now(), visibility: state.visibility.value, content: state.contentController.text, - type: postType, + type: state.postType, meta: null, viewsUnique: 0, viewsTotal: 0, @@ -333,54 +333,7 @@ class ComposeLogic { BuildContext context, ) async { try { - final draft = SnPost( - id: state.draftId, - title: state.titleController.text, - description: state.descriptionController.text, - language: null, - editedAt: null, - publishedAt: DateTime.now(), - visibility: state.visibility.value, - content: state.contentController.text, - type: 0, - meta: null, - viewsUnique: 0, - viewsTotal: 0, - upvotes: 0, - downvotes: 0, - repliesCount: 0, - threadedPostId: null, - threadedPost: null, - repliedPostId: null, - repliedPost: null, - forwardedPostId: null, - forwardedPost: null, - attachments: [], // TODO: Handle attachments - publisher: SnPublisher( - id: '', - type: 0, - name: '', - nick: '', - picture: null, - background: null, - account: null, - accountId: null, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - deletedAt: null, - realmId: null, - verification: null, - ), - reactions: [], - tags: [], - categories: [], - collections: [], - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - deletedAt: null, - ); - - await ref.read(composeStorageNotifierProvider.notifier).saveDraft(draft); + await saveDraft(ref, state); if (context.mounted) { showSnackBar('draftSaved'.tr()); @@ -535,7 +488,6 @@ class ComposeLogic { SnPost? originalPost, SnPost? repliedPost, SnPost? forwardedPost, - int? postType, // 0 for regular post, 1 for article }) async { if (state.submitting.value) return; @@ -581,7 +533,7 @@ class ComposeLogic { .where((e) => e.isOnCloud) .map((e) => e.data.id) .toList(), - if (postType != null) 'type': postType, + 'type': state.postType, if (repliedPost != null) 'replied_post_id': repliedPost.id, if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id, 'tags': state.tagsController.getTags, @@ -599,7 +551,7 @@ class ComposeLogic { ); // Delete draft after successful submission - if (postType == 1) { + if (state.postType == 1) { // Delete article draft await ref .read(composeStorageNotifierProvider.notifier) @@ -642,7 +594,6 @@ class ComposeLogic { SnPost? originalPost, SnPost? repliedPost, SnPost? forwardedPost, - int? postType, }) { if (event is! RawKeyDownEvent) return; @@ -663,7 +614,6 @@ class ComposeLogic { originalPost: originalPost, repliedPost: repliedPost, forwardedPost: forwardedPost, - postType: postType, ); } } diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 7be669f..53f91ae 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -242,6 +242,47 @@ class PostItem extends HookConsumerWidget { ? EdgeInsets.only(bottom: 8) : null, ), + // Render tags and categories if they exist + if (item.tags.isNotEmpty || item.categories.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item.tags.isNotEmpty) + Wrap( + children: [ + for (final tag in item.tags) + InkWell( + child: Row( + spacing: 4, + children: [ + const Icon(Symbols.label, size: 13), + Text(tag.name ?? '#${tag.slug}') + .fontSize(13) + ], + ), + onTap: () {}, + ), + ], + ), + if (item.categories.isNotEmpty) + Wrap( + children: [ + for (final category in item.categories) + InkWell( + child: Row( + spacing: 4, + children: [ + const Icon(Symbols.category, size: 13), + Text(category.name ?? '#${category.slug}') + .fontSize(13) + ], + ), + onTap: () {}, + ), + ], + ), + ], + ), // Show truncation hint if post is truncated if (item.isTruncated && !isFullPost) _PostTruncateHint().padding( diff --git a/pubspec.lock b/pubspec.lock index 4c6ea5b..b7b094c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2301,10 +2301,11 @@ packages: textfield_tags: dependency: "direct main" description: - name: textfield_tags - sha256: d1f2204114157a1296bb97c20d7f8c8c7fd036212812afb2e19de7bb34acc55b - url: "https://pub.dev" - source: hosted + path: "." + ref: "fixes/allow-controller-re-registration" + resolved-ref: "7574e79649e34df1c3cc0c49b2f0cc2b92de6a7b" + url: "https://github.com/lionelmennig/textfield_tags.git" + source: git version: "3.0.1" timezone: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index e4d80f0..7a08991 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,7 +122,10 @@ dependencies: share_plus: ^11.0.0 receive_sharing_intent: ^1.8.1 top_snackbar_flutter: ^3.3.0 - textfield_tags: ^3.0.1 + textfield_tags: + git: + url: https://github.com/lionelmennig/textfield_tags.git + ref: fixes/allow-controller-re-registration dev_dependencies: flutter_test: