From fcabfc7f6d3cf883440fb2f5a4c26e093baec1fb Mon Sep 17 00:00:00 2001 From: ice-ajax <190340101+ice-ajax@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:34:12 +0100 Subject: [PATCH] feat: who can reply logic (#467) ## Description ## Additional Notes ## Type of Change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [ ] Refactoring - [ ] Documentation - [ ] Chore ## Screenshots (if applicable) --- .../providers/create_article_provider.c.dart | 5 +- .../components/create_article_topics.dart | 8 +-- ...=> select_article_who_can_reply_item.dart} | 12 ++-- .../create_article_preview_modal.dart | 7 ++- .../providers/create_post_notifier.c.dart | 4 +- .../post_submit_button.dart | 3 + .../create_post_modal/create_post_modal.dart | 10 +-- .../data/models/entities/article_data.c.dart | 31 +++++++++ .../data/models/entities/post_data.c.dart | 15 ++++- .../models/visibility_settings_options.dart | 35 ----------- .../models/who_can_reply_settings_option.dart | 44 +++++++++++++ ...elected_visibility_options_provider.c.dart | 18 ------ ...ected_who_can_reply_option_provider.c.dart | 18 ++++++ .../views/pages/story_preview_page.dart | 12 ++-- .../who_can_reply_toolbar.dart} | 12 ++-- .../who_can_reply_settings_list.dart} | 14 ++--- .../who_can_reply_settings_list_item.dart} | 14 ++--- .../who_can_reply_settings_modal.dart} | 8 +-- .../features/nostr/model/event_setting.c.dart | 63 +++++++++++++++++++ 19 files changed, 230 insertions(+), 103 deletions(-) rename lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/{select_article_visibility_item.dart => select_article_who_can_reply_item.dart} (77%) delete mode 100644 lib/app/features/feed/data/models/visibility_settings_options.dart create mode 100644 lib/app/features/feed/data/models/who_can_reply_settings_option.dart delete mode 100644 lib/app/features/feed/providers/selected_visibility_options_provider.c.dart create mode 100644 lib/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart rename lib/app/features/feed/views/components/{visibility_settings_toolbar/visibility_settings_toolbar.dart => who_can_reply_toolbar/who_can_reply_toolbar.dart} (72%) rename lib/app/features/feed/views/pages/{visibility_settings_modal/components/visibility_settings_list.dart => who_can_reply_settings_modal/components/who_can_reply_settings_list.dart} (57%) rename lib/app/features/feed/views/pages/{visibility_settings_modal/components/visibility_settings_list_item.dart => who_can_reply_settings_modal/components/who_can_reply_settings_list_item.dart} (74%) rename lib/app/features/feed/views/pages/{visibility_settings_modal/visibility_settings_modal.dart => who_can_reply_settings_modal/who_can_reply_settings_modal.dart} (77%) create mode 100644 lib/app/features/nostr/model/event_setting.c.dart diff --git a/lib/app/features/feed/create_article/providers/create_article_provider.c.dart b/lib/app/features/feed/create_article/providers/create_article_provider.c.dart index 9e11b8a55..aee4b3e07 100644 --- a/lib/app/features/feed/create_article/providers/create_article_provider.c.dart +++ b/lib/app/features/feed/create_article/providers/create_article_provider.c.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:ion/app/exceptions/exceptions.dart'; import 'package:ion/app/features/feed/data/models/entities/article_data.c.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; import 'package:ion/app/features/gallery/providers/providers.dart'; import 'package:ion/app/features/nostr/model/file_alt.dart'; import 'package:ion/app/features/nostr/model/file_metadata.c.dart'; @@ -24,6 +25,7 @@ class CreateArticle extends _$CreateArticle { Future create({ required String content, + required WhoCanReplySettingsOption whoCanReply, String? title, String? summary, String? imageId, @@ -43,7 +45,7 @@ class CreateArticle extends _$CreateArticle { final relatedHashtags = ArticleData.extractHashtagsFromMarkdown(updatedContent); - final articleData = ArticleData( + final articleData = ArticleData.fromData( title: title, summary: summary, image: imageUrl, @@ -51,6 +53,7 @@ class CreateArticle extends _$CreateArticle { media: {for (final attachment in mediaAttachments) attachment.url: attachment}, relatedHashtags: relatedHashtags, publishedAt: publishedAt ?? DateTime.now(), + whoCanReplySettings: {whoCanReply}, ); await ref.read(nostrNotifierProvider.notifier).sendEntitiesData([...files, articleData]); diff --git a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/create_article_topics.dart b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/create_article_topics.dart index 34f41425c..6055d7071 100644 --- a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/create_article_topics.dart +++ b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/create_article_topics.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/components/list_item/list_item.dart'; import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/app/features/feed/providers/selected_visibility_options_provider.c.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart'; import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart'; import 'package:ion/generated/assets.gen.dart'; @@ -16,7 +16,7 @@ class CreateArticleTopics extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedOption = ref.watch(selectedVisibilityOptionsProvider); + final selectedOption = ref.watch(selectedWhoCanReplyOptionProvider); return ListItem( title: Text( @@ -33,7 +33,7 @@ class CreateArticleTopics extends ConsumerWidget { onTap: () { showSimpleBottomSheet( context: context, - child: const VisibilitySettingsModal(), + child: const WhoCanReplySettingsModal(), ); }, ); diff --git a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_visibility_item.dart b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_who_can_reply_item.dart similarity index 77% rename from lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_visibility_item.dart rename to lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_who_can_reply_item.dart index 64d113b50..5c3aba766 100644 --- a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_visibility_item.dart +++ b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_who_can_reply_item.dart @@ -5,17 +5,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/components/list_item/list_item.dart'; import 'package:ion/app/components/screen_offset/screen_side_offset.dart'; import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/app/features/feed/providers/selected_visibility_options_provider.c.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart'; import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart'; import 'package:ion/generated/assets.gen.dart'; -class SelectArticleVisibilityItem extends ConsumerWidget { - const SelectArticleVisibilityItem({super.key}); +class SelectArticleWhoCanReplyItem extends ConsumerWidget { + const SelectArticleWhoCanReplyItem({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedOption = ref.watch(selectedVisibilityOptionsProvider); + final selectedOption = ref.watch(selectedWhoCanReplyOptionProvider); return ScreenSideOffset.medium( child: ListItem( @@ -46,7 +46,7 @@ class SelectArticleVisibilityItem extends ConsumerWidget { onTap: () { showSimpleBottomSheet( context: context, - child: VisibilitySettingsModal(title: context.i18n.article_settings_title), + child: WhoCanReplySettingsModal(title: context.i18n.article_settings_title), ); }, ), diff --git a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/create_article_preview_modal.dart b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/create_article_preview_modal.dart index 1b4b26b5b..a8903615a 100644 --- a/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/create_article_preview_modal.dart +++ b/lib/app/features/feed/create_article/views/pages/create_article_preview_modal/create_article_preview_modal.dart @@ -11,7 +11,8 @@ import 'package:ion/app/features/feed/create_article/providers/create_article_pr import 'package:ion/app/features/feed/create_article/providers/draft_article_provider.c.dart'; import 'package:ion/app/features/feed/create_article/views/pages/create_article_preview_modal/components/article_preview.dart'; import 'package:ion/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_topics_item.dart'; -import 'package:ion/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_visibility_item.dart'; +import 'package:ion/app/features/feed/create_article/views/pages/create_article_preview_modal/components/select_article_who_can_reply_item.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart'; import 'package:ion/app/router/components/sheet_content/sheet_content.dart'; import 'package:ion/generated/assets.gen.dart'; @@ -24,6 +25,7 @@ class CreateArticlePreviewModal extends HookConsumerWidget { final paddingValue = 20.0.s; final DraftArticleState(:title, :image, :imageIds, :content) = ref.watch(draftArticleProvider); + final whoCanReply = ref.watch(selectedWhoCanReplyOptionProvider); return SheetContent( bottomPadding: 0, @@ -42,7 +44,7 @@ class CreateArticlePreviewModal extends HookConsumerWidget { SizedBox(height: 20.0.s), const HorizontalSeparator(), SizedBox(height: 20.0.s), - const SelectArticleVisibilityItem(), + const SelectArticleWhoCanReplyItem(), const Spacer(), Align( alignment: Alignment.bottomCenter, @@ -60,6 +62,7 @@ class CreateArticlePreviewModal extends HookConsumerWidget { content: content, imageId: image?.path, mediaIds: imageIds, + whoCanReply: whoCanReply, ); if (!ref.read(createArticleProvider).hasError && ref.context.mounted) { diff --git a/lib/app/features/feed/create_post/providers/create_post_notifier.c.dart b/lib/app/features/feed/create_post/providers/create_post_notifier.c.dart index 349807fa2..96bf4e86c 100644 --- a/lib/app/features/feed/create_post/providers/create_post_notifier.c.dart +++ b/lib/app/features/feed/create_post/providers/create_post_notifier.c.dart @@ -7,6 +7,7 @@ import 'package:ion/app/features/core/providers/env_provider.c.dart'; import 'package:ion/app/features/feed/create_post/model/create_post_option.dart'; import 'package:ion/app/features/feed/data/models/entities/article_data.c.dart'; import 'package:ion/app/features/feed/data/models/entities/post_data.c.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; import 'package:ion/app/features/feed/providers/counters/replies_count_provider.c.dart'; import 'package:ion/app/features/feed/providers/counters/reposts_count_provider.c.dart'; import 'package:ion/app/features/nostr/model/entity_expiration.c.dart'; @@ -36,6 +37,7 @@ class CreatePostNotifier extends _$CreatePostNotifier { Future create({ required String content, + required WhoCanReplySettingsOption whoCanReply, EventReference? parentEvent, EventReference? quotedEvent, List? mediaFiles, @@ -43,7 +45,7 @@ class CreatePostNotifier extends _$CreatePostNotifier { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { - var data = PostData.fromRawContent(content.trim()); + var data = PostData.fromRawContent(content.trim(), whoCanReplySettings: {whoCanReply}); final files = []; if (mediaFiles != null) { diff --git a/lib/app/features/feed/create_post/views/components/post_submit_button/post_submit_button.dart b/lib/app/features/feed/create_post/views/components/post_submit_button/post_submit_button.dart index ee7247689..004709065 100644 --- a/lib/app/features/feed/create_post/views/components/post_submit_button/post_submit_button.dart +++ b/lib/app/features/feed/create_post/views/components/post_submit_button/post_submit_button.dart @@ -13,6 +13,7 @@ import 'package:ion/app/features/core/providers/poll/poll_title_notifier.c.dart' import 'package:ion/app/features/feed/create_post/model/create_post_option.dart'; import 'package:ion/app/features/feed/create_post/providers/create_post_notifier.c.dart'; import 'package:ion/app/features/feed/create_post/views/pages/create_post_modal/hooks/use_has_poll.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_has_content.dart'; import 'package:ion/app/features/feed/views/components/toolbar_buttons/toolbar_send_button.dart'; import 'package:ion/app/features/nostr/model/event_reference.c.dart'; @@ -45,6 +46,7 @@ class PostSubmitButton extends HookConsumerWidget { final pollTitle = ref.watch(pollTitleNotifierProvider); final pollAnswers = ref.watch(pollAnswersNotifierProvider); final hasPoll = useHasPoll(textEditorController); + final whoCanReply = ref.watch(selectedWhoCanReplyOptionProvider); final isSubmitButtonEnabled = useMemoized( () { @@ -81,6 +83,7 @@ class PostSubmitButton extends HookConsumerWidget { parentEvent: parentEvent, quotedEvent: quotedEvent, mediaFiles: convertedMediaFiles, + whoCanReply: whoCanReply, ), ); diff --git a/lib/app/features/feed/create_post/views/pages/create_post_modal/create_post_modal.dart b/lib/app/features/feed/create_post/views/pages/create_post_modal/create_post_modal.dart index 237b8a334..289978d0d 100644 --- a/lib/app/features/feed/create_post/views/pages/create_post_modal/create_post_modal.dart +++ b/lib/app/features/feed/create_post/views/pages/create_post_modal/create_post_modal.dart @@ -22,7 +22,7 @@ import 'package:ion/app/features/feed/views/components/actions_toolbar/actions_t import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_quill_controller.dart'; import 'package:ion/app/features/feed/views/components/text_editor/text_editor.dart'; import 'package:ion/app/features/feed/views/components/toolbar_buttons/toolbar_buttons.dart'; -import 'package:ion/app/features/feed/views/components/visibility_settings_toolbar/visibility_settings_toolbar.dart'; +import 'package:ion/app/features/feed/views/components/who_can_reply_toolbar/who_can_reply_toolbar.dart'; import 'package:ion/app/features/feed/views/pages/cancel_creation_modal/cancel_creation_modal.dart'; import 'package:ion/app/features/nostr/model/event_reference.c.dart'; import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart'; @@ -51,13 +51,13 @@ class CreatePostModal extends HookConsumerWidget { final textEditorController = useQuillController(defaultText: content); final scrollController = useScrollController(); - final visibilityToolbarKey = useMemoized(GlobalKey.new); + final whoCanReplyToolbarKey = useMemoized(GlobalKey.new); final actionsToolbarKey = useMemoized(GlobalKey.new); final textInputKey = useMemoized(GlobalKey.new); useKeyboardScrollHandler( scrollController: scrollController, - keysToMeasure: [visibilityToolbarKey, actionsToolbarKey, textInputKey], + keysToMeasure: [whoCanReplyToolbarKey, actionsToolbarKey, textInputKey], ); final createOption = videoPath != null @@ -153,8 +153,8 @@ class CreatePostModal extends HookConsumerWidget { ), const HorizontalSeparator(), ScreenSideOffset.small( - key: visibilityToolbarKey, - child: const VisibilitySettingsToolbar(), + key: whoCanReplyToolbarKey, + child: const WhoCanReplyToolbar(), ), ScreenSideOffset.small( key: actionsToolbarKey, diff --git a/lib/app/features/feed/data/models/entities/article_data.c.dart b/lib/app/features/feed/data/models/entities/article_data.c.dart index 802258f45..9058e590e 100644 --- a/lib/app/features/feed/data/models/entities/article_data.c.dart +++ b/lib/app/features/feed/data/models/entities/article_data.c.dart @@ -7,7 +7,9 @@ import 'package:collection/collection.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:ion/app/extensions/extensions.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; import 'package:ion/app/features/nostr/model/event_serializable.dart'; +import 'package:ion/app/features/nostr/model/event_setting.c.dart'; import 'package:ion/app/features/nostr/model/media_attachment.dart'; import 'package:ion/app/features/nostr/model/nostr_entity.dart'; import 'package:ion/app/features/nostr/model/related_hashtag.c.dart'; @@ -74,6 +76,7 @@ class ArticleData with _$ArticleData implements EventSerializable { String? summary, DateTime? publishedAt, List? relatedHashtags, + List? settings, }) = _ArticleData; const ArticleData._(); @@ -103,6 +106,33 @@ class ArticleData with _$ArticleData implements EventSerializable { summary: summary, publishedAt: publishedAt, relatedHashtags: tags[RelatedHashtag.tagName]?.map(RelatedHashtag.fromTag).toList(), + settings: tags[EventSetting.settingTagName]?.map(EventSetting.fromTag).toList(), + ); + } + + factory ArticleData.fromData({ + required String content, + required Map media, + String? title, + String? image, + String? summary, + DateTime? publishedAt, + List? relatedHashtags, + Set whoCanReplySettings = const {}, + }) { + final setting = whoCanReplySettings.isNotEmpty + ? WhoCanReplyEventSetting(values: whoCanReplySettings) + : null; + + return ArticleData( + content: content, + media: media, + title: title, + image: image, + summary: summary, + publishedAt: publishedAt, + relatedHashtags: relatedHashtags, + settings: setting != null ? [setting] : null, ); } @@ -127,6 +157,7 @@ class ArticleData with _$ArticleData implements EventSerializable { ['published_at', (publishedAt!.millisecondsSinceEpoch ~/ 1000).toString()], if (media.isNotEmpty) ...media.values.map((mediaAttachment) => mediaAttachment.toTag()), ['d', uniqueIdForEditing], + if (settings != null) ...settings!.map((setting) => setting.toTag()), ], content: content, ); diff --git a/lib/app/features/feed/data/models/entities/post_data.c.dart b/lib/app/features/feed/data/models/entities/post_data.c.dart index 3430d5a13..4f0eb80df 100644 --- a/lib/app/features/feed/data/models/entities/post_data.c.dart +++ b/lib/app/features/feed/data/models/entities/post_data.c.dart @@ -6,9 +6,11 @@ import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:ion/app/exceptions/exceptions.dart'; import 'package:ion/app/extensions/extensions.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; import 'package:ion/app/features/nostr/model/entity_expiration.c.dart'; import 'package:ion/app/features/nostr/model/entity_media_data.dart'; import 'package:ion/app/features/nostr/model/event_serializable.dart'; +import 'package:ion/app/features/nostr/model/event_setting.c.dart'; import 'package:ion/app/features/nostr/model/media_attachment.dart'; import 'package:ion/app/features/nostr/model/nostr_entity.dart'; import 'package:ion/app/features/nostr/model/related_event.c.dart'; @@ -69,6 +71,7 @@ class PostData with _$PostData, EntityMediaDataMixin implements EventSerializabl List? relatedEvents, List? relatedPubkeys, List? relatedHashtags, + List? settings, }) = _PostData; factory PostData.fromEventMessage(EventMessage eventMessage) { @@ -88,10 +91,14 @@ class PostData with _$PostData, EntityMediaDataMixin implements EventSerializabl relatedEvents: tags[RelatedEvent.tagName]?.map(RelatedEvent.fromTag).toList(), relatedPubkeys: tags[RelatedPubkey.tagName]?.map(RelatedPubkey.fromTag).toList(), relatedHashtags: tags[RelatedHashtag.tagName]?.map(RelatedHashtag.fromTag).toList(), + settings: tags[EventSetting.settingTagName]?.map(EventSetting.fromTag).toList(), ); } - factory PostData.fromRawContent(String content) { + factory PostData.fromRawContent( + String content, { + Set whoCanReplySettings = const {}, + }) { final parsedContent = TextParser.allMatchers().parse(content); final hashtags = parsedContent @@ -99,10 +106,15 @@ class PostData with _$PostData, EntityMediaDataMixin implements EventSerializabl .map((match) => RelatedHashtag(value: match.text)) .toList(); + final setting = whoCanReplySettings.isNotEmpty + ? WhoCanReplyEventSetting(values: whoCanReplySettings) + : null; + return PostData( content: parsedContent, relatedHashtags: hashtags, media: {}, + settings: [setting].whereNotNull().toList(), ); } @@ -127,6 +139,7 @@ class PostData with _$PostData, EntityMediaDataMixin implements EventSerializabl if (relatedHashtags != null) ...relatedHashtags!.map((hashtag) => hashtag.toTag()), if (relatedEvents != null) ...relatedEvents!.map((event) => event.toTag()), if (media.isNotEmpty) ...media.values.map((mediaAttachment) => mediaAttachment.toTag()), + if (settings != null) ...settings!.map((setting) => setting.toTag()), ], ); } diff --git a/lib/app/features/feed/data/models/visibility_settings_options.dart b/lib/app/features/feed/data/models/visibility_settings_options.dart deleted file mode 100644 index 694567bc9..000000000 --- a/lib/app/features/feed/data/models/visibility_settings_options.dart +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: ice License 1.0 - -import 'package:flutter/material.dart'; -import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/generated/assets.gen.dart'; - -enum VisibilitySettingsOptions { - everyone, - followedAccounts, - verifiedAccounts, - mentionedAccounts; - - String getTitle(BuildContext context) { - return switch (this) { - VisibilitySettingsOptions.everyone => context.i18n.visibility_settings_everyone, - VisibilitySettingsOptions.followedAccounts => - context.i18n.visibility_settings_followed_accounts, - VisibilitySettingsOptions.verifiedAccounts => - context.i18n.visibility_settings_verified_accounts, - VisibilitySettingsOptions.mentionedAccounts => - context.i18n.visibility_settings_mentioned_accounts, - }; - } - - Widget getIcon(BuildContext context) { - final icon = switch (this) { - VisibilitySettingsOptions.everyone => Assets.svg.iconPostEveryone, - VisibilitySettingsOptions.followedAccounts => Assets.svg.iconSearchFollow, - VisibilitySettingsOptions.verifiedAccounts => Assets.svg.iconPostVerifyaccount, - VisibilitySettingsOptions.mentionedAccounts => Assets.svg.iconFieldNickname, - }; - - return icon.icon(color: context.theme.appColors.primaryAccent); - } -} diff --git a/lib/app/features/feed/data/models/who_can_reply_settings_option.dart b/lib/app/features/feed/data/models/who_can_reply_settings_option.dart new file mode 100644 index 000000000..ffd3450b7 --- /dev/null +++ b/lib/app/features/feed/data/models/who_can_reply_settings_option.dart @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'package:flutter/material.dart'; +import 'package:ion/app/extensions/extensions.dart'; +import 'package:ion/generated/assets.gen.dart'; + +enum WhoCanReplySettingsOption { + everyone(null), + followedAccounts('following'), + // TODO: Add support for verified accounts setting when API is ready + verifiedAccounts(null), + mentionedAccounts('mentioned'); + + const WhoCanReplySettingsOption(this.tagValue); + + final String? tagValue; + + static WhoCanReplySettingsOption fromTagValue(String value) { + return values.firstWhere((option) => option.tagValue == value, orElse: () => everyone); + } + + String getTitle(BuildContext context) { + return switch (this) { + WhoCanReplySettingsOption.everyone => context.i18n.visibility_settings_everyone, + WhoCanReplySettingsOption.followedAccounts => + context.i18n.visibility_settings_followed_accounts, + WhoCanReplySettingsOption.verifiedAccounts => + context.i18n.visibility_settings_verified_accounts, + WhoCanReplySettingsOption.mentionedAccounts => + context.i18n.visibility_settings_mentioned_accounts, + }; + } + + Widget getIcon(BuildContext context) { + final icon = switch (this) { + WhoCanReplySettingsOption.everyone => Assets.svg.iconPostEveryone, + WhoCanReplySettingsOption.followedAccounts => Assets.svg.iconSearchFollow, + WhoCanReplySettingsOption.verifiedAccounts => Assets.svg.iconPostVerifyaccount, + WhoCanReplySettingsOption.mentionedAccounts => Assets.svg.iconFieldNickname, + }; + + return icon.icon(color: context.theme.appColors.primaryAccent); + } +} diff --git a/lib/app/features/feed/providers/selected_visibility_options_provider.c.dart b/lib/app/features/feed/providers/selected_visibility_options_provider.c.dart deleted file mode 100644 index a3d366bbe..000000000 --- a/lib/app/features/feed/providers/selected_visibility_options_provider.c.dart +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: ice License 1.0 - -import 'package:ion/app/features/feed/data/models/visibility_settings_options.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'selected_visibility_options_provider.c.g.dart'; - -@riverpod -class SelectedVisibilityOptions extends _$SelectedVisibilityOptions { - @override - VisibilitySettingsOptions build() { - return VisibilitySettingsOptions.everyone; - } - - set selectedOption(VisibilitySettingsOptions option) { - state = option; - } -} diff --git a/lib/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart b/lib/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart new file mode 100644 index 000000000..a0419cb46 --- /dev/null +++ b/lib/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'selected_who_can_reply_option_provider.c.g.dart'; + +@riverpod +class SelectedWhoCanReplyOption extends _$SelectedWhoCanReplyOption { + @override + WhoCanReplySettingsOption build() { + return WhoCanReplySettingsOption.everyone; + } + + set option(WhoCanReplySettingsOption option) { + state = option; + } +} diff --git a/lib/app/features/feed/stories/views/pages/story_preview_page.dart b/lib/app/features/feed/stories/views/pages/story_preview_page.dart index 9f1118d5f..31b63e555 100644 --- a/lib/app/features/feed/stories/views/pages/story_preview_page.dart +++ b/lib/app/features/feed/stories/views/pages/story_preview_page.dart @@ -8,11 +8,12 @@ import 'package:ion/app/extensions/extensions.dart'; import 'package:ion/app/features/core/model/media_type.dart'; import 'package:ion/app/features/feed/create_post/model/create_post_option.dart'; import 'package:ion/app/features/feed/create_post/providers/create_post_notifier.c.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; import 'package:ion/app/features/feed/stories/views/components/story_preview/actions/story_share_button.dart'; import 'package:ion/app/features/feed/stories/views/components/story_preview/media/story_image_preview.dart'; import 'package:ion/app/features/feed/stories/views/components/story_preview/media/story_video_preview.dart'; import 'package:ion/app/features/feed/stories/views/components/story_preview/user/verified_account_list_item.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart'; import 'package:ion/app/router/app_routes.c.dart'; import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart'; import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart'; @@ -31,6 +32,7 @@ class StoryPreviewPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final mediaType = mimeType != null ? MediaType.fromMimeType(mimeType!) : MediaType.unknown; + final whoCanReply = ref.watch(selectedWhoCanReplyOptionProvider); return Scaffold( body: SafeArea( @@ -61,7 +63,7 @@ class StoryPreviewPage extends ConsumerWidget { onTap: () async { await showSimpleBottomSheet( context: context, - child: VisibilitySettingsModal( + child: WhoCanReplySettingsModal( title: context.i18n.story_settings_title, ), ); @@ -79,10 +81,8 @@ class StoryPreviewPage extends ConsumerWidget { onPressed: () { final file = MediaFile(path: path, mimeType: mimeType); ref - .read( - createPostNotifierProvider(CreatePostOption.story).notifier, - ) - .create(content: '', mediaFiles: [file]); + .read(createPostNotifierProvider(CreatePostOption.story).notifier) + .create(content: '', mediaFiles: [file], whoCanReply: whoCanReply); FeedRoute().go(context); }, ), diff --git a/lib/app/features/feed/views/components/visibility_settings_toolbar/visibility_settings_toolbar.dart b/lib/app/features/feed/views/components/who_can_reply_toolbar/who_can_reply_toolbar.dart similarity index 72% rename from lib/app/features/feed/views/components/visibility_settings_toolbar/visibility_settings_toolbar.dart rename to lib/app/features/feed/views/components/who_can_reply_toolbar/who_can_reply_toolbar.dart index f171316ef..2bbb0575f 100644 --- a/lib/app/features/feed/views/components/visibility_settings_toolbar/visibility_settings_toolbar.dart +++ b/lib/app/features/feed/views/components/who_can_reply_toolbar/who_can_reply_toolbar.dart @@ -4,19 +4,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/components/list_item/list_item.dart'; import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/app/features/feed/providers/selected_visibility_options_provider.c.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart'; import 'package:ion/app/router/utils/show_simple_bottom_sheet.dart'; import 'package:ion/generated/assets.gen.dart'; -class VisibilitySettingsToolbar extends ConsumerWidget { - const VisibilitySettingsToolbar({ +class WhoCanReplyToolbar extends ConsumerWidget { + const WhoCanReplyToolbar({ super.key, }); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedOption = ref.watch(selectedVisibilityOptionsProvider); + final selectedOption = ref.watch(selectedWhoCanReplyOptionProvider); return ListItem( title: Text( @@ -33,7 +33,7 @@ class VisibilitySettingsToolbar extends ConsumerWidget { onTap: () { showSimpleBottomSheet( context: context, - child: const VisibilitySettingsModal(), + child: const WhoCanReplySettingsModal(), ); }, ); diff --git a/lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list.dart b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list.dart similarity index 57% rename from lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list.dart rename to lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list.dart index d2e0b4bf0..270e9225f 100644 --- a/lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list.dart +++ b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/components/screen_offset/screen_side_offset.dart'; import 'package:ion/app/components/separated/separator.dart'; -import 'package:ion/app/features/feed/data/models/visibility_settings_options.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list_item.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list_item.dart'; -class VisibilitySettingsList extends ConsumerWidget { - const VisibilitySettingsList({ +class WhoCanReplySettingsList extends ConsumerWidget { + const WhoCanReplySettingsList({ super.key, }); @@ -19,12 +19,12 @@ class VisibilitySettingsList extends ConsumerWidget { padding: EdgeInsets.symmetric( horizontal: ScreenSideOffset.defaultSmallMargin, ), - itemCount: VisibilitySettingsOptions.values.length, + itemCount: WhoCanReplySettingsOption.values.length, separatorBuilder: (_, __) => const HorizontalSeparator(), itemBuilder: (BuildContext context, int index) { - final option = VisibilitySettingsOptions.values[index]; + final option = WhoCanReplySettingsOption.values[index]; - return VisibilitySettingsListItem(option: option); + return WhoCanReplySettingsListItem(option: option); }, ); } diff --git a/lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list_item.dart b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list_item.dart similarity index 74% rename from lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list_item.dart rename to lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list_item.dart index 20b2d99d0..275a146b7 100644 --- a/lib/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list_item.dart +++ b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list_item.dart @@ -4,25 +4,25 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:ion/app/components/list_item/list_item.dart'; import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/app/features/feed/data/models/visibility_settings_options.dart'; -import 'package:ion/app/features/feed/providers/selected_visibility_options_provider.c.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; +import 'package:ion/app/features/feed/providers/selected_who_can_reply_option_provider.c.dart'; import 'package:ion/generated/assets.gen.dart'; -class VisibilitySettingsListItem extends ConsumerWidget { - const VisibilitySettingsListItem({ +class WhoCanReplySettingsListItem extends ConsumerWidget { + const WhoCanReplySettingsListItem({ required this.option, super.key, }); - final VisibilitySettingsOptions option; + final WhoCanReplySettingsOption option; @override Widget build(BuildContext context, WidgetRef ref) { - final selectedOption = ref.watch(selectedVisibilityOptionsProvider); + final selectedOption = ref.watch(selectedWhoCanReplyOptionProvider); final isSelected = selectedOption == option; void onSelect() { - ref.read(selectedVisibilityOptionsProvider.notifier).selectedOption = option; + ref.read(selectedWhoCanReplyOptionProvider.notifier).option = option; Navigator.pop(context, false); } diff --git a/lib/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart similarity index 77% rename from lib/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart rename to lib/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart index a3ee8b903..6188315bb 100644 --- a/lib/app/features/feed/views/pages/visibility_settings_modal/visibility_settings_modal.dart +++ b/lib/app/features/feed/views/pages/who_can_reply_settings_modal/who_can_reply_settings_modal.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:ion/app/components/screen_offset/screen_bottom_offset.dart'; import 'package:ion/app/components/separated/separator.dart'; import 'package:ion/app/extensions/extensions.dart'; -import 'package:ion/app/features/feed/views/pages/visibility_settings_modal/components/visibility_settings_list.dart'; +import 'package:ion/app/features/feed/views/pages/who_can_reply_settings_modal/components/who_can_reply_settings_list.dart'; import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart'; -class VisibilitySettingsModal extends StatelessWidget { - const VisibilitySettingsModal({ +class WhoCanReplySettingsModal extends StatelessWidget { + const WhoCanReplySettingsModal({ this.title, super.key, }); @@ -26,7 +26,7 @@ class VisibilitySettingsModal extends StatelessWidget { ), SizedBox(height: 12.0.s), const HorizontalSeparator(), - const VisibilitySettingsList(), + const WhoCanReplySettingsList(), const HorizontalSeparator(), ScreenBottomOffset( margin: 0, diff --git a/lib/app/features/nostr/model/event_setting.c.dart b/lib/app/features/nostr/model/event_setting.c.dart new file mode 100644 index 000000000..4ec916e6b --- /dev/null +++ b/lib/app/features/nostr/model/event_setting.c.dart @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:ion/app/exceptions/exceptions.dart'; +import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart'; + +part 'event_setting.c.freezed.dart'; + +abstract class EventSetting { + factory EventSetting.fromTag(List tag) { + if (tag[0] != EventSetting.settingTagName) { + throw IncorrectEventTagNameException(actual: tag[0], expected: settingTagName); + } + if (tag.length < 4) { + throw IncorrectEventTagException(tag: tag.toString()); + } + + switch (tag[1]) { + case WhoCanReplyEventSetting.tagName: + return WhoCanReplyEventSetting.fromTag(tag); + default: + throw IncorrectEventTagException(tag: tag.toString()); + } + } + + List toTag(); + + static const String settingTagName = 'settings'; +} + +@freezed +class WhoCanReplyEventSetting with _$WhoCanReplyEventSetting implements EventSetting { + const factory WhoCanReplyEventSetting({ + required Set values, + }) = _WhoCanReplyEventSetting; + + const WhoCanReplyEventSetting._(); + + /// https://github.com/ice-blockchain/subzero/blob/master/.ion-connect-protocol/ICIP-4000.md#settings-tag + factory WhoCanReplyEventSetting.fromTag(List tag) { + if (tag[0] != EventSetting.settingTagName) { + throw IncorrectEventTagNameException(actual: tag[0], expected: EventSetting.settingTagName); + } + if (tag.length < 4) { + throw IncorrectEventTagException(tag: tag.toString()); + } + + final values = tag[2].split(',').map(WhoCanReplySettingsOption.fromTagValue).toSet(); + return WhoCanReplyEventSetting(values: values); + } + + @override + List toTag() { + return [ + EventSetting.settingTagName, + tagName, + values.map((value) => value.tagValue).nonNulls.join(','), + (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + ]; + } + + static const String tagName = 'who_can_reply'; +}