Skip to content

Commit dc6061e

Browse files
committed
[WIP] Mobile toolbar - Bottom Dialog Block Option;
1 parent af83efa commit dc6061e

File tree

6 files changed

+322
-4
lines changed

6 files changed

+322
-4
lines changed

lib/src/widgets/controller.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:math' as math;
22

33
import 'package:flutter/cupertino.dart';
4+
import 'package:flutter_quill/models/documents/nodes/leaf.dart';
45
import 'package:flutter_quill/src/models/documents/nodes/line.dart';
56
import 'package:flutter_quill/src/widgets/sym_widgets/sym_title_widgets/sym_title_kalpataru.dart';
67
import 'package:tuple/tuple.dart';
@@ -266,5 +267,40 @@ class QuillController extends ChangeNotifier {
266267
extentOffset: math.min(selection.extentOffset, end));
267268
}
268269

269-
void showMenuBlockCreation() {}
270+
void deleteCurrentLine() {
271+
final line = document.getLineFromTextIndex(selection.baseOffset);
272+
273+
final isEmbed = (line.children.first as Leaf).value is Embeddable;
274+
final textIndex = line.offset;
275+
int textLength;
276+
if (!isEmbed) {
277+
textLength = line.length + 1;
278+
} else {
279+
textLength = 1;
280+
}
281+
282+
/* SOMEHOW IT DOESN'T WORK LIKE ON THE SymMenuBlockOption */
283+
if (textLength < document.length) {
284+
document.delete(textIndex, line.length);
285+
286+
final lastCursorIndex = selection.baseOffset;
287+
288+
if (lastCursorIndex > textIndex) {
289+
var newCursorIndex = lastCursorIndex - textLength;
290+
291+
if (newCursorIndex < 0) {
292+
newCursorIndex = 0;
293+
}
294+
295+
updateSelection(
296+
TextSelection(
297+
baseOffset: newCursorIndex, extentOffset: newCursorIndex),
298+
ChangeSource.LOCAL);
299+
}
300+
} else {
301+
print('replaceText: ${line.length}');
302+
replaceText(
303+
0, line.length - 1, '\n', const TextSelection.collapsed(offset: 0));
304+
}
305+
}
270306
}

lib/src/widgets/sym_widgets/sym_asset_image.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class SymAssetImage extends StatelessWidget {
77
this.assetName, {
88
this.size,
99
this.color,
10-
this.fit = BoxFit.none,
10+
this.fit = BoxFit.scaleDown,
1111
Key? key,
1212
}) : super(key: key);
1313

lib/src/widgets/sym_widgets/sym_menu_block_option.dart

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,41 @@ import '../common_widgets/gap.dart';
1212
import 'sym_text.dart';
1313
import '../text_line.dart';
1414

