fix: navigation bar issue on linked pages(#7111) (#7142)

This commit is contained in:
Morn 2025-01-09 11:27:37 +08:00 committed by GitHub
parent 89c4629ec2
commit 155817a0f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 212 additions and 62 deletions

View File

@ -58,11 +58,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
expect(
find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()),
findsNWidgets(3),
@ -77,12 +72,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -101,11 +90,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -154,11 +138,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -212,11 +191,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -253,11 +227,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -303,11 +272,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -346,11 +310,6 @@ void main() {
await tester.insertSubPageFromSlashMenu(true);
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -394,11 +353,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
expect(find.byType(SubPageBlockComponent), findsOneWidget);
@ -421,12 +375,6 @@ void main() {
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
@ -447,11 +395,6 @@ void main() {
await tester.insertSubPageFromSlashMenu(true);
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
expect(find.byType(SubPageBlockComponent), findsOneWidget);
final beforeNode = tester.editor.getNodeAtPath([1]);
@ -532,6 +475,11 @@ void main() {
await tester.pumpAndSettle();
await tester.openPage(firstPage);
await tester.expandOrCollapsePage(
pageName: firstPage,
layout: ViewLayoutPB.Document,
);
/// check if there is a icon in document
final iconWidget = find.byWidgetPredicate((w) {
if (w is! RawEmojiIconWidget) return false;

View File

@ -1,8 +1,12 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@ -44,5 +48,82 @@ void main() {
);
expect(isExpanded(type: FolderSpaceType.private), true);
});
testWidgets('Expanding with subpage', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
const page1 = 'SubPageBloc', page2 = '$page1 2';
await tester.createNewPageWithNameUnderParent(name: page1);
await tester.createNewPageWithNameUnderParent(
name: page2,
parentName: page1,
);
await tester.expandOrCollapsePage(
pageName: gettingStarted,
layout: ViewLayoutPB.Document,
);
await tester.tapNewPageButton();
await tester.editor.tapLineOfEditorAt(0);
await tester.pumpAndSettle();
await tester.editor.showSlashMenu();
await tester.pumpAndSettle();
final slashMenu = find
.ancestor(
of: find.byType(SelectionMenuItemWidget),
matching: find.byWidgetPredicate(
(widget) => widget is Scrollable,
),
)
.first;
final slashMenuItem = find.text(
LocaleKeys.document_slashMenu_name_linkedDoc.tr(),
);
await tester.scrollUntilVisible(
slashMenuItem,
100,
scrollable: slashMenu,
duration: const Duration(milliseconds: 250),
);
final menuItemFinder = find.byWidgetPredicate(
(w) =>
w is SelectionMenuItemWidget &&
w.item.name == LocaleKeys.document_slashMenu_name_linkedDoc.tr(),
);
final menuItem =
menuItemFinder.evaluate().first.widget as SelectionMenuItemWidget;
/// tapSlashMenuItemWithName is not working, so invoke this function directly
menuItem.item.handler(
menuItem.editorState,
menuItem.menuService,
menuItemFinder.evaluate().first,
);
await tester.pumpAndSettle();
final actionHandler = find.byType(InlineActionsHandler);
final subPage = find.descendant(
of: actionHandler,
matching: find.text(page2, findRichText: true),
);
await tester.tapButton(subPage);
final subpageBlock = find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.text(page2, findRichText: true),
);
expect(find.text(page2, findRichText: true), findsOneWidget);
await tester.tapButton(subpageBlock);
/// one is in SectionFolder, another one is in CoverTitle
/// the last one is in FlowyNavigation
expect(find.text(page2, findRichText: true), findsNWidgets(3));
});
});
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/startup/tasks/feature_flag_task.dart';
import 'package:appflowy/util/expand_views.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy_backend/appflowy_backend.dart';
import 'package:appflowy_backend/log.dart';
@ -182,6 +183,7 @@ Future<void> initGetIt(
},
);
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
getIt.registerSingleton<ViewExpanderRegistry>(ViewExpanderRegistry());
await DependencyResolver.resolve(getIt, mode);
}
@ -207,6 +209,7 @@ abstract class LaunchTask {
LaunchTaskType get type => LaunchTaskType.dataProcessing;
Future<void> initialize(LaunchContext context);
Future<void> dispose();
}
@ -248,7 +251,9 @@ enum IntegrationMode {
// test mode
bool get isTest => isUnitTest || isIntegrationTest;
bool get isUnitTest => this == IntegrationMode.unitTest;
bool get isIntegrationTest => this == IntegrationMode.integrationTest;
// release mode

View File

@ -0,0 +1,40 @@
import 'package:flutter/cupertino.dart';
class ViewExpanderRegistry {
/// the key is view id
final Map<String, Set<ViewExpander>> _viewExpanders = {};
bool isViewExpanded(String id) => getExpander(id)?.isViewExpanded ?? false;
void register(String id, ViewExpander expander) {
final expanders = _viewExpanders[id] ?? {};
expanders.add(expander);
_viewExpanders[id] = expanders;
}
void unregister(String id, ViewExpander expander) {
final expanders = _viewExpanders[id] ?? {};
expanders.remove(expander);
if (expanders.isEmpty) {
_viewExpanders.remove(id);
} else {
_viewExpanders[id] = expanders;
}
}
ViewExpander? getExpander(String id) {
final expanders = _viewExpanders[id] ?? {};
return expanders.isEmpty ? null : expanders.first;
}
}
class ViewExpander {
ViewExpander(this._isExpandedCallback, this._expandCallback);
final ValueGetter<bool> _isExpandedCallback;
final VoidCallback _expandCallback;
bool get isViewExpanded => _isExpandedCallback.call();
void expand() => _expandCallback.call();
}

View File

@ -1,11 +1,19 @@
import 'dart:convert';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/plugins/blank/blank.dart';
import 'package:appflowy/plugins/util.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/expand_views.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -75,6 +83,7 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
return;
}
_setLatestOpenView(view);
if (view != null) _expandAncestors(view);
}
},
closeOtherTabs: (String pluginId) {
@ -213,6 +222,32 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
}
}
Future<void> _expandAncestors(ViewPB view) async {
final viewExpanderRegistry = getIt.get<ViewExpanderRegistry>();
if (viewExpanderRegistry.isViewExpanded(view.parentViewId)) return;
final value = await getIt<KeyValueStorage>().get(KVKeys.expandedViews);
try {
final Map expandedViews = value == null ? {} : jsonDecode(value);
final List<String> ancestors =
await ViewBackendService.getViewAncestors(view.id)
.fold((s) => s.items.map((e) => e.id).toList(), (f) => []);
ViewExpander? viewExpander;
for (final id in ancestors) {
expandedViews[id] = true;
final expander = viewExpanderRegistry.getExpander(id);
if (expander == null) continue;
if (!expander.isViewExpanded && viewExpander == null) {
viewExpander = expander;
}
}
await getIt<KeyValueStorage>()
.set(KVKeys.expandedViews, jsonEncode(expandedViews));
viewExpander?.expand();
} catch (e) {
Log.error('expandAncestors error', e);
}
}
int _adjustCurrentIndex({
required int currentIndex,
required int tabIndex,
@ -250,26 +285,37 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
@freezed
class TabsEvent with _$TabsEvent {
const factory TabsEvent.moveTab() = _MoveTab;
const factory TabsEvent.closeTab(String pluginId) = _CloseTab;
const factory TabsEvent.closeOtherTabs(String pluginId) = _CloseOtherTabs;
const factory TabsEvent.closeCurrentTab() = _CloseCurrentTab;
const factory TabsEvent.selectTab(int index) = _SelectTab;
const factory TabsEvent.togglePin(String pluginId) = _TogglePin;
const factory TabsEvent.openTab({
required Plugin plugin,
required ViewPB view,
}) = _OpenTab;
const factory TabsEvent.openPlugin({
required Plugin plugin,
ViewPB? view,
@Default(true) bool setLatest,
}) = _OpenPlugin;
const factory TabsEvent.openSecondaryPlugin({
required Plugin plugin,
ViewPB? view,
}) = _OpenSecondaryPlugin;
const factory TabsEvent.closeSecondaryPlugin() = _CloseSecondaryPlugin;
const factory TabsEvent.expandSecondaryPlugin() = _ExpandSecondaryPlugin;
const factory TabsEvent.switchWorkspace(String workspaceId) =
_SwitchWorkspace;
}
@ -282,8 +328,11 @@ class TabsState {
final int currentIndex;
final List<PageManager> _pageManagers;
int get pages => _pageManagers.length;
PageManager get currentPageManager => _pageManagers[currentIndex];
List<PageManager> get pageManagers => _pageManagers;
bool get isAllPinned => _pageManagers.every((pm) => pm.isPinned);

View File

@ -6,6 +6,7 @@ import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/expand_views.dart';
import 'package:appflowy/workspace/application/favorite/favorite_listener.dart';
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
@ -24,12 +25,22 @@ import 'package:protobuf/protobuf.dart';
part 'view_bloc.freezed.dart';
class ViewBloc extends Bloc<ViewEvent, ViewState> {
ViewBloc({required this.view, this.shouldLoadChildViews = true})
: viewBackendSvc = ViewBackendService(),
ViewBloc({
required this.view,
this.shouldLoadChildViews = true,
this.engagedInExpanding = false,
}) : viewBackendSvc = ViewBackendService(),
listener = ViewListener(viewId: view.id),
favoriteListener = FavoriteListener(),
super(ViewState.init(view)) {
_dispatch();
if (engagedInExpanding) {
expander = ViewExpander(
() => state.isExpanded,
() => add(const ViewEvent.setIsExpanded(true)),
);
getIt<ViewExpanderRegistry>().register(view.id, expander);
}
}
final ViewPB view;
@ -37,11 +48,16 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
final ViewListener listener;
final FavoriteListener favoriteListener;
final bool shouldLoadChildViews;
final bool engagedInExpanding;
late ViewExpander expander;
@override
Future<void> close() async {
await listener.stop();
await favoriteListener.stop();
if (engagedInExpanding) {
getIt<ViewExpanderRegistry>().unregister(view.id, expander);
}
return super.close();
}

View File

@ -103,6 +103,7 @@ class _SectionFolderState extends State<SectionFolder> {
(view) => ViewItem(
key: ValueKey('${widget.spaceType.name} ${view.id}'),
spaceType: widget.spaceType,
engagedInExpanding: true,
isFirstChild: view.id == widget.views.first.id,
view: view,
level: 0,

View File

@ -69,6 +69,7 @@ class ViewItem extends StatelessWidget {
this.extendBuilder,
this.disableSelectedStatus,
this.shouldIgnoreView,
this.engagedInExpanding = false,
this.enableRightClickContext = false,
});
@ -136,12 +137,17 @@ class ViewItem extends StatelessWidget {
///
final bool enableRightClickContext;
/// to record the ViewBlock which is expanded or collapsed
final bool engagedInExpanding;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) =>
ViewBloc(view: view, shouldLoadChildViews: shouldLoadChildViews)
..add(const ViewEvent.initial()),
create: (_) => ViewBloc(
view: view,
shouldLoadChildViews: shouldLoadChildViews,
engagedInExpanding: engagedInExpanding,
)..add(const ViewEvent.initial()),
child: BlocConsumer<ViewBloc, ViewState>(
listenWhen: (p, c) =>
c.lastCreatedView != null &&
@ -183,6 +189,7 @@ class ViewItem extends StatelessWidget {
isExpandedNotifier: isExpandedNotifier,
extendBuilder: extendBuilder,
shouldIgnoreView: shouldIgnoreView,
engagedInExpanding: engagedInExpanding,
);
if (shouldIgnoreView?.call(view) == IgnoreViewType.disable) {
@ -235,6 +242,7 @@ class InnerViewItem extends StatefulWidget {
this.isExpandedNotifier,
required this.extendBuilder,
this.disableSelectedStatus,
this.engagedInExpanding = false,
required this.shouldIgnoreView,
});
@ -270,6 +278,7 @@ class InnerViewItem extends StatefulWidget {
final PropertyValueNotifier<bool>? isExpandedNotifier;
final List<Widget> Function(ViewPB view)? extendBuilder;
final IgnoreViewType Function(ViewPB view)? shouldIgnoreView;
final bool engagedInExpanding;
@override
State<InnerViewItem> createState() => _InnerViewItemState();
@ -345,6 +354,7 @@ class _InnerViewItemState extends State<InnerViewItem> {
rightIconsBuilder: widget.rightIconsBuilder,
extendBuilder: widget.extendBuilder,
shouldIgnoreView: widget.shouldIgnoreView,
engagedInExpanding: widget.engagedInExpanding,
);
}).toList();