lemmur-app-android/lib/widgets/editor/toolbar.dart

215 lines
6.5 KiB
Dart

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]',
]) {
after ??= before;
final beg = text.substring(0, selection.baseOffset);
final mid = () {
final m = text.substring(selection.baseOffset, selection.extentOffset);
if (m.isEmpty) return placeholder;
return m;
}();
final end = text.substring(selection.extentOffset);
value = value.copyWith(
text: '$beg$before$mid$after$end',
selection: selection.copyWith(
baseOffset: selection.baseOffset + 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,
),
);
}
}
class Toolbar extends HookWidget {
final TextEditingController controller;
static const _height = 50.0;
const Toolbar(this.controller);
@override
Widget build(BuildContext context) {
return Container(
height: _height,
width: double.infinity,
color: Theme.of(context).cardColor,
child: Material(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
IconButton(
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.link),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.image),
),
IconButton(
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: () => 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),
),
],
),
),
),
);
}
static Widget safeArea = const SizedBox(height: _height);
}
class AddLinkDialog extends HookWidget {
final String title;
final String url;
final String selection;
static final _websiteRegex = RegExp(r'https?:\/\/', caseSensitive: false);
AddLinkDialog(this.selection)
: title = selection.startsWith(_websiteRegex) ? '' : selection,
url = selection.startsWith(_websiteRegex) ? 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),
);
}
}