import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; import '../controls/autocomplete/mention_autocomplete_options.dart'; import '../controls/entry_media_attachments/gallery_selector_control.dart'; import '../controls/entry_media_attachments/media_uploads_control.dart'; import '../controls/padding.dart'; import '../controls/timeline/status_header_control.dart'; import '../models/image_entry.dart'; import '../models/media_attachment_uploads/new_entry_media_items.dart'; import '../models/timeline_entry.dart'; import '../services/timeline_manager.dart'; import '../utils/snackbar_builder.dart'; class EditorScreen extends StatefulWidget { final String id; final String parentId; const EditorScreen({super.key, this.id = '', this.parentId = ''}); @override State createState() => _EditorScreenState(); } class _EditorScreenState extends State { static final _logger = Logger('$EditorScreen'); final contentController = TextEditingController(); final spoilerController = TextEditingController(); final localEntryTemporaryId = const Uuid().v4(); TimelineEntry? parentEntry; final newMediaItems = NewEntryMediaItems(); final existingMediaItems = []; final focusNode = FocusNode(); var isSubmitting = false; bool get isComment => widget.parentId.isNotEmpty; String get statusType => widget.parentId.isEmpty ? 'Post' : 'Comment'; String get localEntryId => widget.id.isNotEmpty ? widget.id : localEntryTemporaryId; @override void initState() { if (!isComment) { return; } final manager = context.read(); manager.getEntryById(widget.parentId).match(onSuccess: (entry) { spoilerController.text = entry.spoilerText; parentEntry = entry; }, onError: (error) { _logger.finest('Error trying to get parent entry: $error'); }); } Future createStatus( BuildContext context, TimelineManager manager) async { if (contentController.text.isEmpty) { buildSnackbar(context, "Can't submit an empty post/comment"); return; } setState(() { isSubmitting = true; }); final result = await manager.createNewStatus( contentController.text, spoilerText: spoilerController.text, inReplyToId: widget.parentId, newMediaItems: newMediaItems, existingMediaItems: existingMediaItems, ); setState(() { isSubmitting = false; }); if (result.isFailure) { buildSnackbar(context, 'Error posting: ${result.error}'); return; } if (mounted && context.canPop()) { context.pop(); } } @override Widget build(BuildContext context) { _logger.finest('Build editor $isComment $parentEntry'); final manager = context.read(); final mainBody = Padding( padding: const EdgeInsets.all(8.0), child: Container( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ if (isComment && parentEntry != null) buildCommentPreview(context, parentEntry!), TextFormField( readOnly: isSubmitting, enabled: !isSubmitting, controller: spoilerController, decoration: InputDecoration( labelText: '$statusType Spoiler Text (optional)', border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).backgroundColor, ), borderRadius: BorderRadius.circular(5.0), ), ), ), const VerticalPadding(), buildContentField(context), const VerticalPadding(), GallerySelectorControl(entries: existingMediaItems), const VerticalPadding(), MediaUploadsControl( entryMediaItems: newMediaItems, ), buildButtonBar(context, manager), ], ), ), ), ); final submittingBody = Stack( children: [ mainBody, Card( color: Theme.of(context).canvasColor.withOpacity(0.8), child: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), const VerticalPadding(), Text('Submitting New $statusType'), ], ), ), ), ), ], ); return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: Text(widget.id.isEmpty ? 'New $statusType' : 'Edit $statusType'), ), body: isSubmitting ? submittingBody : mainBody, ); } Widget buildContentField(BuildContext context) { return MultiTriggerAutocomplete( textEditingController: contentController, focusNode: focusNode, optionsAlignment: OptionsAlignment.bottomEnd, autocompleteTriggers: [ AutocompleteTrigger( trigger: '@', optionsViewBuilder: (context, autocompleteQuery, controller) { return MentionAutocompleteOptions( query: autocompleteQuery.query, onMentionUserTap: (user) { final autocomplete = MultiTriggerAutocomplete.of(context); return autocomplete.acceptAutocompleteOption(user.handle); }, ); }, ), ], fieldViewBuilder: (context, controller, focusNode) => TextFormField( focusNode: focusNode, readOnly: isSubmitting, enabled: !isSubmitting, maxLines: 10, controller: controller, decoration: InputDecoration( labelText: '$statusType Content', alignLabelWithHint: true, border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).backgroundColor, ), borderRadius: BorderRadius.circular(5.0), ), ), ), ); } Widget buildCommentPreview(BuildContext context, TimelineEntry entry) { _logger.finest('Build preview'); return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Comment for status: ', style: Theme.of(context).textTheme.bodyLarge, ), Card( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ StatusHeaderControl( entry: entry, openRemote: false, showOpenControl: false, ), const VerticalPadding(height: 3), if (entry.spoilerText.isNotEmpty) ...[ Text( 'Content Summary: ${entry.spoilerText}', style: Theme.of(context).textTheme.bodyLarge, ), const VerticalPadding(height: 3) ], HtmlWidget(entry.body), ], ), ), ), const VerticalPadding(), ], ); } Widget buildButtonBar(BuildContext context, TimelineManager manager) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: isSubmitting ? null : () => createStatus(context, manager), child: const Text('Submit'), ), const HorizontalPadding(), ElevatedButton( onPressed: isSubmitting ? null : () { context.pop(); }, child: const Text('Cancel'), ), ], ); } }