mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-11-04 03:54:44 +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 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
This commit is contained in:
		
							parent
							
								
									88cc0caab7
								
							
						
					
					
						commit
						6283649a6b
					
				@ -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<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
 | 
			
		||||
    getIt<MenuSharedState>().latestOpenView = view;
 | 
			
		||||
    unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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<MobileViewPage> {
 | 
			
		||||
    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,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
          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<CalculationsEvent, CalculationsState> {
 | 
			
		||||
  Future<void> _getAllCalculations() async {
 | 
			
		||||
    final calculationsOrFailure = await _calculationsService.getCalculations();
 | 
			
		||||
 | 
			
		||||
    if (isClosed) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final RepeatedCalculationsPB? calculations =
 | 
			
		||||
        calculationsOrFailure.fold((s) => s, (e) => null);
 | 
			
		||||
    if (calculations != null) {
 | 
			
		||||
 | 
			
		||||
@ -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<TabsBloc>().openPlugin(widget.view),
 | 
			
		||||
      child: FlowyButton(
 | 
			
		||||
        useIntrinsicWidth: true,
 | 
			
		||||
        onTap: () {},
 | 
			
		||||
        text: child,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class RenameRowPopover extends StatefulWidget {
 | 
			
		||||
  const RenameRowPopover({
 | 
			
		||||
    super.key,
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,10 @@ class DocumentCollaboratorsBloc
 | 
			
		||||
            if (userProfile != null) {
 | 
			
		||||
              _listener.start(
 | 
			
		||||
                onDocAwarenessUpdate: (states) {
 | 
			
		||||
                  if (isClosed) {
 | 
			
		||||
                    return;
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  add(
 | 
			
		||||
                    DocumentCollaboratorsEvent.update(
 | 
			
		||||
                      userProfile,
 | 
			
		||||
 | 
			
		||||
@ -190,9 +190,12 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
 | 
			
		||||
              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,
 | 
			
		||||
                  },
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
@ -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<ViewTitleBarBloc>()
 | 
			
		||||
@ -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<ViewTitle> createState() => _ViewTitleState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ViewTitleState extends State<_ViewTitle> {
 | 
			
		||||
class _ViewTitleState extends State<ViewTitle> {
 | 
			
		||||
  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: (_) =>
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user