part of '../compose_bar.dart'; Future _sendTextOnlyDraft({ required BuildContext context, required _MarkdownEditingController controller, required ObjectRef> mentionMap, required ObjectRef draftRevision, required int submittedDraftRevision, required FocusNode focusNode, required VoidCallback clearComposer, required Future Function() addMentionedNonMembers, required _ComposeDraftPayload payload, required _OutgoingMentions outgoing, required ComposeBarOnSend onSend, required ScaffoldMessengerState? messenger, }) async { TextEditingValue? clearedDraftText; Map? clearedDraftMentions; int? clearedDraftRevision; void restoreClearedDraft() { if (!context.mounted || clearedDraftText == null || clearedDraftMentions == null || clearedDraftRevision == null || draftRevision.value != clearedDraftRevision) { return; } controller.value = clearedDraftText; mentionMap.value ..clear() ..addAll(clearedDraftMentions); focusNode.requestFocus(); } try { await addMentionedNonMembers(); // Clear before optimistic insertion so the outgoing row and draft never // appear simultaneously during the send transition. If the user edited // while membership changes were pending, preserve that newer draft. if (context.mounted && draftRevision.value == submittedDraftRevision) { clearedDraftText = controller.value; clearedDraftMentions = Map.of(mentionMap.value); clearComposer(); clearedDraftRevision = draftRevision.value; } await onSend( payload.content, outgoing.pubkeys, mediaTags: [...payload.mediaTags, ...outgoing.referenceTags], ); } on StateError { restoreClearedDraft(); _reportSendCancelledByCommunitySwitch(messenger); } catch (error) { // The caller runs unawaited, so surface publish failures and restore the // sent draft unless the user has already started a new one. restoreClearedDraft(); messenger?.showSnackBar( SnackBar(content: Text(_composeSendErrorMessage(error))), ); } } void _useComposeDraftLifecycle({ required WidgetRef ref, required _MarkdownEditingController controller, required String draftKey, required String channelId, required String? threadHeadId, required String draftIdentity, required ObjectRef draftRevision, required ValueNotifier> attachments, required ObjectRef uploadGeneration, required ObjectRef activeUploadCancellation, required ValueNotifier uploadingCount, required ValueNotifier isSending, required ValueNotifier<_AttachmentSurface> attachmentSurface, required ValueNotifier uploadError, required _IOSAttachmentPopoverController iosAttachmentPopover, }) { final lastDraftIdentity = useRef(null); useEffect(() { final identityChanged = lastDraftIdentity.value != null && lastDraftIdentity.value != draftIdentity; lastDraftIdentity.value = draftIdentity; final saved = ref.read(composeDraftsProvider.notifier).textFor(draftKey); if (identityChanged) { draftRevision.value += 1; uploadGeneration.value += 1; activeUploadCancellation.value?.cancel(); activeUploadCancellation.value = null; uploadingCount.value = 0; isSending.value = false; attachmentSurface.value = _AttachmentSurface.closed; uploadError.value = null; unawaited(iosAttachmentPopover.dispose()); final staleAttachments = attachments.value; attachments.value = const []; unawaited(_deleteOwnedAttachments(staleAttachments)); controller.text = saved ?? ''; } else if (saved != null && controller.text.isEmpty) { controller.text = saved; } var lastPersistedText = controller.text; void persistDraft() { final text = controller.text; if (text == lastPersistedText) return; lastPersistedText = text; draftRevision.value += 1; ref .read(composeDraftsProvider.notifier) .save( key: draftKey, channelId: channelId, threadHeadId: threadHeadId, text: text, ); } controller.addListener(persistDraft); return () => controller.removeListener(persistDraft); }, [controller, draftKey, draftIdentity]); }