fix: the new mobile toolbar doesn't work if resizeToAvoidBottomInset is true (#593)
* fix: toolbar issues * fix: adjust the mobile toolbar when resizeToAvoidBottomInset is true
This commit is contained in:
parent
4345105ebe
commit
e665b67c0d
8 changed files with 158 additions and 73 deletions
|
|
@ -87,14 +87,15 @@ class _HomePageState extends State<HomePage> {
|
|||
key: _scaffoldKey,
|
||||
extendBodyBehindAppBar: PlatformExtension.isDesktopOrWeb,
|
||||
drawer: _buildDrawer(context),
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color.fromARGB(255, 134, 46, 247),
|
||||
foregroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
title: const Text('AppFlowy Editor'),
|
||||
),
|
||||
body: _buildBody(context),
|
||||
body: SafeArea(
|
||||
child: _buildBody(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,16 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(PlatformExtension.isMobile);
|
||||
return Column(
|
||||
return MobileToolbarV2(
|
||||
toolbarItems: [
|
||||
textDecorationMobileToolbarItemV2,
|
||||
buildTextAndBackgroundColorMobileToolbarItem(),
|
||||
blocksMobileToolbarItem,
|
||||
linkMobileToolbarItem,
|
||||
dividerMobileToolbarItem,
|
||||
],
|
||||
editorState: editorState,
|
||||
child: Column(
|
||||
children: [
|
||||
// build appflowy editor
|
||||
Expanded(
|
||||
|
|
@ -89,18 +98,8 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
),
|
||||
),
|
||||
),
|
||||
// build mobile toolbar
|
||||
MobileToolbarV2(
|
||||
editorState: editorState,
|
||||
toolbarItems: [
|
||||
textDecorationMobileToolbarItemV2,
|
||||
buildTextAndBackgroundColorMobileToolbarItem(),
|
||||
blocksMobileToolbarItem,
|
||||
linkMobileToolbarItem,
|
||||
dividerMobileToolbarItem,
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -216,10 +216,12 @@ class MobileToolbarWidgetState extends State<MobileToolbarWidget>
|
|||
),
|
||||
child: MobileToolbarItemMenu(
|
||||
editorState: widget.editorState,
|
||||
itemMenuBuilder: () => widget
|
||||
itemMenuBuilder: () =>
|
||||
widget
|
||||
.toolbarItems[_selectedToolbarItemIndex!]
|
||||
// pass current [MobileToolbarWidgetState] to be used to closeItemMenu
|
||||
.itemMenuBuilder!(context, widget.editorState, this),
|
||||
.itemMenuBuilder!(context, widget.editorState, this) ??
|
||||
const SizedBox.shrink(),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef MobileToolbarItemIconBuilder = Widget Function(
|
||||
// if the result is null, the item will be hidden
|
||||
typedef MobileToolbarItemIconBuilder = Widget? Function(
|
||||
BuildContext context,
|
||||
EditorState editorState,
|
||||
// To access to the state of MobileToolbarWidget
|
||||
|
|
|
|||
|
|
@ -1,20 +1,58 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:keyboard_height_plugin/keyboard_height_plugin.dart';
|
||||
|
||||
class MobileToolbarV2 extends StatelessWidget {
|
||||
class MobileToolbarV2 extends StatefulWidget {
|
||||
const MobileToolbarV2({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
required this.toolbarItems,
|
||||
this.bottomOffset = 0.0,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final List<MobileToolbarItem> toolbarItems;
|
||||
final double bottomOffset;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<MobileToolbarV2> createState() => _MobileToolbarV2State();
|
||||
}
|
||||
|
||||
class _MobileToolbarV2State extends State<MobileToolbarV2> {
|
||||
OverlayEntry? toolbarOverlay;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_insertKeyboardToolbar();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeKeyboardToolbar();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<Selection?>(
|
||||
valueListenable: editorState.selectionNotifier,
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
void _removeKeyboardToolbar() {
|
||||
toolbarOverlay?.remove();
|
||||
toolbarOverlay?.dispose();
|
||||
toolbarOverlay = null;
|
||||
}
|
||||
|
||||
void _insertKeyboardToolbar() {
|
||||
_removeKeyboardToolbar();
|
||||
|
||||
Widget child = ValueListenableBuilder<Selection?>(
|
||||
valueListenable: widget.editorState.selectionNotifier,
|
||||
builder: (_, Selection? selection, child) {
|
||||
// if the selection is null, hide the toolbar
|
||||
if (selection == null) {
|
||||
|
|
@ -33,10 +71,34 @@ class MobileToolbarV2 extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
child: _MobileToolbar(
|
||||
editorState: editorState,
|
||||
toolbarItems: toolbarItems,
|
||||
editorState: widget.editorState,
|
||||
toolbarItems: widget.toolbarItems,
|
||||
bottomOffset: widget.bottomOffset,
|
||||
),
|
||||
);
|
||||
|
||||
child = Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Material(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
toolbarOverlay = OverlayEntry(
|
||||
builder: (context) {
|
||||
return child;
|
||||
},
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
Overlay.of(context, rootOverlay: true).insert(toolbarOverlay!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,10 +106,12 @@ class _MobileToolbar extends StatefulWidget {
|
|||
const _MobileToolbar({
|
||||
required this.editorState,
|
||||
required this.toolbarItems,
|
||||
required this.bottomOffset,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final List<MobileToolbarItem> toolbarItems;
|
||||
final double bottomOffset;
|
||||
|
||||
@override
|
||||
State<_MobileToolbar> createState() => _MobileToolbarState();
|
||||
|
|
@ -64,6 +128,9 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||
bool canUpdateCachedKeyboardHeight = true;
|
||||
ValueNotifier<double> cachedKeyboardHeight = ValueNotifier(0.0);
|
||||
double get keyboardHeight => MediaQuery.of(context).viewInsets.bottom;
|
||||
double get safeAreaBottomPadding => MediaQuery.of(context).viewPadding.bottom;
|
||||
|
||||
final keyboardHeightPlugin = KeyboardHeightPlugin();
|
||||
|
||||
// used to check if click the same item again
|
||||
int? selectedMenuIndex;
|
||||
|
|
@ -75,6 +142,11 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||
super.initState();
|
||||
|
||||
currentSelection = widget.editorState.selection;
|
||||
keyboardHeightPlugin.onKeyboardHeightChanged((height) {
|
||||
if (canUpdateCachedKeyboardHeight) {
|
||||
cachedKeyboardHeight.value = height;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -91,6 +163,7 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||
void dispose() {
|
||||
showMenuNotifier.dispose();
|
||||
cachedKeyboardHeight.dispose();
|
||||
keyboardHeightPlugin.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -109,7 +182,11 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||
// update the keyboard height here.
|
||||
// try to get the height in `didChangeMetrics`, but it's not accurate.
|
||||
if (canUpdateCachedKeyboardHeight) {
|
||||
cachedKeyboardHeight.value = keyboardHeight;
|
||||
// cachedKeyboardHeight.value = max(
|
||||
// 0,
|
||||
// keyboardHeight - safeAreaBottomPadding - widget.bottomOffset,
|
||||
// );
|
||||
// print('cachedKeyboardHeight: ${cachedKeyboardHeight.value}');
|
||||
}
|
||||
// toolbar
|
||||
// - if the menu is shown, the toolbar will be pushed up by the height of the menu
|
||||
|
|
@ -233,18 +310,20 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
|||
return ValueListenableBuilder(
|
||||
valueListenable: showMenuNotifier,
|
||||
builder: (_, showingMenu, __) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: height),
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: (showingMenu && selectedMenuIndex != null)
|
||||
? MobileToolbarItemMenu(
|
||||
editorState: widget.editorState,
|
||||
itemMenuBuilder: () => widget
|
||||
itemMenuBuilder: () =>
|
||||
widget
|
||||
.toolbarItems[selectedMenuIndex!].itemMenuBuilder!
|
||||
.call(
|
||||
context,
|
||||
widget.editorState,
|
||||
this,
|
||||
),
|
||||
) ??
|
||||
const SizedBox.shrink(),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ class _BlocksMenuState extends State<_BlocksMenu> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = MobileToolbarTheme.of(context);
|
||||
|
||||
final children = lists.map((list) {
|
||||
// Check if current node is list and its type
|
||||
final node = widget.editorState.getNodeAtPath(
|
||||
|
|
@ -111,11 +113,10 @@ class _BlocksMenuState extends State<_BlocksMenu> {
|
|||
|
||||
return GridView(
|
||||
shrinkWrap: true,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
padding: EdgeInsets.zero,
|
||||
gridDelegate: buildMobileToolbarMenuGridDelegate(
|
||||
mobileToolbarStyle: style,
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 5,
|
||||
),
|
||||
children: children,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class _TextDecorationMenuState extends State<_TextDecorationMenu> {
|
|||
|
||||
return GridView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
gridDelegate: buildMobileToolbarMenuGridDelegate(
|
||||
mobileToolbarStyle: style,
|
||||
crossAxisCount: 2,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ dependencies:
|
|||
diff_match_patch: ^0.4.1
|
||||
string_validator: ^1.0.0
|
||||
universal_html: ^2.2.4
|
||||
keyboard_height_plugin: ^0.0.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in a new issue