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, key: _scaffoldKey,
extendBodyBehindAppBar: PlatformExtension.isDesktopOrWeb, extendBodyBehindAppBar: PlatformExtension.isDesktopOrWeb,
drawer: _buildDrawer(context), drawer: _buildDrawer(context),
resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 134, 46, 247), backgroundColor: const Color.fromARGB(255, 134, 46, 247),
foregroundColor: Colors.white, foregroundColor: Colors.white,
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
title: const Text('AppFlowy Editor'), 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(PlatformExtension.isMobile); assert(PlatformExtension.isMobile);
return Column( return MobileToolbarV2(
toolbarItems: [
textDecorationMobileToolbarItemV2,
buildTextAndBackgroundColorMobileToolbarItem(),
blocksMobileToolbarItem,
linkMobileToolbarItem,
dividerMobileToolbarItem,
],
editorState: editorState,
child: Column(
children: [ children: [
// build appflowy editor // build appflowy editor
Expanded( 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( child: MobileToolbarItemMenu(
editorState: widget.editorState, editorState: widget.editorState,
itemMenuBuilder: () => widget itemMenuBuilder: () =>
widget
.toolbarItems[_selectedToolbarItemIndex!] .toolbarItems[_selectedToolbarItemIndex!]
// pass current [MobileToolbarWidgetState] to be used to closeItemMenu // pass current [MobileToolbarWidgetState] to be used to closeItemMenu
.itemMenuBuilder!(context, widget.editorState, this), .itemMenuBuilder!(context, widget.editorState, this) ??
const SizedBox.shrink(),
), ),
) )
: SizedBox( : SizedBox(

View file

@ -1,7 +1,8 @@
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.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, BuildContext context,
EditorState editorState, EditorState editorState,
// To access to the state of MobileToolbarWidget // To access to the state of MobileToolbarWidget

View file

@ -1,20 +1,58 @@
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:keyboard_height_plugin/keyboard_height_plugin.dart';
class MobileToolbarV2 extends StatelessWidget { class MobileToolbarV2 extends StatefulWidget {
const MobileToolbarV2({ const MobileToolbarV2({
super.key, super.key,
required this.editorState, required this.editorState,
required this.toolbarItems, required this.toolbarItems,
this.bottomOffset = 0.0,
required this.child,
}); });
final EditorState editorState; final EditorState editorState;
final List<MobileToolbarItem> toolbarItems; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<Selection?>( return widget.child;
valueListenable: editorState.selectionNotifier, }
void _removeKeyboardToolbar() {
toolbarOverlay?.remove();
toolbarOverlay?.dispose();
toolbarOverlay = null;
}
void _insertKeyboardToolbar() {
_removeKeyboardToolbar();
Widget child = ValueListenableBuilder<Selection?>(
valueListenable: widget.editorState.selectionNotifier,
builder: (_, Selection? selection, child) { builder: (_, Selection? selection, child) {
// if the selection is null, hide the toolbar // if the selection is null, hide the toolbar
if (selection == null) { if (selection == null) {
@ -33,10 +71,34 @@ class MobileToolbarV2 extends StatelessWidget {
); );
}, },
child: _MobileToolbar( child: _MobileToolbar(
editorState: editorState, editorState: widget.editorState,
toolbarItems: toolbarItems, 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({ const _MobileToolbar({
required this.editorState, required this.editorState,
required this.toolbarItems, required this.toolbarItems,
required this.bottomOffset,
}); });
final EditorState editorState; final EditorState editorState;
final List<MobileToolbarItem> toolbarItems; final List<MobileToolbarItem> toolbarItems;
final double bottomOffset;
@override @override
State<_MobileToolbar> createState() => _MobileToolbarState(); State<_MobileToolbar> createState() => _MobileToolbarState();
@ -64,6 +128,9 @@ class _MobileToolbarState extends State<_MobileToolbar>
bool canUpdateCachedKeyboardHeight = true; bool canUpdateCachedKeyboardHeight = true;
ValueNotifier<double> cachedKeyboardHeight = ValueNotifier(0.0); ValueNotifier<double> cachedKeyboardHeight = ValueNotifier(0.0);
double get keyboardHeight => MediaQuery.of(context).viewInsets.bottom; 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 // used to check if click the same item again
int? selectedMenuIndex; int? selectedMenuIndex;
@ -75,6 +142,11 @@ class _MobileToolbarState extends State<_MobileToolbar>
super.initState(); super.initState();
currentSelection = widget.editorState.selection; currentSelection = widget.editorState.selection;
keyboardHeightPlugin.onKeyboardHeightChanged((height) {
if (canUpdateCachedKeyboardHeight) {
cachedKeyboardHeight.value = height;
}
});
} }
@override @override
@ -91,6 +163,7 @@ class _MobileToolbarState extends State<_MobileToolbar>
void dispose() { void dispose() {
showMenuNotifier.dispose(); showMenuNotifier.dispose();
cachedKeyboardHeight.dispose(); cachedKeyboardHeight.dispose();
keyboardHeightPlugin.dispose();
super.dispose(); super.dispose();
} }
@ -109,7 +182,11 @@ class _MobileToolbarState extends State<_MobileToolbar>
// update the keyboard height here. // update the keyboard height here.
// try to get the height in `didChangeMetrics`, but it's not accurate. // try to get the height in `didChangeMetrics`, but it's not accurate.
if (canUpdateCachedKeyboardHeight) { if (canUpdateCachedKeyboardHeight) {
cachedKeyboardHeight.value = keyboardHeight; // cachedKeyboardHeight.value = max(
// 0,
// keyboardHeight - safeAreaBottomPadding - widget.bottomOffset,
// );
// print('cachedKeyboardHeight: ${cachedKeyboardHeight.value}');
} }
// toolbar // toolbar
// - if the menu is shown, the toolbar will be pushed up by the height of the menu // - 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( return ValueListenableBuilder(
valueListenable: showMenuNotifier, valueListenable: showMenuNotifier,
builder: (_, showingMenu, __) { builder: (_, showingMenu, __) {
return ConstrainedBox( return SizedBox(
constraints: BoxConstraints(minHeight: height), height: height,
child: (showingMenu && selectedMenuIndex != null) child: (showingMenu && selectedMenuIndex != null)
? MobileToolbarItemMenu( ? MobileToolbarItemMenu(
editorState: widget.editorState, editorState: widget.editorState,
itemMenuBuilder: () => widget itemMenuBuilder: () =>
widget
.toolbarItems[selectedMenuIndex!].itemMenuBuilder! .toolbarItems[selectedMenuIndex!].itemMenuBuilder!
.call( .call(
context, context,
widget.editorState, widget.editorState,
this, this,
), ) ??
const SizedBox.shrink(),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
); );

View file

@ -73,6 +73,8 @@ class _BlocksMenuState extends State<_BlocksMenu> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final style = MobileToolbarTheme.of(context);
final children = lists.map((list) { final children = lists.map((list) {
// Check if current node is list and its type // Check if current node is list and its type
final node = widget.editorState.getNodeAtPath( final node = widget.editorState.getNodeAtPath(
@ -111,11 +113,10 @@ class _BlocksMenuState extends State<_BlocksMenu> {
return GridView( return GridView(
shrinkWrap: true, shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( padding: EdgeInsets.zero,
gridDelegate: buildMobileToolbarMenuGridDelegate(
mobileToolbarStyle: style,
crossAxisCount: 2, crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 5,
), ),
children: children, children: children,
); );

View file

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

View file

@ -46,6 +46,7 @@ dependencies:
diff_match_patch: ^0.4.1 diff_match_patch: ^0.4.1
string_validator: ^1.0.0 string_validator: ^1.0.0
universal_html: ^2.2.4 universal_html: ^2.2.4
keyboard_height_plugin: ^0.0.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: