diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart index f6d787c8fb..4011d682e4 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart @@ -21,7 +21,7 @@ void main() { // create a new document await tester.createNewPageWithName( - name: LocaleKeys.document_plugins_createInlineMathEquation.tr(), + name: 'math equation', layout: ViewLayoutPB.Document, ); @@ -67,7 +67,7 @@ void main() { // create a new document await tester.createNewPageWithName( - name: LocaleKeys.document_plugins_createInlineMathEquation.tr(), + name: 'math equation', layout: ViewLayoutPB.Document, ); diff --git a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart index 292910b6f4..2ced391ccf 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart @@ -1,5 +1,7 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/plugins/base/icon/icon_picker.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -22,8 +24,8 @@ class _IconPickerPageState extends State { return Scaffold( appBar: AppBar( titleSpacing: 0, - title: const FlowyText.semibold( - 'Page icon', + title: FlowyText.semibold( + LocaleKeys.titleBar_pageIcon.tr(), fontSize: 14.0, ), leading: AppBarBackButton( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart index 74c83cfdaf..1811d869ce 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -155,6 +155,10 @@ Map getEditorBuilderMap({ configuration: configuration.copyWith( placeholderTextStyle: (_) => styleCustomizer.outlineBlockPlaceholderStyleBuilder(), + padding: (_) => const EdgeInsets.only( + top: 12.0, + bottom: 4.0, + ), ), ), errorBlockComponentBuilderKey: ErrorBlockComponentBuilder( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 3148c5164c..c1274193c9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -251,7 +251,14 @@ class _AppFlowyEditorPageState extends State { contextMenuItems: customContextMenuItems, // customize the header and footer. header: widget.header, - footer: VSpace(PlatformExtension.isDesktopOrWeb ? 200 : 400), + footer: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () async { + // if the last one isn't a empty node, insert a new empty node. + await _ensureLastNodeIsEmptyParagraph(); + }, + child: VSpace(PlatformExtension.isDesktopOrWeb ? 200 : 400), + ), ), ); @@ -474,4 +481,20 @@ class _AppFlowyEditorPageState extends State { void _initEditorL10n() { AppFlowyEditorL10n.current = EditorI18n(); } + + Future _ensureLastNodeIsEmptyParagraph() async { + final editorState = widget.editorState; + final root = editorState.document.root; + final lastNode = root.children.lastOrNull; + if (lastNode == null || + lastNode.delta?.isEmpty == false || + lastNode.type != ParagraphBlockKeys.type) { + final transaction = editorState.transaction; + transaction.insertNode([root.children.length], paragraphNode()); + transaction.afterSelection = Selection.collapsed( + Position(path: [root.children.length]), + ); + await editorState.apply(transaction); + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart index e1b12d05e2..081d87b1c1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart @@ -1,18 +1,78 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:highlight/highlight.dart' as highlight; import 'package:highlight/languages/all.dart'; import 'package:provider/provider.dart'; import 'code_block_themes.dart'; +final supportedLanguages = [ + 'Assembly', + 'Bash', + 'BASIC', + 'C', + 'C#', + 'CPP', + 'Clojure', + 'CS', + 'CSS', + 'Dart', + 'Docker', + 'Elixir', + 'Elm', + 'Erlang', + 'Fortran', + 'Go', + 'GraphQL', + 'Haskell', + 'HTML', + 'Java', + 'JavaScript', + 'JSON', + 'Kotlin', + 'LaTeX', + 'Lisp', + 'Lua', + 'Markdown', + 'MATLAB', + 'Objective-C', + 'OCaml', + 'Perl', + 'PHP', + 'PowerShell', + 'Python', + 'R', + 'Ruby', + 'Rust', + 'Scala', + 'Shell', + 'SQL', + 'Swift', + 'TypeScript', + 'Visual Basic', + 'XML', + 'YAML', +]; + +final codeBlockSupportedLanguages = supportedLanguages + .map((e) => e.toLowerCase()) + .toSet() + .intersection(allLanguages.keys.toSet()) + .toList() + ..add('auto') + ..add('c') + ..sort(); + class CodeBlockKeys { const CodeBlockKeys._(); @@ -123,62 +183,6 @@ class _CodeBlockComponentWidgetState extends State final popoverController = PopoverController(); - final supportedLanguages = [ - 'Assembly', - 'Bash', - 'BASIC', - 'C', - 'C#', - 'CPP', - 'Clojure', - 'CS', - 'CSS', - 'Dart', - 'Docker', - 'Elixir', - 'Elm', - 'Erlang', - 'Fortran', - 'Go', - 'GraphQL', - 'Haskell', - 'HTML', - 'Java', - 'JavaScript', - 'JSON', - 'Kotlin', - 'LaTeX', - 'Lisp', - 'Lua', - 'Markdown', - 'MATLAB', - 'Objective-C', - 'OCaml', - 'Perl', - 'PHP', - 'PowerShell', - 'Python', - 'R', - 'Ruby', - 'Rust', - 'Scala', - 'Shell', - 'SQL', - 'Swift', - 'TypeScript', - 'Visual Basic', - 'XML', - 'YAML', - ]; - late final languages = supportedLanguages - .map((e) => e.toLowerCase()) - .toSet() - .intersection(allLanguages.keys.toSet()) - .toList() - ..add('auto') - ..add('c') - ..sort(); - @override late final editorState = context.read(); @@ -224,10 +228,19 @@ class _CodeBlockComponentWidgetState extends State child: child, ); - if (widget.showActions && widget.actionBuilder != null) { - child = BlockComponentActionWrapper( - node: widget.node, - actionBuilder: widget.actionBuilder!, + if (PlatformExtension.isDesktopOrWeb) { + if (widget.showActions && widget.actionBuilder != null) { + child = BlockComponentActionWrapper( + node: widget.node, + actionBuilder: widget.actionBuilder!, + child: child, + ); + } + } else { + // show a fixed menu on mobile + child = MobileBlockActionButtons( + node: node, + editorState: editorState, child: child, ); } @@ -277,36 +290,57 @@ class _CodeBlockComponentWidgetState extends State Widget _buildSwitchLanguageButton(BuildContext context) { const maxWidth = 100.0; - return AppFlowyPopover( - controller: popoverController, - child: Container( - width: maxWidth, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 4), - child: FlowyTextButton( - '${language?.capitalize() ?? 'Auto'} ', - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 4.0, - ), - constraints: const BoxConstraints(maxWidth: maxWidth), - fontColor: Theme.of(context).colorScheme.onBackground, - fillColor: Colors.transparent, - mainAxisAlignment: MainAxisAlignment.start, - onPressed: () {}, - ), + + Widget child = Container( + width: maxWidth, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only( + top: 4.0, + left: 4.0, + bottom: 12.0, + ), + child: FlowyTextButton( + '${language?.capitalize() ?? 'Auto'} ', + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 6.0, + ), + constraints: const BoxConstraints(maxWidth: maxWidth), + fontColor: Theme.of(context).colorScheme.onBackground, + fillColor: Colors.transparent, + mainAxisAlignment: MainAxisAlignment.start, + onPressed: () async { + if (PlatformExtension.isMobile) { + final language = await context.push( + MobileCodeLanguagePickerScreen.routeName, + ); + if (language != null) { + updateLanguage(language); + } + } + }, ), - popupBuilder: (BuildContext context) { - return SelectableItemListMenu( - items: languages.map((e) => e.capitalize()).toList(), - selectedIndex: languages.indexOf(language ?? ''), - onSelected: (index) { - updateLanguage(languages[index]); - popoverController.close(); - }, - ); - }, ); + + if (PlatformExtension.isDesktopOrWeb) { + child = AppFlowyPopover( + controller: popoverController, + child: child, + popupBuilder: (BuildContext context) { + return SelectableItemListMenu( + items: + codeBlockSupportedLanguages.map((e) => e.capitalize()).toList(), + selectedIndex: codeBlockSupportedLanguages.indexOf(language ?? ''), + onSelected: (index) { + updateLanguage(codeBlockSupportedLanguages[index]); + popoverController.close(); + }, + ); + }, + ); + } + + return child; } Future updateLanguage(String language) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart new file mode 100644 index 0000000000..9e9832e10b --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart @@ -0,0 +1,49 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class MobileCodeLanguagePickerScreen extends StatelessWidget { + static const routeName = '/code_language_picker'; + + const MobileCodeLanguagePickerScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: FlowyText.semibold( + LocaleKeys.titleBar_language.tr(), + fontSize: 14.0, + ), + leading: AppBarBackButton( + onTap: () => context.pop(), + ), + ), + body: SafeArea( + child: ListView.separated( + itemBuilder: (context, index) { + final language = codeBlockSupportedLanguages[index]; + return FlowyTextButton( + language.capitalize(), + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 4.0, + ), + onPressed: () => context.pop(language), + ); + }, + separatorBuilder: (_, __) => const Divider(), + itemCount: codeBlockSupportedLanguages.length, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart index e18e74de6b..eba62b8734 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart @@ -418,48 +418,59 @@ class DocumentCoverState extends State { Positioned( bottom: 8, right: 12, - child: RoundedTextButton( - onPressed: () { - showFlowyMobileBottomSheet( - context, - title: LocaleKeys.document_plugins_cover_changeCover.tr(), - builder: (context) { - return ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: 340, - minHeight: 80, - ), - child: UploadImageMenu( - supportTypes: const [ - UploadImageType.color, - UploadImageType.local, - UploadImageType.url, - UploadImageType.unsplash, - ], - onSelectedLocalImage: (path) async { - context.pop(); - widget.onCoverChanged(CoverType.file, path); - }, - onSelectedAIImage: (_) { - throw UnimplementedError(); - }, - onSelectedNetworkImage: (url) async { - context.pop(); - widget.onCoverChanged(CoverType.file, url); - }, - onSelectedColor: (color) { - context.pop(); - widget.onCoverChanged(CoverType.color, color); - }, - ), + child: Row( + children: [ + RoundedTextButton( + onPressed: () { + showFlowyMobileBottomSheet( + context, + title: LocaleKeys.document_plugins_cover_changeCover.tr(), + builder: (context) { + return ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 340, + minHeight: 80, + ), + child: UploadImageMenu( + supportTypes: const [ + UploadImageType.color, + UploadImageType.local, + UploadImageType.url, + UploadImageType.unsplash, + ], + onSelectedLocalImage: (path) async { + context.pop(); + widget.onCoverChanged(CoverType.file, path); + }, + onSelectedAIImage: (_) { + throw UnimplementedError(); + }, + onSelectedNetworkImage: (url) async { + context.pop(); + widget.onCoverChanged(CoverType.file, url); + }, + onSelectedColor: (color) { + context.pop(); + widget.onCoverChanged(CoverType.color, color); + }, + ), + ); + }, ); }, - ); - }, - fillColor: Theme.of(context).colorScheme.onSurfaceVariant, - width: 120, - height: 32, - title: LocaleKeys.document_plugins_cover_changeCover.tr(), + fillColor: Theme.of(context).colorScheme.onSurfaceVariant, + width: 120, + height: 32, + title: LocaleKeys.document_plugins_cover_changeCover.tr(), + ), + const HSpace(8.0), + SizedBox.square( + dimension: 32.0, + child: DeleteCoverButton( + onTap: () => widget.onCoverChanged(CoverType.none, null), + ), + ), + ], ), ), ], @@ -564,7 +575,7 @@ class DeleteCoverButton extends StatelessWidget { Widget build(BuildContext context) { return FlowyIconButton( hoverColor: Theme.of(context).colorScheme.surface, - fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.5), + fillColor: Theme.of(context).colorScheme.onSurfaceVariant, iconPadding: const EdgeInsets.all(5), width: 28, icon: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart index 8cece8ec97..005af9f8b1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart @@ -79,6 +79,26 @@ class _MobileListMenu extends StatelessWidget { const Icon(Icons.note_rounded), LocaleKeys.document_plugins_callout.tr(), ), + _buildListButton( + context, + CodeBlockKeys.type, + const Icon(Icons.abc), + LocaleKeys.document_selectionMenu_codeBlock.tr(), + ), + // code block + _buildListButton( + context, + CodeBlockKeys.type, + const Icon(Icons.abc), + LocaleKeys.document_selectionMenu_codeBlock.tr(), + ), + // outline + _buildListButton( + context, + OutlineBlockKeys.type, + const Icon(Icons.list_alt), + LocaleKeys.document_selectionMenu_outline.tr(), + ), ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart index 4b36b6b2be..5d6403d708 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -88,14 +89,25 @@ class _OutlineBlockWidgetState extends State return StreamBuilder( stream: stream, builder: (context, snapshot) { - if (widget.showActions && widget.actionBuilder != null) { - return BlockComponentActionWrapper( - node: widget.node, - actionBuilder: widget.actionBuilder!, - child: _buildOutlineBlock(), + Widget child = _buildOutlineBlock(); + + if (PlatformExtension.isDesktopOrWeb) { + if (widget.showActions && widget.actionBuilder != null) { + child = BlockComponentActionWrapper( + node: widget.node, + actionBuilder: widget.actionBuilder!, + child: child, + ); + } + } else { + child = MobileBlockActionButtons( + node: node, + editorState: editorState, + child: child, ); } - return _buildOutlineBlock(); + + return child; }, ); } @@ -119,33 +131,47 @@ class _OutlineBlockWidgetState extends State ), ) .toList(); - if (children.isEmpty) { - return Align( - alignment: Alignment.centerLeft, - child: Text( - LocaleKeys.document_plugins_outline_addHeadingToCreateOutline.tr(), - style: configuration.placeholderTextStyle(node), - ), - ); - } - return DecoratedBox( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(8.0)), - color: backgroundColor, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - textDirection: textDirection, - children: children, + + final child = children.isEmpty + ? Align( + alignment: Alignment.centerLeft, + child: Text( + LocaleKeys.document_plugins_outline_addHeadingToCreateOutline + .tr(), + style: configuration.placeholderTextStyle(node), + ), + ) + : DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + color: backgroundColor, + ), + child: Column( + key: ValueKey(children.hashCode), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + textDirection: textDirection, + children: children, + ), + ); + + return Container( + constraints: const BoxConstraints( + minHeight: 40.0, ), + padding: padding, + child: child, ); } Iterable getHeadingNodes() { final children = editorState.document.root.children; - return children.where((element) => element.type == HeadingBlockKeys.type); + return children.where( + (element) => + element.type == HeadingBlockKeys.type && + element.delta?.isNotEmpty == true, + ); } } @@ -172,6 +198,7 @@ class OutlineItemWidget extends StatelessWidget { ), builder: (context, onHover) { return GestureDetector( + behavior: HitTestBehavior.translucent, onTap: () => scrollToBlock(context), child: Row( textDirection: textDirection, @@ -196,12 +223,13 @@ class OutlineItemWidget extends StatelessWidget { void scrollToBlock(BuildContext context) { final editorState = context.read(); final editorScrollController = context.read(); - editorScrollController.itemScrollController.jumpTo(index: node.path.first); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - editorState.selection = Selection.collapsed( - Position(path: node.path, offset: node.delta?.length ?? 0), - ); - }); + editorScrollController.itemScrollController.jumpTo( + index: node.path.first, + alignment: 0.5, + ); + editorState.selection = Selection.collapsed( + Position(path: node.path, offset: node.delta?.length ?? 0), + ); } } diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index ea27237129..7ccf7ffb1d 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart'; import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; @@ -53,6 +54,9 @@ GoRouter generateRouter(Widget child) { // emoji picker _mobileEmojiPickerPageRoute(), _mobileImagePickerPageRoute(), + + // code language picker + _mobileCodeLanguagePickerPageRoute(), ], // Desktop and Mobile @@ -230,6 +234,18 @@ GoRoute _mobileImagePickerPageRoute() { ); } +GoRoute _mobileCodeLanguagePickerPageRoute() { + return GoRoute( + parentNavigatorKey: AppGlobals.rootNavKey, + path: MobileCodeLanguagePickerScreen.routeName, + pageBuilder: (context, state) { + return const MaterialPage( + child: MobileCodeLanguagePickerScreen(), + ); + }, + ); +} + GoRoute _desktopHomeScreenRoute() { return GoRoute( path: DesktopHomeScreen.routeName, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart index 7d07f490f8..4316d46454 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart @@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -51,7 +52,7 @@ class _ViewTitleBarState extends State { if (ancestors == null) { return const SizedBox.shrink(); } - const maxWidth = WindowSizeManager.minWindowWidth - 100; + const maxWidth = WindowSizeManager.minWindowWidth - 200; final replacement = Row( // refresh the view title bar when the ancestors changed key: ValueKey(ancestors.hashCode), @@ -64,6 +65,7 @@ class _ViewTitleBarState extends State { replacement: replacement, // if the width is too small, only show one view title bar without the ancestors child: _ViewTitle( + key: ValueKey(ancestors.last), view: ancestors.last, behavior: _ViewTitleBehavior.editable, maxTitleWidth: constraints.maxWidth - 50.0, @@ -123,6 +125,7 @@ enum _ViewTitleBehavior { class _ViewTitle extends StatefulWidget { const _ViewTitle({ + super.key, required this.view, this.behavior = _ViewTitleBehavior.editable, this.maxTitleWidth = 180, @@ -187,23 +190,26 @@ class _ViewTitleState extends State<_ViewTitle> { ); } - final child = Row( - children: [ - FlowyText.regular( - icon, - fontSize: 18.0, - ), - const HSpace(2.0), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: widget.maxTitleWidth, + final child = FlowyTooltip( + message: name, + child: Row( + children: [ + FlowyText.regular( + icon, + fontSize: 18.0, ), - child: FlowyText.regular( - name, - overflow: TextOverflow.ellipsis, + const HSpace(2.0), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: widget.maxTitleWidth, + ), + child: FlowyText.regular( + name, + overflow: TextOverflow.ellipsis, + ), ), - ), - ], + ], + ), ); if (widget.behavior == _ViewTitleBehavior.uneditable) { diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index a82f199984..99c026955f 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1061,5 +1061,9 @@ }, "cardDetails": { "notesPlaceholder": "Enter a / to insert a block, or start typing" + }, + "titleBar": { + "pageIcon": "Page icon", + "language": "Language" } } \ No newline at end of file diff --git a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml index d7c9ef4805..cb32d94798 100644 --- a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml +++ b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml @@ -48,6 +48,7 @@ AppDir: include: - libc6:amd64 - libnotify4:amd64 + - libkeybinder-3.0-0:amd64 files: include: [] exclude: