chore: support single column slash menu
This commit is contained in:
parent
268aae905b
commit
218c7c82ba
4 changed files with 90 additions and 31 deletions
|
|
@ -20,6 +20,7 @@ final CharacterShortcutEvent slashCommand = CharacterShortcutEvent(
|
||||||
CharacterShortcutEvent customSlashCommand(
|
CharacterShortcutEvent customSlashCommand(
|
||||||
List<SelectionMenuItem> items, {
|
List<SelectionMenuItem> items, {
|
||||||
bool shouldInsertSlash = true,
|
bool shouldInsertSlash = true,
|
||||||
|
bool singleColumn = true,
|
||||||
SelectionMenuStyle style = SelectionMenuStyle.light,
|
SelectionMenuStyle style = SelectionMenuStyle.light,
|
||||||
}) {
|
}) {
|
||||||
return CharacterShortcutEvent(
|
return CharacterShortcutEvent(
|
||||||
|
|
@ -29,6 +30,7 @@ CharacterShortcutEvent customSlashCommand(
|
||||||
editorState,
|
editorState,
|
||||||
items,
|
items,
|
||||||
shouldInsertSlash: shouldInsertSlash,
|
shouldInsertSlash: shouldInsertSlash,
|
||||||
|
singleColumn: singleColumn,
|
||||||
style: style,
|
style: style,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -48,6 +50,7 @@ Future<bool> _showSlashMenu(
|
||||||
EditorState editorState,
|
EditorState editorState,
|
||||||
List<SelectionMenuItem> items, {
|
List<SelectionMenuItem> items, {
|
||||||
bool shouldInsertSlash = true,
|
bool shouldInsertSlash = true,
|
||||||
|
bool singleColumn = true,
|
||||||
SelectionMenuStyle style = SelectionMenuStyle.light,
|
SelectionMenuStyle style = SelectionMenuStyle.light,
|
||||||
}) async {
|
}) async {
|
||||||
if (PlatformExtension.isMobile) {
|
if (PlatformExtension.isMobile) {
|
||||||
|
|
@ -101,6 +104,7 @@ Future<bool> _showSlashMenu(
|
||||||
editorState: editorState,
|
editorState: editorState,
|
||||||
selectionMenuItems: items,
|
selectionMenuItems: items,
|
||||||
deleteSlashByDefault: shouldInsertSlash,
|
deleteSlashByDefault: shouldInsertSlash,
|
||||||
|
singleColumn: singleColumn,
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
_selectionMenuService?.show();
|
_selectionMenuService?.show();
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,14 @@ class SelectionMenu extends SelectionMenuService {
|
||||||
this.deleteSlashByDefault = true,
|
this.deleteSlashByDefault = true,
|
||||||
this.style = SelectionMenuStyle.light,
|
this.style = SelectionMenuStyle.light,
|
||||||
this.itemCountFilter = 0,
|
this.itemCountFilter = 0,
|
||||||
|
this.singleColumn = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
final List<SelectionMenuItem> selectionMenuItems;
|
final List<SelectionMenuItem> selectionMenuItems;
|
||||||
final bool deleteSlashByDefault;
|
final bool deleteSlashByDefault;
|
||||||
|
final bool singleColumn;
|
||||||
@override
|
@override
|
||||||
final SelectionMenuStyle style;
|
final SelectionMenuStyle style;
|
||||||
|
|
||||||
|
|
@ -100,6 +102,7 @@ class SelectionMenu extends SelectionMenuService {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SelectionMenuWidget(
|
child: SelectionMenuWidget(
|
||||||
selectionMenuStyle: style,
|
selectionMenuStyle: style,
|
||||||
|
singleColumn: singleColumn,
|
||||||
items: selectionMenuItems
|
items: selectionMenuItems
|
||||||
..forEach((element) {
|
..forEach((element) {
|
||||||
element.deleteSlash = deleteSlashByDefault;
|
element.deleteSlash = deleteSlashByDefault;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
|
|
||||||
typedef SelectionMenuItemHandler = void Function(
|
typedef SelectionMenuItemHandler = void Function(
|
||||||
EditorState editorState,
|
EditorState editorState,
|
||||||
|
|
@ -199,6 +200,7 @@ class SelectionMenuWidget extends StatefulWidget {
|
||||||
required this.selectionMenuStyle,
|
required this.selectionMenuStyle,
|
||||||
required this.itemCountFilter,
|
required this.itemCountFilter,
|
||||||
required this.deleteSlashByDefault,
|
required this.deleteSlashByDefault,
|
||||||
|
this.singleColumn = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<SelectionMenuItem> items;
|
final List<SelectionMenuItem> items;
|
||||||
|
|
@ -214,6 +216,7 @@ class SelectionMenuWidget extends StatefulWidget {
|
||||||
final SelectionMenuStyle selectionMenuStyle;
|
final SelectionMenuStyle selectionMenuStyle;
|
||||||
|
|
||||||
final bool deleteSlashByDefault;
|
final bool deleteSlashByDefault;
|
||||||
|
final bool singleColumn;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SelectionMenuWidget> createState() => _SelectionMenuWidgetState();
|
State<SelectionMenuWidget> createState() => _SelectionMenuWidgetState();
|
||||||
|
|
@ -224,6 +227,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
|
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
List<SelectionMenuItem> _showingItems = [];
|
List<SelectionMenuItem> _showingItems = [];
|
||||||
|
AutoScrollController? _scrollController;
|
||||||
|
|
||||||
int _searchCounter = 0;
|
int _searchCounter = 0;
|
||||||
|
|
||||||
|
|
@ -269,6 +273,9 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_showingItems = widget.items;
|
_showingItems = widget.items;
|
||||||
|
if (widget.singleColumn) {
|
||||||
|
_scrollController = AutoScrollController();
|
||||||
|
}
|
||||||
|
|
||||||
keepEditorFocusNotifier.increase();
|
keepEditorFocusNotifier.increase();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
|
@ -280,6 +287,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
keepEditorFocusNotifier.decrease();
|
keepEditorFocusNotifier.decrease();
|
||||||
|
_scrollController?.dispose();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -313,23 +321,83 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scrollToSelectedIndex() {
|
||||||
|
if (_scrollController == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scrollController?.scrollToIndex(
|
||||||
|
_selectedIndex,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
preferPosition: AutoScrollPosition.begin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildResultsWidget(
|
Widget _buildResultsWidget(
|
||||||
BuildContext buildContext,
|
BuildContext buildContext,
|
||||||
List<SelectionMenuItem> items,
|
List<SelectionMenuItem> items,
|
||||||
int itemCountFilter,
|
int itemCountFilter,
|
||||||
int selectedIndex,
|
int selectedIndex,
|
||||||
) {
|
) {
|
||||||
List<Widget> columns = [];
|
if (widget.singleColumn) {
|
||||||
List<Widget> itemWidgets = [];
|
List<Widget> itemWidgets = [];
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
itemWidgets.add(
|
||||||
|
AutoScrollTag(
|
||||||
|
key: ValueKey(i),
|
||||||
|
index: i,
|
||||||
|
controller: _scrollController!,
|
||||||
|
child: SelectionMenuItemWidget(
|
||||||
|
item: items[i],
|
||||||
|
isSelected: selectedIndex == i,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
menuService: widget.menuService,
|
||||||
|
selectionMenuStyle: widget.selectionMenuStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 300,
|
||||||
|
minWidth: 300,
|
||||||
|
maxWidth: 300,
|
||||||
|
),
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
controller: _scrollController,
|
||||||
|
children: itemWidgets,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
List<Widget> columns = [];
|
||||||
|
List<Widget> itemWidgets = [];
|
||||||
|
// apply item count filter
|
||||||
|
if (itemCountFilter > 0) {
|
||||||
|
items = items.take(itemCountFilter).toList();
|
||||||
|
}
|
||||||
|
|
||||||
// apply item count filter
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
if (i != 0 && i % (widget.maxItemInRow) == 0) {
|
||||||
if (itemCountFilter > 0) {
|
columns.add(
|
||||||
items = items.take(itemCountFilter).toList();
|
Column(
|
||||||
}
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: itemWidgets,
|
||||||
for (var i = 0; i < items.length; i++) {
|
),
|
||||||
if (i != 0 && i % (widget.maxItemInRow) == 0) {
|
);
|
||||||
|
itemWidgets = [];
|
||||||
|
}
|
||||||
|
itemWidgets.add(
|
||||||
|
SelectionMenuItemWidget(
|
||||||
|
item: items[i],
|
||||||
|
isSelected: selectedIndex == i,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
menuService: widget.menuService,
|
||||||
|
selectionMenuStyle: widget.selectionMenuStyle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (itemWidgets.isNotEmpty) {
|
||||||
columns.add(
|
columns.add(
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -338,29 +406,11 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
);
|
);
|
||||||
itemWidgets = [];
|
itemWidgets = [];
|
||||||
}
|
}
|
||||||
itemWidgets.add(
|
return Row(
|
||||||
SelectionMenuItemWidget(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
item: items[i],
|
children: columns,
|
||||||
isSelected: selectedIndex == i,
|
|
||||||
editorState: widget.editorState,
|
|
||||||
menuService: widget.menuService,
|
|
||||||
selectionMenuStyle: widget.selectionMenuStyle,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (itemWidgets.isNotEmpty) {
|
|
||||||
columns.add(
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: itemWidgets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
itemWidgets = [];
|
|
||||||
}
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: columns,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNoResultsWidget(BuildContext context) {
|
Widget _buildNoResultsWidget(BuildContext context) {
|
||||||
|
|
@ -444,6 +494,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
||||||
if (newSelectedIndex != _selectedIndex) {
|
if (newSelectedIndex != _selectedIndex) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedIndex = newSelectedIndex.clamp(0, _showingItems.length - 1);
|
_selectedIndex = newSelectedIndex.clamp(0, _showingItems.length - 1);
|
||||||
|
_scrollToSelectedIndex();
|
||||||
});
|
});
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ dependencies:
|
||||||
printing: ^5.11.1
|
printing: ^5.11.1
|
||||||
url_launcher: ^6.1.11
|
url_launcher: ^6.1.11
|
||||||
visibility_detector: ^0.4.0+2
|
visibility_detector: ^0.4.0+2
|
||||||
|
scroll_to_index: ^3.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue