feat: support moving view across sections (#5015)

This commit is contained in:
Lucas.Xu 2024-04-01 14:27:29 +08:00 committed by GitHub
parent 893d23d6a3
commit 723423d423
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 140 additions and 2 deletions

View File

@ -207,6 +207,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
),
);
},
updateViewVisibility: (value) async {
final view = value.view;
await ViewBackendService.updateViewsVisibility(
[view],
value.isPublic,
);
},
);
},
);
@ -370,6 +377,8 @@ class ViewEvent with _$ViewEvent {
) = ViewDidUpdate;
const factory ViewEvent.viewUpdateChildView(ViewPB result) =
ViewUpdateChildView;
const factory ViewEvent.updateViewVisibility(ViewPB view, bool isPublic) =
UpdateViewVisibility;
}
@freezed

View File

@ -280,4 +280,15 @@ class ViewBackendService {
);
});
}
static Future<FlowyResult<void, FlowyError>> updateViewsVisibility(
List<ViewPB> views,
bool isPublic,
) async {
final payload = UpdateViewVisibilityStatusPayloadPB(
viewIds: views.map((e) => e.id).toList(),
isPublic: isPublic,
);
return FolderEventUpdateViewVisibilityStatus(payload).send();
}
}

View File

@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_folder_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
@ -95,6 +96,25 @@ class SectionFolder extends StatelessWidget {
isHoverEnabled: isHoverEnabled,
),
),
if (views.isEmpty)
ViewItem(
categoryType: categoryType,
view: ViewPB(
parentViewId: context
.read<UserWorkspaceBloc>()
.state
.currentWorkspace
?.workspaceId ??
'',
),
level: 0,
leftPadding: 16,
isFeedback: false,
onSelected: (_) {},
onTertiarySelected: (_) {},
isHoverEnabled: isHoverEnabled,
isPlaceholder: true,
),
],
);
},

View File

@ -26,6 +26,7 @@ class DraggableViewItem extends StatefulWidget {
this.topHighlightColor,
this.bottomHighlightColor,
this.onDragging,
this.onMove,
});
final Widget child;
@ -36,6 +37,7 @@ class DraggableViewItem extends StatefulWidget {
final Color? topHighlightColor;
final Color? bottomHighlightColor;
final void Function(bool isDragging)? onDragging;
final void Function(ViewPB from, ViewPB to)? onMove;
@override
State<DraggableViewItem> createState() => _DraggableViewItemState();
@ -189,6 +191,11 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
return;
}
if (widget.onMove != null) {
widget.onMove?.call(from, to);
return;
}
final fromSection = getViewSection(from);
final toSection = getViewSection(to);

View File

