diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index dbc74be7e4..6e433b7513 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -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" diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart index 9d288c3411..69f5d8951a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart @@ -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'; @@ -22,6 +23,18 @@ class MobileSpace extends StatefulWidget { } class _MobileSpaceState extends State { + @override + void initState() { + super.initState(); + createNewPageNotifier.addListener(_createNewPage); + } + + @override + void dispose() { + createNewPageNotifier.removeListener(_createNewPage); + super.dispose(); + } + @override Widget build(BuildContext context) { return BlocBuilder( @@ -81,6 +94,14 @@ class _MobileSpaceState extends State { }, ); } + + void _createNewPage() { + context.read().add( + SpaceEvent.createPage( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + ), + ); + } } class _Pages extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index d50369e5f9..5d33dfa500 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -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 createNewPageNotifier = + PropertyValueNotifier(null); + const _homeLabel = 'home'; const _addLabel = 'add'; const _notificationLabel = 'notification'; @@ -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 @@ -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'); - }); - }, - ); - }, - ); - } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index f6a18ff45e..95cd855667 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -73,8 +73,7 @@ class _AppFlowyMobileToolbarState extends State { 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 @@ -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(), + ); + }, ); }, ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart index 3a390ac3e0..63872ff12f 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart @@ -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'; @@ -257,6 +258,10 @@ class SpaceBloc extends Bloc { ); }, reset: (userProfile, workspaceId) async { + if (workspaceId == _workspaceId) { + return; + } + _reset(userProfile, workspaceId); add( @@ -321,6 +326,7 @@ class SpaceBloc extends Bloc { required String icon, required String iconColor, required SpacePermission permission, + String? viewId, }) async { final section = switch (permission) { SpacePermission.publicToAll => ViewSectionPB.Public, @@ -331,6 +337,7 @@ class SpaceBloc extends Bloc { name: name, viewSection: section, setAsCurrent: false, + viewId: viewId, ); return await result.fold((space) async { Log.info('Space created: $space'); @@ -476,56 +483,82 @@ class SpaceBloc extends Bloc { 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(); - // 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, - ); - } - } - } - } - // 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, + var publicViews = await _workspaceService.getPublicViews().getOrThrow(); + final containsPublicSpace = publicViews.any( + (e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll, ); - if (privateSpace != null) { - for (final view in privateViews.reversed) { + 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.isEmpty || containsPublicSpace) { + return true; + } + + 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, + ); + + 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'); @@ -538,14 +571,16 @@ class SpaceBloc extends Bloc { required List publicViews, required List 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; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart index b85467d41f..d123e72c4f 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -38,6 +38,7 @@ class ViewBackendService { /// If the index is null, the view will be added to the end of the list. int? index, ViewSectionPB? section, + final String? viewId, }) { final payload = CreateViewPayloadPB.create() ..parentViewId = parentViewId @@ -63,6 +64,10 @@ class ViewBackendService { payload.section = section; } + if (viewId != null) { + payload.viewId = viewId; + } + return FolderEventCreateView(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart index 9931a1e456..8da9ad4854 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart @@ -18,6 +18,7 @@ class WorkspaceService { int? index, ViewLayoutPB? layout, bool? setAsCurrent, + String? viewId, }) { final payload = CreateViewPayloadPB.create() ..parentViewId = workspaceId @@ -37,6 +38,10 @@ class WorkspaceService { payload.setAsCurrent = setAsCurrent; } + if (viewId != null) { + payload.viewId = viewId; + } + return FolderEventCreateView(payload).send(); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart index df8aee9dda..1f4c903625 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart @@ -1,5 +1,19 @@ +import 'package:uuid/data.dart'; +import 'package:uuid/rng.dart'; import 'package:uuid/uuid.dart'; +const _uuid = Uuid(); + String uuid() { - return const Uuid().v4(); + return _uuid.v4(); +} + +String fixedUuid(int seed, UuidType type) { + return _uuid.v4(config: V4Options(null, MathRNG(seed: seed + type.index))); +} + +enum UuidType { + // 0.6.0 + publicSpace, + privateSpace, } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index a16b2e4163..e066d6e0dd 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -2174,10 +2174,10 @@ packages: dependency: "direct overridden" description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.4.0" value_layout_builder: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 67241c3ec0..dffab82055 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.0 +version: 0.6.1 environment: flutter: ">=3.22.0" @@ -199,7 +199,7 @@ dependency_overrides: ref: e44458d path: sheet - uuid: ^4.1.0 + uuid: ^4.4.0 flutter_cache_manager: git: diff --git a/frontend/rust-lib/event-integration-test/src/chat_event.rs b/frontend/rust-lib/event-integration-test/src/chat_event.rs index 60e8ba65b1..bb0090fe35 100644 --- a/frontend/rust-lib/event-integration-test/src/chat_event.rs +++ b/frontend/rust-lib/event-integration-test/src/chat_event.rs @@ -21,6 +21,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/database_event.rs b/frontend/rust-lib/event-integration-test/src/database_event.rs index 79b8b528d2..4b6968ec06 100644 --- a/frontend/rust-lib/event-integration-test/src/database_event.rs +++ b/frontend/rust-lib/event-integration-test/src/database_event.rs @@ -45,6 +45,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -76,6 +77,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -102,6 +104,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/document/document_event.rs b/frontend/rust-lib/event-integration-test/src/document/document_event.rs index 93d3ccc80f..8595ac056b 100644 --- a/frontend/rust-lib/event-integration-test/src/document/document_event.rs +++ b/frontend/rust-lib/event-integration-test/src/document/document_event.rs @@ -65,6 +65,7 @@ impl DocumentEventTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(core.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/document_event.rs b/frontend/rust-lib/event-integration-test/src/document_event.rs index 6f99998e50..8374bc9de3 100644 --- a/frontend/rust-lib/event-integration-test/src/document_event.rs +++ b/frontend/rust-lib/event-integration-test/src/document_event.rs @@ -42,6 +42,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; let view = EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/folder_event.rs b/frontend/rust-lib/event-integration-test/src/folder_event.rs index af4de3f5d0..47c2bfa45c 100644 --- a/frontend/rust-lib/event-integration-test/src/folder_event.rs +++ b/frontend/rust-lib/event-integration-test/src/folder_event.rs @@ -230,6 +230,7 @@ impl EventIntegrationTest { set_as_current: false, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -293,6 +294,7 @@ impl ViewTest { set_as_current: true, index: None, section: None, + view_id: None, }; let view = EventBuilder::new(sdk.clone()) diff --git a/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs b/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs index 7be83964e3..0ebbd64efa 100644 --- a/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs +++ b/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs @@ -252,6 +252,7 @@ pub async fn create_view( set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(sdk.clone()) .event(CreateView) diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs index d5945ccaa3..d0568995f6 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view.rs @@ -258,6 +258,9 @@ pub struct CreateViewPayloadPB { // The view in private section will only be shown in the user's private view list. #[pb(index = 10, one_of)] pub section: Option, + + #[pb(index = 11, one_of)] + pub view_id: Option, } #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Default)] @@ -313,7 +316,8 @@ impl TryInto for CreateViewPayloadPB { fn try_into(self) -> Result { let name = ViewName::parse(self.name)?.0; let parent_view_id = ViewIdentify::parse(self.parent_view_id)?.0; - let view_id = gen_view_id().to_string(); + // if view_id is not provided, generate a new view_id + let view_id = self.view_id.unwrap_or_else(|| gen_view_id().to_string()); Ok(CreateViewParams { parent_view_id, diff --git a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml index a0da34dec9..cd8103df9e 100644 --- a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml +++ b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml @@ -59,8 +59,6 @@ AppDir: - libwayland-cursor0:amd64 - libwayland-client0:amd64 - libwayland-egl1:amd64 - - libmpv-dev:amd64 - - mpv:amd64 files: include: [] exclude: