diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart new file mode 100644 index 0000000000000..1b94c654368e6 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart @@ -0,0 +1,65 @@ +import 'dart:io'; + +import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart'; +import 'package:appflowy/mobile/presentation/presentation.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.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +import '../../shared/emoji.dart'; +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('document title:', () { + testWidgets('update page custom icon in title bar', (tester) async { + await tester.launchInAnonymousMode(); + + /// prepare local image + final imagePath = await rootBundle.load('assets/test/images/sample.jpeg'); + final tempDirectory = await getTemporaryDirectory(); + final localImagePath = p.join(tempDirectory.path, 'sample.jpeg'); + final imageFile = File(localImagePath) + ..writeAsBytesSync(imagePath.buffer.asUint8List()); + final iconData = EmojiIconData.custom(imageFile.path); + + /// create an empty page + await tester + .tapButton(find.byKey(BottomNavigationBarItemType.add.valueKey)); + + /// show Page style page + await tester.tapButton(find.byType(MobileViewPageLayoutButton)); + final pageStyleIcon = find.byType(PageStyleIcon); + final iconInPageStyleIcon = find.descendant( + of: pageStyleIcon, + matching: find.byType(RawEmojiIconWidget), + ); + expect(iconInPageStyleIcon, findsNothing); + + /// show icon picker + await tester.tapButton(pageStyleIcon); + + /// upload custom icon + await tester.pickImage(iconData); + + /// check result + final documentPage = find.byType(MobileDocumentScreen); + final rawEmojiIconWidget = find + .descendant( + of: documentPage, + matching: find.byType(RawEmojiIconWidget), + ) + .evaluate() + .first + .widget as RawEmojiIconWidget; + final iconDataInWidget = rawEmojiIconWidget.emoji; + expect(iconDataInWidget.type, FlowyIconType.custom); + }); + }); +} diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart index 8fc32ab5dbbdd..aa02495a49526 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.join('-'); + } 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..0535ec76b80c2 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; @@ -193,6 +196,7 @@ class _MobileViewPageState extends State { data: { MobileDocumentScreen.viewFixedTitle: widget.fixedTitle, MobileDocumentScreen.viewBlockId: widget.blockId, + MobileDocumentScreen.viewSelectTabs: widget.tabs, }, ); }, @@ -242,6 +246,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..b01f72c37161a 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,9 +124,11 @@ class MobileViewPageLayoutButton extends StatelessWidget { required this.view, required this.isImmersiveMode, required this.appBarOpacity, + required this.tabs, }); final ViewPB view; + final List tabs; final bool isImmersiveMode; final ValueListenable appBarOpacity; @@ -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/mobile/presentation/home/section_folder/mobile_home_section_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart index d610b4452b9d7..0079ed319a3a8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart @@ -3,6 +3,7 @@ import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -101,7 +102,14 @@ class _Pages extends StatelessWidget { level: 0, leftPadding: HomeSpaceViewSizes.leftPadding, isFeedback: false, - onSelected: context.pushView, + onSelected: (v) => context.pushView( + v, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), endActionPane: (context) { final view = context.read().state.view; return buildEndActionPane( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart index e589ce7cd2c32..638e20839e7e3 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart @@ -11,6 +11,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emo import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; @@ -81,7 +82,14 @@ class MobileViewPage extends StatelessWidget { spaceRatio: 4, ), child: AnimatedGestureDetector( - onTapUp: () => context.pushView(view), + onTapUp: () => context.pushView( + view, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -140,7 +148,8 @@ class MobileViewPage extends StatelessWidget { final iconUrl = userProfile?.iconUrl; if (iconUrl == null || iconUrl.isEmpty || - view.createdBy != userProfile?.id) { + view.createdBy != userProfile?.id || + !isURL(iconUrl)) { return const SizedBox.shrink(); } @@ -182,7 +191,7 @@ class MobileViewPage extends StatelessWidget { WidgetSpan( child: SizedBox( width: 20, - child: EmojiIconWidget( + child: RawEmojiIconWidget( emoji: icon, emojiSize: 18.0, ), @@ -206,7 +215,7 @@ class MobileViewPage extends StatelessWidget { Widget _buildAuthor(BuildContext context, RecentViewState state) { return FlowyText.regular( // view.createdBy.toString(), - 'Lucas', + '', fontSize: 12.0, color: Theme.of(context).hintColor, overflow: TextOverflow.ellipsis, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart index a6545ce0538f8..e0dd520b2de01 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/shared/list_extension.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; @@ -151,7 +152,14 @@ class _Pages extends StatelessWidget { level: 0, leftPadding: HomeSpaceViewSizes.leftPadding, isFeedback: false, - onSelected: context.pushView, + onSelected: (v) => context.pushView( + v, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), endActionPane: (context) { final view = context.read().state.view; final actions = [ diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart index 9d78e39645a37..1a19845152669 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart @@ -6,6 +6,7 @@ import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; @@ -82,7 +83,14 @@ class _MobileSpaceTabState extends State listener: (context, state) { final lastCreatedPage = state.lastCreatedRootView; if (lastCreatedPage != null) { - context.pushView(lastCreatedPage); + context.pushView( + lastCreatedPage, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ); } }, ), diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart index e97b33df49e68..47f257d174b00 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart @@ -36,6 +36,7 @@ class MobileEmojiPickerScreen extends StatelessWidget { body: SafeArea( child: FlowyIconEmojiPicker( tabs: tabs, + documentId: documentId, initialType: selectedType, onSelectedEmoji: (r) { context.pop(r.data); diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart index dcfc1906a373e..2f2c15f52a152 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart @@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/document_collaborators.da import 'package:appflowy/plugins/shared/share/share_button.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/shared/feature_flags.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; @@ -107,6 +108,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder final ViewInfoBloc bloc; final ViewPluginNotifier notifier; + ViewPB get view => notifier.view; int? deletedViewIndex; final Selection? initialSelection; @@ -130,6 +132,11 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder final fixedTitle = data?[MobileDocumentScreen.viewFixedTitle]; final blockId = initialBlockId ?? data?[MobileDocumentScreen.viewBlockId]; + final tabs = data?[MobileDocumentScreen.viewSelectTabs] ?? + const [ + PickerTabType.emoji, + PickerTabType.icon, + ]; return BlocProvider.value( value: bloc, @@ -141,6 +148,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder initialSelection: initialSelection, initialBlockId: blockId, fixedTitle: fixedTitle, + tabs: tabs, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart index 22080a94fc20b..afa16fac1a187 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart @@ -10,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_con import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; @@ -28,6 +29,7 @@ class DocumentPage extends StatefulWidget { super.key, required this.view, required this.onDeleted, + required this.tabs, this.initialSelection, this.initialBlockId, this.fixedTitle, @@ -38,6 +40,7 @@ class DocumentPage extends StatefulWidget { final Selection? initialSelection; final String? initialBlockId; final String? fixedTitle; + final List tabs; @override State createState() => _DocumentPageState(); @@ -208,6 +211,7 @@ class _DocumentPageState extends State return DocumentImmersiveCover( fixedTitle: widget.fixedTitle, view: widget.view, + tabs: widget.tabs, userProfilePB: userProfilePB, ); } @@ -215,6 +219,7 @@ class _DocumentPageState extends State final page = editorState.document.root; return DocumentCoverWidget( node: page, + tabs: widget.tabs, editorState: editorState, view: widget.view, onIconChanged: (icon) async => ViewBackendService.updateViewIcon( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart index 7485bfe0182d7..96d39d7500dbb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart @@ -11,6 +11,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; import 'package:appflowy/shared/google_fonts_extension.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -36,12 +37,14 @@ class DocumentImmersiveCover extends StatefulWidget { super.key, required this.view, required this.userProfilePB, + required this.tabs, this.fixedTitle, }); final ViewPB view; final UserProfilePB userProfilePB; final String? fixedTitle; + final List tabs; @override State createState() => _DocumentImmersiveCoverState(); @@ -228,6 +231,8 @@ class _DocumentImmersiveCoverState extends State { child: Expanded( child: FlowyIconEmojiPicker( initialType: icon.type.toPickerTabType(), + tabs: widget.tabs, + documentId: widget.view.id, onSelectedEmoji: (r) { pageStyleIconBloc.add( PageStyleIconEvent.updateIcon(r.data, true), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart index 9c0577bb9338c..a565fd4e43d6c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart @@ -74,12 +74,14 @@ class DocumentCoverWidget extends StatefulWidget { required this.editorState, required this.onIconChanged, required this.view, + required this.tabs, }); final Node node; final EditorState editorState; final ValueChanged onIconChanged; final ViewPB view; + final List tabs; @override State createState() => _DocumentCoverWidgetState(); @@ -172,6 +174,7 @@ class _DocumentCoverWidgetState extends State { offset: offset, isCoverTitleHovered: isCoverTitleHovered, documentId: view.id, + tabs: widget.tabs, ), ), if (hasCover) @@ -344,6 +347,7 @@ class DocumentHeaderToolbar extends StatefulWidget { required this.offset, this.documentId, required this.isCoverTitleHovered, + required this.tabs, }); final Node node; @@ -355,6 +359,7 @@ class DocumentHeaderToolbar extends StatefulWidget { final double offset; final String? documentId; final ValueNotifier isCoverTitleHovered; + final List tabs; @override State createState() => _DocumentHeaderToolbarState(); @@ -473,11 +478,7 @@ class _DocumentHeaderToolbarState extends State { popupBuilder: (BuildContext popoverContext) { isPopoverOpen = true; return FlowyIconEmojiPicker( - tabs: const [ - PickerTabType.emoji, - PickerTabType.icon, - PickerTabType.custom, - ], + tabs: widget.tabs, documentId: widget.documentId, onSelectedEmoji: (r) { widget.onIconOrCoverChanged(icon: r.data); 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 c15ab33351368..5d7d095272f69 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 @@ -4,10 +4,9 @@ import 'dart:io'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; -import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:string_validator/string_validator.dart'; import '../../../../../shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; @@ -102,17 +101,25 @@ class RawEmojiIconWidget extends StatelessWidget { case FlowyIconType.custom: final url = emoji.emoji; if (isURL(url)) { - final userProfilePB = - context.read()?.userProfile; return SizedBox.square( dimension: emojiSize, - child: FlowyNetworkImage( - url: url, - width: emojiSize, - height: emojiSize, - userProfilePB: userProfilePB, - errorWidgetBuilder: (context, url, error) => - const SizedBox.shrink(), + child: FutureBuilder( + future: UserBackendService.getCurrentUserProfile(), + builder: (context, value) { + final userProfile = value.data?.fold( + (userProfile) => userProfile, + (l) => null, + ); + if (userProfile == null) return const SizedBox.shrink(); + return FlowyNetworkImage( + url: url, + width: emojiSize, + height: emojiSize, + userProfilePB: userProfile, + errorWidgetBuilder: (context, url, error) => + const SizedBox.shrink(), + ); + }, ), ); } 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..a71b083c817f8 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(); @@ -33,9 +36,9 @@ class _PageStyleIconState extends State { ..add(const PageStyleIconEvent.initial()), child: BlocBuilder( builder: (context, state) { - final icon = state.icon ?? EmojiIconData.none(); + final icon = state.icon; return GestureDetector( - onTap: () => _showIconSelector(context, icon), + onTap: () => icon == null ? null : _showIconSelector(context, icon), behavior: HitTestBehavior.opaque, child: Container( height: 52, @@ -48,14 +51,14 @@ class _PageStyleIconState extends State { const HSpace(16.0), FlowyText(LocaleKeys.document_plugins_emoji.tr()), const Spacer(), - icon.isEmpty + (icon?.isEmpty ?? true) ? FlowyText( LocaleKeys.pageStyle_none.tr(), fontSize: 16.0, ) : RawEmojiIconWidget( - emoji: icon, - emojiSize: 22.0, + emoji: icon!, + emojiSize: 16.0, ), const HSpace(6.0), const FlowySvg(FlowySvgs.m_page_style_arrow_right_s), @@ -89,6 +92,8 @@ class _PageStyleIconState extends State { child: Expanded( child: FlowyIconEmojiPicker( initialType: icon.type.toPickerTabType(), + documentId: widget.view.id, + 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_icon_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart index a930c8d87e2bf..2578b6de070d3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart @@ -34,11 +34,7 @@ class PageStyleIconBloc extends Bloc { ); }, updateIcon: (icon, shouldUpdateRemote) async { - emit( - state.copyWith( - icon: icon, - ), - ); + emit(state.copyWith(icon: icon)); if (shouldUpdateRemote && icon != null) { await ViewBackendService.updateViewIcon( viewId: view.id, 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/flowy_icon_emoji_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart index 08d19e87c4358..f089d8b56a2ae 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart @@ -144,6 +144,12 @@ class _FlowyIconEmojiPickerState extends State length: widget.tabs.length, vsync: this, ); + controller.addListener(() { + final currentType = widget.tabs[currentIndex]; + if (currentType == PickerTabType.custom) { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + } + }); } @override diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index c60a680a3df9b..3e5b354fb2dd6 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -530,6 +530,20 @@ 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, @@ -537,6 +551,7 @@ GoRoute _mobileEditorScreenRoute() { showMoreButton: showMoreButton ?? true, fixedTitle: fixedTitle, blockId: blockId, + tabs: tabs, ), ); },