forked from AppFlowy-IO/AppFlowy
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support moving page across spaces (AppFlowy-IO#5618)
* feat: support moving page across spaces * feat: refacotor move api * feat: filter the database views * feat: support searching in move page menu
- Loading branch information
Showing
11 changed files
with
642 additions
and
216 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_search_bloc.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import 'package:appflowy/workspace/application/view/view_service.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; | ||
import 'package:appflowy_result/appflowy_result.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
|
||
part 'space_search_bloc.freezed.dart'; | ||
|
||
class SpaceSearchBloc extends Bloc<SpaceSearchEvent, SpaceSearchState> { | ||
SpaceSearchBloc() : super(SpaceSearchState.initial()) { | ||
on<SpaceSearchEvent>( | ||
(event, emit) async { | ||
await event.when( | ||
initial: () async { | ||
_allViews = await ViewBackendService.getAllViews().fold( | ||
(s) => s.items, | ||
(_) => <ViewPB>[], | ||
); | ||
}, | ||
search: (query) { | ||
if (query.isEmpty) { | ||
emit( | ||
state.copyWith( | ||
queryResults: null, | ||
), | ||
); | ||
} else { | ||
final queryResults = _allViews.where( | ||
(view) => view.name.toLowerCase().contains(query.toLowerCase()), | ||
); | ||
emit( | ||
state.copyWith( | ||
queryResults: queryResults.toList(), | ||
), | ||
); | ||
} | ||
}, | ||
); | ||
}, | ||
); | ||
} | ||
|
||
late final List<ViewPB> _allViews; | ||
} | ||
|
||
@freezed | ||
class SpaceSearchEvent with _$SpaceSearchEvent { | ||
const factory SpaceSearchEvent.initial() = _Initial; | ||
const factory SpaceSearchEvent.search(String query) = _Search; | ||
} | ||
|
||
@freezed | ||
class SpaceSearchState with _$SpaceSearchState { | ||
const factory SpaceSearchState({ | ||
List<ViewPB>? queryResults, | ||
}) = _SpaceSearchState; | ||
|
||
factory SpaceSearchState.initial() => const SpaceSearchState(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
190 changes: 190 additions & 0 deletions
190
...appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/move_to/move_page_menu.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; | ||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; | ||
import 'package:appflowy/workspace/application/sidebar/space/space_search_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-user/protobuf.dart'; | ||
import 'package:appflowy_editor/appflowy_editor.dart'; | ||
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
|
||
class MovePageMenu extends StatefulWidget { | ||
const MovePageMenu({ | ||
super.key, | ||
required this.sourceView, | ||
required this.userProfile, | ||
required this.workspaceId, | ||
required this.onSelected, | ||
}); | ||
|
||
final ViewPB sourceView; | ||
final UserProfilePB userProfile; | ||
final String workspaceId; | ||
final void Function(ViewPB view) onSelected; | ||
|
||
@override | ||
State<MovePageMenu> createState() => _MovePageMenuState(); | ||
} | ||
|
||
class _MovePageMenuState extends State<MovePageMenu> { | ||
final isExpandedNotifier = PropertyValueNotifier(true); | ||
final isHoveredNotifier = ValueNotifier(true); | ||
|
||
@override | ||
void dispose() { | ||
isExpandedNotifier.dispose(); | ||
isHoveredNotifier.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return MultiBlocProvider( | ||
providers: [ | ||
BlocProvider( | ||
create: (context) => SpaceBloc() | ||
..add( | ||
SpaceEvent.initial( | ||
widget.userProfile, | ||
widget.workspaceId, | ||
openFirstPage: false, | ||
), | ||
), | ||
), | ||
BlocProvider( | ||
create: (context) => SpaceSearchBloc() | ||
..add( | ||
const SpaceSearchEvent.initial(), | ||
), | ||
), | ||
], | ||
child: BlocBuilder<SpaceBloc, SpaceState>( | ||
builder: (context, state) { | ||
final space = state.currentSpace; | ||
if (space == null) { | ||
return const SizedBox.shrink(); | ||
} | ||
return Column( | ||
children: [ | ||
SpaceSearchField( | ||
width: 240, | ||
onSearch: (context, value) { | ||
context.read<SpaceSearchBloc>().add( | ||
SpaceSearchEvent.search( | ||
value, | ||
), | ||
); | ||
}, | ||
), | ||
const VSpace(10), | ||
BlocBuilder<SpaceSearchBloc, SpaceSearchState>( | ||
builder: (context, state) { | ||
if (state.queryResults == null) { | ||
return Expanded( | ||
child: _buildSpace(space), | ||
); | ||
} | ||
return Expanded( | ||
child: _buildGroupedViews(state.queryResults!), | ||
); | ||
}, | ||
), | ||
], | ||
); | ||
}, | ||
), | ||
); | ||
} | ||
|
||
Widget _buildGroupedViews(List<ViewPB> views) { | ||
final groupedViews = views | ||
.where( | ||
(view) => | ||
!_shouldIgnoreView(view, widget.sourceView) && !view.isSpace, | ||
) | ||
.toList(); | ||
return _MovePageGroupedViews( | ||
views: groupedViews, | ||
onSelected: widget.onSelected, | ||
); | ||
} | ||
|
||
Column _buildSpace(ViewPB space) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
SpacePopup( | ||
child: CurrentSpace( | ||
space: space, | ||
), | ||
), | ||
Expanded( | ||
child: SingleChildScrollView( | ||
physics: const ClampingScrollPhysics(), | ||
child: SpacePages( | ||
key: ValueKey(space.id), | ||
space: space, | ||
isHovered: isHoveredNotifier, | ||
isExpandedNotifier: isExpandedNotifier, | ||
shouldIgnoreView: (view) => _shouldIgnoreView( | ||
view, | ||
widget.sourceView, | ||
), | ||
// hide the hover status and disable the editing actions | ||
disableSelectedStatus: true, | ||
// hide the ... and + buttons | ||
rightIconsBuilder: (context, view) => [], | ||
onSelected: (_, view) => widget.onSelected(view), | ||
), | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} | ||
|
||
class _MovePageGroupedViews extends StatelessWidget { | ||
const _MovePageGroupedViews({ | ||
required this.views, | ||
required this.onSelected, | ||
}); | ||
|
||
final List<ViewPB> views; | ||
final void Function(ViewPB view) onSelected; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return SingleChildScrollView( | ||
child: Column( | ||
mainAxisSize: MainAxisSize.min, | ||
children: views | ||
.map( | ||
(e) => ViewItem( | ||
key: ValueKey(e.id), | ||
view: e, | ||
spaceType: FolderSpaceType.unknown, | ||
level: 0, | ||
onSelected: (_, view) => onSelected(view), | ||
isFeedback: false, | ||
isDraggable: false, | ||
shouldRenderChildren: false, | ||
leftIconBuilder: (_, __) => const HSpace(0.0), | ||
rightIconsBuilder: (_, view) => [], | ||
), | ||
) | ||
.toList(), | ||
), | ||
); | ||
} | ||
} | ||
|
||
bool _shouldIgnoreView(ViewPB view, ViewPB sourceView) { | ||
// ignore the source view and database view, don't render it in the list. | ||
if (view.layout != ViewLayoutPB.Document) { | ||
return true; | ||
} | ||
return view.id == sourceView.id; | ||
} |
Oops, something went wrong.