mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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 c81e5ef0ed
.
* 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
This commit is contained in:
parent
88cc0caab7
commit
6283649a6b
@ -2,20 +2,23 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy/mobile/presentation/chat/mobile_chat_screen.dart';
|
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/board/mobile_board_screen.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/mobile_calendar_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/database/mobile_grid_screen.dart';
|
||||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.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:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
extension MobileRouter on BuildContext {
|
extension MobileRouter on BuildContext {
|
||||||
Future<void> pushView(ViewPB view, [Map<String, dynamic>? arguments]) async {
|
Future<void> pushView(
|
||||||
|
ViewPB view, {
|
||||||
|
Map<String, dynamic>? arguments,
|
||||||
|
bool addInRecent = true,
|
||||||
|
}) async {
|
||||||
// set the current view before pushing the new view
|
// set the current view before pushing the new view
|
||||||
getIt<MenuSharedState>().latestOpenView = view;
|
getIt<MenuSharedState>().latestOpenView = view;
|
||||||
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
|
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
|
||||||
|
@ -10,7 +10,7 @@ enum FlowyAppBarLeadingType {
|
|||||||
Widget getWidget(VoidCallback? onTap) {
|
Widget getWidget(VoidCallback? onTap) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case FlowyAppBarLeadingType.back:
|
case FlowyAppBarLeadingType.back:
|
||||||
return AppBarBackButton(onTap: onTap);
|
return AppBarImmersiveBackButton(onTap: onTap);
|
||||||
case FlowyAppBarLeadingType.close:
|
case FlowyAppBarLeadingType.close:
|
||||||
return AppBarCloseButton(onTap: onTap);
|
return AppBarCloseButton(onTap: onTap);
|
||||||
case FlowyAppBarLeadingType.cancel:
|
case FlowyAppBarLeadingType.cancel:
|
||||||
|
@ -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 {
|
class AppBarCloseButton extends StatelessWidget {
|
||||||
const AppBarCloseButton({
|
const AppBarCloseButton({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -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/app_bar/app_bar.dart';
|
||||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.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/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/plugins/document/presentation/document_collaborators.dart';
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
@ -232,19 +231,20 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (icon != null && icon.isNotEmpty)
|
if (icon != null && icon.isNotEmpty) ...[
|
||||||
ConstrainedBox(
|
FlowyText.emoji(
|
||||||
constraints: const BoxConstraints.tightFor(width: 34.0),
|
icon,
|
||||||
child: EmojiText(
|
fontSize: 15.0,
|
||||||
emoji: '$icon ',
|
figmaLineHeight: 18.0,
|
||||||
fontSize: 22.0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
const HSpace(4),
|
||||||
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
view?.name ?? widget.title ?? '',
|
view?.name ?? widget.title ?? '',
|
||||||
fontSize: 15.0,
|
fontSize: 15.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
figmaLineHeight: 18.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -52,6 +52,7 @@ class _MobileBottomSheetRenameWidgetState
|
|||||||
height: 42.0,
|
height: 42.0,
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
onSubmitted: (text) => widget.onRename(text),
|
onSubmitted: (text) => widget.onRename(text),
|
||||||
),
|
),
|
||||||
|
@ -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.dart';
|
||||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.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/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/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/bloc/text_cell_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||||
@ -366,6 +367,9 @@ class MobileRowDetailPageContentState
|
|||||||
if (rowDetailState.numHiddenFields != 0) ...[
|
if (rowDetailState.numHiddenFields != 0) ...[
|
||||||
const ToggleHiddenFieldsVisibilityButton(),
|
const ToggleHiddenFieldsVisibilityButton(),
|
||||||
],
|
],
|
||||||
|
OpenRowPageButton(
|
||||||
|
documentId: rowController.rowMeta.documentId,
|
||||||
|
),
|
||||||
MobileRowDetailCreateFieldButton(
|
MobileRowDetailCreateFieldButton(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
|
@ -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<OpenRowPageButton> createState() => _OpenRowPageButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OpenRowPageButtonState extends State<OpenRowPageButton> {
|
||||||
|
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>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
overlayColor: WidgetStateProperty.all<Color>(
|
||||||
|
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<void> _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<void> _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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,11 +39,13 @@ class CalculationsBloc extends Bloc<CalculationsEvent, CalculationsState> {
|
|||||||
_startListening();
|
_startListening();
|
||||||
await _getAllCalculations();
|
await _getAllCalculations();
|
||||||
|
|
||||||
add(
|
if (!isClosed) {
|
||||||
CalculationsEvent.didReceiveFieldUpdate(
|
add(
|
||||||
_fieldController.fieldInfos,
|
CalculationsEvent.didReceiveFieldUpdate(
|
||||||
),
|
_fieldController.fieldInfos,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (fields) async {
|
didReceiveFieldUpdate: (fields) async {
|
||||||
emit(
|
emit(
|
||||||
@ -131,6 +133,10 @@ class CalculationsBloc extends Bloc<CalculationsEvent, CalculationsState> {
|
|||||||
Future<void> _getAllCalculations() async {
|
Future<void> _getAllCalculations() async {
|
||||||
final calculationsOrFailure = await _calculationsService.getCalculations();
|
final calculationsOrFailure = await _calculationsService.getCalculations();
|
||||||
|
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final RepeatedCalculationsPB? calculations =
|
final RepeatedCalculationsPB? calculations =
|
||||||
calculationsOrFailure.fold((s) => s, (e) => null);
|
calculationsOrFailure.fold((s) => s, (e) => null);
|
||||||
if (calculations != null) {
|
if (calculations != null) {
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/bloc/text_cell_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.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_builder.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.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/database/widgets/row/cells/cell_container.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.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/presentation/widgets/view_title_bar.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -47,20 +44,16 @@ class ViewTitleBarWithRow extends StatelessWidget {
|
|||||||
if (state.ancestors.isEmpty) {
|
if (state.ancestors.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
const maxWidth = WindowSizeManager.minWindowWidth - 200;
|
return SingleChildScrollView(
|
||||||
return LayoutBuilder(
|
scrollDirection: Axis.horizontal,
|
||||||
builder: (context, constraints) {
|
child: SizedBox(
|
||||||
return Visibility(
|
height: 24,
|
||||||
visible: maxWidth < constraints.maxWidth,
|
child: Row(
|
||||||
// if the width is too small, only show one view title bar without the ancestors
|
// refresh the view title bar when the ancestors changed
|
||||||
replacement: _buildRowName(),
|
key: ValueKey(state.ancestors.hashCode),
|
||||||
child: Row(
|
children: _buildViewTitles(state.ancestors),
|
||||||
// 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
|
// if the level is too deep, only show the root view, the database view and the row
|
||||||
return views.length > 2
|
return views.length > 2
|
||||||
? [
|
? [
|
||||||
_buildViewButton(views.first),
|
_buildViewButton(views[1]),
|
||||||
const FlowyText.regular('/'),
|
const FlowySvg(FlowySvgs.title_bar_divider_s),
|
||||||
const FlowyText.regular(' ... /'),
|
const FlowyText.regular(' ... '),
|
||||||
|
const FlowySvg(FlowySvgs.title_bar_divider_s),
|
||||||
_buildViewButton(views.last),
|
_buildViewButton(views.last),
|
||||||
const FlowyText.regular('/'),
|
const FlowySvg(FlowySvgs.title_bar_divider_s),
|
||||||
_buildRowName(),
|
_buildRowName(),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
...views
|
...views
|
||||||
.map((e) => [_buildViewButton(e), const FlowyText.regular('/')])
|
.map(
|
||||||
|
(e) => [
|
||||||
|
_buildViewButton(e),
|
||||||
|
const FlowySvg(FlowySvgs.title_bar_divider_s),
|
||||||
|
],
|
||||||
|
)
|
||||||
.flattened,
|
.flattened,
|
||||||
_buildRowName(),
|
_buildRowName(),
|
||||||
];
|
];
|
||||||
@ -89,9 +88,9 @@ class ViewTitleBarWithRow extends StatelessWidget {
|
|||||||
Widget _buildViewButton(ViewPB view) {
|
Widget _buildViewButton(ViewPB view) {
|
||||||
return FlowyTooltip(
|
return FlowyTooltip(
|
||||||
message: view.name,
|
message: view.name,
|
||||||
child: _ViewTitle(
|
child: ViewTitle(
|
||||||
view: view,
|
view: view,
|
||||||
behavior: _ViewTitleBehavior.uneditable,
|
behavior: ViewTitleBehavior.uneditable,
|
||||||
onUpdated: () {},
|
onUpdated: () {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -180,11 +179,14 @@ class _TitleSkin extends IEditableTextCellSkin {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
text: Row(
|
text: Row(
|
||||||
children: [
|
children: [
|
||||||
EmojiText(
|
if (state.icon != null) ...[
|
||||||
emoji: state.icon ?? "",
|
FlowyText.emoji(
|
||||||
fontSize: 18.0,
|
state.icon!,
|
||||||
),
|
fontSize: 14.0,
|
||||||
const HSpace(2.0),
|
figmaLineHeight: 18.0,
|
||||||
|
),
|
||||||
|
const HSpace(4.0),
|
||||||
|
],
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 180),
|
constraints: const BoxConstraints(maxWidth: 180),
|
||||||
child: FlowyText.regular(
|
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<TabsBloc>().openPlugin(widget.view),
|
|
||||||
child: FlowyButton(
|
|
||||||
useIntrinsicWidth: true,
|
|
||||||
onTap: () {},
|
|
||||||
text: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenameRowPopover extends StatefulWidget {
|
class RenameRowPopover extends StatefulWidget {
|
||||||
const RenameRowPopover({
|
const RenameRowPopover({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -39,6 +39,10 @@ class DocumentCollaboratorsBloc
|
|||||||
if (userProfile != null) {
|
if (userProfile != null) {
|
||||||
_listener.start(
|
_listener.start(
|
||||||
onDocAwarenessUpdate: (states) {
|
onDocAwarenessUpdate: (states) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
add(
|
add(
|
||||||
DocumentCollaboratorsEvent.update(
|
DocumentCollaboratorsEvent.update(
|
||||||
userProfile,
|
userProfile,
|
||||||
|
@ -190,9 +190,12 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
|||||||
if (view != null) {
|
if (view != null) {
|
||||||
final view = action.arguments?[ActionArgumentKeys.view];
|
final view = action.arguments?[ActionArgumentKeys.view];
|
||||||
final rowId = action.arguments?[ActionArgumentKeys.rowId];
|
final rowId = action.arguments?[ActionArgumentKeys.rowId];
|
||||||
AppGlobals.rootNavKey.currentContext?.pushView(view, {
|
AppGlobals.rootNavKey.currentContext?.pushView(
|
||||||
PluginArgumentKeys.rowId: rowId,
|
view,
|
||||||
});
|
arguments: {
|
||||||
|
PluginArgumentKeys.rowId: rowId,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -79,11 +79,11 @@ class ViewTitleBar extends StatelessWidget {
|
|||||||
final child = FlowyTooltip(
|
final child = FlowyTooltip(
|
||||||
key: ValueKey(view.id),
|
key: ValueKey(view.id),
|
||||||
message: view.name,
|
message: view.name,
|
||||||
child: _ViewTitle(
|
child: ViewTitle(
|
||||||
view: view,
|
view: view,
|
||||||
behavior: i == views.length - 1
|
behavior: i == views.length - 1
|
||||||
? _ViewTitleBehavior.editable // only the last one is editable
|
? ViewTitleBehavior.editable // only the last one is editable
|
||||||
: _ViewTitleBehavior.uneditable, // others are not editable
|
: ViewTitleBehavior.uneditable, // others are not editable
|
||||||
onUpdated: () {
|
onUpdated: () {
|
||||||
context
|
context
|
||||||
.read<ViewTitleBarBloc>()
|
.read<ViewTitleBarBloc>()
|
||||||
@ -103,27 +103,28 @@ class ViewTitleBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _ViewTitleBehavior {
|
enum ViewTitleBehavior {
|
||||||
editable,
|
editable,
|
||||||
uneditable,
|
uneditable,
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewTitle extends StatefulWidget {
|
class ViewTitle extends StatefulWidget {
|
||||||
const _ViewTitle({
|
const ViewTitle({
|
||||||
|
super.key,
|
||||||
required this.view,
|
required this.view,
|
||||||
this.behavior = _ViewTitleBehavior.editable,
|
this.behavior = ViewTitleBehavior.editable,
|
||||||
required this.onUpdated,
|
required this.onUpdated,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
final _ViewTitleBehavior behavior;
|
final ViewTitleBehavior behavior;
|
||||||
final VoidCallback onUpdated;
|
final VoidCallback onUpdated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ViewTitle> createState() => _ViewTitleState();
|
State<ViewTitle> createState() => _ViewTitleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewTitleState extends State<_ViewTitle> {
|
class _ViewTitleState extends State<ViewTitle> {
|
||||||
final popoverController = PopoverController();
|
final popoverController = PopoverController();
|
||||||
final textEditingController = TextEditingController();
|
final textEditingController = TextEditingController();
|
||||||
|
|
||||||
@ -137,7 +138,7 @@ class _ViewTitleState extends State<_ViewTitle> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isEditable = widget.behavior == _ViewTitleBehavior.editable;
|
final isEditable = widget.behavior == ViewTitleBehavior.editable;
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
|
@ -1327,6 +1327,7 @@
|
|||||||
"addOption": "Add option",
|
"addOption": "Add option",
|
||||||
"editProperty": "Edit property",
|
"editProperty": "Edit property",
|
||||||
"newProperty": "New property",
|
"newProperty": "New property",
|
||||||
|
"openRowDocument": "Open document",
|
||||||
"deleteFieldPromptMessage": "Are you sure? This property will be deleted",
|
"deleteFieldPromptMessage": "Are you sure? This property will be deleted",
|
||||||
"clearFieldPromptMessage": "Are you sure? All cells in this column will be emptied",
|
"clearFieldPromptMessage": "Are you sure? All cells in this column will be emptied",
|
||||||
"newColumn": "New Column",
|
"newColumn": "New Column",
|
||||||
@ -2411,4 +2412,4 @@
|
|||||||
"commentAddedSuccessfully": "Comment added successfully.",
|
"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?"
|
"commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user