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
This commit is contained in:
Filip Krawczyk 2022-06-28 00:40:37 +02:00
parent 6f8fed149c
commit b972e4485a
1 changed files with 164 additions and 35 deletions

View File

@ -1,31 +1,61 @@
import 'package:flutter/material.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 {
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
void surround(String before, String after,
[String placeholder = '[write text here]']) {
void surround(
String before, [
String? after,
String placeholder = '[write text here]',
]) {
after ??= before;
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);
if (mid.isEmpty) {
value = value.copyWith(
text: '$beg$before$placeholder$after$end',
selection: selection.copyWith(
baseOffset: selection.baseOffset + before.length,
extentOffset:
selection.baseOffset + before.length + placeholder.length,
),
);
} else {
value = value.copyWith(
text: '$beg$before$mid$after$end',
selection: selection.copyWith(
baseOffset: selection.baseOffset + before.length,
extentOffset: selection.extentOffset + before.length,
extentOffset: selection.baseOffset + before.length + mid.length,
));
}
void reformat(Reformat Function(String selection) reformatter) {
final beg = beforeSelectionText;
final mid = selectionText;
final end = afterSelectionText;
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(
children: [
IconButton(
onPressed: () {
controller.surround('**', '**');
onPressed: () => controller.surround('**'),
icon: const Icon(Icons.format_bold),
),
IconButton(
onPressed: () => controller.surround('*'),
icon: const Icon(Icons.format_italic),
),
IconButton(
onPressed: () async {
final r = await AddLinkDialog.show(
context, controller.selectionText);
if (r != null) controller.reformat((_) => r);
},
icon: const Icon(Icons.format_bold)),
IconButton(
onPressed: () {}, icon: const Icon(Icons.format_italic)),
IconButton(onPressed: () {}, icon: const Icon(Icons.link)),
IconButton(onPressed: () {}, icon: const Icon(Icons.image)),
IconButton(
onPressed: () {}, icon: const Icon(Icons.h_mobiledata)),
icon: const Icon(Icons.link),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.format_strikethrough)),
icon: const Icon(Icons.image),
),
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(
onPressed: () {},
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(
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('http?s://') ? '' : selection,
url = selection.startsWith('http?s://') ? 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),
);
}
}