diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart new file mode 100644 index 0000000000000..0734ef4363423 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart'; +import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/emoji.dart'; +import '../../shared/util.dart'; + +void main() { + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + + testWidgets('change icon', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final iconData = await tester.loadIcon(); + + const pageName = 'Database'; + await tester.createNewPageWithNameUnderParent( + layout: ViewLayoutPB.Grid, + name: pageName, + ); + + /// create board + final addButton = find.byType(AddDatabaseViewButton); + await tester.tapButton(addButton); + await tester.tapButton( + find.text( + '${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Board.layoutName}', + findRichText: true, + ), + ); + + /// create calendar + await tester.tapButton(addButton); + await tester.tapButton( + find.text( + '${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Calendar.layoutName}', + findRichText: true, + ), + ); + + final databaseTabBarItem = find.byType(DatabaseTabBarItem); + expect(databaseTabBarItem, findsNWidgets(3)); + final gridItem = databaseTabBarItem.first, + boardItem = databaseTabBarItem.at(1), + calendarItem = databaseTabBarItem.last; + + /// change the icon of grid + /// the first tapping is to select specific item + /// the second tapping is to show the menu + await tester.tapButton(gridItem); + await tester.tapButton(gridItem); + + /// change icon + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final gridIcon = find.descendant( + of: gridItem, + matching: find.byType(RawEmojiIconWidget), + ); + final gridIconWidget = + gridIcon.evaluate().first.widget as RawEmojiIconWidget; + final iconsData = IconsData.fromJson(jsonDecode(iconData.emoji)); + final gridIconsData = + IconsData.fromJson(jsonDecode(gridIconWidget.emoji.emoji)); + expect(gridIconsData.iconName, iconsData.iconName); + + /// change the icon of board + await tester.tapButton(boardItem); + await tester.tapButton(boardItem); + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final boardIcon = find.descendant( + of: boardItem, + matching: find.byType(RawEmojiIconWidget), + ); + final boardIconWidget = + boardIcon.evaluate().first.widget as RawEmojiIconWidget; + final boardIconsData = + IconsData.fromJson(jsonDecode(boardIconWidget.emoji.emoji)); + expect(boardIconsData.iconName, iconsData.iconName); + + /// change the icon of calendar + await tester.tapButton(calendarItem); + await tester.tapButton(calendarItem); + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final calendarIcon = find.descendant( + of: calendarItem, + matching: find.byType(RawEmojiIconWidget), + ); + final calendarIconWidget = + calendarIcon.evaluate().first.widget as RawEmojiIconWidget; + final calendarIconsData = + IconsData.fromJson(jsonDecode(calendarIconWidget.emoji.emoji)); + expect(calendarIconsData.iconName, iconsData.iconName); + }); + + testWidgets('change database icon from sidebar', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final iconData = await tester.loadIcon(); + final icon = IconsData.fromJson(jsonDecode(iconData.emoji)), emoji = '😄'; + + const pageName = 'Database'; + await tester.createNewPageWithNameUnderParent( + layout: ViewLayoutPB.Grid, + name: pageName, + ); + final viewItem = find.descendant( + of: find.byType(SidebarFolder), + matching: find.byWidgetPredicate( + (w) => w is ViewItem && w.view.name == pageName, + ), + ); + + /// change icon to emoji + await tester.tapButton( + find.descendant( + of: viewItem, + matching: find.byType(FlowySvg), + ), + ); + await tester.tapEmoji(emoji); + final iconWidget = find.descendant( + of: viewItem, + matching: find.byType(RawEmojiIconWidget), + ); + expect( + (iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji, + emoji, + ); + + /// the icon will not be displayed in database item + Finder databaseIcon = find.descendant( + of: find.byType(DatabaseTabBarItem), + matching: find.byType(FlowySvg), + ); + expect( + (databaseIcon.evaluate().first.widget as FlowySvg).svg, + FlowySvgs.icon_grid_s, + ); + + /// change emoji to icon + await tester.tapButton(iconWidget); + await tester.tapIcon(iconData); + expect( + (iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji, + iconData.emoji, + ); + + databaseIcon = find.descendant( + of: find.byType(DatabaseTabBarItem), + matching: find.byType(RawEmojiIconWidget), + ); + final databaseIconWidget = + databaseIcon.evaluate().first.widget as RawEmojiIconWidget; + final databaseIconsData = + IconsData.fromJson(jsonDecode(databaseIconWidget.emoji.emoji)); + expect(icon.iconContent, databaseIconsData.iconContent); + expect(icon.color, isNotEmpty); + expect(icon.color, databaseIconsData.color); + + /// the icon in database item should not show the color + expect(databaseIconWidget.enableColor, false); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/shared/emoji.dart b/frontend/appflowy_flutter/integration_test/shared/emoji.dart index c2032eef6b569..3a750e08039d3 100644 --- a/frontend/appflowy_flutter/integration_test/shared/emoji.dart +++ b/frontend/appflowy_flutter/integration_test/shared/emoji.dart @@ -21,7 +21,7 @@ extension EmojiTestExtension on WidgetTester { await tapButton(emojiWidget); } - Future tapIcon(EmojiIconData icon) async { + Future tapIcon(EmojiIconData icon, {bool enableColor = true}) async { final iconsData = IconsData.fromJson(jsonDecode(icon.emoji)); final pickTab = find.byType(PickerTab); expect(pickTab, findsOneWidget); @@ -43,25 +43,28 @@ extension EmojiTestExtension on WidgetTester { /// test for tapping down, it should not display the ColorPicker unless tapping up await tapDown(selectedSvg); expect(find.byType(IconColorPicker), findsNothing); - await tapButton(selectedSvg); - final colorPicker = find.byType(IconColorPicker); - expect(colorPicker, findsOneWidget); - final selectedColor = find.descendant( - of: colorPicker, - matching: find.byWidgetPredicate((w) { - if (w is Container) { - final d = w.decoration; - if (d is ShapeDecoration) { - if (d.color == - Color(int.parse(iconsData.color ?? builtInSpaceColors.first))) { - return true; + if (enableColor) { + final colorPicker = find.byType(IconColorPicker); + expect(colorPicker, findsOneWidget); + final selectedColor = find.descendant( + of: colorPicker, + matching: find.byWidgetPredicate((w) { + if (w is Container) { + final d = w.decoration; + if (d is ShapeDecoration) { + if (d.color == + Color( + int.parse(iconsData.color ?? builtInSpaceColors.first), + )) { + return true; + } } } - } - return false; - }), - ); - await tapButton(selectedColor); + return false; + }), + ); + await tapButton(selectedColor); + } } } diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart index 8fc32ab5dbbdd..aa13d273a0029 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart @@ -21,6 +21,7 @@ extension MobileRouter on BuildContext { bool showMoreButton = true, String? fixedTitle, String? blockId, + List? tabs, }) async { // set the current view before pushing the new view getIt().latestOpenView = view; @@ -37,6 +38,9 @@ extension MobileRouter on BuildContext { queryParameters[MobileDocumentScreen.viewBlockId] = blockId; } } + if (tabs != null) { + queryParameters[MobileDocumentScreen.viewSelectTabs] = tabs; + } final uri = Uri( path: view.routeName, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart index 4fd8e246d775f..6a08be9ea11a2 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart @@ -10,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/document_collaborators.da import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -34,6 +35,7 @@ class MobileViewPage extends StatefulWidget { this.fixedTitle, this.showMoreButton = true, this.blockId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); /// view id @@ -43,6 +45,7 @@ class MobileViewPage extends StatefulWidget { final Map? arguments; final bool showMoreButton; final String? blockId; + final List tabs; // only used in row page final String? fixedTitle; @@ -242,6 +245,7 @@ class _MobileViewPageState extends State { view: view, isImmersiveMode: isImmersiveMode, appBarOpacity: _appBarOpacity, + tabs: widget.tabs, ), ]); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart index a228ac0229fef..4121d56c0271c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart @@ -9,6 +9,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -123,11 +124,13 @@ class MobileViewPageLayoutButton extends StatelessWidget { required this.view, required this.isImmersiveMode, required this.appBarOpacity, + required this.tabs, }); final ViewPB view; final bool isImmersiveMode; final ValueListenable appBarOpacity; + final List tabs; @override Widget build(BuildContext context) { @@ -156,6 +159,7 @@ class MobileViewPageLayoutButton extends StatelessWidget { ], child: PageStyleBottomSheet( view: context.read().state.view, + tabs: tabs, ), ), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart index d7ac40d66a66e..fa3494002dbba 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart @@ -8,6 +8,7 @@ import 'package:appflowy/plugins/database/application/cell/cell_controller.dart' import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; @@ -116,6 +117,7 @@ class _OpenRowPageButtonState extends State { addInRecent: false, showMoreButton: false, fixedTitle: fieldName, + tabs: [PickerTabType.emoji.name], ); } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart index ab056d174e4b1..373558a4807e0 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart @@ -1,4 +1,5 @@ import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ class MobileDocumentScreen extends StatelessWidget { this.showMoreButton = true, this.fixedTitle, this.blockId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); /// view id @@ -18,6 +20,7 @@ class MobileDocumentScreen extends StatelessWidget { final bool showMoreButton; final String? fixedTitle; final String? blockId; + final List tabs; static const routeName = '/docs'; static const viewId = 'id'; @@ -25,6 +28,7 @@ class MobileDocumentScreen extends StatelessWidget { static const viewShowMoreButton = 'show_more_button'; static const viewFixedTitle = 'fixed_title'; static const viewBlockId = 'block_id'; + static const viewSelectTabs = 'select_tabs'; @override Widget build(BuildContext context) { @@ -35,6 +39,7 @@ class MobileDocumentScreen extends StatelessWidget { showMoreButton: showMoreButton, fixedTitle: fixedTitle, blockId: blockId, + tabs: tabs, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index e9408660ee632..c2538949ea962 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -2,7 +2,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.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/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -115,9 +119,9 @@ class _DatabaseTabBarState extends State { view: state.tabBars[index].view, isSelected: state.selectedIndex == index, onTap: (selectedView) { - context.read().add( - DatabaseTabBarEvent.selectView(selectedView.id), - ); + context + .read() + .add(DatabaseTabBarEvent.selectView(selectedView.id)); }, ), separatorBuilder: (context, index) => VerticalDivider( @@ -179,7 +183,7 @@ class DatabaseTabBarItem extends StatelessWidget { } } -class TabBarItemButton extends StatelessWidget { +class TabBarItemButton extends StatefulWidget { const TabBarItemButton({ super.key, required this.view, @@ -191,77 +195,148 @@ class TabBarItemButton extends StatelessWidget { final bool isSelected; final VoidCallback onTap; + @override + State createState() => _TabBarItemButtonState(); +} + +class _TabBarItemButtonState extends State { + final menuController = PopoverController(); + final iconController = PopoverController(); + @override Widget build(BuildContext context) { - return PopoverActionList( + Color? color; + if (!widget.isSelected) { + color = Theme.of(context).hintColor; + } + if (Theme.of(context).brightness == Brightness.dark) { + color = null; + } + return AppFlowyPopover( + controller: menuController, + constraints: const BoxConstraints( + minWidth: 120, + maxWidth: 460, + maxHeight: 300, + ), direction: PopoverDirection.bottomWithCenterAligned, - actions: TabBarViewAction.values, - buildChild: (controller) { - Color? color; - if (!isSelected) { - color = Theme.of(context).hintColor; - } - if (Theme.of(context).brightness == Brightness.dark) { - color = null; - } - return IntrinsicWidth( - child: FlowyButton( - radius: Corners.s6Border, - hoverColor: AFThemeExtension.of(context).greyHover, - onTap: onTap, - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - onSecondaryTap: () { - controller.show(); - }, - leftIcon: FlowySvg( - view.iconData, - size: const Size(14, 14), - color: color, - ), - text: FlowyText( - view.nameOrDefault, - lineHeight: 1.0, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - color: color, - fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400, + clickHandler: PopoverClickHandler.gestureDetector, + popupBuilder: (_) { + return IntrinsicHeight( + child: IntrinsicWidth( + child: Column( + children: [ + ActionCellWidget( + action: TabBarViewAction.rename, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + NavigatorTextFieldDialog( + title: LocaleKeys.menuAppHeader_renameDialog.tr(), + value: widget.view.nameOrDefault, + onConfirm: (newValue, _) { + context.read().add( + DatabaseTabBarEvent.renameView( + widget.view.id, + newValue, + ), + ); + }, + ).show(context); + }, + ), + AppFlowyPopover( + controller: iconController, + direction: PopoverDirection.rightWithCenterAligned, + constraints: BoxConstraints.loose(const Size(364, 356)), + margin: const EdgeInsets.all(0), + child: ActionCellWidget( + action: TabBarViewAction.changeIcon, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + iconController.show(); + }, + ), + popupBuilder: (context) { + return FlowyIconEmojiPicker( + tabs: const [PickerTabType.icon], + enableBackgroundColorSelection: false, + onSelectedEmoji: (r) { + ViewBackendService.updateViewIcon( + viewId: widget.view.id, + viewIcon: r.data, + ); + if (!r.keepOpen) { + iconController.close(); + menuController.close(); + } + }, + ); + }, + ), + ActionCellWidget( + action: TabBarViewAction.delete, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + NavigatorAlertDialog( + title: LocaleKeys.grid_deleteView.tr(), + confirm: () { + context.read().add( + DatabaseTabBarEvent.deleteView(widget.view.id), + ); + }, + ).show(context); + }, + ), + ], ), ), ); }, - onSelected: (action, controller) { - switch (action) { - case TabBarViewAction.rename: - NavigatorTextFieldDialog( - title: LocaleKeys.menuAppHeader_renameDialog.tr(), - value: view.nameOrDefault, - onConfirm: (newValue, _) { - context.read().add( - DatabaseTabBarEvent.renameView(view.id, newValue), - ); - }, - ).show(context); - break; - case TabBarViewAction.delete: - NavigatorAlertDialog( - title: LocaleKeys.grid_deleteView.tr(), - confirm: () { - context.read().add( - DatabaseTabBarEvent.deleteView(view.id), - ); - }, - ).show(context); - - break; - } - controller.close(); - }, + child: IntrinsicWidth( + child: FlowyButton( + radius: Corners.s6Border, + hoverColor: AFThemeExtension.of(context).greyHover, + onTap: () { + if (widget.isSelected) menuController.show(); + widget.onTap.call(); + }, + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + onSecondaryTap: () { + menuController.show(); + }, + leftIcon: _buildViewIcon(), + text: FlowyText( + widget.view.nameOrDefault, + lineHeight: 1.0, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + color: color, + fontWeight: widget.isSelected ? FontWeight.w500 : FontWeight.w400, + ), + ), + ), ); } + + Widget _buildViewIcon() { + final iconData = widget.view.icon.toEmojiIconData(); + Widget icon; + if (iconData.isEmpty || iconData.type != FlowyIconType.icon) { + icon = widget.view.defaultIcon(); + } else { + icon = RawEmojiIconWidget( + emoji: iconData, + emojiSize: 14.0, + enableColor: false, + ); + } + return Opacity(opacity: 0.6, child: icon); + } } enum TabBarViewAction implements ActionCell { rename, + changeIcon, delete; @override @@ -269,6 +344,8 @@ enum TabBarViewAction implements ActionCell { switch (this) { case TabBarViewAction.rename: return LocaleKeys.disclosureAction_rename.tr(); + case TabBarViewAction.changeIcon: + return LocaleKeys.disclosureAction_changeIcon.tr(); case TabBarViewAction.delete: return LocaleKeys.disclosureAction_delete.tr(); } @@ -278,6 +355,8 @@ enum TabBarViewAction implements ActionCell { switch (this) { case TabBarViewAction.rename: return const FlowySvg(FlowySvgs.edit_s); + case TabBarViewAction.changeIcon: + return const FlowySvg(FlowySvgs.change_icon_s); case TabBarViewAction.delete: return const FlowySvg(FlowySvgs.delete_s); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart index 3606ed82a3d34..d0ff3b4f19424 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart @@ -1,17 +1,17 @@ -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/database/view/database_view_list.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/setting/mobile_database_controls.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/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class MobileTabBarHeader extends StatelessWidget { @@ -143,9 +143,9 @@ class _DatabaseViewSelectorButton extends StatelessWidget { Widget _buildViewIconButton(BuildContext context, ViewPB view) { return view.icon.value.isNotEmpty - ? EmojiText( - emoji: view.icon.value, - fontSize: 16.0, + ? RawEmojiIconWidget( + emoji: view.icon.toEmojiIconData(), + emojiSize: 16, ) : SizedBox.square( dimension: 16.0, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart index bf7b1c9d634ee..3e5b9d9d95e9c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart @@ -61,10 +61,12 @@ class RawEmojiIconWidget extends StatelessWidget { super.key, required this.emoji, required this.emojiSize, + this.enableColor = true, }); final EmojiIconData emoji; final double emojiSize; + final bool enableColor; @override Widget build(BuildContext context) { @@ -85,7 +87,10 @@ class RawEmojiIconWidget extends StatelessWidget { textAlign: TextAlign.center, ); case FlowyIconType.icon: - final iconData = IconsData.fromJson(jsonDecode(emoji.emoji)); + IconsData iconData = IconsData.fromJson(jsonDecode(emoji.emoji)); + if (!enableColor) { + iconData = iconData.noColor(); + } /// Under the same width conditions, icons on macOS seem to appear /// larger than emojis, so 0.9 is used here to slightly reduce the diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart index e3045a9c02440..7695d36b2fda4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -17,9 +18,11 @@ class PageStyleIcon extends StatefulWidget { const PageStyleIcon({ super.key, required this.view, + required this.tabs, }); final ViewPB view; + final List tabs; @override State createState() => _PageStyleIconState(); @@ -89,6 +92,7 @@ class _PageStyleIconState extends State { child: Expanded( child: FlowyIconEmojiPicker( initialType: icon.type.toPickerTabType(), + tabs: widget.tabs, onSelectedEmoji: (r) { pageStyleIconBloc.add( PageStyleIconEvent.updateIcon(r.data, true), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart index 555881a38f1d2..013a056a7c1e9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -12,9 +13,11 @@ class PageStyleBottomSheet extends StatelessWidget { const PageStyleBottomSheet({ super.key, required this.view, + required this.tabs, }); final ViewPB view; + final List tabs; @override Widget build(BuildContext context) { @@ -50,6 +53,7 @@ class PageStyleBottomSheet extends StatelessWidget { const VSpace(8.0), PageStyleIcon( view: view, + tabs: tabs, ), ], ), diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart index f06e3334770db..48f4d5b21c3c1 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart @@ -165,7 +165,9 @@ class _FlowyIconPickerState extends State { if (value == null) { return; } - final color = generateRandomSpaceColor(); + final color = widget.enableBackgroundColorSelection + ? generateRandomSpaceColor() + : null; widget.onSelectedIcon( IconsData( value.$1.name, @@ -244,6 +246,8 @@ class IconsData { EmojiIconData toEmojiIconData() => EmojiIconData.icon(this); + IconsData noColor() => IconsData(groupName, iconContent, iconName, null); + static IconsData fromJson(dynamic json) { return IconsData( json['groupName'], diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index 6d4fc1684193a..3e7792b3b97c2 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -523,6 +523,21 @@ GoRoute _mobileEditorScreenRoute() { final blockId = state.uri.queryParameters[MobileDocumentScreen.viewBlockId]; + final selectTabs = + state.uri.queryParameters[MobileDocumentScreen.viewSelectTabs] ?? ''; + List tabs = []; + try { + tabs = selectTabs + .split('-') + .map((e) => PickerTabType.values.byName(e)) + .toList(); + } on ArgumentError catch (e) { + Log.error('convert selectTabs to pickerTab error', e); + } + if (tabs.isEmpty) { + tabs = const [PickerTabType.emoji, PickerTabType.icon]; + } + return MaterialExtendedPage( child: MobileDocumentScreen( id: id, @@ -530,6 +545,7 @@ GoRoute _mobileEditorScreenRoute() { showMoreButton: showMoreButton ?? true, fixedTitle: fixedTitle, blockId: blockId, + tabs: tabs, ), ); },