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:
Lucas.Xu 2023-11-16 10:05:37 +08:00 • committed by GitHub
commit e665b67c0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 73 deletions

View file

@ -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),
),
);
}

View file

@ -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,
],
),
],
);
}

View file

@ -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(

View file

@ -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

View file

@ -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(),
);

View file

@ -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,
);

View file

@ -96,6 +96,7 @@ class _TextDecorationMenuState extends State<_TextDecorationMenu> {
return GridView(
shrinkWrap: true,
padding: EdgeInsets.zero,
gridDelegate: buildMobileToolbarMenuGridDelegate(
mobileToolbarStyle: style,
crossAxisCount: 2,

View file

@ -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: