kopia lustrzana https://gitlab.com/mysocialportal/relatica
283 wiersze
8.8 KiB
Dart
283 wiersze
8.8 KiB
Dart
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/hashtag_autocomplete_options.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<EditorScreen> createState() => _EditorScreenState();
|
|
}
|
|
|
|
class _EditorScreenState extends State<EditorScreen> {
|
|
static final _logger = Logger('$EditorScreen');
|
|
final contentController = TextEditingController();
|
|
final spoilerController = TextEditingController();
|
|
final localEntryTemporaryId = const Uuid().v4();
|
|
TimelineEntry? parentEntry;
|
|
final newMediaItems = NewEntryMediaItems();
|
|
final existingMediaItems = <ImageEntry>[];
|
|
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<TimelineManager>();
|
|
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<void> 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<TimelineManager>();
|
|
|
|
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);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
AutocompleteTrigger(
|
|
trigger: '#',
|
|
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
|
return HashtagAutocompleteOptions(
|
|
query: autocompleteQuery.query,
|
|
onHashtagTap: (hashtag) {
|
|
final autocomplete = MultiTriggerAutocomplete.of(context);
|
|
return autocomplete.acceptAutocompleteOption(hashtag);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
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,
|
|
),
|
|
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'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|