15+
class MenuBlockOptionItem {
16+
MenuBlockOptionItem({
17+
required this.title,
18+
required this.iconAssetName,
19+
required this.type,
20+
});
21+
22+
final _BlockOptionType type;
23+
final String title;
24+
final String iconAssetName;
25+
}
26+
27+
class MenuBlockOptionSection {
28+
MenuBlockOptionSection(this.title, this.items);
29+
30+
final String title;
31+
final List<MenuBlockOptionItem> items;
32+
}
33+
34+
abstract class _BlockOptionType {}
35+
36+
class BlockOptionTypeAction extends _BlockOptionType {
37+
BlockOptionTypeAction(this.type);
38+
39+
final BlockActionItem type;
40+
}
41+
42+
enum BlockActionItem { DELETE, COPY, DUPLICATE, INDENT_LEFT, INDENT_RIGHT }
43+
44+
class BlockOptionTypeAttribute extends _BlockOptionType {
45+
BlockOptionTypeAttribute(this.attr);
46+
47+
final Attribute attr;
48+
}
49+
1550
class SymMenuBlockOption extends StatefulWidget {
1651
const SymMenuBlockOption({
1752
required this.renderEditableTextLine,
@@ -291,7 +326,8 @@ class _SymMenuBlockOptionState extends State<SymMenuBlockOption> {
291326
actionListener.onDuplicate();
292327
}),
293328
if (!widget.isEmbeddable)
294-
_itemMenuContent(Assets.INDENT_LEFT_INACTIVE, 'Indent kiri', maxMenuWidth,
329+
_itemMenuContent(
330+
Assets.INDENT_LEFT_INACTIVE, 'Indent kiri', maxMenuWidth,
295331
enabled: (indentLevel ?? 0) > 0, onTap: () {
296332
if (indentLevel == 1) {
297333
widget.controller.formatLine(
@@ -382,3 +418,69 @@ class MenuBlockOptionTurnIntoListener {
382418

383419
final Function(Attribute) turnInto;
384420
}
421+
422+
/* DEFAULT MENU ITEM */
423+
424+
List<MenuBlockOptionSection> defaultMenuBlockOptionSections(bool isEmbed) {
425+
return [
426+
MenuBlockOptionSection('Aksi', [
427+
MenuBlockOptionItem(
428+
title: 'Hapus blok',
429+
iconAssetName: Assets.TRASH,
430+
type: BlockOptionTypeAction(BlockActionItem.DELETE)),
431+
MenuBlockOptionItem(
432+
title: 'Salin teks',
433+
iconAssetName: Assets.COPY,
434+
type: BlockOptionTypeAction(BlockActionItem.COPY)),
435+
MenuBlockOptionItem(
436+
title: 'Duplikat blok',
437+
iconAssetName: Assets.DUPLICATE,
438+
type: BlockOptionTypeAction(BlockActionItem.DUPLICATE)),
439+
if (!isEmbed)
440+
MenuBlockOptionItem(
441+
title: 'Indent kiri',
442+
iconAssetName: Assets.INDENT_LEFT_INACTIVE,
443+
type: BlockOptionTypeAction(BlockActionItem.INDENT_LEFT)),
444+
if (!isEmbed)
445+
MenuBlockOptionItem(
446+
title: 'Indent kanan',
447+
iconAssetName: Assets.INDENT_RIGHT_ACTIVE,
448+
type: BlockOptionTypeAction(BlockActionItem.INDENT_RIGHT)),
449+
]),
450+
if (!isEmbed)
451+
MenuBlockOptionSection('Change Section Into', [
452+
MenuBlockOptionItem(
453+
title: 'Text Biasa',
454+
iconAssetName: Assets.H1,
455+
type: BlockOptionTypeAttribute(Attribute.h1)),
456+
MenuBlockOptionItem(
457+
title: 'Judul Sedang',
458+
iconAssetName: Assets.H2,
459+
type: BlockOptionTypeAttribute(Attribute.h2)),
460+
MenuBlockOptionItem(
461+
title: 'Judul Kecil',
462+
iconAssetName: Assets.H3,
463+
type: BlockOptionTypeAttribute(Attribute.h3)),
464+
MenuBlockOptionItem(
465+
title: 'Bullet List',
466+
iconAssetName: Assets.BULLET_LIST,
467+
type: BlockOptionTypeAttribute(Attribute.ul)),
468+
MenuBlockOptionItem(
469+
title: 'Numbering List',
470+
iconAssetName: Assets.NUMBERING_LIST,
471+
type: BlockOptionTypeAttribute(Attribute.ol)),
472+
MenuBlockOptionItem(
473+
title: 'To-Do List',
474+
iconAssetName: Assets.TODO_LIST,
475+
type: BlockOptionTypeAttribute(Attribute.checked)),
476+
MenuBlockOptionItem(
477+
title: 'Block Code',
478+
iconAssetName: Assets.FORMAT_INLINECODE_INACTIVE,
479+
type: BlockOptionTypeAttribute(Attribute.codeBlock)),
480+
MenuBlockOptionItem(
481+
title: 'Blockquote',
482+
iconAssetName: Assets.FORMAT_INLINECODE_INACTIVE,
483+
type: BlockOptionTypeAttribute(Attribute.blockQuote)),
484+
]),
485+
];
486+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flutter_quill/flutter_quill.dart';
4+
import 'package:flutter_quill/src/utils/color.dart';
5+
import 'package:flutter_quill/src/widgets/common_widgets/gap.dart';
6+
import 'package:flutter_quill/src/widgets/sym_widgets/sym_asset_image.dart';
7+
import 'package:flutter_quill/src/widgets/sym_widgets/sym_menu_block_option.dart';
8+
import 'package:flutter_quill/src/widgets/sym_widgets/sym_text.dart';
9+
import 'package:flutter_quill/utils/assets.dart';
10+
import '../../../../src/utils/iterator_ext.dart';
11+
12+
class SymBottomMenuBlockOption {
13+
SymBottomMenuBlockOption._(this.context, this.controller) {
14+
showModalBottomSheet(
15+
context: context,
16+
isScrollControlled: true,
17+
backgroundColor: Colors.transparent,
18+
builder: (context) => _layout
19+
);
20+
}
21+
22+
factory SymBottomMenuBlockOption.show(
23+
BuildContext context, QuillController controller) {
24+
return SymBottomMenuBlockOption._(context, controller);
25+
}
26+
27+
final BuildContext context;
28+
final QuillController controller;
29+
30+
Widget get _layout {
31+
final sections = defaultMenuBlockOptionSections(false);
32+
final children = Container(
33+
color: SymColors.light_bgWhite,
34+
child: Column(
35+
children: [
36+
_headerMenu(() => Navigator.pop(context)),
37+
const GapV(21),
38+
...sections.mapIndexed((e, i) {
39+
return _buildSection(e, i != sections.length - 1);
40+
}).toList()
41+
],
42+
),
43+
);
44+
return DraggableScrollableSheet(
45+
initialChildSize: 0.6,
46+
builder: (context, scrollController) {
47+
return SingleChildScrollView(
48+
controller: scrollController,
49+
child: children,
50+
);
51+
}
52+
);
53+
}
54+
55+
Widget _headerMenu(VoidCallback onClose) {
56+
return Column(
57+
children: [
58+
Padding(
59+
padding: const EdgeInsets.only(left: 16, right: 16, top: 12),
60+
child: Row(
61+
children: [
62+
InkWell(
63+
onTap: onClose,
64+
child: const SymAssetImage(
65+
Assets.CIRCLE_CLOSE,
66+
size: Size(40, 40),
67+
),
68+
),
69+
const GapH(16),
70+
const SymText(
71+
'Opsi format lainnya',
72+
size: 16,
73+
bold: true,
74+
),
75+
Container(
76+
color: SymColors.light_line,
77+
height: 1,
78+
),
79+
],
80+
),
81+
),
82+
GapV(12),
83+
Container(
84+
color: SymColors.light_line,
85+
height: 1,
86+
),
87+
],
88+
);
89+
}
90+
91+
Widget _buildSection(MenuBlockOptionSection section, bool widthDivider) {
92+
final title = section.title;
93+
final items = section.items;
94+
95+
return Column(
96+
crossAxisAlignment: CrossAxisAlignment.start,
97+
children: [
98+
Container(
99+
color: SymColors.light_bgWhite,
100+
child: Padding(
101+
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 20),
102+
child: SymText(
103+
title,
104+
)),
105+
),
106+
...items.map(_buildItem).toList(),
107+
if (widthDivider) _divider()
108+
],
109+
);
110+
}
111+
112+
Widget _buildItem(MenuBlockOptionItem item) {
113+
final title = item.title;
114+
final type = item.type;
115+
final iconAssetName = item.iconAssetName;
116+
117+
return Material(
118+
child: InkWell(
119+
onTap: () {
120+
Navigator.pop(context);
121+
WidgetsBinding.instance!.addPostFrameCallback((_) {
122+
if (type is BlockOptionTypeAction) {
123+
switch (type.type) {
124+
case BlockActionItem.DELETE:
125+
controller.deleteCurrentLine();
126+
break;
127+
case BlockActionItem.COPY:
128+
// TODO: Handle this case.
129+
break;
130+
case BlockActionItem.DUPLICATE:
131+
// TODO: Handle this case.
132+
break;
133+
case BlockActionItem.INDENT_LEFT:
134+
// TODO: Handle this case.
135+
break;
136+
case BlockActionItem.INDENT_RIGHT:
137+
// TODO: Handle this case.
138+
break;
139+
}
140+
}
141+
});
142+
},
143+
child: Ink(
144+
color: SymColors.light_bgWhite,
145+
child: Container(
146+
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 20),
147+
child: Row(
148+
children: [
149+
SymAssetImage(
150+
iconAssetName,
151+
size: const Size(17, 17),
152+
),
153+
const GapH(17),
154+
SymText(
155+
title,
156+
size: 14,
157+
bold: true,
158+
)
159+
],
160+
),
161+
),
162+
),
163+
),
164+
);
165+
}
166+
167+
Widget _divider() {
168+
return Container(
169+
margin: const EdgeInsets.only(top: 10, bottom: 16, left: 16, right: 16),
170+
color: SymColors.light_line,
171+
height: 1,
172+
);
173+
}
174+
}

lib/src/widgets/sym_widgets/sym_toolbar/sym_toolbar.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/widgets.dart';
33
import 'package:flutter_quill/src/widgets/sym_widgets/sym_toolbar/sym_bottom_menu_block_creation.dart';
4+
import 'package:flutter_quill/src/widgets/sym_widgets/sym_toolbar/sym_bottom_menu_block_option.dart';
45
import 'package:flutter_quill/src/widgets/sym_widgets/sym_toolbar/sym_toolbar_button/sym_toggle_style_button.dart';
56
import 'package:flutter_quill/src/widgets/sym_widgets/sym_toolbar/sym_toolbar_button/sym_toolbar_button.dart';
67
import 'package:flutter_quill/utils/assets.dart';
@@ -34,7 +35,9 @@ class SymToolbar extends StatelessWidget implements PreferredSizeWidget {
3435
SymBottomMenuBlockCreation.show(context, controller);
3536
}),
3637
divider,
37-
SymToolbarButton(iconAssetName: Assets.MORE_VERT, onPressed: () {}),
38+
SymToolbarButton(iconAssetName: Assets.MORE_VERT, onPressed: () {
39+
SymBottomMenuBlockOption.show(context, controller);
40+
}),
3841
divider,
3942
SymToggleStyleButton(
4043
attribute: Attribute.bold,

lib/src/widgets/sym_widgets/sym_toolbar/sym_toolbar_button/sym_toggle_style_button.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class _SymToggleStyleButtonState extends State<SymToggleStyleButton> {
7171
}
7272

7373
void _didChangeEditingValue() {
74+
if (!mounted) {
75+
return;
76+
}
7477
setState(() {
7578
_isToggled = _getIsToggled(_selectionStyle.attributes);
7679
});

0 commit comments

Comments
 (0)