@ -43,6 +43,7 @@ class ViewItem extends StatelessWidget {
required this.isFeedback,
this.height = 28.0,
this.isHoverEnabled = true,
this.isPlaceholder = false,
});
final ViewPB view;
@ -78,6 +79,10 @@ class ViewItem extends StatelessWidget {
final bool isHoverEnabled;
// all the view movement depends on the [ViewItem] widget, so we have to add a
// placeholder widget to receive the drop event when moving view across sections.
final bool isPlaceholder;
@override
Widget build(BuildContext context) {
return BlocProvider(
@ -105,6 +110,7 @@ class ViewItem extends StatelessWidget {
isFeedback: isFeedback,
height: height,
isHoverEnabled: isHoverEnabled,
isPlaceholder: isPlaceholder,
);
},
),
@ -132,6 +138,7 @@ class InnerViewItem extends StatelessWidget {
required this.isFeedback,
required this.height,
this.isHoverEnabled = true,
this.isPlaceholder = false,
});
final ViewPB view;
@ -154,6 +161,7 @@ class InnerViewItem extends StatelessWidget {
final double height;
final bool isHoverEnabled;
final bool isPlaceholder;
@override
Widget build(BuildContext context) {
@ -170,6 +178,7 @@ class InnerViewItem extends StatelessWidget {
leftPadding: leftPadding,
isFeedback: isFeedback,
height: height,
isPlaceholder: isPlaceholder,
);
// if the view is expanded and has child views, render its child views
@ -188,6 +197,7 @@ class InnerViewItem extends StatelessWidget {
isDraggable: isDraggable,
leftPadding: leftPadding,
isFeedback: isFeedback,
isPlaceholder: isPlaceholder,
);
}).toList();
@ -222,14 +232,17 @@ class InnerViewItem extends StatelessWidget {
}
// wrap the child with DraggableItem if isDraggable is true
if (isDraggable && !isReferencedDatabaseView(view, parentView)) {
if ((isDraggable || isPlaceholder) &&
!isReferencedDatabaseView(view, parentView)) {
child = DraggableViewItem(
isFirstChild: isFirstChild,
view: view,
child: child,
onDragging: (isDragging) {
_isDragging = isDragging;
},
onMove: isPlaceholder
? (from, to) => _moveViewCrossSection(context, from, to)
: null,
feedback: (context) {
return ViewItem(
view: view,
@ -243,6 +256,7 @@ class InnerViewItem extends StatelessWidget {
isFeedback: true,
);
},
child: child,
);
} else {
// keep the same height of the DraggableItem
@ -254,6 +268,37 @@ class InnerViewItem extends StatelessWidget {
return child;
}
void _moveViewCrossSection(
BuildContext context,
ViewPB from,
ViewPB to,
) {
if (isReferencedDatabaseView(view, parentView)) {
return;
}
final fromSection = categoryType == FolderCategoryType.public
? ViewSectionPB.Private
: ViewSectionPB.Public;
final toSection = categoryType == FolderCategoryType.public
? ViewSectionPB.Public
: ViewSectionPB.Private;
context.read<ViewBloc>().add(
ViewEvent.move(
from,
to.parentViewId,
null,
fromSection,
toSection,
),
);
context.read<ViewBloc>().add(
ViewEvent.updateViewVisibility(
from,
categoryType == FolderCategoryType.public,
),
);
}
}
class SingleInnerViewItem extends StatefulWidget {
@ -272,6 +317,7 @@ class SingleInnerViewItem extends StatefulWidget {
required this.isFeedback,
required this.height,
this.isHoverEnabled = true,
this.isPlaceholder = false,
});
final ViewPB view;
@ -291,6 +337,7 @@ class SingleInnerViewItem extends StatefulWidget {
final double height;
final bool isHoverEnabled;
final bool isPlaceholder;
@override
State<SingleInnerViewItem> createState() => _SingleInnerViewItemState();
@ -305,6 +352,13 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
final isSelected =
getIt<MenuSharedState>().latestOpenView?.id == widget.view.id;
if (widget.isPlaceholder) {
return const SizedBox(
height: 4,
width: double.infinity,
);
}
if (widget.isFeedback || !widget.isHoverEnabled) {
return _buildViewItem(
false,

View File

@ -475,6 +475,15 @@ pub struct UpdateRecentViewPayloadPB {
pub add_in_recent: bool,
}
#[derive(Default, ProtoBuf)]
pub struct UpdateViewVisibilityStatusPayloadPB {
#[pb(index = 1)]
pub view_ids: Vec<String>,
#[pb(index = 2)]
pub is_public: bool,
}
// impl<'de> Deserialize<'de> for ViewDataType {
// fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
// where

View File

@ -363,3 +363,14 @@ pub(crate) async fn reload_workspace_handler(
folder.reload_workspace().await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, folder), err)]
pub(crate) async fn update_view_visibility_status_handler(
data: AFPluginData<UpdateViewVisibilityStatusPayloadPB>,
folder: AFPluginState<Weak<FolderManager>>,
) -> Result<(), FlowyError> {
let folder = upgrade_folder(folder)?;
let params = data.into_inner();
folder.set_views_visibility(params.view_ids, params.is_public);
Ok(())
}

View File

@ -40,6 +40,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
.event(FolderEvent::ReloadWorkspace, reload_workspace_handler)
.event(FolderEvent::ReadPrivateViews, read_private_views_handler)
.event(FolderEvent::ReadCurrentWorkspaceViews, get_current_workspace_views_handler)
.event(FolderEvent::UpdateViewVisibilityStatus, update_view_visibility_status_handler)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
@ -166,4 +167,7 @@ pub enum FolderEvent {
/// Only the first level of child views are included.
#[event(output = "RepeatedViewPB")]
ReadCurrentWorkspaceViews = 40,
#[event(input = "UpdateViewVisibilityStatusPayloadPB")]
UpdateViewVisibilityStatus = 41,
}

View File

@ -1120,6 +1120,19 @@ impl FolderManager {
&self.cloud_service
}
pub fn set_views_visibility(&self, view_ids: Vec<String>, is_public: bool) {
self.with_folder(
|| (),
|folder| {
if is_public {
folder.delete_private_view_ids(view_ids);
} else {
folder.add_private_view_ids(view_ids);
}
},
);
}
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
self.with_folder(Vec::new, |folder| {
let trash_ids = folder