From 6283649a6b51e3c61dd72b00611470269a74ab11 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 15 Aug 2024 20:12:09 +0800 Subject: [PATCH] feat: open the row page on mobile (#5975) * chore: add dart dependency validator * feat: open the row page on mobile * Revert "chore: add dart dependency validator" This reverts commit c81e5ef0ed7b0f1e74d6ba499722a9e2b566862f. * chore: update translations * feat: preload row page to reduce open time * chore: don't add orphan doc into recent records * fix: bloc error * fix: migrate the row page title to latest design * chore: optimize database mobile UI --- .../lib/mobile/application/mobile_router.dart | 11 +- .../presentation/base/app_bar/app_bar.dart | 2 +- .../base/app_bar/app_bar_actions.dart | 25 +++ .../presentation/base/mobile_view_page.dart | 16 +- .../bottom_sheet_rename_widget.dart | 1 + .../mobile_card_detail_screen.dart | 4 + .../card_detail/widgets/row_page_button.dart | 116 +++++++++++++ .../calculations/calculations_bloc.dart | 16 +- .../presentation/database_document_title.dart | 162 ++++-------------- .../document_collaborators_bloc.dart | 4 + .../lib/startup/tasks/app_widget.dart | 9 +- .../presentation/widgets/view_title_bar.dart | 23 +-- frontend/resources/translations/en.json | 3 +- 13 files changed, 229 insertions(+), 163 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart index 8b9f1e70ff..7fc1f0824b 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart @@ -2,20 +2,23 @@ import 'dart:async'; import 'dart:convert'; import 'package:appflowy/mobile/presentation/chat/mobile_chat_screen.dart'; -import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/mobile/presentation/database/board/mobile_board_screen.dart'; import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart'; import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/recent/cached_recent_service.dart'; +import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; extension MobileRouter on BuildContext { - Future pushView(ViewPB view, [Map? arguments]) async { + Future pushView( + ViewPB view, { + Map? arguments, + bool addInRecent = true, + }) async { // set the current view before pushing the new view getIt().latestOpenView = view; unawaited(getIt().updateRecentViews([view.id], true)); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart index 335f1af489..396ecd6bb8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart @@ -10,7 +10,7 @@ enum FlowyAppBarLeadingType { Widget getWidget(VoidCallback? onTap) { switch (this) { case FlowyAppBarLeadingType.back: - return AppBarBackButton(onTap: onTap); + return AppBarImmersiveBackButton(onTap: onTap); case FlowyAppBarLeadingType.close: return AppBarCloseButton(onTap: onTap); case FlowyAppBarLeadingType.cancel: diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart index b59c1e68cc..72142d446b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart @@ -26,6 +26,31 @@ class AppBarBackButton extends StatelessWidget { } } +class AppBarImmersiveBackButton extends StatelessWidget { + const AppBarImmersiveBackButton({ + super.key, + this.onTap, + }); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return AppBarButton( + onTap: (_) => (onTap ?? () => Navigator.pop(context)).call(), + padding: const EdgeInsets.only( + left: 12.0, + top: 8.0, + bottom: 8.0, + right: 4.0, + ), + child: const FlowySvg( + FlowySvgs.m_app_bar_back_s, + ), + ); + } +} + class AppBarCloseButton extends StatelessWidget { const AppBarCloseButton({ super.key, 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 603005fc38..55d67ad6af 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 @@ -4,7 +4,6 @@ import 'package:appflowy/mobile/application/page_style/document_page_style_bloc. import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/document/presentation/document_collaborators.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; @@ -232,19 +231,20 @@ class _MobileViewPageState extends State { return Row( mainAxisSize: MainAxisSize.min, children: [ - if (icon != null && icon.isNotEmpty) - ConstrainedBox( - constraints: const BoxConstraints.tightFor(width: 34.0), - child: EmojiText( - emoji: '$icon ', - fontSize: 22.0, - ), + if (icon != null && icon.isNotEmpty) ...[ + FlowyText.emoji( + icon, + fontSize: 15.0, + figmaLineHeight: 18.0, ), + const HSpace(4), + ], Expanded( child: FlowyText.medium( view?.name ?? widget.title ?? '', fontSize: 15.0, overflow: TextOverflow.ellipsis, + figmaLineHeight: 18.0, ), ), ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart index e61f27b6b2..cabc234fec 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart @@ -52,6 +52,7 @@ class _MobileBottomSheetRenameWidgetState height: 42.0, child: FlowyTextField( controller: controller, + textStyle: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.text, onSubmitted: (text) => widget.onRename(text), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index c65f899c34..17c97b1f85 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; @@ -366,6 +367,9 @@ class MobileRowDetailPageContentState if (rowDetailState.numHiddenFields != 0) ...[ const ToggleHiddenFieldsVisibilityButton(), ], + OpenRowPageButton( + documentId: rowController.rowMeta.documentId, + ), MobileRowDetailCreateFieldButton( viewId: viewId, fieldController: fieldController, 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 new file mode 100644 index 0000000000..c90205e85a --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart @@ -0,0 +1,116 @@ +import 'dart:async'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/application/mobile_router.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/workspace/application/view/prelude.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/log.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'; +import 'package:flutter/material.dart'; + +class OpenRowPageButton extends StatefulWidget { + const OpenRowPageButton({ + super.key, + required this.documentId, + }); + + final String documentId; + + @override + State createState() => _OpenRowPageButtonState(); +} + +class _OpenRowPageButtonState extends State { + ViewPB? view; + + @override + void initState() { + super.initState(); + + _preloadView(context, createDocumentIfMissed: true); + } + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, + minHeight: GridSize.headerHeight, + ), + child: TextButton.icon( + style: Theme.of(context).textButtonTheme.style?.copyWith( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + ), + overlayColor: WidgetStateProperty.all( + Theme.of(context).hoverColor, + ), + alignment: AlignmentDirectional.centerStart, + splashFactory: NoSplash.splashFactory, + padding: const WidgetStatePropertyAll( + EdgeInsets.symmetric(vertical: 14, horizontal: 6), + ), + ), + label: FlowyText.medium( + LocaleKeys.grid_field_openRowDocument.tr(), + fontSize: 15, + ), + icon: const Padding( + padding: EdgeInsets.all(4.0), + child: FlowySvg( + FlowySvgs.full_view_s, + size: Size.square(16.0), + ), + ), + onPressed: () => _openRowPage(context), + ), + ); + } + + Future _openRowPage(BuildContext context) async { + Log.info('Open row page(${widget.documentId})'); + + if (view == null) { + showToastNotification(context, message: 'Failed to open row page'); + // reload the view again + unawaited(_preloadView(context)); + Log.error('Failed to open row page(${widget.documentId})'); + return; + } + + if (context.mounted) { + // the document in row is an orphan document, so we don't add it to recent + await context.pushView( + view!, + addInRecent: false, + ); + } + } + + // preload view to reduce the time to open the view + Future _preloadView( + BuildContext context, { + bool createDocumentIfMissed = false, + }) async { + Log.info('Preload row page(${widget.documentId})'); + final result = await ViewBackendService.getView(widget.documentId); + view = result.fold((s) => s, (f) => null); + + if (view == null && createDocumentIfMissed) { + // create view if not exists + Log.info('Create row page(${widget.documentId})'); + final result = await ViewBackendService.createOrphanView( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + viewId: widget.documentId, + layoutType: ViewLayoutPB.Document, + ); + view = result.fold((s) => s, (f) => null); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart index e41fa61b2f..a2b80a29df 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart @@ -39,11 +39,13 @@ class CalculationsBloc extends Bloc { _startListening(); await _getAllCalculations(); - add( - CalculationsEvent.didReceiveFieldUpdate( - _fieldController.fieldInfos, - ), - ); + if (!isClosed) { + add( + CalculationsEvent.didReceiveFieldUpdate( + _fieldController.fieldInfos, + ), + ); + } }, didReceiveFieldUpdate: (fields) async { emit( @@ -131,6 +133,10 @@ class CalculationsBloc extends Bloc { Future _getAllCalculations() async { final calculationsOrFailure = await _calculationsService.getCalculations(); + if (isClosed) { + return; + } + final RepeatedCalculationsPB? calculations = calculationsOrFailure.fold((s) => s, (e) => null); if (calculations != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart index 8a71e26efa..d509aa2f25 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart @@ -1,15 +1,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; -import 'package:appflowy/startup/tasks/app_window_size_manager.dart'; -import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_listener.dart'; +import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; @@ -47,20 +44,16 @@ class ViewTitleBarWithRow extends StatelessWidget { if (state.ancestors.isEmpty) { return const SizedBox.shrink(); } - const maxWidth = WindowSizeManager.minWindowWidth - 200; - return LayoutBuilder( - builder: (context, constraints) { - return Visibility( - visible: maxWidth < constraints.maxWidth, - // if the width is too small, only show one view title bar without the ancestors - replacement: _buildRowName(), - child: Row( - // refresh the view title bar when the ancestors changed - key: ValueKey(state.ancestors.hashCode), - children: _buildViewTitles(state.ancestors), - ), - ); - }, + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + height: 24, + child: Row( + // refresh the view title bar when the ancestors changed + key: ValueKey(state.ancestors.hashCode), + children: _buildViewTitles(state.ancestors), + ), + ), ); }, ), @@ -71,16 +64,22 @@ class ViewTitleBarWithRow extends StatelessWidget { // if the level is too deep, only show the root view, the database view and the row return views.length > 2 ? [ - _buildViewButton(views.first), - const FlowyText.regular('/'), - const FlowyText.regular(' ... /'), + _buildViewButton(views[1]), + const FlowySvg(FlowySvgs.title_bar_divider_s), + const FlowyText.regular(' ... '), + const FlowySvg(FlowySvgs.title_bar_divider_s), _buildViewButton(views.last), - const FlowyText.regular('/'), + const FlowySvg(FlowySvgs.title_bar_divider_s), _buildRowName(), ] : [ ...views - .map((e) => [_buildViewButton(e), const FlowyText.regular('/')]) + .map( + (e) => [ + _buildViewButton(e), + const FlowySvg(FlowySvgs.title_bar_divider_s), + ], + ) .flattened, _buildRowName(), ]; @@ -89,9 +88,9 @@ class ViewTitleBarWithRow extends StatelessWidget { Widget _buildViewButton(ViewPB view) { return FlowyTooltip( message: view.name, - child: _ViewTitle( + child: ViewTitle( view: view, - behavior: _ViewTitleBehavior.uneditable, + behavior: ViewTitleBehavior.uneditable, onUpdated: () {}, ), ); @@ -180,11 +179,14 @@ class _TitleSkin extends IEditableTextCellSkin { onTap: () {}, text: Row( children: [ - EmojiText( - emoji: state.icon ?? "", - fontSize: 18.0, - ), - const HSpace(2.0), + if (state.icon != null) ...[ + FlowyText.emoji( + state.icon!, + fontSize: 14.0, + figmaLineHeight: 18.0, + ), + const HSpace(4.0), + ], ConstrainedBox( constraints: const BoxConstraints(maxWidth: 180), child: FlowyText.regular( @@ -204,106 +206,6 @@ class _TitleSkin extends IEditableTextCellSkin { } } -enum _ViewTitleBehavior { - editable, - uneditable, -} - -class _ViewTitle extends StatefulWidget { - const _ViewTitle({ - required this.view, - this.behavior = _ViewTitleBehavior.editable, - required this.onUpdated, - }) : maxTitleWidth = 180; - - final ViewPB view; - final _ViewTitleBehavior behavior; - final double maxTitleWidth; - final VoidCallback onUpdated; - - @override - State<_ViewTitle> createState() => _ViewTitleState(); -} - -class _ViewTitleState extends State<_ViewTitle> { - late final viewListener = ViewListener(viewId: widget.view.id); - - String name = ''; - String icon = ''; - - @override - void initState() { - super.initState(); - - name = widget.view.name.isEmpty - ? LocaleKeys.document_title_placeholder.tr() - : widget.view.name; - icon = widget.view.icon.value; - - viewListener.start( - onViewUpdated: (view) { - if (name != view.name || icon != view.icon.value) { - widget.onUpdated(); - } - setState(() { - name = view.name.isEmpty - ? LocaleKeys.document_title_placeholder.tr() - : view.name; - icon = view.icon.value; - }); - }, - ); - } - - @override - void dispose() { - viewListener.stop(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // root view - if (widget.view.parentViewId.isEmpty) { - return Row( - children: [ - FlowyText.regular(name), - const HSpace(4.0), - ], - ); - } - - final child = Row( - children: [ - EmojiText( - emoji: icon, - fontSize: 18.0, - ), - const HSpace(2.0), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: widget.maxTitleWidth, - ), - child: FlowyText.regular( - name, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - - return Listener( - onPointerDown: (_) => context.read().openPlugin(widget.view), - child: FlowyButton( - useIntrinsicWidth: true, - onTap: () {}, - text: child, - ), - ); - } -} - class RenameRowPopover extends StatefulWidget { const RenameRowPopover({ super.key, diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart index 7e5e4eb528..b6352b0430 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart @@ -39,6 +39,10 @@ class DocumentCollaboratorsBloc if (userProfile != null) { _listener.start( onDocAwarenessUpdate: (states) { + if (isClosed) { + return; + } + add( DocumentCollaboratorsEvent.update( userProfile, diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index d879f07578..5574749d64 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -190,9 +190,12 @@ class _ApplicationWidgetState extends State { if (view != null) { final view = action.arguments?[ActionArgumentKeys.view]; final rowId = action.arguments?[ActionArgumentKeys.rowId]; - AppGlobals.rootNavKey.currentContext?.pushView(view, { - PluginArgumentKeys.rowId: rowId, - }); + AppGlobals.rootNavKey.currentContext?.pushView( + view, + arguments: { + PluginArgumentKeys.rowId: rowId, + }, + ); } } }); 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 43b14d9d1a..68849661b5 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 @@ -79,11 +79,11 @@ class ViewTitleBar extends StatelessWidget { final child = FlowyTooltip( key: ValueKey(view.id), message: view.name, - child: _ViewTitle( + child: ViewTitle( view: view, behavior: i == views.length - 1 - ? _ViewTitleBehavior.editable // only the last one is editable - : _ViewTitleBehavior.uneditable, // others are not editable + ? ViewTitleBehavior.editable // only the last one is editable + : ViewTitleBehavior.uneditable, // others are not editable onUpdated: () { context .read() @@ -103,27 +103,28 @@ class ViewTitleBar extends StatelessWidget { } } -enum _ViewTitleBehavior { +enum ViewTitleBehavior { editable, uneditable, } -class _ViewTitle extends StatefulWidget { - const _ViewTitle({ +class ViewTitle extends StatefulWidget { + const ViewTitle({ + super.key, required this.view, - this.behavior = _ViewTitleBehavior.editable, + this.behavior = ViewTitleBehavior.editable, required this.onUpdated, }); final ViewPB view; - final _ViewTitleBehavior behavior; + final ViewTitleBehavior behavior; final VoidCallback onUpdated; @override - State<_ViewTitle> createState() => _ViewTitleState(); + State createState() => _ViewTitleState(); } -class _ViewTitleState extends State<_ViewTitle> { +class _ViewTitleState extends State { final popoverController = PopoverController(); final textEditingController = TextEditingController(); @@ -137,7 +138,7 @@ class _ViewTitleState extends State<_ViewTitle> { @override Widget build(BuildContext context) { - final isEditable = widget.behavior == _ViewTitleBehavior.editable; + final isEditable = widget.behavior == ViewTitleBehavior.editable; return BlocProvider( create: (_) => diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 7196a64640..8e5bbb8dc6 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1327,6 +1327,7 @@ "addOption": "Add option", "editProperty": "Edit property", "newProperty": "New property", + "openRowDocument": "Open document", "deleteFieldPromptMessage": "Are you sure? This property will be deleted", "clearFieldPromptMessage": "Are you sure? All cells in this column will be emptied", "newColumn": "New Column", @@ -2411,4 +2412,4 @@ "commentAddedSuccessfully": "Comment added successfully.", "commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?" } -} \ No newline at end of file +}