fix: issues related to the emoji icon picker (#7063)

* fix: remove the scrolling conflict of the icon picker on macOS

* fix: the icon is not supported in sites tab

* feat: keep the icon panel open after click ramdom

* feat: the type of selector opened depends on the already set icon or emoji

* feat: the skin tone of the random emoji follows the selected skin ton

* fix: unit testing error
This commit is contained in:
Morn 2024-12-30 16:42:14 +08:00 committed by Lucas.Xu
parent 02eb0e0b83
commit 3836545682
25 changed files with 281 additions and 91 deletions

View file

@ -1,4 +1,5 @@
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@ -9,7 +10,14 @@ import '../../shared/database_test_op.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('calendar', () {
testWidgets('update calendar layout', (tester) async {

View file

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/select/select_option.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@ -14,7 +14,14 @@ import '../../shared/database_test_op.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('grid edit field test:', () {
testWidgets('rename existing field', (tester) async {

View file

@ -1,6 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
@ -12,7 +13,14 @@ import '../../shared/emoji.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('Sidebar view item tests', () {
testWidgets('Access view item context menu by right click', (tester) async {

View file

@ -13,6 +13,18 @@ import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
EmojiData? kCachedEmojiData;
const _kRecentEmojiCategoryId = 'Recent';
class EmojiPickerResult {
EmojiPickerResult({
required this.emojiId,
required this.emoji,
this.isRandom = false,
});
final String emojiId;
final String emoji;
final bool isRandom;
}
class FlowyEmojiPicker extends StatefulWidget {
const FlowyEmojiPicker({
super.key,
@ -21,7 +33,7 @@ class FlowyEmojiPicker extends StatefulWidget {
this.ensureFocus = false,
});
final EmojiSelectedCallback onEmojiSelected;
final ValueChanged<EmojiPickerResult> onEmojiSelected;
final int emojiPerLine;
final bool ensureFocus;
@ -70,7 +82,9 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
defaultSkinTone: lastSelectedEmojiSkinTone ?? EmojiSkinTone.none,
),
onEmojiSelected: (id, emoji) {
widget.onEmojiSelected.call(id, emoji);
widget.onEmojiSelected.call(
EmojiPickerResult(emojiId: id, emoji: emoji),
);
RecentIcons.putEmoji(id);
},
padding: const EdgeInsets.symmetric(horizontal: 16.0),
@ -106,7 +120,12 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
onSkinToneChanged: (value) {
skinTone.value = value;
},
onRandomEmojiSelected: widget.onEmojiSelected,
onRandomEmojiSelected: (id, emoji) {
widget.onEmojiSelected.call(
EmojiPickerResult(emojiId: id, emoji: emoji, isRandom: true),
);
RecentIcons.putEmoji(id);
},
),
);
},

View file

@ -11,14 +11,17 @@ class MobileEmojiPickerScreen extends StatelessWidget {
const MobileEmojiPickerScreen({
super.key,
this.title,
this.selectedType,
this.tabs = const [PickerTabType.emoji, PickerTabType.icon],
});
final PickerTabType? selectedType;
final String? title;
final List<PickerTabType> tabs;
static const routeName = '/emoji_picker';
static const pageTitle = 'title';
static const iconSelectedType = 'iconSelectedType';
static const selectTabs = 'tabs';
@override
@ -30,8 +33,9 @@ class MobileEmojiPickerScreen extends StatelessWidget {
body: SafeArea(
child: FlowyIconEmojiPicker(
tabs: tabs,
initialType: selectedType,
onSelectedEmoji: (r) {
context.pop<EmojiIconData>(r);
context.pop<EmojiIconData>(r.data);
},
),
),

View file

@ -244,9 +244,9 @@ class _RenameRowPopoverState extends State<RenameRowPopover> {
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 18),
defaultIcon: const FlowySvg(FlowySvgs.document_s),
onSubmitted: (emoji, _) {
widget.onUpdateIcon(emoji);
PopoverContainer.of(context).close();
onSubmitted: (r, _) {
widget.onUpdateIcon(r.data);
if (!r.keepOpen) PopoverContainer.of(context).close();
},
),
const HSpace(6),

View file

@ -26,8 +26,10 @@ class EmojiPickerButton extends StatelessWidget {
final EmojiIconData emoji;
final double emojiSize;
final Size emojiPickerSize;
final void Function(EmojiIconData emoji, PopoverController? controller)
onSubmitted;
final void Function(
SelectedEmojiIconResult result,
PopoverController? controller,
) onSubmitted;
final PopoverController popoverController = PopoverController();
final Widget? defaultIcon;
final Offset? offset;
@ -85,8 +87,10 @@ class _DesktopEmojiPickerButton extends StatelessWidget {
final EmojiIconData emoji;
final double emojiSize;
final Size emojiPickerSize;
final void Function(EmojiIconData emoji, PopoverController? controller)
onSubmitted;
final void Function(
SelectedEmojiIconResult result,
PopoverController? controller,
) onSubmitted;
final PopoverController popoverController = PopoverController();
final Widget? defaultIcon;
final Offset? offset;
@ -113,6 +117,7 @@ class _DesktopEmojiPickerButton extends StatelessWidget {
height: emojiPickerSize.height,
padding: const EdgeInsets.all(4.0),
child: FlowyIconEmojiPicker(
initialType: emoji.type.toPickerTabType(),
onSelectedEmoji: (r) {
onSubmitted(r, popoverController);
},
@ -156,8 +161,10 @@ class _MobileEmojiPickerButton extends StatelessWidget {
final EmojiIconData emoji;
final double emojiSize;
final void Function(EmojiIconData emoji, PopoverController? controller)
onSubmitted;
final void Function(
SelectedEmojiIconResult result,
PopoverController? controller,
) onSubmitted;
final String? title;
final bool enable;
final EdgeInsets? margin;
@ -177,11 +184,14 @@ class _MobileEmojiPickerButton extends StatelessWidget {
final result = await context.push<EmojiIconData>(
Uri(
path: MobileEmojiPickerScreen.routeName,
queryParameters: {MobileEmojiPickerScreen.pageTitle: title},
queryParameters: {
MobileEmojiPickerScreen.pageTitle: title,
MobileEmojiPickerScreen.iconSelectedType: emoji.type.name,
},
).toString(),
);
if (result != null) {
onSubmitted(result, null);
onSubmitted(result.toSelectedResult(), null);
}
}
: null,

View file

@ -227,11 +227,12 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
value: pageStyleIconBloc,
child: Expanded(
child: FlowyIconEmojiPicker(
initialType: icon.type.toPickerTabType(),
onSelectedEmoji: (r) {
pageStyleIconBloc.add(
PageStyleIconEvent.updateIcon(r, true),
PageStyleIconEvent.updateIcon(r.data, true),
);
Navigator.pop(context);
if (!r.keepOpen) Navigator.pop(context);
},
),
),

View file

@ -468,9 +468,9 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
popupBuilder: (BuildContext popoverContext) {
isPopoverOpen = true;
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onIconOrCoverChanged(icon: result);
_popoverController.close();
onSelectedEmoji: (r) {
widget.onIconOrCoverChanged(icon: r.data);
if (!r.keepOpen) _popoverController.close();
},
);
},
@ -838,9 +838,7 @@ class _DocumentIconState extends State<DocumentIcon> {
@override
Widget build(BuildContext context) {
Widget child = EmojiIconWidget(
emoji: widget.icon,
);
Widget child = EmojiIconWidget(emoji: widget.icon);
if (UniversalPlatform.isDesktopOrWeb) {
child = AppFlowyPopover(
@ -852,9 +850,10 @@ class _DocumentIconState extends State<DocumentIcon> {
child: child,
popupBuilder: (BuildContext popoverContext) {
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onChangeIcon(result);
_popoverController.close();
initialType: widget.icon.type.toPickerTabType(),
onSelectedEmoji: (r) {
widget.onChangeIcon(r.data);
if (!r.keepOpen) _popoverController.close();
},
);
},
@ -864,7 +863,12 @@ class _DocumentIconState extends State<DocumentIcon> {
child: child,
onTap: () async {
final result = await context.push<EmojiIconData>(
MobileEmojiPickerScreen.routeName,
Uri(
path: MobileEmojiPickerScreen.routeName,
queryParameters: {
MobileEmojiPickerScreen.iconSelectedType: widget.icon.type.name,
},
).toString(),
);
if (result != null) {
widget.onChangeIcon(result);

View file

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
@ -67,24 +68,35 @@ class RawEmojiIconWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final defaultEmoji = EmojiText(
emoji: '',
fontSize: emojiSize,
textAlign: TextAlign.center,
final defaultEmoji = SizedBox(
width: emojiSize,
child: EmojiText(
emoji: '',
fontSize: emojiSize,
textAlign: TextAlign.center,
),
);
try {
switch (emoji.type) {
case FlowyIconType.emoji:
return EmojiText(
emoji: emoji.emoji,
fontSize: emojiSize,
textAlign: TextAlign.center,
return SizedBox(
width: emojiSize,
child: EmojiText(
emoji: emoji.emoji,
fontSize: emojiSize,
textAlign: TextAlign.center,
),
);
case FlowyIconType.icon:
final iconData = IconsData.fromJson(jsonDecode(emoji.emoji));
/// Under the same width conditions, icons on macOS seem to appear
/// larger than emojis, so 0.9 is used here to slightly reduce the
/// size of the icons
final iconSize = Platform.isMacOS ? emojiSize * 0.9 : emojiSize;
return IconWidget(
data: iconData,
size: emojiSize,
size: iconSize,
);
default:
return defaultEmoji;

View file

@ -35,7 +35,7 @@ class _PageStyleIconState extends State<PageStyleIcon> {
builder: (context, state) {
final icon = state.icon ?? EmojiIconData.none();
return GestureDetector(
onTap: () => _showIconSelector(context),
onTap: () => _showIconSelector(context, icon),
behavior: HitTestBehavior.opaque,
child: Container(
height: 52,
@ -66,7 +66,7 @@ class _PageStyleIconState extends State<PageStyleIcon> {
);
}
void _showIconSelector(BuildContext context) {
void _showIconSelector(BuildContext context, EmojiIconData icon) {
Navigator.pop(context);
final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial());
@ -85,11 +85,12 @@ class _PageStyleIconState extends State<PageStyleIcon> {
value: pageStyleIconBloc,
child: Expanded(
child: FlowyIconEmojiPicker(
initialType: icon.type.toPickerTabType(),
onSelectedEmoji: (r) {
pageStyleIconBloc.add(
PageStyleIconEvent.updateIcon(r, true),
PageStyleIconEvent.updateIcon(r.data, true),
);
Navigator.pop(ctx);
if (!r.keepOpen) Navigator.pop(ctx);
},
),
),

View file

@ -34,6 +34,7 @@ class FlowyEmojiSearchBar extends StatefulWidget {
class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
final TextEditingController controller = TextEditingController();
EmojiSkinTone skinTone = lastSelectedEmojiSkinTone ?? EmojiSkinTone.none;
@override
void dispose() {
@ -58,12 +59,18 @@ class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
),
const HSpace(8.0),
_RandomEmojiButton(
skinTone: skinTone,
emojiData: widget.emojiData,
onRandomEmojiSelected: widget.onRandomEmojiSelected,
),
const HSpace(8.0),
FlowyEmojiSkinToneSelector(
onEmojiSkinToneChanged: widget.onSkinToneChanged,
onEmojiSkinToneChanged: (v) {
setState(() {
skinTone = v;
});
widget.onSkinToneChanged.call(v);
},
),
],
),
@ -73,10 +80,12 @@ class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
class _RandomEmojiButton extends StatelessWidget {
const _RandomEmojiButton({
required this.skinTone,
required this.emojiData,
required this.onRandomEmojiSelected,
});
final EmojiSkinTone skinTone;
final EmojiData emojiData;
final EmojiSelectedCallback onRandomEmojiSelected;
@ -100,9 +109,14 @@ class _RandomEmojiButton extends StatelessWidget {
),
onTap: () {
final random = emojiData.random;
final emojiId = random.$1;
final emoji = emojiData.getEmojiById(
emojiId,
skinTone: skinTone,
);
onRandomEmojiSelected(
random.$1,
random.$2,
emojiId,
emoji,
);
},
),
@ -131,6 +145,9 @@ class _SearchTextFieldState extends State<_SearchTextField> {
@override
void initState() {
super.initState();
/// Sometimes focus is lost due to the [SelectionGestureInterceptor] in [KeyboardServiceWidgetState]
/// this is to ensure that focus can be regained within a short period of time
if (widget.ensureFocus) {
Future.delayed(const Duration(milliseconds: 200), () {
if (!mounted || focusNode.hasFocus) return;

View file

@ -6,6 +6,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/icon.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart' hide Icon;
import 'package:flutter/services.dart';
import 'package:universal_platform/universal_platform.dart';
extension ToProto on FlowyIconType {
@ -46,6 +47,10 @@ enum FlowyIconType {
custom;
}
extension FlowyIconTypeToPickerTabType on FlowyIconType {
PickerTabType? toPickerTabType() => name.toPickerTabType();
}
class EmojiIconData {
factory EmojiIconData.none() => const EmojiIconData(FlowyIconType.icon, '');
@ -78,17 +83,35 @@ class EmojiIconData {
bool get isNotEmpty => emoji.isNotEmpty;
}
class SelectedEmojiIconResult {
SelectedEmojiIconResult(this.data, this.keepOpen);
final EmojiIconData data;
final bool keepOpen;
FlowyIconType get type => data.type;
String get emoji => data.emoji;
}
extension EmojiIconDataToSelectedResultExtension on EmojiIconData {
SelectedEmojiIconResult toSelectedResult({bool keepOpen = false}) =>
SelectedEmojiIconResult(this, keepOpen);
}
class FlowyIconEmojiPicker extends StatefulWidget {
const FlowyIconEmojiPicker({
super.key,
this.onSelectedEmoji,
this.initialType,
this.enableBackgroundColorSelection = true,
this.tabs = const [PickerTabType.emoji, PickerTabType.icon],
});
final ValueChanged<EmojiIconData>? onSelectedEmoji;
final ValueChanged<SelectedEmojiIconResult>? onSelectedEmoji;
final bool enableBackgroundColorSelection;
final List<PickerTabType> tabs;
final PickerTabType? initialType;
@override
State<FlowyIconEmojiPicker> createState() => _FlowyIconEmojiPickerState();
@ -96,12 +119,23 @@ class FlowyIconEmojiPicker extends StatefulWidget {
class _FlowyIconEmojiPickerState extends State<FlowyIconEmojiPicker>
with SingleTickerProviderStateMixin {
late final controller = TabController(
length: widget.tabs.length,
vsync: this,
);
late TabController controller;
int currentIndex = 0;
@override
void initState() {
super.initState();
final initialType = widget.initialType;
if (initialType != null) {
currentIndex = widget.tabs.indexOf(initialType);
}
controller = TabController(
initialIndex: currentIndex,
length: widget.tabs.length,
vsync: this,
);
}
@override
void dispose() {
controller.dispose();
@ -127,7 +161,8 @@ class _FlowyIconEmojiPickerState extends State<FlowyIconEmojiPicker>
),
_RemoveIconButton(
onTap: () {
widget.onSelectedEmoji?.call(EmojiIconData.none());
widget.onSelectedEmoji
?.call(EmojiIconData.none().toSelectedResult());
},
),
],
@ -155,9 +190,12 @@ class _FlowyIconEmojiPickerState extends State<FlowyIconEmojiPicker>
return FlowyEmojiPicker(
ensureFocus: true,
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) => widget.onSelectedEmoji?.call(
EmojiIconData.emoji(emoji),
),
onEmojiSelected: (r) {
widget.onSelectedEmoji?.call(
EmojiIconData.emoji(r.emoji).toSelectedResult(keepOpen: r.isRandom),
);
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
);
}
@ -171,9 +209,13 @@ class _FlowyIconEmojiPickerState extends State<FlowyIconEmojiPicker>
Widget _buildIconPicker() {
return FlowyIconPicker(
ensureFocus: true,
enableBackgroundColorSelection: widget.enableBackgroundColorSelection,
onSelectedIcon: (result) {
widget.onSelectedEmoji?.call(result.toEmojiIconData());
onSelectedIcon: (r) {
widget.onSelectedEmoji?.call(
r.data.toEmojiIconData().toSelectedResult(keepOpen: r.isRandom),
);
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
);
}

View file

@ -75,17 +75,31 @@ Future<List<IconGroup>> loadIconGroups() async {
}
}
class IconPickerResult {
IconPickerResult(this.data, this.isRandom);
final IconsData data;
final bool isRandom;
}
extension IconsDataToIconPickerResultExtension on IconsData {
IconPickerResult toResult({bool isRandom = false}) =>
IconPickerResult(this, isRandom);
}
class FlowyIconPicker extends StatefulWidget {
const FlowyIconPicker({
super.key,
required this.onSelectedIcon,
required this.enableBackgroundColorSelection,
this.iconPerLine = 9,
this.ensureFocus = false,
});
final bool enableBackgroundColorSelection;
final ValueChanged<IconsData> onSelectedIcon;
final ValueChanged<IconPickerResult> onSelectedIcon;
final int iconPerLine;
final bool ensureFocus;
@override
State<FlowyIconPicker> createState() => _FlowyIconPickerState();
@ -142,6 +156,7 @@ class _FlowyIconPickerState extends State<FlowyIconPicker> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: IconSearchBar(
ensureFocus: widget.ensureFocus,
onRandomTap: () {
final value = kIconGroups?.randomIcon();
if (value == null) {
@ -154,8 +169,9 @@ class _FlowyIconPickerState extends State<FlowyIconPicker> {
value.$2.content,
value.$2.name,
color,
),
).toResult(isRandom: true),
);
RecentIcons.putIcon(value.$2);
},
onKeywordChanged: (keyword) => {
debounce.call(() {
@ -193,14 +209,14 @@ class _FlowyIconPickerState extends State<FlowyIconPicker> {
iconGroups: filteredIconGroups,
enableBackgroundColorSelection:
widget.enableBackgroundColorSelection,
onSelectedIcon: widget.onSelectedIcon,
onSelectedIcon: (r) => widget.onSelectedIcon.call(r.toResult()),
iconPerLine: widget.iconPerLine,
);
}
return IconPicker(
iconGroups: iconGroups,
enableBackgroundColorSelection: widget.enableBackgroundColorSelection,
onSelectedIcon: widget.onSelectedIcon,
onSelectedIcon: (r) => widget.onSelectedIcon.call(r.toResult()),
iconPerLine: widget.iconPerLine,
);
},
@ -278,6 +294,7 @@ class _IconPickerState extends State<IconPicker> {
crossAxisCount: widget.iconPerLine,
),
itemCount: iconGroup.icons.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
final icon = iconGroup.icons[index];

View file

@ -16,9 +16,11 @@ class IconSearchBar extends StatefulWidget {
super.key,
required this.onRandomTap,
required this.onKeywordChanged,
this.ensureFocus = false,
});
final VoidCallback onRandomTap;
final bool ensureFocus;
final IconKeywordChangedCallback onKeywordChanged;
@override
@ -46,6 +48,7 @@ class _IconSearchBarState extends State<IconSearchBar> {
Expanded(
child: _SearchTextField(
onKeywordChanged: widget.onKeywordChanged,
ensureFocus: widget.ensureFocus,
),
),
const HSpace(8.0),
@ -93,9 +96,11 @@ class _RandomIconButton extends StatelessWidget {
class _SearchTextField extends StatefulWidget {
const _SearchTextField({
required this.onKeywordChanged,
this.ensureFocus = false,
});
final IconKeywordChangedCallback onKeywordChanged;
final bool ensureFocus;
@override
State<_SearchTextField> createState() => _SearchTextFieldState();
@ -105,6 +110,20 @@ class _SearchTextFieldState extends State<_SearchTextField> {
final TextEditingController controller = TextEditingController();
final FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
/// Sometimes focus is lost due to the [SelectionGestureInterceptor] in [KeyboardServiceWidgetState]
/// this is to ensure that focus can be regained within a short period of time
if (widget.ensureFocus) {
Future.delayed(const Duration(milliseconds: 200), () {
if (!mounted || focusNode.hasFocus) return;
focusNode.requestFocus();
});
}
}
@override
void dispose() {
controller.dispose();

View file

@ -15,6 +15,16 @@ enum PickerTabType {
}
}
extension StringToPickerTabType on String {
PickerTabType? toPickerTabType() {
try {
return PickerTabType.values.byName(this);
} on ArgumentError {
return null;
}
}
}
class PickerTab extends StatelessWidget {
const PickerTab({
super.key,

View file

@ -285,14 +285,21 @@ GoRoute _mobileEmojiPickerPageRoute() {
state.uri.queryParameters[MobileEmojiPickerScreen.pageTitle];
final selectTabs =
state.uri.queryParameters[MobileEmojiPickerScreen.selectTabs] ?? '';
final selectedType = state
.uri.queryParameters[MobileEmojiPickerScreen.iconSelectedType]
?.toPickerTabType();
final tabs = selectTabs
.split('-')
.map((e) => PickerTabType.values.byName(e))
.toList();
return MaterialExtendedPage(
child: tabs.isEmpty
? MobileEmojiPickerScreen(title: title)
: MobileEmojiPickerScreen(title: title, tabs: tabs),
? MobileEmojiPickerScreen(title: title, selectedType: selectedType)
: MobileEmojiPickerScreen(
title: title,
selectedType: selectedType,
tabs: tabs,
),
);
},
);

View file

@ -96,9 +96,9 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
margin: const EdgeInsets.all(0),
popupBuilder: (_) => FlowyIconEmojiPicker(
tabs: const [PickerTabType.emoji],
onSelectedEmoji: (result) {
widget.onSelected(result);
controller.close();
onSelectedEmoji: (r) {
widget.onSelected(r.data);
if (!r.keepOpen) controller.close();
},
),
child: MouseRegion(

View file

@ -641,12 +641,13 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
popupBuilder: (context) {
isIconPickerOpened = true;
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
initialType: iconData.type.toPickerTabType(),
onSelectedEmoji: (r) {
ViewBackendService.updateViewIcon(
viewId: widget.view.id,
viewIcon: result,
viewIcon: r.data,
);
controller.close();
if (!r.keepOpen) controller.close();
},
);
},
@ -770,13 +771,12 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
context.read<ViewBloc>().add(const ViewEvent.collapseAllPages());
break;
case ViewMoreActionType.changeIcon:
if (data is! EmojiIconData) {
if (data is! SelectedEmojiIconResult) {
return;
}
final result = data;
await ViewBackendService.updateViewIcon(
viewId: widget.view.id,
viewIcon: result,
viewIcon: data.data,
);
break;
case ViewMoreActionType.moveTo:

View file

@ -58,7 +58,11 @@ class ViewMoreActionPopover extends StatelessWidget {
(e) => ViewMoreActionTypeWrapper(e, view, (controller, data) {
onEditing(false);
onAction(e, data);
controller.close();
bool enableClose = true;
if (data is SelectedEmojiIconResult) {
if (data.keepOpen) enableClose = false;
}
if (enableClose) controller.close();
}),
)
.toList();
@ -172,6 +176,7 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
margin: const EdgeInsets.all(0),
clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => FlowyIconEmojiPicker(
initialType: sourceView.icon.toEmojiIconData().type.toPickerTabType(),
onSelectedEmoji: (result) => onTap(controller, result),
),
child: child,

View file

@ -1,4 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
@ -51,13 +53,9 @@ class PublishInfoViewItem extends StatelessWidget {
}
Widget _buildIcon() {
final icon = publishInfoView.view.icon.value;
final icon = publishInfoView.view.icon.toEmojiIconData();
return icon.isNotEmpty
? FlowyText.emoji(
icon,
fontSize: 16.0,
figmaLineHeight: 18.0,
)
? RawEmojiIconWidget(emoji: icon, emojiSize: 16.0)
: publishInfoView.view.defaultIcon();
}
}

View file

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/decoration.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
SelectionMenuItem emojiMenuItem = SelectionMenuItem(
getName: LocaleKeys.document_plugins_emoji.tr,
@ -109,7 +108,7 @@ class _EmojiSelectionMenuState extends State<EmojiSelectionMenu> {
@override
Widget build(BuildContext context) {
return FlowyEmojiPicker(
onEmojiSelected: (_, emoji) => widget.onSubmitted(emoji),
onEmojiSelected: (r) => widget.onSubmitted(r.emoji),
);
}
}

View file

@ -91,13 +91,15 @@ class _RenameViewPopoverState extends State<RenameViewPopover> {
}
Future<void> _updateViewIcon(
EmojiIconData emoji,
SelectedEmojiIconResult r,
PopoverController? _,
) async {
await ViewBackendService.updateViewIcon(
viewId: widget.viewId,
viewIcon: emoji,
viewIcon: r.data,
);
widget.popoverController.close();
if (!r.keepOpen) {
widget.popoverController.close();
}
}
}

View file

@ -757,8 +757,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "8a9fa49"
resolved-ref: "8a9fa491cb3b86baf78b0a33c2c37a29d1cae028"
ref: "355aa56"
resolved-ref: "355aa56e9c74a91e00370a882739e0bb98c30bd8"
url: "https://github.com/LucasXu0/emoji_mart.git"
source: git
version: "1.0.2"

View file

@ -73,7 +73,7 @@ dependencies:
flutter_emoji_mart:
git:
url: https://github.com/LucasXu0/emoji_mart.git
ref: "8a9fa49"
ref: "355aa56"
flutter_math_fork: ^0.7.3
flutter_slidable: ^3.0.0