From b1d35531107f856567b6c91abcd6f60b4ad1518c Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 7 May 2025 21:10:23 +0800 Subject: [PATCH] refactor: view selector (#7891) * refactor: extract common view selection logic * refactor: extract common component * chore: make selected section selectable * chore: max num of selected top-level pages --- frontend/appflowy_flutter/lib/ai/ai.dart | 1 + .../service/view_selector_cubit.dart} | 244 ++++++++------- .../select_sources_bottom_sheet.dart | 62 ++-- .../prompt_input/select_sources_menu.dart | 290 +++++++++--------- .../lib/ai/widgets/view_selector.dart | 39 +++ .../ai_chat/application/ai_chat_prelude.dart | 1 - .../chat_message_selector_banner.dart | 38 ++- .../message/ai_message_action_bar.dart | 125 ++++---- 8 files changed, 406 insertions(+), 394 deletions(-) rename frontend/appflowy_flutter/lib/{plugins/ai_chat/application/chat_select_sources_cubit.dart => ai/service/view_selector_cubit.dart} (55%) create mode 100644 frontend/appflowy_flutter/lib/ai/widgets/view_selector.dart diff --git a/frontend/appflowy_flutter/lib/ai/ai.dart b/frontend/appflowy_flutter/lib/ai/ai.dart index ac27e6cbba..de27a98210 100644 --- a/frontend/appflowy_flutter/lib/ai/ai.dart +++ b/frontend/appflowy_flutter/lib/ai/ai.dart @@ -5,6 +5,7 @@ export 'service/appflowy_ai_service.dart'; export 'service/error.dart'; export 'service/ai_model_state_notifier.dart'; export 'service/select_model_bloc.dart'; +export 'service/view_selector_cubit.dart'; export 'widgets/loading_indicator.dart'; export 'widgets/prompt_input/action_buttons.dart'; export 'widgets/prompt_input/desktop_prompt_input.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart b/frontend/appflowy_flutter/lib/ai/service/view_selector_cubit.dart similarity index 55% rename from frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart rename to frontend/appflowy_flutter/lib/ai/service/view_selector_cubit.dart index 76cbd69cdf..761de75e00 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart +++ b/frontend/appflowy_flutter/lib/ai/service/view_selector_cubit.dart @@ -6,13 +6,12 @@ import 'package:appflowy_result/appflowy_result.dart'; import 'package:bloc/bloc.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -part 'chat_select_sources_cubit.freezed.dart'; +part 'view_selector_cubit.freezed.dart'; -const int _kMaxSelectedParentPageCount = 3; - -enum SourceSelectedStatus { +enum ViewSelectedStatus { unselected, selected, partiallySelected; @@ -22,45 +21,46 @@ enum SourceSelectedStatus { bool get isPartiallySelected => this == partiallySelected; } -class ChatSource { - ChatSource({ +class ViewSelectorItem { + ViewSelectorItem({ required this.view, required this.parentView, required this.children, required bool isExpanded, - required SourceSelectedStatus selectedStatus, - required IgnoreViewType ignoreStatus, + required ViewSelectedStatus selectedStatus, + required bool isDisabled, }) : isExpandedNotifier = ValueNotifier(isExpanded), selectedStatusNotifier = ValueNotifier(selectedStatus), - ignoreStatusNotifier = ValueNotifier(ignoreStatus); + isDisabledNotifier = ValueNotifier(isDisabled); final ViewPB view; final ViewPB? parentView; - final List children; + final List children; final ValueNotifier isExpandedNotifier; - final ValueNotifier selectedStatusNotifier; - final ValueNotifier ignoreStatusNotifier; + final ValueNotifier isDisabledNotifier; + final ValueNotifier selectedStatusNotifier; bool get isExpanded => isExpandedNotifier.value; - SourceSelectedStatus get selectedStatus => selectedStatusNotifier.value; - IgnoreViewType get ignoreStatus => ignoreStatusNotifier.value; + ViewSelectedStatus get selectedStatus => selectedStatusNotifier.value; + bool get isDisabled => isDisabledNotifier.value; void toggleIsExpanded() { - isExpandedNotifier.value = !isExpanded; + isExpandedNotifier.value = !isExpandedNotifier.value; } - ChatSource copy() { - return ChatSource( + ViewSelectorItem copy() { + return ViewSelectorItem( view: view, parentView: parentView, - children: children.map((child) => child.copy()).toList(), - ignoreStatus: ignoreStatus, - isExpanded: isExpanded, - selectedStatus: selectedStatus, + children: + children.map((child) => child.copy()).toList(), + isDisabled: isDisabledNotifier.value, + isExpanded: isExpandedNotifier.value, + selectedStatus: selectedStatusNotifier.value, ); } - ChatSource? findChildBySourceId(String sourceId) { + ViewSelectorItem? findChildBySourceId(String sourceId) { if (view.id == sourceId) { return this; } @@ -73,20 +73,11 @@ class ChatSource { return null; } - void resetIgnoreViewTypeRecursive() { - ignoreStatusNotifier.value = view.layout.isDocumentView - ? IgnoreViewType.none - : IgnoreViewType.disable; + void setIsDisabledRecursive(bool Function(ViewPB) newIsDisabled) { + isDisabledNotifier.value = newIsDisabled(view); for (final child in children) { - child.resetIgnoreViewTypeRecursive(); - } - } - - void updateIgnoreViewTypeRecursive(IgnoreViewType newIgnoreViewType) { - ignoreStatusNotifier.value = newIgnoreViewType; - for (final child in children) { - child.updateIgnoreViewTypeRecursive(newIgnoreViewType); + child.setIsDisabledRecursive(newIsDisabled); } } @@ -96,21 +87,25 @@ class ChatSource { } isExpandedNotifier.dispose(); selectedStatusNotifier.dispose(); - ignoreStatusNotifier.dispose(); + isDisabledNotifier.dispose(); } } -class ChatSettingsCubit extends Cubit { - ChatSettingsCubit({ - this.hideDisabled = false, - }) : super(ChatSettingsState.initial()); +class ViewSelectorCubit extends Cubit { + ViewSelectorCubit({ + required this.getIgnoreViewType, + this.maxSelectedParentPageCount, + }) : super(ViewSelectorState.initial()) { + filterTextController.addListener(onFilterChanged); + } - final bool hideDisabled; + final IgnoreViewType Function(ViewPB) getIgnoreViewType; + final int? maxSelectedParentPageCount; final List selectedSourceIds = []; - final List sources = []; - final List selectedSources = []; - String filter = ''; + final List sources = []; + final List selectedSources = []; + final filterTextController = TextEditingController(); void updateSelectedSources(List newSelectedSourceIds) { selectedSourceIds.clear(); @@ -118,7 +113,7 @@ class ChatSettingsCubit extends Cubit { } void refreshSources(List spaceViews, ViewPB? currentSpace) async { - filter = ""; + filterTextController.clear(); final newSources = await Future.wait( spaceViews.map((view) => _recursiveBuild(view, null)), @@ -155,29 +150,30 @@ class ChatSettingsCubit extends Cubit { ..addAll(selected.map((e) => e.copy())); } - Future _recursiveBuild(ViewPB view, ViewPB? parentView) async { - SourceSelectedStatus selectedStatus = SourceSelectedStatus.unselected; + Future _recursiveBuild( + ViewPB view, + ViewPB? parentView, + ) async { + ViewSelectedStatus selectedStatus = ViewSelectedStatus.unselected; final isThisSourceSelected = selectedSourceIds.contains(view.id); final childrenViews = await ViewBackendService.getChildViews(viewId: view.id).toNullable(); int selectedCount = 0; - final children = []; + final children = []; if (childrenViews != null) { for (final childView in childrenViews) { - if (childView.layout == ViewLayoutPB.Chat) { + if (getIgnoreViewType(childView) == IgnoreViewType.hide) { continue; } - if (childView.layout != ViewLayoutPB.Document && hideDisabled) { - continue; - } - final childChatSource = await _recursiveBuild(childView, view); - if (childChatSource.selectedStatus.isSelected) { + + final childItem = await _recursiveBuild(childView, view); + if (childItem.selectedStatus.isSelected) { selectedCount++; } - children.add(childChatSource); + children.add(childItem); } final areAllChildrenSelectedOrNoChildren = @@ -186,47 +182,49 @@ class ChatSettingsCubit extends Cubit { children.any((e) => !e.selectedStatus.isUnselected); if (isThisSourceSelected && areAllChildrenSelectedOrNoChildren) { - selectedStatus = SourceSelectedStatus.selected; + selectedStatus = ViewSelectedStatus.selected; } else if (isThisSourceSelected || isAnyChildNotUnselected) { - selectedStatus = SourceSelectedStatus.partiallySelected; + selectedStatus = ViewSelectedStatus.partiallySelected; } } else if (isThisSourceSelected) { - selectedStatus = SourceSelectedStatus.selected; + selectedStatus = ViewSelectedStatus.selected; } - return ChatSource( + return ViewSelectorItem( view: view, parentView: parentView, children: children, - ignoreStatus: view.layout.isDocumentView - ? IgnoreViewType.none - : IgnoreViewType.disable, + isDisabled: getIgnoreViewType(view) == IgnoreViewType.disable, isExpanded: false, selectedStatus: selectedStatus, ); } - void _restrictSelectionIfNecessary(List sources) { + void _restrictSelectionIfNecessary(List sources) { + if (maxSelectedParentPageCount == null) { + return; + } for (final source in sources) { - source.resetIgnoreViewTypeRecursive(); + source.setIsDisabledRecursive((view) { + return getIgnoreViewType(view) == IgnoreViewType.disable; + }); } if (sources.where((e) => !e.selectedStatus.isUnselected).length >= - _kMaxSelectedParentPageCount) { + maxSelectedParentPageCount!) { sources - .where((e) => e.selectedStatus == SourceSelectedStatus.unselected) + .where((e) => e.selectedStatus == ViewSelectedStatus.unselected) .forEach( - (e) => e.updateIgnoreViewTypeRecursive(IgnoreViewType.disable), + (e) => e.setIsDisabledRecursive((_) => true), ); } } - void updateFilter(String filter) { - this.filter = filter; + void onFilterChanged() { for (final source in state.visibleSources) { source.dispose(); } if (sources.isEmpty) { - emit(ChatSettingsState.initial()); + emit(ViewSelectorState.initial()); } else { final selected = selectedSources.map(_buildSearchResults).nonNulls.toList(); @@ -242,13 +240,13 @@ class ChatSettingsCubit extends Cubit { } /// traverse tree to build up search query - ChatSource? _buildSearchResults(ChatSource chatSource) { - final isVisible = chatSource.view.nameOrDefault + ViewSelectorItem? _buildSearchResults(ViewSelectorItem item) { + final isVisible = item.view.nameOrDefault .toLowerCase() - .contains(filter.toLowerCase()); + .contains(filterTextController.text.toLowerCase()); - final childrenResults = []; - for (final childSource in chatSource.children) { + final childrenResults = []; + for (final childSource in item.children) { final childResult = _buildSearchResults(childSource); if (childResult != null) { childrenResults.add(childResult); @@ -256,48 +254,50 @@ class ChatSettingsCubit extends Cubit { } return isVisible || childrenResults.isNotEmpty - ? ChatSource( - view: chatSource.view, - parentView: chatSource.parentView, + ? ViewSelectorItem( + view: item.view, + parentView: item.parentView, children: childrenResults, - ignoreStatus: chatSource.ignoreStatus, - isExpanded: chatSource.isExpanded, - selectedStatus: chatSource.selectedStatus, + isDisabled: item.isDisabled, + isExpanded: item.isExpanded, + selectedStatus: item.selectedStatus, ) : null; } /// traverse tree to build up selected sources - Iterable _buildSelectedSources(ChatSource chatSource) { - final children = []; + Iterable _buildSelectedSources( + ViewSelectorItem item, + ) { + final children = []; - for (final childSource in chatSource.children) { + for (final childSource in item.children) { children.addAll(_buildSelectedSources(childSource)); } - return selectedSourceIds.contains(chatSource.view.id) + return selectedSourceIds.contains(item.view.id) ? [ - ChatSource( - view: chatSource.view, - parentView: chatSource.parentView, + ViewSelectorItem( + view: item.view, + parentView: item.parentView, children: children, - ignoreStatus: chatSource.ignoreStatus, - selectedStatus: chatSource.selectedStatus, + isDisabled: item.isDisabled, + selectedStatus: item.selectedStatus, isExpanded: true, ), ] : children; } - void toggleSelectedStatus(ChatSource chatSource) { - if (chatSource.view.isSpace) { + void toggleSelectedStatus(ViewSelectorItem item, bool isSelectedSection) { + if (item.view.isSpace) { return; } - final allIds = _recursiveGetSourceIds(chatSource); + final allIds = _recursiveGetSourceIds(item); - if (chatSource.selectedStatus.isUnselected || - chatSource.selectedStatus.isPartiallySelected && - !chatSource.view.layout.isDocumentView) { + if (item.selectedStatus.isUnselected || + item.selectedStatus.isPartiallySelected && + !item.view.layout.isDocumentView) { for (final id in allIds) { if (!selectedSourceIds.contains(id)) { selectedSourceIds.add(id); @@ -311,13 +311,20 @@ class ChatSettingsCubit extends Cubit { } } + if (isSelectedSection) { + item.selectedStatusNotifier.value = item.selectedStatus.isUnselected || + item.selectedStatus.isPartiallySelected + ? ViewSelectedStatus.selected + : ViewSelectedStatus.unselected; + } + updateSelectedStatus(); } - List _recursiveGetSourceIds(ChatSource chatSource) { + List _recursiveGetSourceIds(ViewSelectorItem item) { return [ - if (chatSource.view.layout.isDocumentView) chatSource.view.id, - for (final childSource in chatSource.children) + if (item.view.layout.isDocumentView) item.view.id, + for (final childSource in item.children) ..._recursiveGetSourceIds(childSource), ]; } @@ -342,44 +349,42 @@ class ChatSettingsCubit extends Cubit { ); } - SourceSelectedStatus _recursiveUpdateSelectedStatus(ChatSource chatSource) { - SourceSelectedStatus selectedStatus = SourceSelectedStatus.unselected; + ViewSelectedStatus _recursiveUpdateSelectedStatus(ViewSelectorItem item) { + ViewSelectedStatus selectedStatus = ViewSelectedStatus.unselected; int selectedCount = 0; - for (final childSource in chatSource.children) { + for (final childSource in item.children) { final childStatus = _recursiveUpdateSelectedStatus(childSource); if (childStatus.isSelected) { selectedCount++; } } - final isThisSourceSelected = selectedSourceIds.contains(chatSource.view.id); + final isThisSourceSelected = selectedSourceIds.contains(item.view.id); final areAllChildrenSelectedOrNoChildren = - chatSource.children.length == selectedCount; + item.children.length == selectedCount; final isAnyChildNotUnselected = - chatSource.children.any((e) => !e.selectedStatus.isUnselected); + item.children.any((e) => !e.selectedStatus.isUnselected); if (isThisSourceSelected && areAllChildrenSelectedOrNoChildren) { - selectedStatus = SourceSelectedStatus.selected; + selectedStatus = ViewSelectedStatus.selected; } else if (isThisSourceSelected || isAnyChildNotUnselected) { - selectedStatus = SourceSelectedStatus.partiallySelected; + selectedStatus = ViewSelectedStatus.partiallySelected; } - chatSource.selectedStatusNotifier.value = selectedStatus; + item.selectedStatusNotifier.value = selectedStatus; return selectedStatus; } - void toggleIsExpanded(ChatSource chatSource, bool isSelectedSection) { - chatSource.toggleIsExpanded(); + void toggleIsExpanded(ViewSelectorItem item, bool isSelectedSection) { + item.toggleIsExpanded(); if (isSelectedSection) { for (final selectedSource in selectedSources) { - selectedSource - .findChildBySourceId(chatSource.view.id) - ?.toggleIsExpanded(); + selectedSource.findChildBySourceId(item.view.id)?.toggleIsExpanded(); } } else { for (final source in sources) { - final child = source.findChildBySourceId(chatSource.view.id); + final child = source.findChildBySourceId(item.view.id); if (child != null) { child.toggleIsExpanded(); break; @@ -402,18 +407,19 @@ class ChatSettingsCubit extends Cubit { for (final child in state.visibleSources) { child.dispose(); } + filterTextController.dispose(); return super.close(); } } @freezed -class ChatSettingsState with _$ChatSettingsState { - const factory ChatSettingsState({ - required List visibleSources, - required List selectedSources, - }) = _ChatSettingsState; +class ViewSelectorState with _$ViewSelectorState { + const factory ViewSelectorState({ + required List visibleSources, + required List selectedSources, + }) = _ViewSelectorState; - factory ChatSettingsState.initial() => const ChatSettingsState( + factory ViewSelectorState.initial() => const ViewSelectorState( visibleSources: [], selectedSources: [], ); diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart index 70bfa72e60..9ee7040574 100644 --- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart @@ -2,12 +2,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; +import 'package:appflowy/ai/service/view_selector_cubit.dart'; import 'package:appflowy/plugins/base/drag_handler.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -32,7 +33,18 @@ class PromptInputMobileSelectSourcesButton extends StatefulWidget { class _PromptInputMobileSelectSourcesButtonState extends State { - late final cubit = ChatSettingsCubit(); + late final cubit = ViewSelectorCubit( + maxSelectedParentPageCount: 3, + getIgnoreViewType: (view) { + if (view.isSpace) { + return IgnoreViewType.none; + } + if (view.layout != ViewLayoutPB.Document) { + return IgnoreViewType.hide; + } + return IgnoreViewType.none; + }, + ); @override void initState() { @@ -91,7 +103,7 @@ class _PromptInputMobileSelectSourcesButtonState ), onTap: () async { context - .read() + .read() .refreshSources(state.spaces, state.currentSpace); await showMobileBottomSheet( context, @@ -129,32 +141,17 @@ class _PromptInputMobileSelectSourcesButtonState } } -class _MobileSelectSourcesSheetBody extends StatefulWidget { +class _MobileSelectSourcesSheetBody extends StatelessWidget { const _MobileSelectSourcesSheetBody({ required this.scrollController, }); final ScrollController scrollController; - @override - State<_MobileSelectSourcesSheetBody> createState() => - _MobileSelectSourcesSheetBodyState(); -} - -class _MobileSelectSourcesSheetBodyState - extends State<_MobileSelectSourcesSheetBody> { - final textController = TextEditingController(); - - @override - void dispose() { - textController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return CustomScrollView( - controller: widget.scrollController, + controller: scrollController, shrinkWrap: true, slivers: [ SliverPersistentHeader( @@ -183,10 +180,9 @@ class _MobileSelectSourcesSheetBodyState child: SizedBox( height: 44.0, child: FlowySearchTextField( - controller: textController, - onChanged: (value) => context - .read() - .updateFilter(value), + controller: context + .read() + .filterTextController, ), ), ), @@ -196,27 +192,25 @@ class _MobileSelectSourcesSheetBodyState ), ), ), - BlocBuilder( + BlocBuilder( builder: (context, state) { - final sources = state.visibleSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide); return SliverList( delegate: SliverChildBuilderDelegate( - childCount: sources.length, + childCount: state.visibleSources.length, (context, index) { - final source = sources.elementAt(index); - return ChatSourceTreeItem( + final source = state.visibleSources.elementAt(index); + return ViewSelectorTreeItem( key: ValueKey( 'visible_select_sources_tree_item_${source.view.id}', ), - chatSource: source, + viewSelectorItem: source, level: 0, isDescendentOfSpace: source.view.isSpace, isSelectedSection: false, - onSelected: (chatSource) { + onSelected: (item) { context - .read() - .toggleSelectedStatus(chatSource); + .read() + .toggleSelectedStatus(item, false); }, height: 40.0, ); diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart index 28c68f2e0b..7d3f50c9fb 100644 --- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart @@ -2,15 +2,13 @@ import 'dart:math'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; -import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -18,6 +16,8 @@ import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../service/view_selector_cubit.dart'; +import '../view_selector.dart'; import 'layout_define.dart'; import 'mention_page_menu.dart'; @@ -38,7 +38,21 @@ class PromptInputDesktopSelectSourcesButton extends StatefulWidget { class _PromptInputDesktopSelectSourcesButtonState extends State { - late final cubit = ChatSettingsCubit(); + late final cubit = ViewSelectorCubit( + maxSelectedParentPageCount: 3, + getIgnoreViewType: (view) { + if (view.isSpace) { + return IgnoreViewType.none; + } + if (view.layout == ViewLayoutPB.Chat) { + return IgnoreViewType.hide; + } + if (view.layout != ViewLayoutPB.Document) { + return IgnoreViewType.disable; + } + return IgnoreViewType.none; + }, + ); final popoverController = PopoverController(); @override @@ -59,23 +73,10 @@ class _PromptInputDesktopSelectSourcesButtonState @override Widget build(BuildContext context) { - final userWorkspaceBloc = context.read(); - final userProfile = userWorkspaceBloc.state.userProfile; - final workspaceId = - userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; - - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => SpaceBloc( - userProfile: userProfile, - workspaceId: workspaceId, - )..add(const SpaceEvent.initial(openFirstPage: false)), - ), - BlocProvider.value( - value: cubit, - ), - ], + return ViewSelector( + viewSelectorCubit: BlocProvider.value( + value: cubit, + ), child: BlocBuilder( builder: (context, state) { return AppFlowyPopover( @@ -86,18 +87,18 @@ class _PromptInputDesktopSelectSourcesButtonState controller: popoverController, onOpen: () { context - .read() + .read() .refreshSources(state.spaces, state.currentSpace); }, onClose: () { widget.onUpdateSelectedSources(cubit.selectedSourceIds); context - .read() + .read() .refreshSources(state.spaces, state.currentSpace); }, popupBuilder: (_) { return BlocProvider.value( - value: context.read(), + value: context.read(), child: const _PopoverContent(), ); }, @@ -186,20 +187,26 @@ class _PopoverContent extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { + final theme = AppFlowyTheme.of(context); + return Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(8, 12, 8, 8), - child: SpaceSearchField( - width: 600, - onSearch: (context, value) => - context.read().updateFilter(value), + child: AFTextField( + size: AFTextFieldSize.m, + controller: + context.read().filterTextController, + hintText: LocaleKeys.search_label.tr(), ), ), - _buildDivider(), + AFDivider( + startIndent: theme.spacing.l, + endIndent: theme.spacing.l, + ), Flexible( child: ListView( shrinkWrap: true, @@ -208,9 +215,10 @@ class _PopoverContent extends StatelessWidget { ..._buildSelectedSources(context, state), if (state.selectedSources.isNotEmpty && state.visibleSources.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: _buildDivider(), + AFDivider( + spacing: 4.0, + startIndent: theme.spacing.l, + endIndent: theme.spacing.l, ), ..._buildVisibleSources(context, state), ], @@ -222,70 +230,53 @@ class _PopoverContent extends StatelessWidget { ); } - Widget _buildDivider() { - return const Divider( - height: 1.0, - thickness: 1.0, - indent: 8.0, - endIndent: 8.0, - ); - } - Iterable _buildSelectedSources( BuildContext context, - ChatSettingsState state, + ViewSelectorState state, ) { - return state.selectedSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .map( - (e) => ChatSourceTreeItem( - key: ValueKey( - 'selected_select_sources_tree_item_${e.view.id}', - ), - chatSource: e, - level: 0, - isDescendentOfSpace: e.view.isSpace, - isSelectedSection: true, - onSelected: (chatSource) { - context - .read() - .toggleSelectedStatus(chatSource); - }, - height: 30.0, - ), - ); + return state.selectedSources.map( + (e) => ViewSelectorTreeItem( + key: ValueKey( + 'selected_select_sources_tree_item_${e.view.id}', + ), + viewSelectorItem: e, + level: 0, + isDescendentOfSpace: e.view.isSpace, + isSelectedSection: true, + onSelected: (item) { + context.read().toggleSelectedStatus(item, true); + }, + height: 30.0, + ), + ); } Iterable _buildVisibleSources( BuildContext context, - ChatSettingsState state, + ViewSelectorState state, ) { - return state.visibleSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .map( - (e) => ChatSourceTreeItem( - key: ValueKey( - 'visible_select_sources_tree_item_${e.view.id}', - ), - chatSource: e, - level: 0, - isDescendentOfSpace: e.view.isSpace, - isSelectedSection: false, - onSelected: (chatSource) { - context - .read() - .toggleSelectedStatus(chatSource); - }, - height: 30.0, - ), - ); + return state.visibleSources.map( + (e) => ViewSelectorTreeItem( + key: ValueKey( + 'visible_select_sources_tree_item_${e.view.id}', + ), + viewSelectorItem: e, + level: 0, + isDescendentOfSpace: e.view.isSpace, + isSelectedSection: false, + onSelected: (item) { + context.read().toggleSelectedStatus(item, false); + }, + height: 30.0, + ), + ); } } -class ChatSourceTreeItem extends StatefulWidget { - const ChatSourceTreeItem({ +class ViewSelectorTreeItem extends StatefulWidget { + const ViewSelectorTreeItem({ super.key, - required this.chatSource, + required this.viewSelectorItem, required this.level, required this.isDescendentOfSpace, required this.isSelectedSection, @@ -296,7 +287,7 @@ class ChatSourceTreeItem extends StatefulWidget { this.showCheckbox = true, }); - final ChatSource chatSource; + final ViewSelectorItem viewSelectorItem; /// nested level of the view item final int level; @@ -305,9 +296,9 @@ class ChatSourceTreeItem extends StatefulWidget { final bool isSelectedSection; - final void Function(ChatSource chatSource) onSelected; + final void Function(ViewSelectorItem viewSelectorItem) onSelected; - final void Function(ChatSource chatSource)? onAdd; + final void Function(ViewSelectorItem viewSelectorItem)? onAdd; final bool showSaveButton; @@ -316,16 +307,16 @@ class ChatSourceTreeItem extends StatefulWidget { final bool showCheckbox; @override - State createState() => _ChatSourceTreeItemState(); + State createState() => _ViewSelectorTreeItemState(); } -class _ChatSourceTreeItemState extends State { +class _ViewSelectorTreeItemState extends State { @override Widget build(BuildContext context) { final child = SizedBox( height: widget.height, - child: ChatSourceTreeItemInner( - chatSource: widget.chatSource, + child: ViewSelectorTreeItemInner( + viewSelectorItem: widget.viewSelectorItem, level: widget.level, isDescendentOfSpace: widget.isDescendentOfSpace, isSelectedSection: widget.isSelectedSection, @@ -336,33 +327,30 @@ class _ChatSourceTreeItemState extends State { ), ); - final disabledEnabledChild = - widget.chatSource.ignoreStatus == IgnoreViewType.disable - ? FlowyTooltip( - message: widget.showCheckbox - ? switch (widget.chatSource.view.layout) { - ViewLayoutPB.Document => - LocaleKeys.chat_sourcesLimitReached.tr(), - _ => LocaleKeys.chat_sourceUnsupported.tr(), - } - : "", - child: Opacity( - opacity: 0.5, - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: IgnorePointer(child: child), - ), - ), - ) - : child; + final disabledEnabledChild = widget.viewSelectorItem.isDisabled + ? FlowyTooltip( + message: widget.showCheckbox + ? switch (widget.viewSelectorItem.view.layout) { + ViewLayoutPB.Document => + LocaleKeys.chat_sourcesLimitReached.tr(), + _ => LocaleKeys.chat_sourceUnsupported.tr(), + } + : "", + child: Opacity( + opacity: 0.5, + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: IgnorePointer(child: child), + ), + ), + ) + : child; return ValueListenableBuilder( - valueListenable: widget.chatSource.isExpandedNotifier, + valueListenable: widget.viewSelectorItem.isExpandedNotifier, builder: (context, isExpanded, child) { // filter the child views that should be ignored - final childViews = widget.chatSource.children - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .toList(); + final childViews = widget.viewSelectorItem.children; if (!isExpanded || childViews.isEmpty) { return disabledEnabledChild; @@ -373,11 +361,11 @@ class _ChatSourceTreeItemState extends State { children: [ disabledEnabledChild, ...childViews.map( - (childSource) => ChatSourceTreeItem( + (childSource) => ViewSelectorTreeItem( key: ValueKey( 'select_sources_tree_item_${childSource.view.id}', ), - chatSource: childSource, + viewSelectorItem: childSource, level: widget.level + 1, isDescendentOfSpace: widget.isDescendentOfSpace, isSelectedSection: widget.isSelectedSection, @@ -395,10 +383,10 @@ class _ChatSourceTreeItemState extends State { } } -class ChatSourceTreeItemInner extends StatelessWidget { - const ChatSourceTreeItemInner({ +class ViewSelectorTreeItemInner extends StatelessWidget { + const ViewSelectorTreeItemInner({ super.key, - required this.chatSource, + required this.viewSelectorItem, required this.level, required this.isDescendentOfSpace, required this.isSelectedSection, @@ -408,34 +396,27 @@ class ChatSourceTreeItemInner extends StatelessWidget { this.onAdd, }); - final ChatSource chatSource; + final ViewSelectorItem viewSelectorItem; final int level; final bool isDescendentOfSpace; final bool isSelectedSection; final bool showCheckbox; final bool showSaveButton; - final void Function(ChatSource)? onSelected; - final void Function(ChatSource)? onAdd; + final void Function(ViewSelectorItem)? onSelected; + final void Function(ViewSelectorItem)? onAdd; @override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - if (!isSelectedSection) { - onSelected?.call(chatSource); - } - }, + onTap: () => onSelected?.call(viewSelectorItem), child: FlowyHover( - cursor: isSelectedSection ? SystemMouseCursors.basic : null, style: HoverStyle( - hoverColor: isSelectedSection - ? Colors.transparent - : AFThemeExtension.of(context).lightGreyHover, + hoverColor: AFThemeExtension.of(context).lightGreyHover, ), builder: (context, onHover) { final isSaveButtonVisible = - showSaveButton && !chatSource.view.isSpace; + showSaveButton && !viewSelectorItem.view.isSpace; final isAddButtonVisible = onAdd != null; return Row( children: [ @@ -443,26 +424,26 @@ class ChatSourceTreeItemInner extends StatelessWidget { HSpace(max(20.0 * level - (isDescendentOfSpace ? 2 : 0), 0)), // builds the >, ^ or ยท button ToggleIsExpandedButton( - chatSource: chatSource, + viewSelectorItem: viewSelectorItem, isSelectedSection: isSelectedSection, ), const HSpace(2.0), // checkbox - if (!chatSource.view.isSpace && showCheckbox) ...[ + if (!viewSelectorItem.view.isSpace && showCheckbox) ...[ SourceSelectedStatusCheckbox( - chatSource: chatSource, + viewSelectorItem: viewSelectorItem, ), const HSpace(4.0), ], // icon MentionViewIcon( - view: chatSource.view, + view: viewSelectorItem.view, ), const HSpace(6.0), // title Expanded( child: FlowyText( - chatSource.view.nameOrDefault, + viewSelectorItem.view.nameOrDefault, overflow: TextOverflow.ellipsis, fontSize: 14.0, figmaLineHeight: 18.0, @@ -479,7 +460,7 @@ class ChatSourceTreeItemInner extends StatelessWidget { size: const Size.square(16), color: Theme.of(context).hintColor, ), - onPressed: () => onSelected?.call(chatSource), + onPressed: () => onSelected?.call(viewSelectorItem), ), if (isSaveButtonVisible && isAddButtonVisible) const HSpace(4.0), @@ -492,7 +473,7 @@ class ChatSourceTreeItemInner extends StatelessWidget { size: const Size.square(16), color: Theme.of(context).hintColor, ), - onPressed: () => onAdd?.call(chatSource), + onPressed: () => onAdd?.call(viewSelectorItem), ), const HSpace(4.0), ], @@ -507,27 +488,30 @@ class ChatSourceTreeItemInner extends StatelessWidget { class ToggleIsExpandedButton extends StatelessWidget { const ToggleIsExpandedButton({ super.key, - required this.chatSource, + required this.viewSelectorItem, required this.isSelectedSection, }); - final ChatSource chatSource; + final ViewSelectorItem viewSelectorItem; final bool isSelectedSection; @override Widget build(BuildContext context) { - if (isReferencedDatabaseView(chatSource.view, chatSource.parentView)) { + if (isReferencedDatabaseView( + viewSelectorItem.view, + viewSelectorItem.parentView, + )) { return const _DotIconWidget(); } - if (chatSource.children.isEmpty) { + if (viewSelectorItem.children.isEmpty) { return const SizedBox.square(dimension: 16.0); } return FlowyHover( child: GestureDetector( child: ValueListenableBuilder( - valueListenable: chatSource.isExpandedNotifier, + valueListenable: viewSelectorItem.isExpandedNotifier, builder: (context, value, _) => FlowySvg( value ? FlowySvgs.view_item_expand_s @@ -536,8 +520,8 @@ class ToggleIsExpandedButton extends StatelessWidget { ), ), onTap: () => context - .read() - .toggleIsExpanded(chatSource, isSelectedSection), + .read() + .toggleIsExpanded(viewSelectorItem, isSelectedSection), ), ); } @@ -565,20 +549,20 @@ class _DotIconWidget extends StatelessWidget { class SourceSelectedStatusCheckbox extends StatelessWidget { const SourceSelectedStatusCheckbox({ super.key, - required this.chatSource, + required this.viewSelectorItem, }); - final ChatSource chatSource; + final ViewSelectorItem viewSelectorItem; @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: chatSource.selectedStatusNotifier, + valueListenable: viewSelectorItem.selectedStatusNotifier, builder: (context, selectedStatus, _) => FlowySvg( switch (selectedStatus) { - SourceSelectedStatus.unselected => FlowySvgs.uncheck_s, - SourceSelectedStatus.selected => FlowySvgs.check_filled_s, - SourceSelectedStatus.partiallySelected => FlowySvgs.check_partial_s, + ViewSelectedStatus.unselected => FlowySvgs.uncheck_s, + ViewSelectedStatus.selected => FlowySvgs.check_filled_s, + ViewSelectedStatus.partiallySelected => FlowySvgs.check_partial_s, }, size: const Size.square(18.0), blendMode: null, diff --git a/frontend/appflowy_flutter/lib/ai/widgets/view_selector.dart b/frontend/appflowy_flutter/lib/ai/widgets/view_selector.dart new file mode 100644 index 0000000000..2875a717a3 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/view_selector.dart @@ -0,0 +1,39 @@ +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; +import 'package:appflowy/workspace/application/user/prelude.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/single_child_widget.dart'; + +class ViewSelector extends StatelessWidget { + const ViewSelector({ + super.key, + required this.viewSelectorCubit, + required this.child, + }); + + final SingleChildWidget viewSelectorCubit; + final Widget child; + + @override + Widget build(BuildContext context) { + final userWorkspaceBloc = context.read(); + final userProfile = userWorkspaceBloc.state.userProfile; + final workspaceId = + userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; + + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) { + return SpaceBloc( + userProfile: userProfile, + workspaceId: workspaceId, + )..add(const SpaceEvent.initial(openFirstPage: false)); + }, + ), + viewSelectorCubit, + ], + child: child, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_chat_prelude.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_chat_prelude.dart index 5275c643c2..f67b3979ae 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_chat_prelude.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_chat_prelude.dart @@ -11,5 +11,4 @@ export 'chat_message_service.dart'; export 'chat_message_stream.dart'; export 'chat_notification.dart'; export 'chat_select_message_bloc.dart'; -export 'chat_select_sources_cubit.dart'; export 'chat_user_message_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart index bebcc01d6d..8c878acade 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart @@ -1,17 +1,18 @@ import 'dart:async'; +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/ai/widgets/view_selector.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_select_message_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; -import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -133,23 +134,20 @@ class _SaveToPageButtonState extends State { @override Widget build(BuildContext context) { - final userWorkspaceBloc = context.read(); - final userProfile = userWorkspaceBloc.state.userProfile; - final workspaceId = - userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; - - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => SpaceBloc( - userProfile: userProfile, - workspaceId: workspaceId, - )..add(const SpaceEvent.initial(openFirstPage: false)), + return ViewSelector( + viewSelectorCubit: BlocProvider( + create: (context) => ViewSelectorCubit( + getIgnoreViewType: (view) { + if (view.isSpace) { + return IgnoreViewType.none; + } + if (view.layout != ViewLayoutPB.Document) { + return IgnoreViewType.hide; + } + return IgnoreViewType.none; + }, ), - BlocProvider( - create: (context) => ChatSettingsCubit(hideDisabled: true), - ), - ], + ), child: BlocSelector( selector: (state) => state.currentSpace, builder: (context, spaceView) { @@ -189,7 +187,7 @@ class _SaveToPageButtonState extends State { } else { if (spaceView != null) { context - .read() + .read() .refreshSources([spaceView], spaceView); } popoverController.show(); @@ -210,7 +208,7 @@ class _SaveToPageButtonState extends State { Widget buildPopover(BuildContext context) { return BlocProvider.value( - value: context.read(), + value: context.read(), child: SaveToPagePopoverContent( onAddToNewPage: (parentViewId) async { await addMessageToNewPage(context, parentViewId); diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart index cab3d486cf..292c66f165 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'dart:convert'; import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/ai/widgets/view_selector.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; @@ -15,16 +15,15 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; -import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_result/appflowy_result.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -515,23 +514,20 @@ class _SaveToPageButtonState extends State { @override Widget build(BuildContext context) { - final userWorkspaceBloc = context.read(); - final userProfile = userWorkspaceBloc.state.userProfile; - final workspaceId = - userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; - - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => SpaceBloc( - userProfile: userProfile, - workspaceId: workspaceId, - )..add(const SpaceEvent.initial(openFirstPage: false)), + return ViewSelector( + viewSelectorCubit: BlocProvider( + create: (context) => ViewSelectorCubit( + getIgnoreViewType: (view) { + if (view.isSpace) { + return IgnoreViewType.none; + } + if (view.layout != ViewLayoutPB.Document) { + return IgnoreViewType.hide; + } + return IgnoreViewType.none; + }, ), - BlocProvider( - create: (context) => ChatSettingsCubit(hideDisabled: true), - ), - ], + ), child: BlocSelector( selector: (state) => state.currentSpace, builder: (context, spaceView) { @@ -546,7 +542,7 @@ class _SaveToPageButtonState extends State { onClose: () { if (spaceView != null) { context - .read() + .read() .refreshSources([spaceView], spaceView); } widget.onOverrideVisibility?.call(false); @@ -584,7 +580,7 @@ class _SaveToPageButtonState extends State { widget.onOverrideVisibility?.call(true); if (spaceView != null) { context - .read() + .read() .refreshSources([spaceView], spaceView); } popoverController.show(); @@ -596,7 +592,7 @@ class _SaveToPageButtonState extends State { Widget buildPopover(BuildContext context) { return BlocProvider.value( - value: context.read(), + value: context.read(), child: SaveToPagePopoverContent( onAddToNewPage: (parentViewId) { addMessageToNewPage(context, parentViewId); @@ -697,8 +693,10 @@ class SaveToPagePopoverContent extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { + final theme = AppFlowyTheme.of(context); + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -707,22 +705,26 @@ class SaveToPagePopoverContent extends StatelessWidget { margin: const EdgeInsets.fromLTRB(12, 8, 12, 4), child: Align( alignment: AlignmentDirectional.centerStart, - child: FlowyText( + child: Text( LocaleKeys.chat_addToPageTitle.tr(), - fontSize: 12.0, - color: Theme.of(context).hintColor, + style: theme.textStyle.caption + .standard(color: theme.textColorScheme.secondary), ), ), ), Padding( padding: const EdgeInsets.only(left: 12, right: 12, bottom: 8), - child: SpaceSearchField( - width: 600, - onSearch: (context, value) => - context.read().updateFilter(value), + child: AFTextField( + controller: + context.read().filterTextController, + hintText: LocaleKeys.search_label.tr(), + size: AFTextFieldSize.m, ), ), - _buildDivider(), + AFDivider( + startIndent: theme.spacing.l, + endIndent: theme.spacing.l, + ), Expanded( child: ListView( shrinkWrap: true, @@ -736,44 +738,33 @@ class SaveToPagePopoverContent extends StatelessWidget { ); } - Widget _buildDivider() { - return const Divider( - height: 1.0, - thickness: 1.0, - indent: 12.0, - endIndent: 12.0, - ); - } - Iterable _buildVisibleSources( BuildContext context, - ChatSettingsState state, + ViewSelectorState state, ) { - return state.visibleSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .map( - (e) => ChatSourceTreeItem( - key: ValueKey( - 'save_to_page_tree_item_${e.view.id}', - ), - chatSource: e, - level: 0, - isDescendentOfSpace: e.view.isSpace, - isSelectedSection: false, - showCheckbox: false, - showSaveButton: true, - onSelected: (source) { - if (source.view.isSpace) { - onAddToNewPage(source.view.id); - } else { - onAddToExistingPage(source.view.id); - } - }, - onAdd: (source) { - onAddToNewPage(source.view.id); - }, - height: 30.0, - ), - ); + return state.visibleSources.map( + (e) => ViewSelectorTreeItem( + key: ValueKey( + 'save_to_page_tree_item_${e.view.id}', + ), + viewSelectorItem: e, + level: 0, + isDescendentOfSpace: e.view.isSpace, + isSelectedSection: false, + showCheckbox: false, + showSaveButton: true, + onSelected: (source) { + if (source.view.isSpace) { + onAddToNewPage(source.view.id); + } else { + onAddToExistingPage(source.view.id); + } + }, + onAdd: (source) { + onAddToNewPage(source.view.id); + }, + height: 30.0, + ), + ); } }