♻️ Refactored the post compose sheet
This commit is contained in:
		| @@ -8,7 +8,7 @@ import 'package:island/screens/notification.dart'; | |||||||
| import 'package:island/services/event_bus.dart'; | import 'package:island/services/event_bus.dart'; | ||||||
| import 'package:island/widgets/account/account_picker.dart'; | import 'package:island/widgets/account/account_picker.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/post/compose_dialog.dart'; | import 'package:island/widgets/post/compose_sheet.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
| enum FabMenuType { main, chat, realm } | enum FabMenuType { main, chat, realm } | ||||||
| @@ -166,7 +166,7 @@ class FabMenu extends HookConsumerWidget { | |||||||
|               title: Text('postCompose').tr(), |               title: Text('postCompose').tr(), | ||||||
|               onTap: () async { |               onTap: () async { | ||||||
|                 Navigator.of(context).pop(); |                 Navigator.of(context).pop(); | ||||||
|                 await PostComposeDialog.show(context); |                 await PostComposeSheet.show(context); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|             const Divider(), |             const Divider(), | ||||||
|   | |||||||
| @@ -36,7 +36,8 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|   final VoidCallback? onCancel; |   final VoidCallback? onCancel; | ||||||
|   final Function()? onSubmit; |   final Function()? onSubmit; | ||||||
|   final Function(ComposeState)? onStateChanged; |   final Function(ComposeState)? onStateChanged; | ||||||
|   final bool isDialog; |   final bool isContained; | ||||||
|  |   final bool showHeader; | ||||||
|  |  | ||||||
|   const PostComposeCard({ |   const PostComposeCard({ | ||||||
|     super.key, |     super.key, | ||||||
| @@ -45,7 +46,8 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|     this.onCancel, |     this.onCancel, | ||||||
|     this.onSubmit, |     this.onSubmit, | ||||||
|     this.onStateChanged, |     this.onStateChanged, | ||||||
|     this.isDialog = false, |     this.isContained = false, | ||||||
|  |     this.showHeader = true, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -169,14 +171,12 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     final maxHeight = math.min( |     final maxHeight = math.min(640.0, MediaQuery.of(context).size.height * 0.8); | ||||||
|       640.0, |  | ||||||
|       MediaQuery.of(context).size.height * (isDialog ? 0.8 : 0.72), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return Card( |     return Card( | ||||||
|       margin: EdgeInsets.zero, |       margin: EdgeInsets.zero, | ||||||
|       color: isDialog ? Theme.of(context).colorScheme.surfaceContainer : null, |       color: isContained ? Colors.transparent : null, | ||||||
|  |       elevation: isContained ? 0 : null, | ||||||
|       child: Container( |       child: Container( | ||||||
|         constraints: BoxConstraints(maxHeight: maxHeight), |         constraints: BoxConstraints(maxHeight: maxHeight), | ||||||
|         child: Column( |         child: Column( | ||||||
| @@ -184,9 +184,13 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|           children: [ |           children: [ | ||||||
|             // Header with actions |             // Header with actions | ||||||
|  |             if (showHeader) | ||||||
|               Container( |               Container( | ||||||
|                 height: 65, |                 height: 65, | ||||||
|               padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), |                 padding: const EdgeInsets.symmetric( | ||||||
|  |                   horizontal: 16, | ||||||
|  |                   vertical: 12, | ||||||
|  |                 ), | ||||||
|                 decoration: BoxDecoration( |                 decoration: BoxDecoration( | ||||||
|                   border: Border( |                   border: Border( | ||||||
|                     bottom: BorderSide( |                     bottom: BorderSide( | ||||||
| @@ -199,7 +203,9 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|                     const Gap(4), |                     const Gap(4), | ||||||
|                     Text( |                     Text( | ||||||
|                       'postCompose'.tr(), |                       'postCompose'.tr(), | ||||||
|                     style: theme.textTheme.titleMedium!.copyWith(fontSize: 18), |                       style: theme.textTheme.titleMedium!.copyWith( | ||||||
|  |                         fontSize: 18, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                     const Spacer(), |                     const Spacer(), | ||||||
|                     IconButton( |                     IconButton( | ||||||
| @@ -310,7 +316,7 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|                           onTap: () { |                           onTap: () { | ||||||
|                             if (state.currentPublisher.value == null) { |                             if (state.currentPublisher.value == null) { | ||||||
|                               // No publisher loaded, guide user to create one |                               // No publisher loaded, guide user to create one | ||||||
|                               if (isDialog) { |                               if (isContained) { | ||||||
|                                 Navigator.of(context).pop(); |                                 Navigator.of(context).pop(); | ||||||
|                               } |                               } | ||||||
|                               context.pushNamed('creatorNew').then((value) { |                               context.pushNamed('creatorNew').then((value) { | ||||||
| @@ -347,7 +353,7 @@ class PostComposeCard extends HookConsumerWidget { | |||||||
|                                 onPublisherTap: () { |                                 onPublisherTap: () { | ||||||
|                                   if (state.currentPublisher.value == null) { |                                   if (state.currentPublisher.value == null) { | ||||||
|                                     // No publisher loaded, guide user to create one |                                     // No publisher loaded, guide user to create one | ||||||
|                                     if (isDialog) { |                                     if (isContained) { | ||||||
|                                       Navigator.of(context).pop(); |                                       Navigator.of(context).pop(); | ||||||
|                                     } |                                     } | ||||||
|                                     context.pushNamed('creatorNew').then(( |                                     context.pushNamed('creatorNew').then(( | ||||||
|   | |||||||
| @@ -7,8 +7,13 @@ import 'package:island/models/post.dart'; | |||||||
| import 'package:island/screens/posts/compose.dart'; | 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/services/event_bus.dart'; | import 'package:island/services/event_bus.dart'; | ||||||
| import 'package:island/services/responsive.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_state_utils.dart'; | ||||||
|  | import 'package:island/widgets/post/compose_submit_utils.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. | ||||||
| /// This provides a convenient way to show the compose interface in a modal dialog. | /// This provides a convenient way to show the compose interface in a modal dialog. | ||||||
| @@ -29,16 +34,15 @@ class PostComposeDialog extends HookConsumerWidget { | |||||||
|     SnPost? originalPost, |     SnPost? originalPost, | ||||||
|     PostComposeInitialState? initialState, |     PostComposeInitialState? initialState, | ||||||
|   }) { |   }) { | ||||||
|     return showDialog<bool>( |     return showModalBottomSheet<bool>( | ||||||
|       context: context, |       context: context, | ||||||
|  |       isScrollControlled: true, | ||||||
|       useRootNavigator: true, |       useRootNavigator: true, | ||||||
|       builder: |       builder: | ||||||
|           (context) => Padding( |           (context) => PostComposeDialog( | ||||||
|             padding: EdgeInsets.all(16), |  | ||||||
|             child: PostComposeDialog( |  | ||||||
|             originalPost: originalPost, |             originalPost: originalPost, | ||||||
|             initialState: initialState, |             initialState: initialState, | ||||||
|             ), |             isBottomSheet: true, | ||||||
|           ), |           ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -48,7 +52,41 @@ class PostComposeDialog extends HookConsumerWidget { | |||||||
|     final drafts = ref.watch(composeStorageNotifierProvider); |     final drafts = ref.watch(composeStorageNotifierProvider); | ||||||
|     final restoredInitialState = useState<PostComposeInitialState?>(null); |     final restoredInitialState = useState<PostComposeInitialState?>(null); | ||||||
|     final prompted = useState(false); |     final prompted = useState(false); | ||||||
|     final isWide = isWideScreen(context); |  | ||||||
|  |     final repliedPost = initialState?.replyingTo ?? originalPost?.repliedPost; | ||||||
|  |     final forwardedPost = | ||||||
|  |         initialState?.forwardingTo ?? originalPost?.forwardedPost; | ||||||
|  |  | ||||||
|  |     // Create compose state | ||||||
|  |     final state = useMemoized( | ||||||
|  |       () => ComposeLogic.createState( | ||||||
|  |         originalPost: originalPost, | ||||||
|  |         forwardedPost: forwardedPost, | ||||||
|  |         repliedPost: repliedPost, | ||||||
|  |         postType: 0, | ||||||
|  |       ), | ||||||
|  |       [originalPost, forwardedPost, repliedPost], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // 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, | ||||||
|  |       ]), | ||||||
|  |       [state], | ||||||
|  |     ); | ||||||
|  |     useListenable(stateNotifier); | ||||||
|  |  | ||||||
|  |     // Use shared state management utilities | ||||||
|  |     ComposeStateUtils.usePublisherInitialization(ref, state); | ||||||
|  |     ComposeStateUtils.useInitialStateLoader(state, initialState); | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|       if (!prompted.value && |       if (!prompted.value && | ||||||
| @@ -64,13 +102,58 @@ class PostComposeDialog extends HookConsumerWidget { | |||||||
|       return null; |       return null; | ||||||
|     }, [drafts, prompted.value]); |     }, [drafts, prompted.value]); | ||||||
|  |  | ||||||
|     return Dialog( |     // Helper methods for actions | ||||||
|       insetPadding: isWide ? const EdgeInsets.all(16) : EdgeInsets.zero, |     void showSettingsSheet() { | ||||||
|       child: ConstrainedBox( |       showModalBottomSheet( | ||||||
|         constraints: |         context: context, | ||||||
|             isWide |         isScrollControlled: true, | ||||||
|                 ? const BoxConstraints(maxWidth: 600) |         useRootNavigator: true, | ||||||
|                 : const BoxConstraints.expand(), |         builder: (context) => ComposeSettingsSheet(state: state), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Future<void> performSubmit() async { | ||||||
|  |       await ComposeSubmitUtils.performSubmit( | ||||||
|  |         ref, | ||||||
|  |         state, | ||||||
|  |         context, | ||||||
|  |         originalPost: originalPost, | ||||||
|  |         repliedPost: repliedPost, | ||||||
|  |         forwardedPost: forwardedPost, | ||||||
|  |         onSuccess: () { | ||||||
|  |           // Fire event to notify listeners that a post was created | ||||||
|  |           eventBus.fire(PostCreatedEvent()); | ||||||
|  |           Navigator.of(context).pop(true); | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final actions = [ | ||||||
|  |       IconButton( | ||||||
|  |         icon: const Icon(Symbols.settings), | ||||||
|  |         onPressed: showSettingsSheet, | ||||||
|  |         tooltip: 'postSettings'.tr(), | ||||||
|  |       ), | ||||||
|  |       IconButton( | ||||||
|  |         onPressed: | ||||||
|  |             (state.submitting.value || state.currentPublisher.value == null) | ||||||
|  |                 ? null | ||||||
|  |                 : performSubmit, | ||||||
|  |         icon: | ||||||
|  |             state.submitting.value | ||||||
|  |                 ? SizedBox( | ||||||
|  |                   width: 24, | ||||||
|  |                   height: 24, | ||||||
|  |                   child: const CircularProgressIndicator(strokeWidth: 2), | ||||||
|  |                 ) | ||||||
|  |                 : Icon(originalPost != null ? Symbols.edit : Symbols.upload), | ||||||
|  |         tooltip: originalPost != null ? 'postUpdate'.tr() : 'postPublish'.tr(), | ||||||
|  |       ), | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'postCompose'.tr(), | ||||||
|  |       actions: actions, | ||||||
|       child: PostComposeCard( |       child: PostComposeCard( | ||||||
|         originalPost: originalPost, |         originalPost: originalPost, | ||||||
|         initialState: restoredInitialState.value ?? initialState, |         initialState: restoredInitialState.value ?? initialState, | ||||||
| @@ -80,8 +163,8 @@ class PostComposeDialog extends HookConsumerWidget { | |||||||
|           eventBus.fire(PostCreatedEvent()); |           eventBus.fire(PostCreatedEvent()); | ||||||
|           Navigator.of(context).pop(true); |           Navigator.of(context).pop(true); | ||||||
|         }, |         }, | ||||||
|           isDialog: true, |         isContained: true, | ||||||
|         ), |         showHeader: false, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -157,6 +157,8 @@ class ComposeSettingsSheet extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     final tagInputController = useTextEditingController(); | ||||||
|  |  | ||||||
|     return SheetScaffold( |     return SheetScaffold( | ||||||
|       titleText: 'postSettings'.tr(), |       titleText: 'postSettings'.tr(), | ||||||
|       heightFactor: 0.6, |       heightFactor: 0.6, | ||||||
| @@ -255,6 +257,7 @@ class ComposeSettingsSheet extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                   // Tag input with autocomplete |                   // Tag input with autocomplete | ||||||
|                   TypeAheadField<SnPostTag>( |                   TypeAheadField<SnPostTag>( | ||||||
|  |                     controller: tagInputController, | ||||||
|                     builder: (context, controller, focusNode) { |                     builder: (context, controller, focusNode) { | ||||||
|                       return TextField( |                       return TextField( | ||||||
|                         controller: controller, |                         controller: controller, | ||||||
| @@ -279,6 +282,7 @@ class ComposeSettingsSheet extends HookConsumerWidget { | |||||||
|                           borderRadius: BorderRadius.all(Radius.circular(8)), |                           borderRadius: BorderRadius.all(Radius.circular(8)), | ||||||
|                         ), |                         ), | ||||||
|                         title: Text('#${suggestion.slug}'), |                         title: Text('#${suggestion.slug}'), | ||||||
|  |                         subtitle: Text('${suggestion.usage} posts'), | ||||||
|                         dense: true, |                         dense: true, | ||||||
|                       ); |                       ); | ||||||
|                     }, |                     }, | ||||||
| @@ -289,6 +293,7 @@ class ComposeSettingsSheet extends HookConsumerWidget { | |||||||
|                           suggestion.slug, |                           suggestion.slug, | ||||||
|                         ]; |                         ]; | ||||||
|                       } |                       } | ||||||
|  |                       tagInputController.clear(); | ||||||
|                     }, |                     }, | ||||||
|                     direction: VerticalDirection.down, |                     direction: VerticalDirection.down, | ||||||
|                     hideOnEmpty: true, |                     hideOnEmpty: true, | ||||||
|   | |||||||
							
								
								
									
										300
									
								
								lib/widgets/post/compose_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								lib/widgets/post/compose_sheet.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,300 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | 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/posts/compose.dart'; | ||||||
|  | import 'package:island/services/compose_storage_db.dart'; | ||||||
|  | import 'package:island/services/event_bus.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.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_state_utils.dart'; | ||||||
|  | import 'package:island/widgets/post/compose_submit_utils.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | /// A dialog that wraps PostComposeCard for easy use in dialogs. | ||||||
|  | /// This provides a convenient way to show the compose interface in a modal dialog. | ||||||
|  | class PostComposeSheet extends HookConsumerWidget { | ||||||
|  |   final SnPost? originalPost; | ||||||
|  |   final PostComposeInitialState? initialState; | ||||||
|  |   final bool isBottomSheet; | ||||||
|  |  | ||||||
|  |   const PostComposeSheet({ | ||||||
|  |     super.key, | ||||||
|  |     this.originalPost, | ||||||
|  |     this.initialState, | ||||||
|  |     this.isBottomSheet = false, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   static Future<bool?> show( | ||||||
|  |     BuildContext context, { | ||||||
|  |     SnPost? originalPost, | ||||||
|  |     PostComposeInitialState? initialState, | ||||||
|  |   }) { | ||||||
|  |     return showModalBottomSheet<bool>( | ||||||
|  |       context: context, | ||||||
|  |       isScrollControlled: true, | ||||||
|  |       useRootNavigator: true, | ||||||
|  |       builder: | ||||||
|  |           (context) => PostComposeSheet( | ||||||
|  |             originalPost: originalPost, | ||||||
|  |             initialState: initialState, | ||||||
|  |             isBottomSheet: true, | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final drafts = ref.watch(composeStorageNotifierProvider); | ||||||
|  |     final restoredInitialState = useState<PostComposeInitialState?>(null); | ||||||
|  |     final prompted = useState(false); | ||||||
|  |  | ||||||
|  |     final repliedPost = initialState?.replyingTo ?? originalPost?.repliedPost; | ||||||
|  |     final forwardedPost = | ||||||
|  |         initialState?.forwardingTo ?? originalPost?.forwardedPost; | ||||||
|  |  | ||||||
|  |     // Create compose state | ||||||
|  |     final state = useMemoized( | ||||||
|  |       () => ComposeLogic.createState( | ||||||
|  |         originalPost: originalPost, | ||||||
|  |         forwardedPost: forwardedPost, | ||||||
|  |         repliedPost: repliedPost, | ||||||
|  |         postType: 0, | ||||||
|  |       ), | ||||||
|  |       [originalPost, forwardedPost, repliedPost], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // 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, | ||||||
|  |       ]), | ||||||
|  |       [state], | ||||||
|  |     ); | ||||||
|  |     useListenable(stateNotifier); | ||||||
|  |  | ||||||
|  |     // Use shared state management utilities | ||||||
|  |     ComposeStateUtils.usePublisherInitialization(ref, state); | ||||||
|  |     ComposeStateUtils.useInitialStateLoader(state, initialState); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (!prompted.value && | ||||||
|  |           originalPost == null && | ||||||
|  |           initialState?.replyingTo == null && | ||||||
|  |           initialState?.forwardingTo == null && | ||||||
|  |           drafts.isNotEmpty) { | ||||||
|  |         prompted.value = true; | ||||||
|  |         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||
|  |           _showRestoreDialog(ref, restoredInitialState); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [drafts, prompted.value]); | ||||||
|  |  | ||||||
|  |     // Helper methods for actions | ||||||
|  |     void showSettingsSheet() { | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         useRootNavigator: true, | ||||||
|  |         builder: (context) => ComposeSettingsSheet(state: state), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Future<void> performSubmit() async { | ||||||
|  |       await ComposeSubmitUtils.performSubmit( | ||||||
|  |         ref, | ||||||
|  |         state, | ||||||
|  |         context, | ||||||
|  |         originalPost: originalPost, | ||||||
|  |         repliedPost: repliedPost, | ||||||
|  |         forwardedPost: forwardedPost, | ||||||
|  |         onSuccess: () { | ||||||
|  |           // Fire event to notify listeners that a post was created | ||||||
|  |           eventBus.fire(PostCreatedEvent()); | ||||||
|  |           Navigator.of(context).pop(true); | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final actions = [ | ||||||
|  |       IconButton( | ||||||
|  |         icon: const Icon(Symbols.settings), | ||||||
|  |         onPressed: showSettingsSheet, | ||||||
|  |         tooltip: 'postSettings'.tr(), | ||||||
|  |       ), | ||||||
|  |       IconButton( | ||||||
|  |         onPressed: | ||||||
|  |             (state.submitting.value || state.currentPublisher.value == null) | ||||||
|  |                 ? null | ||||||
|  |                 : performSubmit, | ||||||
|  |         icon: | ||||||
|  |             state.submitting.value | ||||||
|  |                 ? SizedBox( | ||||||
|  |                   width: 24, | ||||||
|  |                   height: 24, | ||||||
|  |                   child: const CircularProgressIndicator(strokeWidth: 2), | ||||||
|  |                 ) | ||||||
|  |                 : Icon(originalPost != null ? Symbols.edit : Symbols.upload), | ||||||
|  |         tooltip: originalPost != null ? 'postUpdate'.tr() : 'postPublish'.tr(), | ||||||
|  |       ), | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'postCompose'.tr(), | ||||||
|  |       actions: actions, | ||||||
|  |       child: PostComposeCard( | ||||||
|  |         originalPost: originalPost, | ||||||
|  |         initialState: restoredInitialState.value ?? initialState, | ||||||
|  |         onCancel: () => Navigator.of(context).pop(), | ||||||
|  |         onSubmit: () { | ||||||
|  |           // Fire event to notify listeners that a post was created | ||||||
|  |           eventBus.fire(PostCreatedEvent()); | ||||||
|  |           Navigator.of(context).pop(true); | ||||||
|  |         }, | ||||||
|  |         isContained: true, | ||||||
|  |         showHeader: false, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> _showRestoreDialog( | ||||||
|  |     WidgetRef ref, | ||||||
|  |     ValueNotifier<PostComposeInitialState?> restoredInitialState, | ||||||
|  |   ) async { | ||||||
|  |     final drafts = ref.read(composeStorageNotifierProvider); | ||||||
|  |     if (drafts.isNotEmpty) { | ||||||
|  |       final latestDraft = drafts.values.last; | ||||||
|  |  | ||||||
|  |       final restore = await showDialog<bool>( | ||||||
|  |         context: ref.context, | ||||||
|  |         useRootNavigator: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => AlertDialog( | ||||||
|  |               title: Text('restoreDraftTitle'.tr()), | ||||||
|  |               content: Column( | ||||||
|  |                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   Text('restoreDraftMessage'.tr()), | ||||||
|  |                   const SizedBox(height: 16), | ||||||
|  |                   _buildCompactDraftPreview(context, latestDraft), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               actions: [ | ||||||
|  |                 TextButton( | ||||||
|  |                   onPressed: () => Navigator.of(context).pop(false), | ||||||
|  |                   child: Text('no'.tr()), | ||||||
|  |                 ), | ||||||
|  |                 TextButton( | ||||||
|  |                   onPressed: () => Navigator.of(context).pop(true), | ||||||
|  |                   child: Text('yes'.tr()), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |       if (restore == true) { | ||||||
|  |         // Delete the old draft | ||||||
|  |         await ref | ||||||
|  |             .read(composeStorageNotifierProvider.notifier) | ||||||
|  |             .deleteDraft(latestDraft.id); | ||||||
|  |         restoredInitialState.value = PostComposeInitialState( | ||||||
|  |           title: latestDraft.title, | ||||||
|  |           description: latestDraft.description, | ||||||
|  |           content: latestDraft.content, | ||||||
|  |           visibility: latestDraft.visibility, | ||||||
|  |           attachments: | ||||||
|  |               latestDraft.attachments | ||||||
|  |                   .map((e) => UniversalFile.fromAttachment(e)) | ||||||
|  |                   .toList(), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildCompactDraftPreview(BuildContext context, SnPost draft) { | ||||||
|  |     return Container( | ||||||
|  |       padding: const EdgeInsets.all(12), | ||||||
|  |       decoration: BoxDecoration( | ||||||
|  |         color: Theme.of(context).colorScheme.surface, | ||||||
|  |         borderRadius: BorderRadius.circular(8), | ||||||
|  |         border: Border.all( | ||||||
|  |           color: Theme.of(context).colorScheme.outline.withOpacity(0.3), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       child: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |         children: [ | ||||||
|  |           Row( | ||||||
|  |             children: [ | ||||||
|  |               Icon( | ||||||
|  |                 Icons.description, | ||||||
|  |                 size: 16, | ||||||
|  |                 color: Theme.of(context).colorScheme.primary, | ||||||
|  |               ), | ||||||
|  |               const SizedBox(width: 8), | ||||||
|  |               Text( | ||||||
|  |                 'draft'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.labelMedium?.copyWith( | ||||||
|  |                   color: Theme.of(context).colorScheme.primary, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |           const SizedBox(height: 8), | ||||||
|  |           if (draft.title?.isNotEmpty ?? false) | ||||||
|  |             Text( | ||||||
|  |               draft.title!, | ||||||
|  |               style: TextStyle( | ||||||
|  |                 fontWeight: FontWeight.w500, | ||||||
|  |                 fontSize: 14, | ||||||
|  |                 color: Theme.of(context).colorScheme.onSurface, | ||||||
|  |               ), | ||||||
|  |               maxLines: 1, | ||||||
|  |               overflow: TextOverflow.ellipsis, | ||||||
|  |             ), | ||||||
|  |           if (draft.content?.isNotEmpty ?? false) | ||||||
|  |             Text( | ||||||
|  |               draft.content!, | ||||||
|  |               style: TextStyle( | ||||||
|  |                 fontSize: 12, | ||||||
|  |                 color: Theme.of(context).colorScheme.onSurfaceVariant, | ||||||
|  |               ), | ||||||
|  |               maxLines: 2, | ||||||
|  |               overflow: TextOverflow.ellipsis, | ||||||
|  |             ), | ||||||
|  |           if (draft.attachments.isNotEmpty) | ||||||
|  |             Row( | ||||||
|  |               mainAxisSize: MainAxisSize.min, | ||||||
|  |               children: [ | ||||||
|  |                 Icon( | ||||||
|  |                   Icons.attach_file, | ||||||
|  |                   size: 12, | ||||||
|  |                   color: Theme.of(context).colorScheme.secondary, | ||||||
|  |                 ), | ||||||
|  |                 const SizedBox(width: 4), | ||||||
|  |                 Text( | ||||||
|  |                   '${draft.attachments.length} attachment${draft.attachments.length > 1 ? 's' : ''}', | ||||||
|  |                   style: TextStyle( | ||||||
|  |                     color: Theme.of(context).colorScheme.secondary, | ||||||
|  |                     fontSize: 11, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -178,10 +178,9 @@ class ComposeToolbar extends HookConsumerWidget { | |||||||
|                       horizontal: -4, |                       horizontal: -4, | ||||||
|                       vertical: -4, |                       vertical: -4, | ||||||
|                     ), |                     ), | ||||||
|                     padding: EdgeInsets.zero, |  | ||||||
|                     constraints: const BoxConstraints( |                     constraints: const BoxConstraints( | ||||||
|                       minWidth: 32, |                       minWidth: 32, | ||||||
|                       minHeight: 32, |                       minHeight: 48, | ||||||
|                     ), |                     ), | ||||||
|                   ) |                   ) | ||||||
|                 else if (originalPost == null) |                 else if (originalPost == null) | ||||||
| @@ -195,10 +194,9 @@ class ComposeToolbar extends HookConsumerWidget { | |||||||
|                       horizontal: -4, |                       horizontal: -4, | ||||||
|                       vertical: -4, |                       vertical: -4, | ||||||
|                     ), |                     ), | ||||||
|                     padding: EdgeInsets.zero, |  | ||||||
|                     constraints: const BoxConstraints( |                     constraints: const BoxConstraints( | ||||||
|                       minWidth: 32, |                       minWidth: 32, | ||||||
|                       minHeight: 32, |                       minHeight: 48, | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|               ], |               ], | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ import 'package:island/widgets/post/embed_view_renderer.dart'; | |||||||
| import 'package:island/widgets/post/post_reaction_sheet.dart'; | import 'package:island/widgets/post/post_reaction_sheet.dart'; | ||||||
| import 'package:island/widgets/safety/abuse_report_helper.dart'; | import 'package:island/widgets/safety/abuse_report_helper.dart'; | ||||||
| import 'package:island/widgets/share/share_sheet.dart'; | import 'package:island/widgets/share/share_sheet.dart'; | ||||||
| import 'package:island/widgets/post/compose_dialog.dart'; | import 'package:island/widgets/post/compose_sheet.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:path_provider/path_provider.dart' show getTemporaryDirectory; | import 'package:path_provider/path_provider.dart' show getTemporaryDirectory; | ||||||
| import 'package:screenshot/screenshot.dart'; | import 'package:screenshot/screenshot.dart'; | ||||||
| @@ -180,7 +180,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|                 title: 'edit'.tr(), |                 title: 'edit'.tr(), | ||||||
|                 image: MenuImage.icon(Symbols.edit), |                 image: MenuImage.icon(Symbols.edit), | ||||||
|                 callback: () async { |                 callback: () async { | ||||||
|                   final result = await PostComposeDialog.show( |                   final result = await PostComposeSheet.show( | ||||||
|                     context, |                     context, | ||||||
|                     originalPost: item, |                     originalPost: item, | ||||||
|                   ); |                   ); | ||||||
| @@ -227,7 +227,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|               title: 'reply'.tr(), |               title: 'reply'.tr(), | ||||||
|               image: MenuImage.icon(Symbols.reply), |               image: MenuImage.icon(Symbols.reply), | ||||||
|               callback: () async { |               callback: () async { | ||||||
|                 final result = await PostComposeDialog.show( |                 final result = await PostComposeSheet.show( | ||||||
|                   context, |                   context, | ||||||
|                   initialState: PostComposeInitialState(replyingTo: item), |                   initialState: PostComposeInitialState(replyingTo: item), | ||||||
|                 ); |                 ); | ||||||
| @@ -240,7 +240,7 @@ class PostActionableItem extends HookConsumerWidget { | |||||||
|               title: 'forward'.tr(), |               title: 'forward'.tr(), | ||||||
|               image: MenuImage.icon(Symbols.forward), |               image: MenuImage.icon(Symbols.forward), | ||||||
|               callback: () async { |               callback: () async { | ||||||
|                 final result = await PostComposeDialog.show( |                 final result = await PostComposeSheet.show( | ||||||
|                   context, |                   context, | ||||||
|                   initialState: PostComposeInitialState(forwardingTo: item), |                   initialState: PostComposeInitialState(forwardingTo: item), | ||||||
|                 ); |                 ); | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import 'package:island/screens/creators/publishers_form.dart'; | |||||||
| import 'package:island/screens/posts/compose.dart'; | import 'package:island/screens/posts/compose.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/post/compose_dialog.dart'; | import 'package:island/widgets/post/compose_sheet.dart'; | ||||||
| import 'package:island/widgets/post/publishers_modal.dart'; | import 'package:island/widgets/post/publishers_modal.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| @@ -124,7 +124,7 @@ class PostQuickReply extends HookConsumerWidget { | |||||||
|                   IconButton( |                   IconButton( | ||||||
|                     onPressed: () async { |                     onPressed: () async { | ||||||
|                       onLaunch?.call(); |                       onLaunch?.call(); | ||||||
|                       final value = await PostComposeDialog.show( |                       final value = await PostComposeSheet.show( | ||||||
|                         context, |                         context, | ||||||
|                         initialState: PostComposeInitialState( |                         initialState: PostComposeInitialState( | ||||||
|                           content: contentController.text, |                           content: contentController.text, | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import 'package:gap/gap.dart'; | |||||||
| import 'package:island/models/thought.dart'; | import 'package:island/models/thought.dart'; | ||||||
| import 'package:island/screens/posts/compose.dart'; | import 'package:island/screens/posts/compose.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/post/compose_dialog.dart'; | import 'package:island/widgets/post/compose_sheet.dart'; | ||||||
| import 'package:island/widgets/thought/function_calls_section.dart'; | import 'package:island/widgets/thought/function_calls_section.dart'; | ||||||
| import 'package:island/widgets/thought/proposals_section.dart'; | import 'package:island/widgets/thought/proposals_section.dart'; | ||||||
| import 'package:island/widgets/thought/reasoning_section.dart'; | import 'package:island/widgets/thought/reasoning_section.dart'; | ||||||
| @@ -29,7 +29,7 @@ void _handleProposalAction(BuildContext context, Map<String, String> proposal) { | |||||||
|   switch (proposal['type']) { |   switch (proposal['type']) { | ||||||
|     case 'post_create': |     case 'post_create': | ||||||
|       // Show post creation dialog with the proposal content |       // Show post creation dialog with the proposal content | ||||||
|       PostComposeDialog.show( |       PostComposeSheet.show( | ||||||
|         context, |         context, | ||||||
|         initialState: PostComposeInitialState( |         initialState: PostComposeInitialState( | ||||||
|           content: (proposal['content'] ?? '').trim(), |           content: (proposal['content'] ?? '').trim(), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user