mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support moving page across spaces (#5618)
* feat: support moving page across spaces * feat: refacotor move api * feat: filter the database views * feat: support searching in move page menu
This commit is contained in:
parent
b9ad2768cf
commit
a8ed93054c
@ -12,7 +12,8 @@ part 'folder_bloc.freezed.dart';
|
||||
enum FolderSpaceType {
|
||||
favorite,
|
||||
private,
|
||||
public;
|
||||
public,
|
||||
unknown;
|
||||
|
||||
ViewSectionPB get toViewSectionPB {
|
||||
switch (this) {
|
||||
@ -21,6 +22,7 @@ enum FolderSpaceType {
|
||||
case FolderSpaceType.public:
|
||||
return ViewSectionPB.Public;
|
||||
case FolderSpaceType.favorite:
|
||||
case FolderSpaceType.unknown:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -310,6 +310,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
|
||||
late WorkspaceService _workspaceService;
|
||||
String? _workspaceId;
|
||||
late UserProfilePB userProfile;
|
||||
WorkspaceSectionsListener? _listener;
|
||||
|
||||
@override
|
||||
@ -401,6 +402,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
void _initial(UserProfilePB userProfile, String workspaceId) {
|
||||
_workspaceService = WorkspaceService(workspaceId: workspaceId);
|
||||
_workspaceId = workspaceId;
|
||||
this.userProfile = userProfile;
|
||||
|
||||
_listener = WorkspaceSectionsListener(
|
||||
user: userProfile,
|
||||
@ -461,7 +463,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
|
||||
Future<bool> _getSpaceExpandStatus(ViewPB? space) async {
|
||||
if (space == null) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return getIt<KeyValueStorage>().get(KVKeys.expandedViews).then((result) {
|
||||
|
@ -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();
|
||||
}
|
@ -6,12 +6,12 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.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_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -41,7 +41,7 @@ class FavoriteMenu extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const VSpace(4),
|
||||
_FavoriteSearchField(
|
||||
SpaceSearchField(
|
||||
width: minWidth - 2 * _kHorizontalPadding,
|
||||
onSearch: (context, text) {
|
||||
context
|
||||
@ -197,72 +197,3 @@ class _FavoriteGroups extends StatelessWidget {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _FavoriteSearchField extends StatefulWidget {
|
||||
const _FavoriteSearchField({
|
||||
required this.width,
|
||||
required this.onSearch,
|
||||
});
|
||||
|
||||
final double width;
|
||||
final void Function(BuildContext context, String text) onSearch;
|
||||
|
||||
@override
|
||||
State<_FavoriteSearchField> createState() => _FavoriteSearchFieldState();
|
||||
}
|
||||
|
||||
class _FavoriteSearchFieldState extends State<_FavoriteSearchField> {
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 30,
|
||||
width: widget.width,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(
|
||||
width: 1.20,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
color: Color(0xFF00BCF0),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: CupertinoSearchTextField(
|
||||
onChanged: (text) => widget.onSearch(context, text),
|
||||
padding: EdgeInsets.zero,
|
||||
focusNode: focusNode,
|
||||
placeholder: LocaleKeys.search_label.tr(),
|
||||
prefixIcon: const FlowySvg(FlowySvgs.m_search_m),
|
||||
prefixInsets: const EdgeInsets.only(left: 12.0, right: 8.0),
|
||||
suffixIcon: const Icon(Icons.close),
|
||||
suffixInsets: const EdgeInsets.only(right: 8.0),
|
||||
itemSize: 16.0,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
placeholderStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).hintColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -1,10 +1,21 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
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/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.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_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/decoration.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -262,3 +273,207 @@ class DeleteSpacePopup extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpacePopup extends StatelessWidget {
|
||||
const SpacePopup({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: HomeSizes.workspaceSectionHeight,
|
||||
child: AppFlowyPopover(
|
||||
constraints: const BoxConstraints(maxWidth: 260),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
clickHandler: PopoverClickHandler.gestureDetector,
|
||||
offset: const Offset(0, 4),
|
||||
popupBuilder: (_) => BlocProvider.value(
|
||||
value: context.read<SpaceBloc>(),
|
||||
child: const SidebarSpaceMenu(),
|
||||
),
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.only(left: 3.0, right: 4.0),
|
||||
iconPadding: 10.0,
|
||||
text: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CurrentSpace extends StatelessWidget {
|
||||
const CurrentSpace({
|
||||
super.key,
|
||||
required this.space,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
SpaceIcon(
|
||||
dimension: 20,
|
||||
space: space,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
const HSpace(10),
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
space.name,
|
||||
fontSize: 14.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(4.0),
|
||||
const FlowySvg(
|
||||
FlowySvgs.workspace_drop_down_menu_show_s,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpacePages extends StatelessWidget {
|
||||
const SpacePages({
|
||||
super.key,
|
||||
required this.space,
|
||||
required this.isHovered,
|
||||
required this.isExpandedNotifier,
|
||||
required this.onSelected,
|
||||
this.rightIconsBuilder,
|
||||
this.disableSelectedStatus = false,
|
||||
this.onTertiarySelected,
|
||||
this.shouldIgnoreView,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
final ValueNotifier<bool> isHovered;
|
||||
final PropertyValueNotifier<bool> isExpandedNotifier;
|
||||
final bool disableSelectedStatus;
|
||||
final ViewItemRightIconsBuilder? rightIconsBuilder;
|
||||
final ViewItemOnSelected onSelected;
|
||||
final ViewItemOnSelected? onTertiarySelected;
|
||||
final bool Function(ViewPB view)? shouldIgnoreView;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
ViewBloc(view: space)..add(const ViewEvent.initial()),
|
||||
child: BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
// filter the child views that should be ignored
|
||||
var childViews = state.view.childViews;
|
||||
if (shouldIgnoreView != null) {
|
||||
childViews = childViews
|
||||
.where((childView) => !shouldIgnoreView!(childView))
|
||||
.toList();
|
||||
}
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: childViews
|
||||
.map(
|
||||
(view) => ViewItem(
|
||||
key: ValueKey('${space.id} ${view.id}'),
|
||||
spaceType:
|
||||
space.spacePermission == SpacePermission.publicToAll
|
||||
? FolderSpaceType.public
|
||||
: FolderSpaceType.private,
|
||||
isFirstChild: view.id == childViews.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: HomeSpaceViewSizes.leftPadding,
|
||||
isFeedback: false,
|
||||
isHovered: isHovered,
|
||||
disableSelectedStatus: disableSelectedStatus,
|
||||
isExpandedNotifier: isExpandedNotifier,
|
||||
rightIconsBuilder: rightIconsBuilder,
|
||||
onSelected: onSelected,
|
||||
onTertiarySelected: onTertiarySelected,
|
||||
shouldIgnoreView: shouldIgnoreView,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpaceSearchField extends StatefulWidget {
|
||||
const SpaceSearchField({
|
||||
super.key,
|
||||
required this.width,
|
||||
required this.onSearch,
|
||||
});
|
||||
|
||||
final double width;
|
||||
final void Function(BuildContext context, String text) onSearch;
|
||||
|
||||
@override
|
||||
State<SpaceSearchField> createState() => _SpaceSearchFieldState();
|
||||
}
|
||||
|
||||
class _SpaceSearchFieldState extends State<SpaceSearchField> {
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 30,
|
||||
width: widget.width,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(
|
||||
width: 1.20,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
color: Color(0xFF00BCF0),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: CupertinoSearchTextField(
|
||||
onChanged: (text) => widget.onSearch(context, text),
|
||||
padding: EdgeInsets.zero,
|
||||
focusNode: focusNode,
|
||||
placeholder: LocaleKeys.search_label.tr(),
|
||||
prefixIcon: const FlowySvg(FlowySvgs.magnifier_s),
|
||||
prefixInsets: const EdgeInsets.only(left: 12.0, right: 8.0),
|
||||
suffixIcon: const Icon(Icons.close),
|
||||
suffixInsets: const EdgeInsets.only(right: 8.0),
|
||||
itemSize: 16.0,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
placeholderStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).hintColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
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/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/rename_view_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/create_space_popup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@ -115,11 +111,19 @@ class _SpaceState extends State<_Space> {
|
||||
MouseRegion(
|
||||
onEnter: (_) => isHovered.value = true,
|
||||
onExit: (_) => isHovered.value = false,
|
||||
child: _Pages(
|
||||
child: SpacePages(
|
||||
key: ValueKey(currentSpace.id),
|
||||
isExpandedNotifier: isExpandedNotifier,
|
||||
space: currentSpace,
|
||||
isHovered: isHovered,
|
||||
onSelected: (context, view) {
|
||||
if (HardwareKeyboard.instance.isControlPressed) {
|
||||
context.read<TabsBloc>().openTab(view);
|
||||
}
|
||||
context.read<TabsBloc>().openPlugin(view);
|
||||
},
|
||||
onTertiarySelected: (context, view) =>
|
||||
context.read<TabsBloc>().openTab(view),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -169,57 +173,3 @@ class _SpaceState extends State<_Space> {
|
||||
context.read<SpaceBloc>().add(const SpaceEvent.switchToNextSpace());
|
||||
}
|
||||
}
|
||||
|
||||
class _Pages extends StatelessWidget {
|
||||
const _Pages({
|
||||
super.key,
|
||||
required this.space,
|
||||
required this.isHovered,
|
||||
required this.isExpandedNotifier,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
final ValueNotifier<bool> isHovered;
|
||||
final PropertyValueNotifier<bool> isExpandedNotifier;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
ViewBloc(view: space)..add(const ViewEvent.initial()),
|
||||
child: BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: state.view.childViews
|
||||
.map(
|
||||
(view) => ViewItem(
|
||||
key: ValueKey('${space.id} ${view.id}'),
|
||||
spaceType:
|
||||
space.spacePermission == SpacePermission.publicToAll
|
||||
? FolderSpaceType.public
|
||||
: FolderSpaceType.private,
|
||||
isFirstChild: view.id == state.view.childViews.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: HomeSpaceViewSizes.leftPadding,
|
||||
isFeedback: false,
|
||||
isHovered: isHovered,
|
||||
isExpandedNotifier: isExpandedNotifier,
|
||||
onSelected: (viewContext, view) {
|
||||
if (HardwareKeyboard.instance.isControlPressed) {
|
||||
context.read<TabsBloc>().openTab(view);
|
||||
}
|
||||
|
||||
context.read<TabsBloc>().openPlugin(view);
|
||||
},
|
||||
onTertiarySelected: (viewContext, view) =>
|
||||
context.read<TabsBloc>().openTab(view),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,10 @@ import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/manage_space_popup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
@ -68,24 +65,8 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
|
||||
left: 3,
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
child: SizedBox(
|
||||
height: HomeSizes.workspaceSectionHeight,
|
||||
child: AppFlowyPopover(
|
||||
constraints: const BoxConstraints(maxWidth: 252),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
clickHandler: PopoverClickHandler.gestureDetector,
|
||||
offset: const Offset(0, 4),
|
||||
popupBuilder: (_) => BlocProvider.value(
|
||||
value: context.read<SpaceBloc>(),
|
||||
child: const SidebarSpaceMenu(),
|
||||
),
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.only(left: 3.0, right: 4.0),
|
||||
iconPadding: 10.0,
|
||||
text: _buildChild(),
|
||||
),
|
||||
),
|
||||
child: SpacePopup(
|
||||
child: _buildChild(),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@ -135,29 +116,8 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
|
||||
);
|
||||
return FlowyTooltip(
|
||||
richMessage: textSpan,
|
||||
child: Row(
|
||||
children: [
|
||||
SpaceIcon(
|
||||
dimension: 20,
|
||||
space: widget.space,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
const HSpace(10),
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
widget.space.name,
|
||||
lineHeight: 1.15,
|
||||
fontSize: 14.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(4.0),
|
||||
FlowySvg(
|
||||
widget.isExpanded
|
||||
? FlowySvgs.workspace_drop_down_menu_show_s
|
||||
: FlowySvgs.workspace_drop_down_menu_hide_s,
|
||||
),
|
||||
],
|
||||
child: CurrentSpace(
|
||||
space: widget.space,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class ViewItem extends StatelessWidget {
|
||||
this.isDraggable = true,
|
||||
required this.isFeedback,
|
||||
this.height = HomeSpaceViewSizes.viewHeight,
|
||||
this.isHoverEnabled = true,
|
||||
this.isHoverEnabled = false,
|
||||
this.isPlaceholder = false,
|
||||
this.isHovered,
|
||||
this.shouldRenderChildren = true,
|
||||
@ -61,6 +61,8 @@ class ViewItem extends StatelessWidget {
|
||||
this.shouldLoadChildViews = true,
|
||||
this.isExpandedNotifier,
|
||||
this.extendBuilder,
|
||||
this.disableSelectedStatus,
|
||||
this.shouldIgnoreView,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -116,6 +118,12 @@ class ViewItem extends StatelessWidget {
|
||||
|
||||
final List<Widget> Function(ViewPB view)? extendBuilder;
|
||||
|
||||
// disable the selected status of the view item
|
||||
final bool? disableSelectedStatus;
|
||||
|
||||
// ignore the views when rendering the child views
|
||||
final bool Function(ViewPB view)? shouldIgnoreView;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
@ -129,15 +137,23 @@ class ViewItem extends StatelessWidget {
|
||||
listener: (context, state) =>
|
||||
context.read<TabsBloc>().openPlugin(state.lastCreatedView!),
|
||||
builder: (context, state) {
|
||||
// filter the child views that should be ignored
|
||||
var childViews = state.view.childViews;
|
||||
if (shouldIgnoreView != null) {
|
||||
childViews = childViews
|
||||
.where((childView) => !shouldIgnoreView!(childView))
|
||||
.toList();
|
||||
}
|
||||
return InnerViewItem(
|
||||
view: state.view,
|
||||
parentView: parentView,
|
||||
childViews: state.view.childViews,
|
||||
childViews: childViews,
|
||||
spaceType: spaceType,
|
||||
level: level,
|
||||
leftPadding: leftPadding,
|
||||
showActions: state.isEditing,
|
||||
isExpanded: state.isExpanded,
|
||||
disableSelectedStatus: disableSelectedStatus,
|
||||
onSelected: onSelected,
|
||||
onTertiarySelected: onTertiarySelected,
|
||||
isFirstChild: isFirstChild,
|
||||
@ -152,6 +168,7 @@ class ViewItem extends StatelessWidget {
|
||||
rightIconsBuilder: rightIconsBuilder,
|
||||
isExpandedNotifier: isExpandedNotifier,
|
||||
extendBuilder: extendBuilder,
|
||||
shouldIgnoreView: shouldIgnoreView,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -186,6 +203,8 @@ class InnerViewItem extends StatefulWidget {
|
||||
required this.rightIconsBuilder,
|
||||
this.isExpandedNotifier,
|
||||
required this.extendBuilder,
|
||||
this.disableSelectedStatus,
|
||||
required this.shouldIgnoreView,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -209,6 +228,7 @@ class InnerViewItem extends StatefulWidget {
|
||||
|
||||
final bool isHoverEnabled;
|
||||
final bool isPlaceholder;
|
||||
final bool? disableSelectedStatus;
|
||||
final ValueNotifier<bool>? isHovered;
|
||||
final bool shouldRenderChildren;
|
||||
final ViewItemLeftIconBuilder? leftIconBuilder;
|
||||
@ -216,6 +236,7 @@ class InnerViewItem extends StatefulWidget {
|
||||
|
||||
final PropertyValueNotifier<bool>? isExpandedNotifier;
|
||||
final List<Widget> Function(ViewPB view)? extendBuilder;
|
||||
final bool Function(ViewPB view)? shouldIgnoreView;
|
||||
|
||||
@override
|
||||
State<InnerViewItem> createState() => _InnerViewItemState();
|
||||
@ -254,6 +275,8 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
leftIconBuilder: widget.leftIconBuilder,
|
||||
rightIconsBuilder: widget.rightIconsBuilder,
|
||||
extendBuilder: widget.extendBuilder,
|
||||
disableSelectedStatus: widget.disableSelectedStatus,
|
||||
shouldIgnoreView: widget.shouldIgnoreView,
|
||||
);
|
||||
|
||||
// if the view is expanded and has child views, render its child views
|
||||
@ -271,6 +294,7 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
onSelected: widget.onSelected,
|
||||
onTertiarySelected: widget.onTertiarySelected,
|
||||
isDraggable: widget.isDraggable,
|
||||
disableSelectedStatus: widget.disableSelectedStatus,
|
||||
leftPadding: widget.leftPadding,
|
||||
isFeedback: widget.isFeedback,
|
||||
isPlaceholder: widget.isPlaceholder,
|
||||
@ -278,6 +302,7 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
leftIconBuilder: widget.leftIconBuilder,
|
||||
rightIconsBuilder: widget.rightIconsBuilder,
|
||||
extendBuilder: widget.extendBuilder,
|
||||
shouldIgnoreView: widget.shouldIgnoreView,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
@ -300,7 +325,14 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
_isDragging = isDragging;
|
||||
},
|
||||
onMove: widget.isPlaceholder
|
||||
? (from, to) => _moveViewCrossSection(context, from, to)
|
||||
? (from, to) => _moveViewCrossSection(
|
||||
context,
|
||||
widget.view,
|
||||
widget.parentView,
|
||||
widget.spaceType,
|
||||
from,
|
||||
to.parentViewId,
|
||||
)
|
||||
: null,
|
||||
feedback: (context) {
|
||||
return Container(
|
||||
@ -324,6 +356,7 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
leftIconBuilder: widget.leftIconBuilder,
|
||||
rightIconsBuilder: widget.rightIconsBuilder,
|
||||
extendBuilder: widget.extendBuilder,
|
||||
shouldIgnoreView: widget.shouldIgnoreView,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -345,37 +378,6 @@ class _InnerViewItemState extends State<InnerViewItem> {
|
||||
context.read<ViewBloc>().add(const ViewEvent.collapseAllPages());
|
||||
}
|
||||
}
|
||||
|
||||
void _moveViewCrossSection(
|
||||
BuildContext context,
|
||||
ViewPB from,
|
||||
ViewPB to,
|
||||
) {
|
||||
if (isReferencedDatabaseView(widget.view, widget.parentView)) {
|
||||
return;
|
||||
}
|
||||
final fromSection = widget.spaceType == FolderSpaceType.public
|
||||
? ViewSectionPB.Private
|
||||
: ViewSectionPB.Public;
|
||||
final toSection = widget.spaceType == FolderSpaceType.public
|
||||
? ViewSectionPB.Public
|
||||
: ViewSectionPB.Private;
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.move(
|
||||
from,
|
||||
to.parentViewId,
|
||||
null,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.updateViewVisibility(
|
||||
from,
|
||||
widget.spaceType == FolderSpaceType.public,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleInnerViewItem extends StatefulWidget {
|
||||
@ -399,6 +401,8 @@ class SingleInnerViewItem extends StatefulWidget {
|
||||
required this.leftIconBuilder,
|
||||
required this.rightIconsBuilder,
|
||||
required this.extendBuilder,
|
||||
required this.disableSelectedStatus,
|
||||
required this.shouldIgnoreView,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
@ -419,11 +423,13 @@ class SingleInnerViewItem extends StatefulWidget {
|
||||
|
||||
final bool isHoverEnabled;
|
||||
final bool isPlaceholder;
|
||||
final bool? disableSelectedStatus;
|
||||
final ValueNotifier<bool>? isHovered;
|
||||
final ViewItemLeftIconBuilder? leftIconBuilder;
|
||||
final ViewItemRightIconsBuilder? rightIconsBuilder;
|
||||
|
||||
final List<Widget> Function(ViewPB view)? extendBuilder;
|
||||
final bool Function(ViewPB view)? shouldIgnoreView;
|
||||
|
||||
@override
|
||||
State<SingleInnerViewItem> createState() => _SingleInnerViewItemState();
|
||||
@ -435,8 +441,11 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSelected =
|
||||
var isSelected =
|
||||
getIt<MenuSharedState>().latestOpenView?.id == widget.view.id;
|
||||
if (widget.disableSelectedStatus == true) {
|
||||
isSelected = false;
|
||||
}
|
||||
|
||||
if (widget.isPlaceholder) {
|
||||
return const SizedBox(
|
||||
@ -714,6 +723,22 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
iconType: result.type.toProto(),
|
||||
);
|
||||
break;
|
||||
case ViewMoreActionType.moveTo:
|
||||
final target = data;
|
||||
if (target is! ViewPB) {
|
||||
return;
|
||||
}
|
||||
debugPrint(
|
||||
'Move view ${widget.view.id}, ${widget.view.name} to ${target.id}, ${target.name}',
|
||||
);
|
||||
_moveViewCrossSection(
|
||||
context,
|
||||
widget.view,
|
||||
widget.parentView,
|
||||
widget.spaceType,
|
||||
widget.view,
|
||||
target.id,
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError('$action is not supported');
|
||||
}
|
||||
@ -765,3 +790,42 @@ bool isReferencedDatabaseView(ViewPB view, ViewPB? parentView) {
|
||||
}
|
||||
return view.layout.isDatabaseView && parentView.layout.isDatabaseView;
|
||||
}
|
||||
|
||||
void _moveViewCrossSection(
|
||||
BuildContext context,
|
||||
ViewPB view,
|
||||
ViewPB? parentView,
|
||||
FolderSpaceType spaceType,
|
||||
ViewPB from,
|
||||
String toId,
|
||||
) {
|
||||
if (isReferencedDatabaseView(view, parentView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from.id == toId) {
|
||||
return;
|
||||
}
|
||||
|
||||
final fromSection = spaceType == FolderSpaceType.public
|
||||
? ViewSectionPB.Private
|
||||
: ViewSectionPB.Public;
|
||||
final toSection = spaceType == FolderSpaceType.public
|
||||
? ViewSectionPB.Public
|
||||
: ViewSectionPB.Private;
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.move(
|
||||
from,
|
||||
toId,
|
||||
null,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
context.read<ViewBloc>().add(
|
||||
ViewEvent.updateViewVisibility(
|
||||
from,
|
||||
spaceType == FolderSpaceType.public,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/move_to/move_page_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
/// ··· button beside the view name
|
||||
class ViewMoreActionButton extends StatelessWidget {
|
||||
@ -52,7 +55,7 @@ class ViewMoreActionButton extends StatelessWidget {
|
||||
final actionTypes = _buildActionTypes();
|
||||
return actionTypes
|
||||
.map(
|
||||
(e) => ViewMoreActionTypeWrapper(e, (controller, data) {
|
||||
(e) => ViewMoreActionTypeWrapper(e, view, (controller, data) {
|
||||
onEditing(false);
|
||||
onAction(e, data);
|
||||
controller.close();
|
||||
@ -92,6 +95,7 @@ class ViewMoreActionButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
actionTypes.addAll([
|
||||
ViewMoreActionType.moveTo,
|
||||
ViewMoreActionType.delete,
|
||||
ViewMoreActionType.divider,
|
||||
]);
|
||||
@ -110,9 +114,14 @@ class ViewMoreActionButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class ViewMoreActionTypeWrapper extends CustomActionCell {
|
||||
ViewMoreActionTypeWrapper(this.inner, this.onTap);
|
||||
ViewMoreActionTypeWrapper(
|
||||
this.inner,
|
||||
this.sourceView,
|
||||
this.onTap,
|
||||
);
|
||||
|
||||
final ViewMoreActionType inner;
|
||||
final ViewPB sourceView;
|
||||
final void Function(PopoverController controller, dynamic data) onTap;
|
||||
|
||||
@override
|
||||
@ -125,9 +134,11 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
|
||||
return _buildCreated(context);
|
||||
} else if (inner == ViewMoreActionType.changeIcon) {
|
||||
return _buildEmojiActionButton(context, controller);
|
||||
} else {
|
||||
return _buildNormalActionButton(context, controller);
|
||||
} else if (inner == ViewMoreActionType.moveTo) {
|
||||
return _buildMoveToActionButton(context, controller);
|
||||
}
|
||||
|
||||
return _buildNormalActionButton(context, controller);
|
||||
}
|
||||
|
||||
Widget _buildNormalActionButton(
|
||||
@ -154,6 +165,42 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMoveToActionButton(
|
||||
BuildContext context,
|
||||
PopoverController controller,
|
||||
) {
|
||||
final child = _buildActionButton(context, null);
|
||||
final userProfile = context.read<SpaceBloc>().userProfile;
|
||||
final workspaceId = context.read<SpaceBloc>().state.currentSpace?.id;
|
||||
|
||||
return AppFlowyPopover(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 260,
|
||||
maxHeight: 345,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 14.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
clickHandler: PopoverClickHandler.gestureDetector,
|
||||
popupBuilder: (_) {
|
||||
if (workspaceId == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return MovePageMenu(
|
||||
sourceView: sourceView,
|
||||
userProfile: userProfile,
|
||||
workspaceId: workspaceId,
|
||||
onSelected: (view) {
|
||||
onTap(controller, view);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDivider() {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
|
6
frontend/resources/flowy_icons/16x/magnifier.svg
Normal file
6
frontend/resources/flowy_icons/16x/magnifier.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.6">
|
||||
<path d="M7.66536 14.0007C11.1632 14.0007 13.9987 11.1651 13.9987 7.66732C13.9987 4.16951 11.1632 1.33398 7.66536 1.33398C4.16756 1.33398 1.33203 4.16951 1.33203 7.66732C1.33203 11.1651 4.16756 14.0007 7.66536 14.0007Z" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.6654 14.6673L13.332 13.334" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 519 B |
Loading…
Reference in New Issue
Block a user