🐛 Fix compose sheet
This commit is contained in:
		@@ -38,6 +38,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
  final Function(ComposeState)? onStateChanged;
 | 
			
		||||
  final bool isContained;
 | 
			
		||||
  final bool showHeader;
 | 
			
		||||
  final ComposeState? providedState;
 | 
			
		||||
 | 
			
		||||
  const PostComposeCard({
 | 
			
		||||
    super.key,
 | 
			
		||||
@@ -48,6 +49,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
    this.onStateChanged,
 | 
			
		||||
    this.isContained = false,
 | 
			
		||||
    this.showHeader = true,
 | 
			
		||||
    this.providedState,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -64,7 +66,9 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
    final notifier = ref.read(composeStorageNotifierProvider.notifier);
 | 
			
		||||
 | 
			
		||||
    // Create compose state
 | 
			
		||||
    final state = useMemoized(
 | 
			
		||||
    final ComposeState composeState =
 | 
			
		||||
        providedState ??
 | 
			
		||||
        useMemoized(
 | 
			
		||||
          () => ComposeLogic.createState(
 | 
			
		||||
            originalPost: originalPost,
 | 
			
		||||
            forwardedPost: forwardedPost,
 | 
			
		||||
@@ -77,54 +81,55 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
    // 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,
 | 
			
		||||
        composeState.titleController,
 | 
			
		||||
        composeState.descriptionController,
 | 
			
		||||
        composeState.contentController,
 | 
			
		||||
        composeState.visibility,
 | 
			
		||||
        composeState.attachments,
 | 
			
		||||
        composeState.attachmentProgress,
 | 
			
		||||
        composeState.currentPublisher,
 | 
			
		||||
        composeState.submitting,
 | 
			
		||||
      ]),
 | 
			
		||||
      [state],
 | 
			
		||||
      [composeState],
 | 
			
		||||
    );
 | 
			
		||||
    useListenable(stateNotifier);
 | 
			
		||||
 | 
			
		||||
    // Notify parent of state changes
 | 
			
		||||
    useEffect(() {
 | 
			
		||||
      onStateChanged?.call(state);
 | 
			
		||||
      onStateChanged?.call(composeState);
 | 
			
		||||
      return null;
 | 
			
		||||
    }, [state]);
 | 
			
		||||
    }, [composeState]);
 | 
			
		||||
 | 
			
		||||
    // Use shared state management utilities
 | 
			
		||||
    ComposeStateUtils.usePublisherInitialization(ref, state);
 | 
			
		||||
    ComposeStateUtils.useInitialStateLoader(state, initialState);
 | 
			
		||||
    ComposeStateUtils.usePublisherInitialization(ref, composeState);
 | 
			
		||||
    ComposeStateUtils.useInitialStateLoader(composeState, initialState);
 | 
			
		||||
 | 
			
		||||
    // Dispose state when widget is disposed
 | 
			
		||||
    useEffect(() {
 | 
			
		||||
      return () {
 | 
			
		||||
        if (providedState == null) {
 | 
			
		||||
          if (!submitted.value &&
 | 
			
		||||
              originalPost == null &&
 | 
			
		||||
            state.currentPublisher.value != null) {
 | 
			
		||||
              composeState.currentPublisher.value != null) {
 | 
			
		||||
            final hasContent =
 | 
			
		||||
              state.titleController.text.trim().isNotEmpty ||
 | 
			
		||||
              state.descriptionController.text.trim().isNotEmpty ||
 | 
			
		||||
              state.contentController.text.trim().isNotEmpty;
 | 
			
		||||
          final hasAttachments = state.attachments.value.isNotEmpty;
 | 
			
		||||
                composeState.titleController.text.trim().isNotEmpty ||
 | 
			
		||||
                composeState.descriptionController.text.trim().isNotEmpty ||
 | 
			
		||||
                composeState.contentController.text.trim().isNotEmpty;
 | 
			
		||||
            final hasAttachments = composeState.attachments.value.isNotEmpty;
 | 
			
		||||
            if (hasContent || hasAttachments) {
 | 
			
		||||
              final draft = SnPost(
 | 
			
		||||
              id: state.draftId,
 | 
			
		||||
              title: state.titleController.text,
 | 
			
		||||
              description: state.descriptionController.text,
 | 
			
		||||
              content: state.contentController.text,
 | 
			
		||||
              visibility: state.visibility.value,
 | 
			
		||||
              type: state.postType,
 | 
			
		||||
                id: composeState.draftId,
 | 
			
		||||
                title: composeState.titleController.text,
 | 
			
		||||
                description: composeState.descriptionController.text,
 | 
			
		||||
                content: composeState.contentController.text,
 | 
			
		||||
                visibility: composeState.visibility.value,
 | 
			
		||||
                type: composeState.postType,
 | 
			
		||||
                attachments:
 | 
			
		||||
                  state.attachments.value
 | 
			
		||||
                    composeState.attachments.value
 | 
			
		||||
                        .where((e) => e.isOnCloud)
 | 
			
		||||
                        .map((e) => e.data as SnCloudFile)
 | 
			
		||||
                        .toList(),
 | 
			
		||||
              publisher: state.currentPublisher.value!,
 | 
			
		||||
                publisher: composeState.currentPublisher.value!,
 | 
			
		||||
                updatedAt: DateTime.now(),
 | 
			
		||||
              );
 | 
			
		||||
              notifier
 | 
			
		||||
@@ -132,7 +137,8 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                  .catchError((e) => debugPrint('Failed to save draft: $e'));
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ComposeLogic.dispose(state);
 | 
			
		||||
          ComposeLogic.dispose(composeState);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
@@ -142,14 +148,14 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
        context: context,
 | 
			
		||||
        isScrollControlled: true,
 | 
			
		||||
        useRootNavigator: true,
 | 
			
		||||
        builder: (context) => ComposeSettingsSheet(state: state),
 | 
			
		||||
        builder: (context) => ComposeSettingsSheet(state: composeState),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Future<void> performSubmit() async {
 | 
			
		||||
      await ComposeSubmitUtils.performSubmit(
 | 
			
		||||
        ref,
 | 
			
		||||
        state,
 | 
			
		||||
        composeState,
 | 
			
		||||
        context,
 | 
			
		||||
        originalPost: originalPost,
 | 
			
		||||
        repliedPost: repliedPost,
 | 
			
		||||
@@ -161,10 +167,10 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
          // Delete draft after successful submission
 | 
			
		||||
          ref
 | 
			
		||||
              .read(composeStorageNotifierProvider.notifier)
 | 
			
		||||
              .deleteDraft(state.draftId);
 | 
			
		||||
              .deleteDraft(composeState.draftId);
 | 
			
		||||
 | 
			
		||||
          // Reset the form for new composition
 | 
			
		||||
          ComposeStateUtils.resetForm(state);
 | 
			
		||||
          ComposeStateUtils.resetForm(composeState);
 | 
			
		||||
 | 
			
		||||
          onSubmit?.call();
 | 
			
		||||
        },
 | 
			
		||||
@@ -219,12 +225,12 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                    ),
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      onPressed:
 | 
			
		||||
                          (state.submitting.value ||
 | 
			
		||||
                                  state.currentPublisher.value == null)
 | 
			
		||||
                          (composeState.submitting.value ||
 | 
			
		||||
                                  composeState.currentPublisher.value == null)
 | 
			
		||||
                              ? null
 | 
			
		||||
                              : performSubmit,
 | 
			
		||||
                      icon:
 | 
			
		||||
                          state.submitting.value
 | 
			
		||||
                          composeState.submitting.value
 | 
			
		||||
                              ? SizedBox(
 | 
			
		||||
                                width: 24,
 | 
			
		||||
                                height: 24,
 | 
			
		||||
@@ -288,7 +294,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                onKeyEvent:
 | 
			
		||||
                    (event) => ComposeLogic.handleKeyPress(
 | 
			
		||||
                      event,
 | 
			
		||||
                      state,
 | 
			
		||||
                      composeState,
 | 
			
		||||
                      ref,
 | 
			
		||||
                      context,
 | 
			
		||||
                      originalPost: originalPost,
 | 
			
		||||
@@ -306,22 +312,27 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                        // Publisher profile picture
 | 
			
		||||
                        GestureDetector(
 | 
			
		||||
                          child: ProfilePictureWidget(
 | 
			
		||||
                            fileId: state.currentPublisher.value?.picture?.id,
 | 
			
		||||
                            fileId:
 | 
			
		||||
                                composeState
 | 
			
		||||
                                    .currentPublisher
 | 
			
		||||
                                    .value
 | 
			
		||||
                                    ?.picture
 | 
			
		||||
                                    ?.id,
 | 
			
		||||
                            radius: 20,
 | 
			
		||||
                            fallbackIcon:
 | 
			
		||||
                                state.currentPublisher.value == null
 | 
			
		||||
                                composeState.currentPublisher.value == null
 | 
			
		||||
                                    ? Symbols.question_mark
 | 
			
		||||
                                    : null,
 | 
			
		||||
                          ),
 | 
			
		||||
                          onTap: () {
 | 
			
		||||
                            if (state.currentPublisher.value == null) {
 | 
			
		||||
                            if (composeState.currentPublisher.value == null) {
 | 
			
		||||
                              // No publisher loaded, guide user to create one
 | 
			
		||||
                              if (isContained) {
 | 
			
		||||
                                Navigator.of(context).pop();
 | 
			
		||||
                              }
 | 
			
		||||
                              context.pushNamed('creatorNew').then((value) {
 | 
			
		||||
                                if (value != null) {
 | 
			
		||||
                                  state.currentPublisher.value =
 | 
			
		||||
                                  composeState.currentPublisher.value =
 | 
			
		||||
                                      value as SnPublisher;
 | 
			
		||||
                                  ref.invalidate(publishersManagedProvider);
 | 
			
		||||
                                }
 | 
			
		||||
@@ -335,7 +346,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                                builder: (context) => const PublisherModal(),
 | 
			
		||||
                              ).then((value) {
 | 
			
		||||
                                if (value != null) {
 | 
			
		||||
                                  state.currentPublisher.value = value;
 | 
			
		||||
                                  composeState.currentPublisher.value = value;
 | 
			
		||||
                                }
 | 
			
		||||
                              });
 | 
			
		||||
                            }
 | 
			
		||||
@@ -348,10 +359,11 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                            crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              ComposeFormFields(
 | 
			
		||||
                                state: state,
 | 
			
		||||
                                state: composeState,
 | 
			
		||||
                                showPublisherAvatar: false,
 | 
			
		||||
                                onPublisherTap: () {
 | 
			
		||||
                                  if (state.currentPublisher.value == null) {
 | 
			
		||||
                                  if (composeState.currentPublisher.value ==
 | 
			
		||||
                                      null) {
 | 
			
		||||
                                    // No publisher loaded, guide user to create one
 | 
			
		||||
                                    if (isContained) {
 | 
			
		||||
                                      Navigator.of(context).pop();
 | 
			
		||||
@@ -360,7 +372,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                                      value,
 | 
			
		||||
                                    ) {
 | 
			
		||||
                                      if (value != null) {
 | 
			
		||||
                                        state.currentPublisher.value =
 | 
			
		||||
                                        composeState.currentPublisher.value =
 | 
			
		||||
                                            value as SnPublisher;
 | 
			
		||||
                                        ref.invalidate(
 | 
			
		||||
                                          publishersManagedProvider,
 | 
			
		||||
@@ -377,14 +389,18 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                                          (context) => const PublisherModal(),
 | 
			
		||||
                                    ).then((value) {
 | 
			
		||||
                                      if (value != null) {
 | 
			
		||||
                                        state.currentPublisher.value = value;
 | 
			
		||||
                                        composeState.currentPublisher.value =
 | 
			
		||||
                                            value;
 | 
			
		||||
                                      }
 | 
			
		||||
                                    });
 | 
			
		||||
                                  }
 | 
			
		||||
                                },
 | 
			
		||||
                              ),
 | 
			
		||||
                              const Gap(8),
 | 
			
		||||
                              ComposeAttachments(state: state, isCompact: true),
 | 
			
		||||
                              ComposeAttachments(
 | 
			
		||||
                                state: composeState,
 | 
			
		||||
                                isCompact: true,
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
@@ -404,7 +420,7 @@ class PostComposeCard extends HookConsumerWidget {
 | 
			
		||||
                  bottomRight: Radius.circular(8),
 | 
			
		||||
                ),
 | 
			
		||||
                child: ComposeToolbar(
 | 
			
		||||
                  state: state,
 | 
			
		||||
                  state: composeState,
 | 
			
		||||
                  originalPost: originalPost,
 | 
			
		||||
                  isCompact: true,
 | 
			
		||||
                ),
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class PostComposeSheet extends HookConsumerWidget {
 | 
			
		||||
        initialState?.forwardingTo ?? originalPost?.forwardedPost;
 | 
			
		||||
 | 
			
		||||
    // Create compose state
 | 
			
		||||
    final state = useMemoized(
 | 
			
		||||
    final ComposeState state = useMemoized(
 | 
			
		||||
      () => ComposeLogic.createState(
 | 
			
		||||
        originalPost: originalPost,
 | 
			
		||||
        forwardedPost: forwardedPost,
 | 
			
		||||
@@ -102,6 +102,9 @@ class PostComposeSheet extends HookConsumerWidget {
 | 
			
		||||
      return null;
 | 
			
		||||
    }, [drafts, prompted.value]);
 | 
			
		||||
 | 
			
		||||
    // Dispose state when widget is disposed
 | 
			
		||||
    useEffect(() => () => ComposeLogic.dispose(state), []);
 | 
			
		||||
 | 
			
		||||
    // Helper methods for actions
 | 
			
		||||
    void showSettingsSheet() {
 | 
			
		||||
      showModalBottomSheet(
 | 
			
		||||
@@ -165,6 +168,7 @@ class PostComposeSheet extends HookConsumerWidget {
 | 
			
		||||
        },
 | 
			
		||||
        isContained: true,
 | 
			
		||||
        showHeader: false,
 | 
			
		||||
        providedState: state,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,23 @@ class ComposeSubmitUtils {
 | 
			
		||||
      throw Exception('Already submitting');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't submit empty posts (no content and no attachments)
 | 
			
		||||
    final hasContent =
 | 
			
		||||
        state.titleController.text.trim().isNotEmpty ||
 | 
			
		||||
        state.descriptionController.text.trim().isNotEmpty ||
 | 
			
		||||
        state.contentController.text.trim().isNotEmpty;
 | 
			
		||||
    final hasAttachments = state.attachments.value.isNotEmpty;
 | 
			
		||||
 | 
			
		||||
    if (!hasContent && !hasAttachments) {
 | 
			
		||||
      // Show error message if context is mounted
 | 
			
		||||
      if (context.mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(
 | 
			
		||||
          context,
 | 
			
		||||
        ).showSnackBar(SnackBar(content: Text('postContentEmpty')));
 | 
			
		||||
      }
 | 
			
		||||
      throw Exception('Post content is empty'); // Don't submit empty posts
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      state.submitting.value = true;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user