Skip to content

Commit

Permalink
chore: adjust select sources (AppFlowy-IO#7019)
Browse files Browse the repository at this point in the history
* chore: adjust select sources

* chore: restrict number of selected highest-level documents

* chore: ignore chat views

* chore: code cleanup
  • Loading branch information
richardshiue authored Dec 20, 2024
1 parent ddcdd54 commit 30131fd
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 190 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:bloc/bloc.dart';
Expand All @@ -8,6 +9,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';

part 'chat_select_sources_cubit.freezed.dart';

const int _kMaxSelectedParentPageCount = 3;

enum SourceSelectedStatus {
unselected,
selected,
Expand All @@ -25,17 +28,21 @@ class ChatSource {
required this.children,
required bool isExpanded,
required SourceSelectedStatus selectedStatus,
required IgnoreViewType ignoreStatus,
}) : isExpandedNotifier = ValueNotifier(isExpanded),
selectedStatusNotifier = ValueNotifier(selectedStatus);
selectedStatusNotifier = ValueNotifier(selectedStatus),
ignoreStatusNotifier = ValueNotifier(ignoreStatus);

final ViewPB view;
final ViewPB? parentView;
final List<ChatSource> children;
final ValueNotifier<bool> isExpandedNotifier;
final ValueNotifier<SourceSelectedStatus> selectedStatusNotifier;
final ValueNotifier<IgnoreViewType> ignoreStatusNotifier;

bool get isExpanded => isExpandedNotifier.value;
SourceSelectedStatus get selectedStatus => selectedStatusNotifier.value;
IgnoreViewType get ignoreStatus => ignoreStatusNotifier.value;

void toggleIsExpanded() {
isExpandedNotifier.value = !isExpanded;
Expand All @@ -46,6 +53,7 @@ class ChatSource {
view: view,
parentView: parentView,
children: children.map<ChatSource>((child) => child.copy()).toList(),
ignoreStatus: ignoreStatus,
isExpanded: isExpanded,
selectedStatus: selectedStatus,
);
Expand All @@ -64,12 +72,30 @@ class ChatSource {
return null;
}

void resetIgnoreViewTypeRecursive() {
ignoreStatusNotifier.value = view.layout.isDocumentView
? IgnoreViewType.none
: IgnoreViewType.disable;

for (final child in children) {
child.resetIgnoreViewTypeRecursive();
}
}

void updateIgnoreViewTypeRecursive(IgnoreViewType newIgnoreViewType) {
ignoreStatusNotifier.value = newIgnoreViewType;
for (final child in children) {
child.updateIgnoreViewTypeRecursive(newIgnoreViewType);
}
}

void dispose() {
for (final child in children) {
child.dispose();
}
isExpandedNotifier.dispose();
selectedStatusNotifier.dispose();
ignoreStatusNotifier.dispose();
}
}

Expand All @@ -87,54 +113,12 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
selectedSourceIds = [...newSelectedSourceIds];
}

void updateSelectedStatus() {
if (source == null) {
return;
}
if (source != null) {
_recursiveUpdateSelectedStatus(source!);
}
for (final source in state.visibleSources) {
_recursiveUpdateSelectedStatus(source);
}
final selected = _buildSelectedSources(source!).toList();
emit(state.copyWith(selectedSources: selected));
selectedSources
..forEach((e) => e.dispose())
..clear()
..addAll(selected.map((e) => e.copy()));
}

SourceSelectedStatus _recursiveUpdateSelectedStatus(ChatSource chatSource) {
SourceSelectedStatus selectedStatus = SourceSelectedStatus.unselected;

int selectedCount = 0;
for (final childSource in chatSource.children) {
final childStatus = _recursiveUpdateSelectedStatus(childSource);
if (childStatus.isSelected) {
selectedCount++;
}
}

final isThisSourceSelected = selectedSourceIds.contains(chatSource.view.id);
final areAllChildrenSelectedOrNoChildren =
chatSource.children.length == selectedCount;
final isAnyChildNotUnselected =
chatSource.children.any((e) => !e.selectedStatus.isUnselected);

if (isThisSourceSelected && areAllChildrenSelectedOrNoChildren) {
selectedStatus = SourceSelectedStatus.selected;
} else if (isThisSourceSelected || isAnyChildNotUnselected) {
selectedStatus = SourceSelectedStatus.partiallySelected;
}

chatSource.selectedStatusNotifier.value = selectedStatus;
return selectedStatus;
}

