Skip to content

Commit

Permalink
feat: duplicate space (AppFlowy-IO#5612)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasXu0 authored Jun 25, 2024
1 parent 54c9d12 commit b9ad276
Show file tree
Hide file tree
Showing 35 changed files with 418 additions and 222 deletions.
2 changes: 1 addition & 1 deletion frontend/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.6.0"
APPFLOWY_VERSION = "0.6.1"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/mobile/presentation/presentation.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';
Expand All @@ -22,6 +23,18 @@ class MobileSpace extends StatefulWidget {
}

class _MobileSpaceState extends State<MobileSpace> {
@override
void initState() {
super.initState();
createNewPageNotifier.addListener(_createNewPage);
}

@override
void dispose() {
createNewPageNotifier.removeListener(_createNewPage);
super.dispose();
}

@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceBloc, SpaceState>(
Expand Down Expand Up @@ -81,6 +94,14 @@ class _MobileSpaceState extends State<MobileSpace> {
},
);
}

void _createNewPage() {
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
),
);
}
}

class _Pages extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import 'dart:ui';

import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final PropertyValueNotifier<ViewLayoutPB?> createNewPageNotifier =
PropertyValueNotifier(null);

const _homeLabel = 'home';
const _addLabel = 'add';
const _notificationLabel = 'notification';
Expand Down Expand Up @@ -93,7 +90,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
void _onTap(BuildContext context, int bottomBarIndex) {
if (_items[bottomBarIndex].label == _addLabel) {
// show an add dialog
_showCreatePageBottomSheet(context);
createNewPageNotifier.value = ViewLayoutPB.Document;
return;
}
// When navigating to a new branch, it's recommended to use the goBranch
Expand All @@ -108,40 +105,4 @@ class MobileBottomNavigationBar extends StatelessWidget {
initialLocation: bottomBarIndex == navigationShell.currentIndex,
);
}

