Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize editing experience on mobile #592

Merged
merged 4 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example/lib/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@ 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: SafeArea(child: _buildBody(context)),
body: _buildBody(context),
);
}

Expand Down
10 changes: 3 additions & 7 deletions example/lib/pages/mobile_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,14 @@ class _MobileEditorState extends State<MobileEditor> {
),
),
// build mobile toolbar
MobileToolbar(
MobileToolbarV2(
editorState: editorState,
toolbarItems: [
textDecorationMobileToolbarItem,
textDecorationMobileToolbarItemV2,
buildTextAndBackgroundColorMobileToolbarItem(),
headingMobileToolbarItem,
todoListMobileToolbarItem,
listMobileToolbarItem,
blocksMobileToolbarItem,
linkMobileToolbarItem,
quoteMobileToolbarItem,
dividerMobileToolbarItem,
codeMobileToolbarItem,
],
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
late final TextInputService textInputService;
late final FocusNode focusNode;

// previous selection
Selection? previousSelection;

// use for IME only
bool enableShortcuts = true;

Expand Down Expand Up @@ -187,9 +190,17 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
if (doNotAttach == true) {
return;
}
enableShortcuts = true;

// attach the delta text input service if needed
final selection = editorState.selection;

if (PlatformExtension.isMobile && previousSelection == selection) {
// no need to attach the text input service if the selection is not changed.
return;
}

enableShortcuts = true;

if (selection == null) {
textInputService.close();
} else {
Expand All @@ -202,6 +213,8 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
Log.editor.debug('keyboard service - request focus');
}
}

previousSelection = selection;
}

