Compare commits

...

3 Commits

Author SHA1 Message Date
Filip Krawczyk b5bb5dc1ff regex typo fix 2022-06-28 01:18:45 +02:00
Filip Krawczyk f21c6b7c8c remove focus node that is not needed thanks to flutter being smart 2022-06-28 00:41:00 +02:00
Filip Krawczyk b972e4485a add functionality to more buttons
* add several extensions on TextEditingController for convinience
* add "add link" dialog + functionality
* add functionality to surround buttons:
  * italics,
  * stikethough,
  * superscript,
  * subscript,
  * code
2022-06-28 00:40:37 +02:00
2 changed files with 164 additions and 38 deletions

View File

@ -31,7 +31,6 @@ class CreatePostPage extends HookWidget {
); );
final titleFocusNode = useFocusNode(); final titleFocusNode = useFocusNode();
final bodyFocusNode = useFocusNode();
handleSubmit(Jwt token) async { handleSubmit(Jwt token) async {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
@ -47,7 +46,6 @@ class CreatePostPage extends HookWidget {
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
validator: Validators.required(L10n.of(context).required_field), validator: Validators.required(L10n.of(context).required_field),
onFieldSubmitted: (_) => bodyFocusNode.requestFocus(),
onChanged: (title) => store.title = title, onChanged: (title) => store.title = title,
minLines: 1, minLines: 1,
maxLines: 2, maxLines: 2,
@ -61,7 +59,6 @@ class CreatePostPage extends HookWidget {
final body = ObserverBuilder<CreatePostStore>( final body = ObserverBuilder<CreatePostStore>(
builder: (context, store) => Editor( builder: (context, store) => Editor(
controller: bodyController, controller: bodyController,
focusNode: bodyFocusNode,
onChanged: (body) => store.body = body, onChanged: (body) => store.body = body,
labelText: L10n.of(context).body, labelText: L10n.of(context).body,
instanceHost: store.instanceHost, instanceHost: store.instanceHost,

View File

@ -1,31 +1,61 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import '../../util/extensions/spaced.dart';
class Reformat {
final String text;
final int selectionBeginningShift;
final int selectionEndingShift;
Reformat({
required this.text,
this.selectionBeginningShift = 0,
this.selectionEndingShift = 0,
});
}
extension on TextEditingController { extension on TextEditingController {
String get selectionText =>
text.substring(selection.baseOffset, selection.extentOffset);
String get beforeSelectionText => text.substring(0, selection.baseOffset);
String get afterSelectionText => text.substring(selection.extentOffset);
/// surroungs selection with given strings. If nothing is selected, placeholder is used in the middle /// surroungs selection with given strings. If nothing is selected, placeholder is used in the middle
void surround(String before, String after, void surround(
[String placeholder = '[write text here]']) { String before, [
String? after,
String placeholder = '[write text here]',
]) {
after ??= before;
final beg = text.substring(0, selection.baseOffset); final beg = text.substring(0, selection.baseOffset);
final mid = text.substring(selection.baseOffset, selection.extentOffset); final mid = () {
final m = text.substring(selection.baseOffset, selection.extentOffset);
if (m.isEmpty) return placeholder;
return m;
}();
final end = text.substring(selection.extentOffset); final end = text.substring(selection.extentOffset);
if (mid.isEmpty) { value = value.copyWith(
value = value.copyWith( text: '$beg$before$mid$after$end',
text: '$beg$before$placeholder$after$end',
selection: selection.copyWith( selection: selection.copyWith(
baseOffset: selection.baseOffset + before.length, baseOffset: selection.baseOffset + before.length,
extentOffset: extentOffset: selection.baseOffset + before.length + mid.length,
selection.baseOffset + before.length + placeholder.length, ));
), }
);
} else { void reformat(Reformat Function(String selection) reformatter) {
value = value.copyWith( final beg = beforeSelectionText;
text: '$beg$before$mid$after$end', final mid = selectionText;
selection: selection.copyWith( final end = afterSelectionText;
baseOffset: selection.baseOffset + before.length,
extentOffset: selection.extentOffset + before.length, final r = reformatter(mid);
)); value = value.copyWith(
} text: '$beg${r.text}$end',
selection: selection.copyWith(
baseOffset: selection.baseOffset + r.selectionBeginningShift,
extentOffset: selection.extentOffset + r.selectionEndingShift,
),
);
} }
} }
@ -46,31 +76,68 @@ class Toolbar extends HookWidget {
child: Row( child: Row(
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () => controller.surround('**'),
controller.surround('**', '**'); icon: const Icon(Icons.format_bold),
}, ),
icon: const Icon(Icons.format_bold)),
IconButton( IconButton(
onPressed: () {}, icon: const Icon(Icons.format_italic)), onPressed: () => controller.surround('*'),
IconButton(onPressed: () {}, icon: const Icon(Icons.link)), icon: const Icon(Icons.format_italic),
IconButton(onPressed: () {}, icon: const Icon(Icons.image)), ),
IconButton( IconButton(
onPressed: () {}, icon: const Icon(Icons.h_mobiledata)), onPressed: () async {
final r = await AddLinkDialog.show(
context, controller.selectionText);
if (r != null) controller.reformat((_) => r);
},
icon: const Icon(Icons.link),
),
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.format_strikethrough)), icon: const Icon(Icons.image),
),
IconButton( IconButton(
onPressed: () {}, icon: const Icon(Icons.format_quote)), onPressed: () {},
icon: const Icon(Icons.person),
),
IconButton(
onPressed: () {
//
},
icon: const Icon(Icons.home),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.h_mobiledata),
),
IconButton(
onPressed: () => controller.surround('~~'),
icon: const Icon(Icons.format_strikethrough),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.format_quote),
),
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.format_list_bulleted)), icon: const Icon(Icons.format_list_bulleted)),
IconButton(onPressed: () {}, icon: const Icon(Icons.code)),
IconButton(onPressed: () {}, icon: const Icon(Icons.subscript)),
IconButton(onPressed: () {}, icon: const Icon(Icons.superscript)),
// IconButton(onPressed: () {}, icon: const Icon(Icons.warning)),//spoiler
IconButton(onPressed: () {}, icon: const Icon(Icons.superscript)),
IconButton( IconButton(
onPressed: () {}, icon: const Icon(Icons.question_mark)), onPressed: () => controller.surround('`'),
icon: const Icon(Icons.code),
),
IconButton(
onPressed: () => controller.surround('~'),
icon: const Icon(Icons.subscript),
),
IconButton(
onPressed: () => controller.surround('^'),
icon: const Icon(Icons.superscript),
),
//spoiler
IconButton(onPressed: () {}, icon: const Icon(Icons.warning)),
IconButton(
onPressed: () {},
icon: const Icon(Icons.question_mark),
),
], ],
), ),
), ),
@ -78,3 +145,65 @@ class Toolbar extends HookWidget {
); );
} }
} }
class AddLinkDialog extends HookWidget {
final String title;
final String url;
final String selection;
AddLinkDialog(this.selection)
: title = selection.startsWith('https?://') ? '' : selection,
url = selection.startsWith('https?://') ? selection : '';
@override
Widget build(BuildContext context) {
final titleController = useTextEditingController(text: title);
final urlController = useTextEditingController(text: url);
void submit() {
final finalString = '(${titleController.text})[${urlController.text}]';
Navigator.of(context).pop(Reformat(
text: finalString,
selectionBeginningShift: finalString.length,
selectionEndingShift: finalString.length - selection.length,
));
}
return AlertDialog(
title: const Text('Add link'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(hintText: 'title'),
textInputAction: TextInputAction.next,
autofocus: true,
),
TextField(
controller: urlController,
decoration: const InputDecoration(hintText: 'https://example.com'),
onEditingComplete: submit,
autocorrect: false,
),
].spaced(10),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel')),
ElevatedButton(
onPressed: submit,
child: const Text('Add link'),
)
],
);
}
static Future<Reformat?> show(BuildContext context, String selection) async {
return showDialog(
context: context,
builder: (context) => AddLinkDialog(selection),
);
}
}