void _showCreatePageBottomSheet(BuildContext context) {
showMobileBottomSheet(
context,
showHeader: true,
title: LocaleKeys.sideBar_addAPage.tr(),
showDragHandle: true,
showCloseButton: true,
useRootNavigator: true,
builder: (sheetContext) {
return AddNewPageWidgetBottomSheet(
view: ViewPB(),
onAction: (layout) async {
Navigator.of(sheetContext).pop();
final currentWorkspaceId =
await FolderEventReadCurrentWorkspace().send();
await currentWorkspaceId.fold((s) async {
final workspaceService = WorkspaceService(workspaceId: s.id);
final result = await workspaceService.createView(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
viewSection: ViewSectionPB.Private,
layout: layout,
);
result.fold((s) {
context.pushView(s);
}, (e) {
Log.error('Failed to create new page: $e');
});
}, (e) {
Log.error('Failed to read current workspace: $e');
});
},
);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
ValueListenableBuilder(
valueListenable: isKeyboardShow,
builder: (context, isKeyboardShow, __) {
return AnimatedContainer(
duration: const Duration(milliseconds: 110),
return SizedBox(
// only adding padding when the keyboard is triggered by editor
height: isKeyboardShow && widget.editorState.selection != null
? widget.toolbarHeight
Expand Down Expand Up @@ -383,37 +382,26 @@ class _MobileToolbarState extends State<_MobileToolbar>
return ValueListenableBuilder(
valueListenable: cachedKeyboardHeight,
builder: (_, height, ___) {
var paddingHeight = height;
if (Platform.isAndroid) {
// use the viewInsets to get the keyboard height on Android
paddingHeight = MediaQuery.of(context).viewInsets.bottom;
// if the padding height is 0 and the keyboard height is not 0,
// use the keyboard height
if (paddingHeight == 0 && height != 0) {
paddingHeight = height + MediaQuery.of(context).viewPadding.bottom;
}
}
debugPrint('Keyboard height: $paddingHeight');
return AnimatedContainer(
duration: const Duration(microseconds: 110),
height: paddingHeight,
child: ValueListenableBuilder(
valueListenable: showMenuNotifier,
builder: (_, showingMenu, __) {
return AnimatedContainer(
duration: const Duration(microseconds: 110),
height: height,
child: (showingMenu && selectedMenuIndex != null)
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
context,
widget.editorState,
this,
) ??
const SizedBox.shrink()
: const SizedBox.shrink(),
);
},
),
return ValueListenableBuilder(
valueListenable: showMenuNotifier,
builder: (_, showingMenu, __) {
var paddingHeight = height;
if (Platform.isAndroid) {
paddingHeight =
height + MediaQuery.of(context).viewPadding.bottom;
}
return SizedBox(
height: paddingHeight,
child: (showingMenu && selectedMenuIndex != null)
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
context,
widget.editorState,
this,
) ??
const SizedBox.shrink()
: const SizedBox.shrink(),
);
},
);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:appflowy_result/appflowy_result.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart';
Expand Down Expand Up @@ -257,6 +258,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
);
},
reset: (userProfile, workspaceId) async {
if (workspaceId == _workspaceId) {
return;
}

_reset(userProfile, workspaceId);

add(
Expand Down Expand Up @@ -286,6 +291,18 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
final nextSpace = spaces[nextIndex];
add(SpaceEvent.open(nextSpace));
},
duplicate: () async {
final currentSpace = state.currentSpace;
if (currentSpace == null) {
return;
}
await ViewBackendService.duplicate(
view: currentSpace,
openAfterDuplicate: false,
includeChildren: true,
);
add(const SpaceEvent.didReceiveSpaceUpdate());
},
);
},
);
Expand Down Expand Up @@ -321,6 +338,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
required String icon,
required String iconColor,
required SpacePermission permission,
String? viewId,
}) async {
final section = switch (permission) {
SpacePermission.publicToAll => ViewSectionPB.Public,
Expand All @@ -331,6 +349,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
name: name,
viewSection: section,
setAsCurrent: false,
viewId: viewId,
);
return await result.fold((space) async {
Log.info('Space created: $space');
Expand Down Expand Up @@ -476,56 +495,82 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
if (members.items.length == 1 || isOwner) {
// create a new public space and a new private space
// move all the views in the workspace to the new public/private space
final publicViews =
await _workspaceService.getPublicViews().getOrThrow();
var publicViews = await _workspaceService.getPublicViews().getOrThrow();
final containsPublicSpace = publicViews.any(
(e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll,
);
publicViews = publicViews.where((e) => !e.isSpace).toList();
// if there is already a public space, don't migrate the public space
// only migrate the public space if there are any public views
if (publicViews.isNotEmpty) {
final publicSpace = await _createSpace(
name: 'Shared',
icon: builtInSpaceIcons.first,
iconColor: builtInSpaceColors.first,
permission: SpacePermission.publicToAll,
);

if (publicSpace != null) {
for (final view in publicViews.reversed) {
if (view.isSpace) {
continue;
}
await ViewBackendService.moveViewV2(
viewId: view.id,
newParentId: publicSpace.id,
prevViewId: view.parentViewId,
);
}
}
if (publicViews.isEmpty || containsPublicSpace) {
return true;
}
}
// create a new private space
final privateViews =
await _workspaceService.getPrivateViews().getOrThrow();
// only migrate the private space if there are any private views
if (privateViews.isNotEmpty) {
final privateSpace = await _createSpace(
name: 'Private',
icon: builtInSpaceIcons.last,
iconColor: builtInSpaceColors.last,
permission: SpacePermission.private,

final viewId = fixedUuid(user.id.toInt(), UuidType.publicSpace);
final publicSpace = await _createSpace(
name: 'Shared',
icon: builtInSpaceIcons.first,
iconColor: builtInSpaceColors.first,
permission: SpacePermission.publicToAll,
viewId: viewId,
);
if (privateSpace != null) {
for (final view in privateViews.reversed) {

Log.info('migrating: created a new public space: ${publicSpace?.id}');

if (publicSpace != null) {
for (final view in publicViews.reversed) {
if (view.isSpace) {
continue;
}
await ViewBackendService.moveViewV2(
viewId: view.id,
newParentId: privateSpace.id,
prevViewId: view.parentViewId,
newParentId: publicSpace.id,
prevViewId: null,
);
Log.info(
'migrating: migrate ${view.name}(${view.id}) to public space(${publicSpace.id})',
);
}
}
}

// create a new private space
final viewId = fixedUuid(user.id.toInt(), UuidType.privateSpace);
var privateViews = await _workspaceService.getPrivateViews().getOrThrow();
// if there is already a private space, don't migrate the private space
final containsPrivateSpace = privateViews.any(
(e) => e.isSpace && e.spacePermission == SpacePermission.private,
);
privateViews = privateViews.where((e) => !e.isSpace).toList();
if (privateViews.isEmpty || containsPrivateSpace) {
return true;
}
// only migrate the private space if there are any private views
final privateSpace = await _createSpace(
name: 'Private',
icon: builtInSpaceIcons.last,
iconColor: builtInSpaceColors.last,
permission: SpacePermission.private,
viewId: viewId,
);
Log.info('migrating: created a new private space: ${privateSpace?.id}');

if (privateSpace != null) {
for (final view in privateViews.reversed) {
if (view.isSpace) {
continue;
}
await ViewBackendService.moveViewV2(
viewId: view.id,
newParentId: privateSpace.id,
prevViewId: null,
);
Log.info(
'migrating: migrate ${view.name}(${view.id}) to private space(${privateSpace.id})',
);
}
}

return true;
} catch (e) {
Log.error('migrate space error: $e');
Expand All @@ -538,14 +583,16 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
required List<ViewPB> publicViews,
required List<ViewPB> privateViews,
}) async {
final publicSpaces =
spaces.where((e) => e.spacePermission == SpacePermission.publicToAll);
final publicSpaces = spaces.where(
(e) => e.spacePermission == SpacePermission.publicToAll,
);
if (publicSpaces.isEmpty && publicViews.isNotEmpty) {
return true;
}

final privateSpaces =
spaces.where((e) => e.spacePermission == SpacePermission.private);
final privateSpaces = spaces.where(
(e) => e.spacePermission == SpacePermission.private,
);
if (privateSpaces.isEmpty && privateViews.isNotEmpty) {
return true;
}
Expand All @@ -571,6 +618,7 @@ class SpaceEvent with _$SpaceEvent {
const factory SpaceEvent.rename(ViewPB space, String name) = _Rename;
const factory SpaceEvent.changeIcon(String icon, String iconColor) =
_ChangeIcon;
const factory SpaceEvent.duplicate() = _Duplicate;
const factory SpaceEvent.update({
String? name,
String? icon,
Expand Down
Loading

0 comments on commit b9ad276

Please sign in to comment.