void _attachTextInputService(Selection selection) {
Expand Down
1 change: 1 addition & 0 deletions lib/src/editor/toolbar/mobile/mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export 'mobile_floating_toolbar/mobile_floating_toolbar.dart';
export 'mobile_toolbar.dart';
export 'mobile_toolbar_item.dart';
export 'mobile_toolbar_style.dart';
export 'mobile_toolbar_v2.dart';
export 'toolbar_items/toolbar_items.dart';
export 'utils/utils.dart';
65 changes: 48 additions & 17 deletions lib/src/editor/toolbar/mobile/mobile_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@
return const SizedBox.shrink();
}
return RepaintBoundary(
child: MobileToolbarStyle(
child: MobileToolbarTheme(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
clearDiagonalLineColor: clearDiagonalLineColor,
itemHighlightColor: itemHighlightColor,
itemOutlineColor: itemOutlineColor,
tabbarSelectedBackgroundColor: tabbarSelectedBackgroundColor,
tabbarSelectedForegroundColor: tabbarSelectedForegroundColor,
tabBarSelectedBackgroundColor: tabbarSelectedBackgroundColor,
tabBarSelectedForegroundColor: tabbarSelectedForegroundColor,
primaryColor: primaryColor,
onPrimaryColor: onPrimaryColor,
outlineColor: outlineColor,
Expand Down Expand Up @@ -90,7 +90,7 @@
void closeItemMenu();
}

class MobileToolbarWidget extends StatefulWidget {
class MobileToolbarWidget extends StatefulWidget with WidgetsBindingObserver {
const MobileToolbarWidget({
super.key,
required this.editorState,
Expand All @@ -111,6 +111,9 @@
bool _showItemMenu = false;
int? _selectedToolbarItemIndex;

double previousKeyboardHeight = 0.0;
bool updateKeyboardHeight = true;

@override
void closeItemMenu() {
if (_showItemMenu) {
Expand All @@ -123,7 +126,14 @@
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final style = MobileToolbarStyle.of(context);
final style = MobileToolbarTheme.of(context);

final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
if (updateKeyboardHeight) {
previousKeyboardHeight = keyboardHeight;
}
// print('object $keyboardHeight');

return Column(
children: [
Container(
Expand All @@ -147,15 +157,27 @@
toolbarItems: widget.toolbarItems,
toolbarWidgetService: this,
itemWithMenuOnPressed: (selectedItemIndex) {
updateKeyboardHeight = false;
setState(() {
// If last selected item is selected again, toggle item menu
if (_selectedToolbarItemIndex == selectedItemIndex) {
_showItemMenu = !_showItemMenu;

if (!_showItemMenu) {

Check warning on line 166 in lib/src/editor/toolbar/mobile/mobile_toolbar.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/toolbar/mobile/mobile_toolbar.dart#L166

Added line #L166 was not covered by tests
// updateKeyboardHeight = true;
widget.editorState.service.keyboardService
?.enableKeyBoard(widget.selection);

Check warning on line 169 in lib/src/editor/toolbar/mobile/mobile_toolbar.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/toolbar/mobile/mobile_toolbar.dart#L168-L169

Added lines #L168 - L169 were not covered by tests
} else {
updateKeyboardHeight = false;
widget.editorState.service.keyboardService
?.closeKeyboard();

Check warning on line 173 in lib/src/editor/toolbar/mobile/mobile_toolbar.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/toolbar/mobile/mobile_toolbar.dart#L171-L173

Added lines #L171 - L173 were not covered by tests
}
} else {
_selectedToolbarItemIndex = selectedItemIndex;
// If not, show item menu
_showItemMenu = true;
// close keyboard when menu pop up

widget.editorState.service.keyboardService
?.closeKeyboard();
}
Expand All @@ -174,8 +196,8 @@
onPressed: () {
setState(() {
_showItemMenu = false;
widget.editorState.service.keyboardService!
.enableKeyBoard(widget.selection);
widget.editorState.service.keyboardService
?.enableKeyBoard(widget.selection);

Check warning on line 200 in lib/src/editor/toolbar/mobile/mobile_toolbar.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/toolbar/mobile/mobile_toolbar.dart#L199-L200

Added lines #L199 - L200 were not covered by tests
});
},
icon: const AFMobileIcon(
Expand All @@ -187,14 +209,22 @@
),
),
// only for MobileToolbarItem.withMenu
if (_showItemMenu && _selectedToolbarItemIndex != null)
MobileToolbarItemMenu(
editorState: widget.editorState,
itemMenuBuilder: () => widget
.toolbarItems[_selectedToolbarItemIndex!]
// pass current [MobileToolbarWidgetState] to be used to closeItemMenu
.itemMenuBuilder!(widget.editorState, widget.selection, this),
),
(_showItemMenu && _selectedToolbarItemIndex != null)
? Container(
constraints: BoxConstraints(
minHeight: previousKeyboardHeight,
),
child: MobileToolbarItemMenu(
editorState: widget.editorState,
itemMenuBuilder: () => widget
.toolbarItems[_selectedToolbarItemIndex!]
// pass current [MobileToolbarWidgetState] to be used to closeItemMenu
.itemMenuBuilder!(context, widget.editorState, this),
),
)
: SizedBox(
height: previousKeyboardHeight,
),
],
);
}
Expand Down Expand Up @@ -239,9 +269,10 @@
return ListView.builder(
itemBuilder: (context, index) {
final toolbarItem = toolbarItems[index];
final icon = toolbarItem.itemIconBuilder(
final icon = toolbarItem.itemIconBuilder?.call(
context,
editorState,
toolbarWidgetService,
);
if (icon == null) {
return const SizedBox.shrink();
Expand All @@ -256,8 +287,8 @@
// close menu if other item's menu is still on the screen
toolbarWidgetService.closeItemMenu();
toolbarItems[index].actionHandler?.call(
context,
editorState,
selection,
);
}
},
Expand Down
23 changes: 12 additions & 11 deletions lib/src/editor/toolbar/mobile/mobile_toolbar_item.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

typedef MobileToolbarItemMenuBuilder = Widget Function(
typedef MobileToolbarItemIconBuilder = Widget Function(
BuildContext context,
EditorState editorState,
Selection selection,
// To access to the state of MobileToolbarWidget
MobileToolbarWidgetService service,
);

typedef MobileToolbarItemActionHandler = void Function(
BuildContext context,
EditorState editorState,
Selection selection,
);

class MobileToolbarItem {
Expand All @@ -19,20 +19,21 @@ class MobileToolbarItem {
required this.itemIconBuilder,
required this.actionHandler,
}) : hasMenu = false,
itemMenuBuilder = null;
itemMenuBuilder = null,
assert(itemIconBuilder != null && actionHandler != null);

/// Tool bar item that opens a menu to show options
const MobileToolbarItem.withMenu({
required this.itemIconBuilder,
required this.itemMenuBuilder,
}) : hasMenu = true,
actionHandler = null;
actionHandler = null,
assert(itemMenuBuilder != null && itemIconBuilder != null);

final bool hasMenu;
final Widget? Function(
BuildContext context,
EditorState editorState,
) itemIconBuilder;
final MobileToolbarItemMenuBuilder? itemMenuBuilder;
// if the result is null, the item will be hidden
final MobileToolbarItemIconBuilder? itemIconBuilder;
final MobileToolbarItemActionHandler? actionHandler;

final bool hasMenu;
final MobileToolbarItemIconBuilder? itemMenuBuilder;
}
66 changes: 34 additions & 32 deletions lib/src/editor/toolbar/mobile/mobile_toolbar_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,35 @@
/// itemHighlightColor -> selected item border color
///
/// itemOutlineColor -> item border color
class MobileToolbarStyle extends InheritedWidget {
class MobileToolbarTheme extends InheritedWidget {
const MobileToolbarTheme({
super.key,
this.backgroundColor = Colors.white,
this.foregroundColor = const Color(0xff676666),
this.clearDiagonalLineColor = const Color(0xffB3261E),
this.itemHighlightColor = const Color(0xff1F71AC),
this.itemOutlineColor = const Color(0xFFE3E3E3),
this.tabBarSelectedBackgroundColor = const Color(0x23808080),
this.tabBarSelectedForegroundColor = Colors.black,
this.primaryColor = const Color(0xff1F71AC),
this.onPrimaryColor = Colors.white,
this.outlineColor = const Color(0xFFE3E3E3),
this.toolbarHeight = 50.0,
this.borderRadius = 6.0,
this.buttonHeight = 40.0,
this.buttonSpacing = 8.0,
this.buttonBorderWidth = 1.0,
this.buttonSelectedBorderWidth = 2.0,
required super.child,
});

final Color backgroundColor;
final Color foregroundColor;
final Color clearDiagonalLineColor;
final Color itemHighlightColor;
final Color itemOutlineColor;
final Color tabbarSelectedBackgroundColor;
final Color tabbarSelectedForegroundColor;
final Color tabBarSelectedBackgroundColor;
final Color tabBarSelectedForegroundColor;
final Color primaryColor;
final Color onPrimaryColor;
final Color outlineColor;
Expand All @@ -25,44 +46,25 @@
final double buttonBorderWidth;
final double buttonSelectedBorderWidth;

const MobileToolbarStyle({
super.key,
required this.backgroundColor,
required this.foregroundColor,
required this.clearDiagonalLineColor,
required this.itemHighlightColor,
required this.itemOutlineColor,
required this.tabbarSelectedBackgroundColor,
required this.tabbarSelectedForegroundColor,
required this.primaryColor,
required this.onPrimaryColor,
required this.outlineColor,
required this.toolbarHeight,
required this.borderRadius,
required this.buttonHeight,
required this.buttonSpacing,
required this.buttonBorderWidth,
required this.buttonSelectedBorderWidth,
required super.child,
});
static MobileToolbarTheme of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MobileToolbarTheme>()!;
}

static MobileToolbarStyle of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MobileToolbarStyle>()!;
static MobileToolbarTheme? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MobileToolbarTheme>();

Check warning on line 54 in lib/src/editor/toolbar/mobile/mobile_toolbar_style.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor/toolbar/mobile/mobile_toolbar_style.dart#L53-L54

Added lines #L53 - L54 were not covered by tests
}

@override
bool updateShouldNotify(covariant MobileToolbarStyle oldWidget) {
// This function is called whenever the inherited widget is rebuilt.
// It should return true if the new widget's values are different from the old widget's values.
bool updateShouldNotify(covariant MobileToolbarTheme oldWidget) {
return backgroundColor != oldWidget.backgroundColor ||
foregroundColor != oldWidget.foregroundColor ||
clearDiagonalLineColor != oldWidget.clearDiagonalLineColor ||
itemHighlightColor != oldWidget.itemHighlightColor ||
itemOutlineColor != oldWidget.itemOutlineColor ||
tabbarSelectedBackgroundColor !=
oldWidget.tabbarSelectedBackgroundColor ||
tabbarSelectedForegroundColor !=
oldWidget.tabbarSelectedForegroundColor ||
tabBarSelectedBackgroundColor !=
oldWidget.tabBarSelectedBackgroundColor ||
tabBarSelectedForegroundColor !=
oldWidget.tabBarSelectedForegroundColor ||
primaryColor != oldWidget.primaryColor ||
onPrimaryColor != oldWidget.onPrimaryColor ||
outlineColor != oldWidget.outlineColor ||
Expand Down
Loading
Loading