297 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:easy_localization/easy_localization.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:island/models/post.dart';
 | |
| import 'package:island/services/compose_storage_db.dart';
 | |
| import 'package:island/widgets/post/compose_embed_sheet.dart';
 | |
| import 'package:island/widgets/post/compose_shared.dart';
 | |
| import 'package:island/widgets/post/draft_manager.dart';
 | |
| import 'package:island/widgets/shared/upload_menu.dart';
 | |
| import 'package:material_symbols_icons/symbols.dart';
 | |
| import 'package:styled_widget/styled_widget.dart';
 | |
| 
 | |
| class ComposeToolbar extends HookConsumerWidget {
 | |
|   final ComposeState state;
 | |
|   final SnPost? originalPost;
 | |
|   final bool isCompact;
 | |
| 
 | |
|   const ComposeToolbar({
 | |
|     super.key,
 | |
|     required this.state,
 | |
|     this.originalPost,
 | |
|     this.isCompact = false,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     void pickPhotoMedia() {
 | |
|       ComposeLogic.pickPhotoMedia(ref, state);
 | |
|     }
 | |
| 
 | |
|     void pickVideoMedia() {
 | |
|       ComposeLogic.pickVideoMedia(ref, state);
 | |
|     }
 | |
| 
 | |
|     void pickGeneralFile() {
 | |
|       ComposeLogic.pickGeneralFile(ref, state);
 | |
|     }
 | |
| 
 | |
|     void addAudio() {
 | |
|       ComposeLogic.recordAudioMedia(ref, state, context);
 | |
|     }
 | |
| 
 | |
|     void linkAttachment() {
 | |
|       ComposeLogic.linkAttachment(ref, state, context);
 | |
|     }
 | |
| 
 | |
|     void saveDraft() {
 | |
|       ComposeLogic.saveDraftManually(ref, state, context);
 | |
|     }
 | |
| 
 | |
|     void pickPoll() {
 | |
|       ComposeLogic.pickPoll(ref, state, context);
 | |
|     }
 | |
| 
 | |
|     void showEmbedSheet() {
 | |
|       showModalBottomSheet(
 | |
|         context: context,
 | |
|         isScrollControlled: true,
 | |
|         useRootNavigator: true,
 | |
|         builder: (context) => ComposeEmbedSheet(state: state),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     void showDraftManager() {
 | |
|       showModalBottomSheet(
 | |
|         context: context,
 | |
|         isScrollControlled: true,
 | |
|         useRootNavigator: true,
 | |
|         builder:
 | |
|             (context) => DraftManagerSheet(
 | |
|               onDraftSelected: (draftId) {
 | |
|                 final draft = ref.read(composeStorageNotifierProvider)[draftId];
 | |
|                 if (draft != null) {
 | |
|                   state.titleController.text = draft.title ?? '';
 | |
|                   state.descriptionController.text = draft.description ?? '';
 | |
|                   state.contentController.text = draft.content ?? '';
 | |
|                   state.visibility.value = draft.visibility;
 | |
|                 }
 | |
|               },
 | |
|             ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     final uploadMenuItems = [
 | |
|       UploadMenuItemData(Symbols.add_a_photo, 'addPhoto', pickPhotoMedia),
 | |
|       UploadMenuItemData(Symbols.videocam, 'addVideo', pickVideoMedia),
 | |
|       UploadMenuItemData(Symbols.mic, 'addAudio', addAudio),
 | |
|       UploadMenuItemData(Symbols.file_upload, 'uploadFile', pickGeneralFile),
 | |
|     ];
 | |
| 
 | |
|     final colorScheme = Theme.of(context).colorScheme;
 | |
| 
 | |
|     if (isCompact) {
 | |
|       return Container(
 | |
|         color: Theme.of(context).colorScheme.surfaceContainerLow,
 | |
|         padding: EdgeInsets.symmetric(horizontal: 8),
 | |
|         child: Center(
 | |
|           child: ConstrainedBox(
 | |
|             constraints: const BoxConstraints(maxWidth: 560),
 | |
|             child: Row(
 | |
|               children: [
 | |
|                 Expanded(
 | |
|                   child: SingleChildScrollView(
 | |
|                     scrollDirection: Axis.horizontal,
 | |
|                     child: Row(
 | |
|                       children: [
 | |
|                         UploadMenu(
 | |
|                           items: uploadMenuItems,
 | |
|                           isCompact: isCompact,
 | |
|                         ),
 | |
|                         IconButton(
 | |
|                           onPressed: linkAttachment,
 | |
|                           icon: const Icon(Symbols.attach_file),
 | |
|                           tooltip: 'linkAttachment'.tr(),
 | |
|                           color: colorScheme.primary,
 | |
|                           visualDensity: const VisualDensity(
 | |
|                             horizontal: -4,
 | |
|                             vertical: -2,
 | |
|                           ),
 | |
|                         ),
 | |
|                         // Poll button with visual state when a poll is linked
 | |
|                         ListenableBuilder(
 | |
|                           listenable: state.pollId,
 | |
|                           builder: (context, _) {
 | |
|                             return IconButton(
 | |
|                               onPressed: pickPoll,
 | |
|                               icon: const Icon(Symbols.how_to_vote),
 | |
|                               tooltip: 'poll'.tr(),
 | |
|                               color: colorScheme.primary,
 | |
|                               visualDensity: const VisualDensity(
 | |
|                                 horizontal: -4,
 | |
|                                 vertical: -2,
 | |
|                               ),
 | |
|                               style: ButtonStyle(
 | |
|                                 backgroundColor: WidgetStatePropertyAll(
 | |
|                                   state.pollId.value != null
 | |
|                                       ? colorScheme.primary.withOpacity(0.15)
 | |
|                                       : null,
 | |
|                                 ),
 | |
|                               ),
 | |
|                             );
 | |
|                           },
 | |
|                         ),
 | |
|                         // Embed button with visual state when embed is present
 | |
|                         ListenableBuilder(
 | |
|                           listenable: state.embedView,
 | |
|                           builder: (context, _) {
 | |
|                             return IconButton(
 | |
|                               onPressed: showEmbedSheet,
 | |
|                               icon: const Icon(Symbols.iframe),
 | |
|                               tooltip: 'embedView'.tr(),
 | |
|                               color: colorScheme.primary,
 | |
|                               visualDensity: const VisualDensity(
 | |
|                                 horizontal: -4,
 | |
|                                 vertical: -2,
 | |
|                               ),
 | |
|                               style: ButtonStyle(
 | |
|                                 backgroundColor: WidgetStatePropertyAll(
 | |
|                                   state.embedView.value != null
 | |
|                                       ? colorScheme.primary.withOpacity(0.15)
 | |
|                                       : null,
 | |
|                                 ),
 | |
|                               ),
 | |
|                             );
 | |
|                           },
 | |
|                         ),
 | |
|                       ],
 | |
|                     ),
 | |
|                   ),
 | |
|                 ),
 | |
|                 if (originalPost == null && state.isEmpty)
 | |
|                   IconButton(
 | |
|                     icon: const Icon(Symbols.draft, size: 20),
 | |
|                     color: colorScheme.primary,
 | |
|                     onPressed: showDraftManager,
 | |
|                     tooltip: 'drafts'.tr(),
 | |
|                     visualDensity: const VisualDensity(
 | |
|                       horizontal: -4,
 | |
|                       vertical: -4,
 | |
|                     ),
 | |
|                     constraints: const BoxConstraints(
 | |
|                       minWidth: 32,
 | |
|                       minHeight: 48,
 | |
|                     ),
 | |
|                   )
 | |
|                 else if (originalPost == null)
 | |
|                   IconButton(
 | |
|                     icon: const Icon(Symbols.save, size: 20),
 | |
|                     color: colorScheme.primary,
 | |
|                     onPressed: saveDraft,
 | |
|                     onLongPress: showDraftManager,
 | |
|                     tooltip: 'saveDraft'.tr(),
 | |
|                     visualDensity: const VisualDensity(
 | |
|                       horizontal: -4,
 | |
|                       vertical: -4,
 | |
|                     ),
 | |
|                     constraints: const BoxConstraints(
 | |
|                       minWidth: 32,
 | |
|                       minHeight: 48,
 | |
|                     ),
 | |
|                   ),
 | |
|               ],
 | |
|             ).padding(horizontal: 8, vertical: 4),
 | |
|           ),
 | |
|         ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return Material(
 | |
|       elevation: 4,
 | |
|       color: Theme.of(context).colorScheme.surfaceContainerLow,
 | |
|       child: Center(
 | |
|         child: ConstrainedBox(
 | |
|           constraints: const BoxConstraints(maxWidth: 560),
 | |
|           child: Row(
 | |
|             children: [
 | |
|               Expanded(
 | |
|                 child: SingleChildScrollView(
 | |
|                   scrollDirection: Axis.horizontal,
 | |
|                   child: Row(
 | |
|                     children: [
 | |
|                       UploadMenu(items: uploadMenuItems, isCompact: isCompact),
 | |
|                       IconButton(
 | |
|                         onPressed: linkAttachment,
 | |
|                         icon: const Icon(Symbols.attach_file),
 | |
|                         tooltip: 'linkAttachment'.tr(),
 | |
|                         color: colorScheme.primary,
 | |
|                       ),
 | |
|                       // Poll button with visual state when a poll is linked
 | |
|                       ListenableBuilder(
 | |
|                         listenable: state.pollId,
 | |
|                         builder: (context, _) {
 | |
|                           return IconButton(
 | |
|                             onPressed: pickPoll,
 | |
|                             icon: const Icon(Symbols.how_to_vote),
 | |
|                             tooltip: 'poll'.tr(),
 | |
|                             color: colorScheme.primary,
 | |
|                             style: ButtonStyle(
 | |
|                               backgroundColor: WidgetStatePropertyAll(
 | |
|                                 state.pollId.value != null
 | |
|                                     ? colorScheme.primary.withOpacity(0.15)
 | |
|                                     : null,
 | |
|                               ),
 | |
|                             ),
 | |
|                           );
 | |
|                         },
 | |
|                       ),
 | |
|                       // Embed button with visual state when embed is present
 | |
|                       ListenableBuilder(
 | |
|                         listenable: state.embedView,
 | |
|                         builder: (context, _) {
 | |
|                           return IconButton(
 | |
|                             onPressed: showEmbedSheet,
 | |
|                             icon: const Icon(Symbols.iframe),
 | |
|                             tooltip: 'embedView'.tr(),
 | |
|                             color: colorScheme.primary,
 | |
|                             style: ButtonStyle(
 | |
|                               backgroundColor: WidgetStatePropertyAll(
 | |
|                                 state.embedView.value != null
 | |
|                                     ? colorScheme.primary.withOpacity(0.15)
 | |
|                                     : null,
 | |
|                               ),
 | |
|                             ),
 | |
|                           );
 | |
|                         },
 | |
|                       ),
 | |
|                     ],
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|               if (originalPost == null && state.isEmpty)
 | |
|                 IconButton(
 | |
|                   icon: const Icon(Symbols.draft),
 | |
|                   color: colorScheme.primary,
 | |
|                   onPressed: showDraftManager,
 | |
|                   tooltip: 'drafts'.tr(),
 | |
|                 )
 | |
|               else if (originalPost == null)
 | |
|                 IconButton(
 | |
|                   icon: const Icon(Symbols.save),
 | |
|                   color: colorScheme.primary,
 | |
|                   onPressed: saveDraft,
 | |
|                   onLongPress: showDraftManager,
 | |
|                   tooltip: 'saveDraft'.tr(),
 | |
|                 ),
 | |
|             ],
 | |
|           ).padding(
 | |
|             bottom: MediaQuery.of(context).padding.bottom + 16,
 | |
|             horizontal: 16,
 | |
|             top: 8,
 | |
|           ),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |