Files
App/lib/widgets/post/compose_submit_utils.dart
2025-10-06 11:55:53 +08:00

137 lines
4.3 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<void> performSubmit(
WidgetRef ref,
ComposeState state,
BuildContext context, {
SnPost? originalPost,
SnPost? repliedPost,
SnPost? forwardedPost,
required VoidCallback onSuccess,
}) async {
if (state.submitting.value) return;
// 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')));
}
return; // 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.tagsController.getTags,
'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
client.request(
endpoint,
queryParameters: {'pub': state.currentPublisher.value?.name},
data: payload,
options: Options(method: isNewPost ? 'POST' : 'PATCH'),
);
// Call the success callback with the created/updated post
onSuccess();
} 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,
);
}
}