Skip to content

Commit

Permalink
feat: who can reply logic (#467)
Browse files Browse the repository at this point in the history
## Description
<!-- Briefly explain the purpose of this PR and what it accomplishes.
-->

## Additional Notes
<!-- Add any extra context or relevant information here. -->

## Type of Change
- [ ] Bug fix
- [x] New feature
- [ ] Breaking change
- [ ] Refactoring
- [ ] Documentation
- [ ] Chore

## Screenshots (if applicable)
<!-- Include screenshots to demonstrate any UI changes. -->
<!-- <img width="180" alt="image" src="image_url_here"> -->
  • Loading branch information
ice-ajax authored Dec 30, 2024
1 parent 79ada87 commit fcabfc7
Show file tree
Hide file tree
Showing 19 changed files with 230 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,6 +25,7 @@ class CreateArticle extends _$CreateArticle {

Future<void> create({
required String content,
required WhoCanReplySettingsOption whoCanReply,
String? title,
String? summary,
String? imageId,
Expand All @@ -43,14 +45,15 @@ class CreateArticle extends _$CreateArticle {

final relatedHashtags = ArticleData.extractHashtagsFromMarkdown(updatedContent);

final articleData = ArticleData(
final articleData = ArticleData.fromData(
title: title,
summary: summary,
image: imageUrl,
content: updatedContent,
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]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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(
Expand All @@ -33,7 +33,7 @@ class CreateArticleTopics extends ConsumerWidget {
onTap: () {
showSimpleBottomSheet<void>(
context: context,
child: const VisibilitySettingsModal(),
child: const WhoCanReplySettingsModal(),
);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -46,7 +46,7 @@ class SelectArticleVisibilityItem extends ConsumerWidget {
onTap: () {
showSimpleBottomSheet<void>(
context: context,
child: VisibilitySettingsModal(title: context.i18n.article_settings_title),
child: WhoCanReplySettingsModal(title: context.i18n.article_settings_title),
);
},
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -36,14 +37,15 @@ class CreatePostNotifier extends _$CreatePostNotifier {

Future<void> create({
required String content,
required WhoCanReplySettingsOption whoCanReply,
EventReference? parentEvent,
EventReference? quotedEvent,
List<MediaFile>? mediaFiles,
}) async {
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 = <FileMetadata>[];

if (mediaFiles != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
() {
Expand Down Expand Up @@ -81,6 +83,7 @@ class PostSubmitButton extends HookConsumerWidget {
parentEvent: parentEvent,
quotedEvent: quotedEvent,
mediaFiles: convertedMediaFiles,
whoCanReply: whoCanReply,
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions lib/app/features/feed/data/models/entities/article_data.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -74,6 +76,7 @@ class ArticleData with _$ArticleData implements EventSerializable {
String? summary,
DateTime? publishedAt,
List<RelatedHashtag>? relatedHashtags,
List<EventSetting>? settings,
}) = _ArticleData;

const ArticleData._();
Expand Down Expand Up @@ -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<String, MediaAttachment> media,
String? title,
String? image,
String? summary,
DateTime? publishedAt,
List<RelatedHashtag>? relatedHashtags,
Set<WhoCanReplySettingsOption> 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,
);
}

Expand All @@ -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,
);
Expand Down
15 changes: 14 additions & 1 deletion lib/app/features/feed/data/models/entities/post_data.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -69,6 +71,7 @@ class PostData with _$PostData, EntityMediaDataMixin implements EventSerializabl
List<RelatedEvent>? relatedEvents,
List<RelatedPubkey>? relatedPubkeys,
List<RelatedHashtag>? relatedHashtags,
List<EventSetting>? settings,
}) = _PostData;

factory PostData.fromEventMessage(EventMessage eventMessage) {
Expand All @@ -88,21 +91,30 @@ 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<WhoCanReplySettingsOption> whoCanReplySettings = const {},
}) {
final parsedContent = TextParser.allMatchers().parse(content);

final hashtags = parsedContent
.where((match) => match.matcher is HashtagMatcher)
.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(),
);
}

Expand All @@ -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()),
],
);
}
Expand Down
35 changes: 0 additions & 35 deletions lib/app/features/feed/data/models/visibility_settings_options.dart

This file was deleted.

Loading

0 comments on commit fcabfc7

Please sign in to comment.