import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/models/post.dart'; import 'package:island/screens/creators/publishers_form.dart'; import 'package:island/screens/posts/compose.dart'; import 'package:island/services/compose_storage_db.dart'; import 'package:island/widgets/post/compose_shared.dart'; /// Utility class for common compose state management logic. class ComposeStateUtils { /// Initializes publisher when data becomes available. static void usePublisherInitialization(WidgetRef ref, ComposeState state) { final publishers = ref.watch(publishersManagedProvider); useEffect(() { if (publishers.value?.isNotEmpty ?? false) { if (state.currentPublisher.value == null) { state.currentPublisher.value = publishers.value!.first; } } return null; }, [publishers]); } /// Loads initial state from provided parameters. static void useInitialStateLoader( ComposeState state, PostComposeInitialState? initialState, ) { useEffect(() { if (initialState != null) { state.titleController.text = initialState.title ?? ''; state.descriptionController.text = initialState.description ?? ''; state.contentController.text = initialState.content ?? ''; if (initialState.visibility != null) { state.visibility.value = initialState.visibility!; } if (initialState.attachments.isNotEmpty) { state.attachments.value = List.from(initialState.attachments); } } return null; }, [initialState]); } /// Loads draft if available (for new posts without initial state). static void useDraftLoader( WidgetRef ref, ComposeState state, SnPost? originalPost, SnPost? repliedPost, SnPost? forwardedPost, PostComposeInitialState? initialState, ) { useEffect(() { if (originalPost == null && forwardedPost == null && repliedPost == null && initialState == null) { // Try to load the most recent draft 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, ); // Only load if the draft has meaningful content if (mostRecentDraft.content?.isNotEmpty == true || mostRecentDraft.title?.isNotEmpty == true) { state.titleController.text = mostRecentDraft.title ?? ''; state.descriptionController.text = mostRecentDraft.description ?? ''; state.contentController.text = mostRecentDraft.content ?? ''; state.visibility.value = mostRecentDraft.visibility; } } } return null; }, []); } /// Handles auto-save functionality for new posts. static void useAutoSave(WidgetRef ref, ComposeState state, bool isNewPost) { useEffect(() { if (isNewPost) { state.startAutoSave(ref); } return () => state.stopAutoSave(); }, [state]); } /// Handles disposal and draft saving logic. static void useDisposalHandler( WidgetRef ref, ComposeState state, SnPost? originalPost, bool submitted, ) { useEffect(() { return () { if (!submitted && originalPost == null && state.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; 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, attachments: state.attachments.value .where((e) => e.isOnCloud) .map((e) => e.data as SnCloudFile) .toList(), publisher: state.currentPublisher.value!, updatedAt: DateTime.now(), ); ref .read(composeStorageNotifierProvider.notifier) .saveDraft(draft) .catchError((e) => debugPrint('Failed to save draft: $e')); } } ComposeLogic.dispose(state); }; }, []); } /// Creates and manages the state notifier for rebuilds. static Listenable useStateNotifier(ComposeState state) { return useMemoized( () => Listenable.merge([ state.titleController, state.descriptionController, state.contentController, state.visibility, state.attachments, state.attachmentProgress, state.currentPublisher, state.submitting, ]), [state], ); } /// Resets form to clean state for new composition. static void resetForm(ComposeState state) { // Clear text fields state.titleController.clear(); state.descriptionController.clear(); state.contentController.clear(); state.slugController.clear(); // Reset visibility to default (0 = public) state.visibility.value = 0; // Clear attachments state.attachments.value = []; // Clear attachment progress state.attachmentProgress.value = {}; // Clear tags state.tagsController.clearTags(); // Clear categories state.categories.value = []; // Clear embed view state.embedView.value = null; // Clear poll state.pollId.value = null; // Clear realm state.realm.value = null; // Generate new draft ID for fresh composition // Note: We don't recreate the entire state, just reset the fields // The existing state object is reused for continuity } }