144 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:dio/dio.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:island/models/post.dart';
 | |
| import 'package:island/pods/network.dart';
 | |
| import 'package:island/widgets/post/compose_settings_sheet.dart';
 | |
| import 'package:island/widgets/post/compose_shared.dart';
 | |
| 
 | |
| /// Utility class for common compose submit logic.
 | |
| class ComposeSubmitUtils {
 | |
|   /// Performs the submit action for posts.
 | |
|   static Future<SnPost> performSubmit(
 | |
|     WidgetRef ref,
 | |
|     ComposeState state,
 | |
|     BuildContext context, {
 | |
|     SnPost? originalPost,
 | |
|     SnPost? repliedPost,
 | |
|     SnPost? forwardedPost,
 | |
|     required Function() onSuccess,
 | |
|   }) async {
 | |
|     if (state.submitting.value) {
 | |
|       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;
 | |
| 
 | |
|       // Upload any local attachments first
 | |
|       await Future.wait(
 | |
|         state.attachments.value
 | |
|             .asMap()
 | |
|             .entries
 | |
|             .where((entry) => entry.value.isOnDevice)
 | |
|             .map(
 | |
|               (entry) => ComposeLogic.uploadAttachment(ref, state, entry.key),
 | |
|             ),
 | |
|       );
 | |
| 
 | |
|       // Prepare API request
 | |
|       final client = ref.read(apiClientProvider);
 | |
|       final isNewPost = originalPost == null;
 | |
|       final endpoint =
 | |
|           '/sphere${isNewPost ? '/posts' : '/posts/${originalPost.id}'}';
 | |
| 
 | |
|       // Create request payload
 | |
|       final payload = {
 | |
|         'title': state.titleController.text,
 | |
|         'description': state.descriptionController.text,
 | |
|         'content': state.contentController.text,
 | |
|         if (state.slugController.text.isNotEmpty)
 | |
|           'slug': state.slugController.text,
 | |
|         'visibility': state.visibility.value,
 | |
|         'attachments':
 | |
|             state.attachments.value
 | |
|                 .where((e) => e.isOnCloud)
 | |
|                 .map((e) => e.data.id)
 | |
|                 .toList(),
 | |
|         'type': state.postType,
 | |
|         if (repliedPost != null) 'replied_post_id': repliedPost.id,
 | |
|         if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id,
 | |
|         'tags': state.tags.value,
 | |
|         'categories': state.categories.value.map((e) => e.slug).toList(),
 | |
|         if (state.realm.value != null) 'realm_id': state.realm.value?.id,
 | |
|         if (state.pollId.value != null) 'poll_id': state.pollId.value,
 | |
|         if (state.embedView.value != null)
 | |
|           'embed_view': state.embedView.value!.toJson(),
 | |
|       };
 | |
| 
 | |
|       // Send request
 | |
|       final response = await client.request(
 | |
|         endpoint,
 | |
|         queryParameters: {'pub': state.currentPublisher.value?.name},
 | |
|         data: payload,
 | |
|         options: Options(method: isNewPost ? 'POST' : 'PATCH'),
 | |
|       );
 | |
| 
 | |
|       // Parse the response into a SnPost
 | |
|       final post = SnPost.fromJson(response.data);
 | |
| 
 | |
|       // Call the success callback
 | |
|       onSuccess();
 | |
| 
 | |
|       return post;
 | |
|     } catch (err) {
 | |
|       // Show error message if context is mounted
 | |
|       if (context.mounted) {
 | |
|         ScaffoldMessenger.of(
 | |
|           context,
 | |
|         ).showSnackBar(SnackBar(content: Text('Error: $err')));
 | |
|       }
 | |
|       rethrow;
 | |
|     } finally {
 | |
|       state.submitting.value = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Shows the settings sheet modal.
 | |
|   static void showSettingsSheet(BuildContext context, ComposeState state) {
 | |
|     showModalBottomSheet(
 | |
|       context: context,
 | |
|       isScrollControlled: true,
 | |
|       builder: (context) => ComposeSettingsSheet(state: state),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /// Handles keyboard press events for compose shortcuts.
 | |
|   static void handleKeyPress(
 | |
|     KeyEvent event,
 | |
|     ComposeState state,
 | |
|     WidgetRef ref,
 | |
|     BuildContext context, {
 | |
|     SnPost? originalPost,
 | |
|     SnPost? repliedPost,
 | |
|     SnPost? forwardedPost,
 | |
|   }) {
 | |
|     ComposeLogic.handleKeyPress(
 | |
|       event,
 | |
|       state,
 | |
|       ref,
 | |
|       context,
 | |
|       originalPost: originalPost,
 | |
|       repliedPost: repliedPost,
 | |
|       forwardedPost: forwardedPost,
 | |
|     );
 | |
|   }
 | |
| }
 |