void refreshSources(ViewPB view) async {
filter = "";
final newSource = await _recursiveBuild(view, null);

_restrictSelectionIfNecessary(newSource.children);

final selected = _buildSelectedSources(newSource).toList();

newSource.toggleIsExpanded();
Expand Down Expand Up @@ -167,6 +151,9 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {

if (childrenViews != null) {
for (final childView in childrenViews) {
if (childView.layout == ViewLayoutPB.Chat) {
continue;
}
final childChatSource = await _recursiveBuild(childView, view);
if (childChatSource.selectedStatus.isSelected) {
selectedCount++;
Expand All @@ -192,11 +179,28 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
view: view,
parentView: parentView,
children: children,
ignoreStatus: view.layout.isDocumentView
? IgnoreViewType.none
: IgnoreViewType.disable,
isExpanded: false,
selectedStatus: selectedStatus,
);
}

void _restrictSelectionIfNecessary(List<ChatSource> sources) {
for (final source in sources) {
source.resetIgnoreViewTypeRecursive();
}
if (sources.where((e) => !e.selectedStatus.isUnselected).length >=
_kMaxSelectedParentPageCount) {
sources
.where((e) => e.selectedStatus == SourceSelectedStatus.unselected)
.forEach(
(e) => e.updateIgnoreViewTypeRecursive(IgnoreViewType.disable),
);
}
}

void updateFilter(String filter) {
this.filter = filter;
for (final source in state.visibleSources) {
Expand Down Expand Up @@ -236,6 +240,7 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
view: chatSource.view,
parentView: chatSource.parentView,
children: childrenResults,
ignoreStatus: chatSource.ignoreStatus,
isExpanded: chatSource.isExpanded,
selectedStatus: chatSource.selectedStatus,
)
Expand All @@ -250,20 +255,24 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
children.addAll(_buildSelectedSources(childSource));
}

return chatSource.selectedStatus.isUnselected
? children
: [
return selectedSourceIds.contains(chatSource.view.id)
? [
ChatSource(
view: chatSource.view,
parentView: chatSource.parentView,
children: children,
ignoreStatus: chatSource.ignoreStatus,
selectedStatus: chatSource.selectedStatus,
isExpanded: true,
),
];
]
: children;
}

void toggleSelectedStatus(ChatSource chatSource) {
if (chatSource.view.isSpace) {
return;
}
final allIds = _recursiveGetSourceIds(chatSource);

if (chatSource.selectedStatus.isUnselected ||
Expand All @@ -281,28 +290,82 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
}
}
}

updateSelectedStatus();
}

List<String> _recursiveGetSourceIds(ChatSource chatSource) {
return [
if (chatSource.view.layout.isDocumentView) chatSource.view.id,
for (final childSource in chatSource.children)
..._recursiveGetSourceIds(childSource),
];
}

void updateSelectedStatus() {
if (source == null) {
return;
}
_recursiveUpdateSelectedStatus(source!);
_restrictSelectionIfNecessary(source!.children);
for (final visibleSource in state.visibleSources) {
visibleSource.dispose();
}
final visible = [_buildSearchResults(source!)].nonNulls.toList();

selectedSources
..forEach((e) => e.dispose())
..clear()
..addAll(_buildSelectedSources(source!));
emit(
state.copyWith(
visibleSources: visible,
selectedSources:
selectedSources.map(_buildSearchResults).nonNulls.toList(),
),
);
}

