♻️ No longer two submit post function
This commit is contained in:
@@ -16,10 +16,8 @@ import 'package:island/widgets/content/sheet.dart';
|
|||||||
import 'package:island/widgets/post/compose_attachments.dart';
|
import 'package:island/widgets/post/compose_attachments.dart';
|
||||||
import 'package:island/widgets/post/compose_form_fields.dart';
|
import 'package:island/widgets/post/compose_form_fields.dart';
|
||||||
import 'package:island/widgets/post/compose_info_banner.dart';
|
import 'package:island/widgets/post/compose_info_banner.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
|
||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
|
||||||
import 'package:island/widgets/post/compose_toolbar.dart';
|
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/publishers_modal.dart';
|
import 'package:island/widgets/post/publishers_modal.dart';
|
||||||
@@ -143,16 +141,11 @@ class PostComposeCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
void showSettingsSheet() {
|
void showSettingsSheet() {
|
||||||
showModalBottomSheet(
|
ComposeLogic.showSettingsSheet(context, composeState);
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => ComposeSettingsSheet(state: composeState),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> performSubmit() async {
|
Future<void> performSubmit() async {
|
||||||
await ComposeSubmitUtils.performSubmit(
|
await ComposeLogic.performSubmit(
|
||||||
ref,
|
ref,
|
||||||
composeState,
|
composeState,
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ import 'package:island/services/compose_storage_db.dart';
|
|||||||
import 'package:island/services/event_bus.dart';
|
import 'package:island/services/event_bus.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/post/compose_card.dart';
|
import 'package:island/widgets/post/compose_card.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
|
||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
||||||
@@ -104,16 +102,11 @@ class PostComposeDialog extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Helper methods for actions
|
// Helper methods for actions
|
||||||
void showSettingsSheet() {
|
void showSettingsSheet() {
|
||||||
showModalBottomSheet(
|
ComposeLogic.showSettingsSheet(context, state);
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => ComposeSettingsSheet(state: state),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> performSubmit() async {
|
Future<void> performSubmit() async {
|
||||||
await ComposeSubmitUtils.performSubmit(
|
await ComposeLogic.performSubmit(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:island/services/event_bus.dart';
|
import 'package:island/services/event_bus.dart';
|
||||||
|
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@@ -21,6 +22,7 @@ import 'package:island/services/compose_storage_db.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/post/compose_link_attachments.dart';
|
import 'package:island/widgets/post/compose_link_attachments.dart';
|
||||||
import 'package:island/widgets/post/compose_poll.dart';
|
import 'package:island/widgets/post/compose_poll.dart';
|
||||||
|
import 'package:island/widgets/post/compose_fund.dart';
|
||||||
import 'package:island/widgets/post/compose_recorder.dart';
|
import 'package:island/widgets/post/compose_recorder.dart';
|
||||||
import 'package:island/pods/file_pool.dart';
|
import 'package:island/pods/file_pool.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
@@ -44,6 +46,8 @@ class ComposeState {
|
|||||||
int postType;
|
int postType;
|
||||||
// Linked poll id for this compose session (nullable)
|
// Linked poll id for this compose session (nullable)
|
||||||
final ValueNotifier<String?> pollId;
|
final ValueNotifier<String?> pollId;
|
||||||
|
// Linked fund id for this compose session (nullable)
|
||||||
|
final ValueNotifier<String?> fundId;
|
||||||
Timer? _autoSaveTimer;
|
Timer? _autoSaveTimer;
|
||||||
|
|
||||||
ComposeState({
|
ComposeState({
|
||||||
@@ -63,7 +67,9 @@ class ComposeState {
|
|||||||
required this.draftId,
|
required this.draftId,
|
||||||
this.postType = 0,
|
this.postType = 0,
|
||||||
String? pollId,
|
String? pollId,
|
||||||
}) : pollId = ValueNotifier<String?>(pollId);
|
String? fundId,
|
||||||
|
}) : pollId = ValueNotifier<String?>(pollId),
|
||||||
|
fundId = ValueNotifier<String?>(fundId);
|
||||||
|
|
||||||
void startAutoSave(WidgetRef ref) {
|
void startAutoSave(WidgetRef ref) {
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
@@ -133,6 +139,8 @@ class ComposeLogic {
|
|||||||
postType: postType,
|
postType: postType,
|
||||||
// initialize without poll by default
|
// initialize without poll by default
|
||||||
pollId: null,
|
pollId: null,
|
||||||
|
// initialize without fund by default
|
||||||
|
fundId: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +166,8 @@ class ComposeLogic {
|
|||||||
draftId: draft.id,
|
draftId: draft.id,
|
||||||
postType: postType,
|
postType: postType,
|
||||||
pollId: null,
|
pollId: null,
|
||||||
|
// initialize without fund by default
|
||||||
|
fundId: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,15 +628,40 @@ class ComposeLogic {
|
|||||||
state.pollId.value = poll.id;
|
state.pollId.value = poll.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> performAction(
|
static Future<void> pickFund(
|
||||||
|
WidgetRef ref,
|
||||||
|
ComposeState state,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
if (state.fundId.value != null) {
|
||||||
|
state.fundId.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fund = await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => const ComposeFundSheet(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fund == null) return;
|
||||||
|
state.fundId.value = fund.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified submit method that returns the created/updated post.
|
||||||
|
static Future<SnPost> performSubmit(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
SnPost? originalPost,
|
SnPost? originalPost,
|
||||||
SnPost? repliedPost,
|
SnPost? repliedPost,
|
||||||
SnPost? forwardedPost,
|
SnPost? forwardedPost,
|
||||||
|
required Function() onSuccess,
|
||||||
}) async {
|
}) async {
|
||||||
if (state.submitting.value) return;
|
if (state.submitting.value) {
|
||||||
|
throw Exception('Already submitting');
|
||||||
|
}
|
||||||
|
|
||||||
// Don't submit empty posts (no content and no attachments)
|
// Don't submit empty posts (no content and no attachments)
|
||||||
final hasContent =
|
final hasContent =
|
||||||
@@ -636,25 +671,31 @@ class ComposeLogic {
|
|||||||
final hasAttachments = state.attachments.value.isNotEmpty;
|
final hasAttachments = state.attachments.value.isNotEmpty;
|
||||||
|
|
||||||
if (!hasContent && !hasAttachments) {
|
if (!hasContent && !hasAttachments) {
|
||||||
|
// Show error message if context is mounted
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('postContentEmpty'.tr());
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text('postContentEmpty')));
|
||||||
}
|
}
|
||||||
return; // Don't submit empty posts
|
throw Exception('Post content is empty'); // Don't submit empty posts
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.submitting.value = true;
|
state.submitting.value = true;
|
||||||
|
|
||||||
// pload any local attachments first
|
// Upload any local attachments first
|
||||||
await Future.wait(
|
await Future.wait(
|
||||||
state.attachments.value
|
state.attachments.value
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.where((entry) => entry.value.isOnDevice)
|
.where((entry) => entry.value.isOnDevice)
|
||||||
.map((entry) => uploadAttachment(ref, state, entry.key)),
|
.map(
|
||||||
|
(entry) => ComposeLogic.uploadAttachment(ref, state, entry.key),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prepare API request
|
// Prepare API request
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
final isNewPost = originalPost == null;
|
final isNewPost = originalPost == null;
|
||||||
final endpoint =
|
final endpoint =
|
||||||
'/sphere${isNewPost ? '/posts' : '/posts/${originalPost.id}'}';
|
'/sphere${isNewPost ? '/posts' : '/posts/${originalPost.id}'}';
|
||||||
@@ -679,18 +720,56 @@ class ComposeLogic {
|
|||||||
'categories': state.categories.value.map((e) => e.slug).toList(),
|
'categories': state.categories.value.map((e) => e.slug).toList(),
|
||||||
if (state.realm.value != null) 'realm_id': state.realm.value?.id,
|
if (state.realm.value != null) 'realm_id': state.realm.value?.id,
|
||||||
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
||||||
|
if (state.fundId.value != null) 'fund_id': state.fundId.value,
|
||||||
if (state.embedView.value != null)
|
if (state.embedView.value != null)
|
||||||
'embed_view': state.embedView.value!.toJson(),
|
'embed_view': state.embedView.value!.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
await client.request(
|
final response = await client.request(
|
||||||
endpoint,
|
endpoint,
|
||||||
queryParameters: {'pub': state.currentPublisher.value?.name},
|
queryParameters: {'pub': state.currentPublisher.value?.name},
|
||||||
data: payload,
|
data: payload,
|
||||||
options: Options(method: isNewPost ? 'POST' : 'PATCH'),
|
options: Options(method: isNewPost ? 'POST' : 'PATCH'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse the response into a SnPost
|
||||||
|
final post = SnPost.fromJson(response.data);
|
||||||
|
|
||||||
|
// Call the success callback
|
||||||
|
onSuccess();
|
||||||
|
eventBus.fire(PostCreatedEvent());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> performAction(
|
||||||
|
WidgetRef ref,
|
||||||
|
ComposeState state,
|
||||||
|
BuildContext context, {
|
||||||
|
SnPost? originalPost,
|
||||||
|
SnPost? repliedPost,
|
||||||
|
SnPost? forwardedPost,
|
||||||
|
}) async {
|
||||||
|
await ComposeLogic.performSubmit(
|
||||||
|
ref,
|
||||||
|
state,
|
||||||
|
context,
|
||||||
|
originalPost: originalPost,
|
||||||
|
repliedPost: repliedPost,
|
||||||
|
forwardedPost: forwardedPost,
|
||||||
|
onSuccess: () async {
|
||||||
// Delete draft after successful submission
|
// Delete draft after successful submission
|
||||||
if (state.postType == 1) {
|
if (state.postType == 1) {
|
||||||
// Delete article draft
|
// Delete article draft
|
||||||
@@ -707,13 +786,17 @@ class ComposeLogic {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).maybePop(true);
|
Navigator.of(context).maybePop(true);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
eventBus.fire(PostCreatedEvent());
|
);
|
||||||
} catch (err) {
|
|
||||||
showErrorAlert(err);
|
|
||||||
} 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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> handlePaste(ComposeState state) async {
|
static Future<void> handlePaste(ComposeState state) async {
|
||||||
@@ -778,5 +861,6 @@ class ComposeLogic {
|
|||||||
state.realm.dispose();
|
state.realm.dispose();
|
||||||
state.embedView.dispose();
|
state.embedView.dispose();
|
||||||
state.pollId.dispose();
|
state.pollId.dispose();
|
||||||
|
state.fundId.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import 'package:island/screens/posts/compose.dart';
|
|||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/post/compose_card.dart';
|
import 'package:island/widgets/post/compose_card.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
|
||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:island/widgets/post/compose_state_utils.dart';
|
import 'package:island/widgets/post/compose_state_utils.dart';
|
||||||
import 'package:island/widgets/post/compose_submit_utils.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
/// A dialog that wraps PostComposeCard for easy use in dialogs.
|
||||||
@@ -106,16 +104,11 @@ class PostComposeSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Helper methods for actions
|
// Helper methods for actions
|
||||||
void showSettingsSheet() {
|
void showSettingsSheet() {
|
||||||
showModalBottomSheet(
|
ComposeLogic.showSettingsSheet(context, state);
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => ComposeSettingsSheet(state: state),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> performSubmit() async {
|
Future<void> performSubmit() async {
|
||||||
await ComposeSubmitUtils.performSubmit(
|
await ComposeLogic.performSubmit(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
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/services/event_bus.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();
|
|
||||||
eventBus.fire(PostCreatedEvent());
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,6 +54,10 @@ class ComposeToolbar extends HookConsumerWidget {
|
|||||||
ComposeLogic.pickPoll(ref, state, context);
|
ComposeLogic.pickPoll(ref, state, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pickFund() {
|
||||||
|
ComposeLogic.pickFund(ref, state, context);
|
||||||
|
}
|
||||||
|
|
||||||
void showEmbedSheet() {
|
void showEmbedSheet() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -143,6 +147,29 @@ class ComposeToolbar extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
// Fund button with visual state when a fund is linked
|
||||||
|
ListenableBuilder(
|
||||||
|
listenable: state.fundId,
|
||||||
|
builder: (context, _) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: pickFund,
|
||||||
|
icon: const Icon(Symbols.account_balance_wallet),
|
||||||
|
tooltip: 'fund'.tr(),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -2,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
state.fundId.value != null
|
||||||
|
? colorScheme.primary.withOpacity(0.15)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Embed button with visual state when embed is present
|
// Embed button with visual state when embed is present
|
||||||
ListenableBuilder(
|
ListenableBuilder(
|
||||||
listenable: state.embedView,
|
listenable: state.embedView,
|
||||||
@@ -252,6 +279,25 @@ class ComposeToolbar extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
// Fund button with visual state when a fund is linked
|
||||||
|
ListenableBuilder(
|
||||||
|
listenable: state.fundId,
|
||||||
|
builder: (context, _) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: pickFund,
|
||||||
|
icon: const Icon(Symbols.account_balance_wallet),
|
||||||
|
tooltip: 'fund'.tr(),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
state.fundId.value != null
|
||||||
|
? colorScheme.primary.withOpacity(0.15)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Embed button with visual state when embed is present
|
// Embed button with visual state when embed is present
|
||||||
ListenableBuilder(
|
ListenableBuilder(
|
||||||
listenable: state.embedView,
|
listenable: state.embedView,
|
||||||
|
|||||||
Reference in New Issue
Block a user