SourceSelectedStatus _recursiveUpdateSelectedStatus(ChatSource chatSource) {
SourceSelectedStatus selectedStatus = SourceSelectedStatus.unselected;

int selectedCount = 0;
for (final childSource in chatSource.children) {
final childStatus = _recursiveUpdateSelectedStatus(childSource);
if (childStatus.isSelected) {
selectedCount++;
}
}

final isThisSourceSelected = selectedSourceIds.contains(chatSource.view.id);
final areAllChildrenSelectedOrNoChildren =
chatSource.children.length == selectedCount;
final isAnyChildNotUnselected =
chatSource.children.any((e) => !e.selectedStatus.isUnselected);

if (isThisSourceSelected && areAllChildrenSelectedOrNoChildren) {
selectedStatus = SourceSelectedStatus.selected;
} else if (isThisSourceSelected || isAnyChildNotUnselected) {
selectedStatus = SourceSelectedStatus.partiallySelected;
}

chatSource.selectedStatusNotifier.value = selectedStatus;
return selectedStatus;
}

void toggleIsExpanded(ChatSource chatSource, bool isSelectedSection) {
chatSource.toggleIsExpanded();
if (isSelectedSection) {
for (final source in selectedSources) {
source.findChildBySourceId(chatSource.view.id)?.toggleIsExpanded();
for (final selectedSource in selectedSources) {
selectedSource
.findChildBySourceId(chatSource.view.id)
?.toggleIsExpanded();
}
} else {
source?.findChildBySourceId(chatSource.view.id)?.toggleIsExpanded();
}
}

List<String> _recursiveGetSourceIds(ChatSource chatSource) {
return [
if (chatSource.view.layout.isDocumentView && !chatSource.view.isSpace)
chatSource.view.id,
for (final childSource in chatSource.children)
..._recursiveGetSourceIds(childSource),
];
}

@override
Future<void> close() {
source?.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ class _PromptInputMobileSelectSourcesButtonState
),
],
),
onTap: () {
onTap: () async {
if (spaceView != null) {
context
.read<ChatSettingsCubit>()
.refreshSources(spaceView);
}
showMobileBottomSheet<void>(
await showMobileBottomSheet<void>(
context,
backgroundColor: Theme.of(context).colorScheme.surface,
maxChildSize: 0.98,
Expand All @@ -115,14 +115,15 @@ class _PromptInputMobileSelectSourcesButtonState
value: cubit,
child: _MobileSelectSourcesSheetBody(
scrollController: scrollController,
onUpdateSelectedSources:
widget.onUpdateSelectedSources,
),
),
);
},
builder: (context) => const SizedBox.shrink(),
);
if (context.mounted) {
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
}
},
),
);
Expand All @@ -137,11 +138,9 @@ class _PromptInputMobileSelectSourcesButtonState
class _MobileSelectSourcesSheetBody extends StatefulWidget {
const _MobileSelectSourcesSheetBody({
required this.scrollController,
required this.onUpdateSelectedSources,
});

final ScrollController scrollController;
final void Function(List<String>) onUpdateSelectedSources;

@override
State<_MobileSelectSourcesSheetBody> createState() =>
Expand Down Expand Up @@ -205,32 +204,27 @@ class _MobileSelectSourcesSheetBodyState
),
BlocBuilder<ChatSettingsCubit, ChatSettingsState>(
builder: (context, state) {
final sources = state.visibleSources
.where((e) => e.ignoreStatus != IgnoreViewType.hide);
return SliverList(
delegate: SliverChildBuilderDelegate(
childCount: state.visibleSources.length,
childCount: sources.length,
(context, index) {
final source = sources.elementAt(index);
return ChatSourceTreeItem(
key: ValueKey(
'visible_select_sources_tree_item_${state.visibleSources[index].view.id}',
'visible_select_sources_tree_item_${source.view.id}',
),
chatSource: state.visibleSources[index],
chatSource: source,
level: 0,
isDescendentOfSpace: source.view.isSpace,
isSelectedSection: false,
onSelected: (chatSource) {
final cubit = context.read<ChatSettingsCubit>();
cubit.toggleSelectedStatus(chatSource);
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
context
.read<ChatSettingsCubit>()
.toggleSelectedStatus(chatSource);
},
height: 40.0,
visibilityGetter: (view) {
if (view.id == context.read<ChatSettingsCubit>().chatId) {
return IgnoreViewType.hide;
}

return view.layout.isDocumentView
? IgnoreViewType.none
: IgnoreViewType.disable;
},
);
},
),
Expand Down
Loading

0 comments on commit 30131fd

Please sign in to comment.