mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: open workspace, rename workspace and update workspace icon (#4818)
* feat: support opening a workspace * feat: support renaming a workspace * fix: rename issue * fix: rename issues * feat: refactor menu bloc * test: add collaborative workspace test * test: add open workspace integration tet * test: add delete workspace integration tet * chore: turn off collab workspace feature
This commit is contained in:
parent
c0210a5778
commit
c8e86f4f26
@ -1,8 +1,9 @@
|
|||||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
||||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||||
|
import 'collaborative_workspace_test.dart' as collaboration_workspace_test;
|
||||||
|
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||||
// import 'document_sync_test.dart' as document_sync_test;
|
// import 'document_sync_test.dart' as document_sync_test;
|
||||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||||
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
preset_af_cloud_env_test.main();
|
preset_af_cloud_env_test.main();
|
||||||
@ -14,4 +15,6 @@ Future<void> main() async {
|
|||||||
user_sync_test.main();
|
user_sync_test.main();
|
||||||
|
|
||||||
anon_user_continue_test.main();
|
anon_user_continue_test.main();
|
||||||
|
|
||||||
|
collaboration_workspace_test.main();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
// ignore_for_file: unused_import
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||||
|
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/uuid.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
import '../util/database_test_op.dart';
|
||||||
|
import '../util/dir.dart';
|
||||||
|
import '../util/emoji.dart';
|
||||||
|
import '../util/mock/mock_file_picker.dart';
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
final email = '${uuid()}@appflowy.io';
|
||||||
|
|
||||||
|
group('collaborative workspace', () {
|
||||||
|
// only run the test when the feature flag is on
|
||||||
|
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine the create and delete workspace test to reduce the time
|
||||||
|
testWidgets('create a new workspace, open it and then delete it',
|
||||||
|
(tester) async {
|
||||||
|
await tester.initializeAppFlowy(
|
||||||
|
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||||
|
email: email,
|
||||||
|
);
|
||||||
|
await tester.tapGoogleLoginInButton();
|
||||||
|
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||||
|
|
||||||
|
const name = 'AppFlowy.IO';
|
||||||
|
await tester.createCollaborativeWorkspace(name);
|
||||||
|
|
||||||
|
// see the success message
|
||||||
|
var success = find.text(LocaleKeys.workspace_createSuccess.tr());
|
||||||
|
expect(success, findsOneWidget);
|
||||||
|
await tester.pumpUntilNotFound(success);
|
||||||
|
|
||||||
|
// check the create result
|
||||||
|
await tester.openCollaborativeWorkspaceMenu();
|
||||||
|
var items = find.byType(WorkspaceMenuItem);
|
||||||
|
expect(items, findsNWidgets(2));
|
||||||
|
expect(
|
||||||
|
tester.widget<WorkspaceMenuItem>(items.last).workspace.name,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
// open the newly created workspace
|
||||||
|
await tester.tapButton(items.last);
|
||||||
|
success = find.text(LocaleKeys.workspace_openSuccess.tr());
|
||||||
|
expect(success, findsOneWidget);
|
||||||
|
await tester.pumpUntilNotFound(success);
|
||||||
|
|
||||||
|
await tester.closeCollaborativeWorkspaceMenu();
|
||||||
|
|
||||||
|
// delete the newly created workspace
|
||||||
|
await tester.openCollaborativeWorkspaceMenu();
|
||||||
|
final secondWorkspace = find.byType(WorkspaceMenuItem).last;
|
||||||
|
await tester.hoverOnWidget(
|
||||||
|
secondWorkspace,
|
||||||
|
onHover: () async {
|
||||||
|
// click the more button
|
||||||
|
final moreButton = find.byType(WorkspaceMoreActionList);
|
||||||
|
expect(moreButton, findsOneWidget);
|
||||||
|
await tester.tapButton(moreButton);
|
||||||
|
// click the delete button
|
||||||
|
final deleteButton = find.text(LocaleKeys.button_delete.tr());
|
||||||
|
expect(deleteButton, findsOneWidget);
|
||||||
|
await tester.tapButton(deleteButton);
|
||||||
|
// see the delete confirm dialog
|
||||||
|
final confirm =
|
||||||
|
find.text(LocaleKeys.workspace_deleteWorkspaceHintText.tr());
|
||||||
|
expect(confirm, findsOneWidget);
|
||||||
|
await tester.tapButton(find.text(LocaleKeys.button_ok.tr()));
|
||||||
|
// delete success
|
||||||
|
success = find.text(LocaleKeys.workspace_createSuccess.tr());
|
||||||
|
expect(success, findsOneWidget);
|
||||||
|
await tester.pumpUntilNotFound(success);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the result
|
||||||
|
await tester.openCollaborativeWorkspaceMenu();
|
||||||
|
items = find.byType(WorkspaceMenuItem);
|
||||||
|
expect(items, findsOneWidget);
|
||||||
|
expect(
|
||||||
|
tester.widget<WorkspaceMenuItem>(items.last).workspace.name != name,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
await tester.closeCollaborativeWorkspaceMenu();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -134,8 +134,9 @@ extension AppFlowyTestBase on WidgetTester {
|
|||||||
Future<void> pumpUntilFound(
|
Future<void> pumpUntilFound(
|
||||||
Finder finder, {
|
Finder finder, {
|
||||||
Duration timeout = const Duration(seconds: 10),
|
Duration timeout = const Duration(seconds: 10),
|
||||||
Duration pumpInterval =
|
Duration pumpInterval = const Duration(
|
||||||
const Duration(milliseconds: 50), // Interval between pumps
|
milliseconds: 50,
|
||||||
|
), // Interval between pumps
|
||||||
}) async {
|
}) async {
|
||||||
bool timerDone = false;
|
bool timerDone = false;
|
||||||
final timer = Timer(timeout, () => timerDone = true);
|
final timer = Timer(timeout, () => timerDone = true);
|
||||||
@ -148,6 +149,24 @@ extension AppFlowyTestBase on WidgetTester {
|
|||||||
timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pumpUntilNotFound(
|
||||||
|
Finder finder, {
|
||||||
|
Duration timeout = const Duration(seconds: 10),
|
||||||
|
Duration pumpInterval = const Duration(
|
||||||
|
milliseconds: 50,
|
||||||
|
), // Interval between pumps
|
||||||
|
}) async {
|
||||||
|
bool timerDone = false;
|
||||||
|
final timer = Timer(timeout, () => timerDone = true);
|
||||||
|
while (!timerDone) {
|
||||||
|
await pump(pumpInterval); // Pump with an interval
|
||||||
|
if (!any(finder)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> tapButton(
|
Future<void> tapButton(
|
||||||
Finder finder, {
|
Finder finder, {
|
||||||
int? pointer,
|
int? pointer,
|
||||||
|
@ -6,10 +6,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||||
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||||
@ -518,6 +521,51 @@ extension CommonOperations on WidgetTester {
|
|||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> openCollaborativeWorkspaceMenu() async {
|
||||||
|
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||||
|
throw UnsupportedError('Collaborative workspace is not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
final workspace = find.byType(SidebarWorkspace);
|
||||||
|
expect(workspace, findsOneWidget);
|
||||||
|
// click it
|
||||||
|
await tapButton(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> closeCollaborativeWorkspaceMenu() async {
|
||||||
|
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||||
|
throw UnsupportedError('Collaborative workspace is not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
await tapAt(Offset.zero);
|
||||||
|
await pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createCollaborativeWorkspace(String name) async {
|
||||||
|
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||||
|
throw UnsupportedError('Collaborative workspace is not enabled');
|
||||||
|
}
|
||||||
|
await openCollaborativeWorkspaceMenu();
|
||||||
|
// expect to see the workspace list, and there should be only one workspace
|
||||||
|
final workspacesMenu = find.byType(WorkspacesMenu);
|
||||||
|
expect(workspacesMenu, findsOneWidget);
|
||||||
|
|
||||||
|
// click the create button
|
||||||
|
final createButton = find.byKey(createWorkspaceButtonKey);
|
||||||
|
expect(createButton, findsOneWidget);
|
||||||
|
await tapButton(createButton);
|
||||||
|
|
||||||
|
// see the create workspace dialog
|
||||||
|
final createWorkspaceDialog = find.byType(CreateWorkspaceDialog);
|
||||||
|
expect(createWorkspaceDialog, findsOneWidget);
|
||||||
|
|
||||||
|
// input the workspace name
|
||||||
|
await enterText(find.byType(TextField), name);
|
||||||
|
await pumpAndSettle();
|
||||||
|
|
||||||
|
await tapButtonWithName(LocaleKeys.button_ok.tr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||||
|
@ -3,7 +3,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
|
||||||
class NotificationParser<T, E> {
|
class NotificationParser<T, E extends Object> {
|
||||||
NotificationParser({
|
NotificationParser({
|
||||||
this.id,
|
this.id,
|
||||||
required this.callback,
|
required this.callback,
|
||||||
|
@ -3,7 +3,7 @@ import 'package:appflowy/mobile/application/mobile_router.dart';
|
|||||||
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
|
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
|
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
|
||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -27,10 +27,13 @@ class MobileFavoritePageFolder extends StatelessWidget {
|
|||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => MenuBloc(
|
create: (_) => SidebarRootViewsBloc()
|
||||||
user: userProfile,
|
..add(
|
||||||
workspaceId: workspaceSetting.workspaceId,
|
SidebarRootViewsEvent.initial(
|
||||||
)..add(const MenuEvent.initial()),
|
userProfile,
|
||||||
|
workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||||
@ -38,11 +41,11 @@ class MobileFavoritePageFolder extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
child: MultiBlocListener(
|
child: MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
BlocListener<MenuBloc, MenuState>(
|
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||||
listenWhen: (p, c) =>
|
listenWhen: (p, c) =>
|
||||||
p.lastCreatedView?.id != c.lastCreatedView?.id,
|
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||||
listener: (context, state) =>
|
listener: (context, state) =>
|
||||||
context.pushView(state.lastCreatedView!),
|
context.pushView(state.lastCreatedRootView!),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Builder(
|
child: Builder(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||||
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder.dart';
|
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder.dart';
|
||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -26,10 +26,13 @@ class MobileFolders extends StatelessWidget {
|
|||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => MenuBloc(
|
create: (_) => SidebarRootViewsBloc()
|
||||||
user: user,
|
..add(
|
||||||
workspaceId: workspaceSetting.workspaceId,
|
SidebarRootViewsEvent.initial(
|
||||||
)..add(const MenuEvent.initial()),
|
user,
|
||||||
|
workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||||
@ -37,16 +40,16 @@ class MobileFolders extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
child: MultiBlocListener(
|
child: MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
BlocListener<MenuBloc, MenuState>(
|
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||||
listenWhen: (p, c) =>
|
listenWhen: (p, c) =>
|
||||||
p.lastCreatedView?.id != c.lastCreatedView?.id,
|
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||||
listener: (context, state) =>
|
listener: (context, state) =>
|
||||||
context.pushView(state.lastCreatedView!),
|
context.pushView(state.lastCreatedRootView!),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final menuState = context.watch<MenuBloc>().state;
|
final menuState = context.watch<SidebarRootViewsBloc>().state;
|
||||||
return SlidableAutoCloseBehavior(
|
return SlidableAutoCloseBehavior(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -67,8 +67,8 @@ class _MobilePersonalFolderHeaderState
|
|||||||
size: Size.square(iconSize),
|
size: Size.square(iconSize),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<MenuBloc>().add(
|
context.read<SidebarRootViewsBloc>().add(
|
||||||
MenuEvent.createApp(
|
SidebarRootViewsEvent.createRootView(
|
||||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
index: 0,
|
index: 0,
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:appflowy/mobile/presentation/notifications/widgets/mobile_notifi
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/notification_filter/notification_filter_bloc.dart';
|
import 'package:appflowy/user/application/notification_filter/notification_filter_bloc.dart';
|
||||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/notifications/reminder_extension.dart';
|
import 'package:appflowy/workspace/presentation/notifications/reminder_extension.dart';
|
||||||
import 'package:appflowy/workspace/presentation/notifications/widgets/inbox_action_bar.dart';
|
import 'package:appflowy/workspace/presentation/notifications/widgets/inbox_action_bar.dart';
|
||||||
@ -80,11 +80,14 @@ class _NotificationScreenContent extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => MenuBloc(
|
create: (_) => SidebarRootViewsBloc()
|
||||||
workspaceId: workspaceSetting.workspaceId,
|
..add(
|
||||||
user: userProfile,
|
SidebarRootViewsEvent.initial(
|
||||||
)..add(const MenuEvent.initial()),
|
userProfile,
|
||||||
child: BlocBuilder<MenuBloc, MenuState>(
|
workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||||
builder: (context, menuState) =>
|
builder: (context, menuState) =>
|
||||||
BlocBuilder<NotificationFilterBloc, NotificationFilterState>(
|
BlocBuilder<NotificationFilterBloc, NotificationFilterState>(
|
||||||
builder: (context, filterState) =>
|
builder: (context, filterState) =>
|
||||||
|
@ -130,4 +130,24 @@ class UserBackendService {
|
|||||||
final request = UserWorkspaceIdPB.create()..workspaceId = workspaceId;
|
final request = UserWorkspaceIdPB.create()..workspaceId = workspaceId;
|
||||||
return UserEventDeleteWorkspace(request).send();
|
return UserEventDeleteWorkspace(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<FlowyResult<void, FlowyError>> renameWorkspace(
|
||||||
|
String workspaceId,
|
||||||
|
String name,
|
||||||
|
) {
|
||||||
|
final request = RenameWorkspacePB()
|
||||||
|
..workspaceId = workspaceId
|
||||||
|
..newName = name;
|
||||||
|
return UserEventRenameWorkspace(request).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<FlowyResult<void, FlowyError>> updateWorkspaceIcon(
|
||||||
|
String workspaceId,
|
||||||
|
String icon,
|
||||||
|
) {
|
||||||
|
final request = ChangeWorkspaceIconPB()
|
||||||
|
..workspaceId = workspaceId
|
||||||
|
..newIcon = icon;
|
||||||
|
return UserEventChangeWorkspaceIcon(request).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
|
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
|
||||||
import 'package:appflowy_backend/log.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
|
|
||||||
part 'menu_bloc.freezed.dart';
|
|
||||||
|
|
||||||
class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|
||||||
MenuBloc({required this.user, required this.workspaceId})
|
|
||||||
: _workspaceService = WorkspaceService(workspaceId: workspaceId),
|
|
||||||
_listener = WorkspaceListener(
|
|
||||||
user: user,
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
),
|
|
||||||
super(MenuState.initial()) {
|
|
||||||
_dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
final WorkspaceService _workspaceService;
|
|
||||||
final WorkspaceListener _listener;
|
|
||||||
final UserProfilePB user;
|
|
||||||
final String workspaceId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await _listener.stop();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _dispatch() {
|
|
||||||
on<MenuEvent>(
|
|
||||||
(event, emit) async {
|
|
||||||
await event.map(
|
|
||||||
initial: (e) async {
|
|
||||||
_listener.start(appsChanged: _handleAppsOrFail);
|
|
||||||
await _fetchApps(emit);
|
|
||||||
},
|
|
||||||
createApp: (_CreateApp event) async {
|
|
||||||
final result = await _workspaceService.createApp(
|
|
||||||
name: event.name,
|
|
||||||
desc: event.desc,
|
|
||||||
index: event.index,
|
|
||||||
);
|
|
||||||
result.fold(
|
|
||||||
(app) => emit(state.copyWith(lastCreatedView: app)),
|
|
||||||
(error) {
|
|
||||||
Log.error(error);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
successOrFailure: FlowyResult.failure(error),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
didReceiveApps: (e) async {
|
|
||||||
emit(
|
|
||||||
e.appsOrFail.fold(
|
|
||||||
(views) => state.copyWith(
|
|
||||||
views: views,
|
|
||||||
successOrFailure: FlowyResult.success(null),
|
|
||||||
),
|
|
||||||
(err) =>
|
|
||||||
state.copyWith(successOrFailure: FlowyResult.failure(err)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
moveApp: (_MoveApp value) {
|
|
||||||
if (state.views.length > value.fromIndex) {
|
|
||||||
final view = state.views[value.fromIndex];
|
|
||||||
_workspaceService.moveApp(
|
|
||||||
appId: view.id,
|
|
||||||
fromIndex: value.fromIndex,
|
|
||||||
toIndex: value.toIndex,
|
|
||||||
);
|
|
||||||
final apps = List<ViewPB>.from(state.views);
|
|
||||||
|
|
||||||
apps.insert(value.toIndex, apps.removeAt(value.fromIndex));
|
|
||||||
emit(state.copyWith(views: apps));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: unused_element
|
|
||||||
Future<void> _fetchApps(Emitter<MenuState> emit) async {
|
|
||||||
final viewsOrError = await _workspaceService.getViews();
|
|
||||||
emit(
|
|
||||||
viewsOrError.fold(
|
|
||||||
(views) => state.copyWith(views: views),
|
|
||||||
(error) {
|
|
||||||
Log.error(error);
|
|
||||||
return state.copyWith(successOrFailure: FlowyResult.failure(error));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleAppsOrFail(FlowyResult<List<ViewPB>, FlowyError> appsOrFail) {
|
|
||||||
appsOrFail.fold(
|
|
||||||
(apps) => add(MenuEvent.didReceiveApps(FlowyResult.success(apps))),
|
|
||||||
(error) => add(MenuEvent.didReceiveApps(FlowyResult.failure(error))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class MenuEvent with _$MenuEvent {
|
|
||||||
const factory MenuEvent.initial() = _Initial;
|
|
||||||
const factory MenuEvent.createApp(String name, {String? desc, int? index}) =
|
|
||||||
_CreateApp;
|
|
||||||
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
|
||||||
const factory MenuEvent.didReceiveApps(
|
|
||||||
FlowyResult<List<ViewPB>, FlowyError> appsOrFail,
|
|
||||||
) = _ReceiveApps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class MenuState with _$MenuState {
|
|
||||||
const factory MenuState({
|
|
||||||
required List<ViewPB> views,
|
|
||||||
required FlowyResult<void, FlowyError> successOrFailure,
|
|
||||||
ViewPB? lastCreatedView,
|
|
||||||
}) = _MenuState;
|
|
||||||
|
|
||||||
factory MenuState.initial() => MenuState(
|
|
||||||
views: [],
|
|
||||||
successOrFailure: FlowyResult.success(null),
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,2 +1,2 @@
|
|||||||
export 'menu_bloc.dart';
|
|
||||||
export 'menu_user_bloc.dart';
|
export 'menu_user_bloc.dart';
|
||||||
|
export 'sidebar_root_views_bloc.dart';
|
||||||
|
@ -0,0 +1,160 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
|
||||||
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'sidebar_root_views_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class SidebarRootViewsBloc
|
||||||
|
extends Bloc<SidebarRootViewsEvent, SidebarRootViewState> {
|
||||||
|
SidebarRootViewsBloc() : super(SidebarRootViewState.initial()) {
|
||||||
|
_dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
late WorkspaceService _workspaceService;
|
||||||
|
WorkspaceListener? _listener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await _listener?.stop();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _dispatch() {
|
||||||
|
on<SidebarRootViewsEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
await event.when(
|
||||||
|
initial: (userProfile, workspaceId) async {
|
||||||
|
_initial(userProfile, workspaceId);
|
||||||
|
await _fetchApps(emit);
|
||||||
|
},
|
||||||
|
reset: (userProfile, workspaceId) async {
|
||||||
|
await _listener?.stop();
|
||||||
|
_initial(userProfile, workspaceId);
|
||||||
|
await _fetchApps(emit);
|
||||||
|
},
|
||||||
|
createRootView: (name, desc, index) async {
|
||||||
|
final result = await _workspaceService.createApp(
|
||||||
|
name: name,
|
||||||
|
desc: desc,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
result.fold(
|
||||||
|
(view) => emit(state.copyWith(lastCreatedRootView: view)),
|
||||||
|
(error) {
|
||||||
|
Log.error(error);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
successOrFailure: FlowyResult.failure(error),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
didReceiveViews: (viewsOrFailure) async {
|
||||||
|
emit(
|
||||||
|
viewsOrFailure.fold(
|
||||||
|
(views) => state.copyWith(
|
||||||
|
views: views,
|
||||||
|
successOrFailure: FlowyResult.success(null),
|
||||||
|
),
|
||||||
|
(err) =>
|
||||||
|
state.copyWith(successOrFailure: FlowyResult.failure(err)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
moveRootView: (int fromIndex, int toIndex) {
|
||||||
|
if (state.views.length > fromIndex) {
|
||||||
|
final view = state.views[fromIndex];
|
||||||
|
|
||||||
|
_workspaceService.moveApp(
|
||||||
|
appId: view.id,
|
||||||
|
fromIndex: fromIndex,
|
||||||
|
toIndex: toIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
final views = List<ViewPB>.from(state.views);
|
||||||
|
views.insert(toIndex, views.removeAt(fromIndex));
|
||||||
|
emit(state.copyWith(views: views));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchApps(Emitter<SidebarRootViewState> emit) async {
|
||||||
|
final viewsOrError = await _workspaceService.getViews();
|
||||||
|
emit(
|
||||||
|
viewsOrError.fold(
|
||||||
|
(views) => state.copyWith(views: views),
|
||||||
|
(error) {
|
||||||
|
Log.error(error);
|
||||||
|
return state.copyWith(successOrFailure: FlowyResult.failure(error));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleAppsOrFail(FlowyResult<List<ViewPB>, FlowyError> viewsOrFail) {
|
||||||
|
viewsOrFail.fold(
|
||||||
|
(views) => add(
|
||||||
|
SidebarRootViewsEvent.didReceiveViews(FlowyResult.success(views)),
|
||||||
|
),
|
||||||
|
(error) => add(
|
||||||
|
SidebarRootViewsEvent.didReceiveViews(FlowyResult.failure(error)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initial(UserProfilePB userProfile, String workspaceId) {
|
||||||
|
_workspaceService = WorkspaceService(workspaceId: workspaceId);
|
||||||
|
_listener = WorkspaceListener(
|
||||||
|
user: userProfile,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
)..start(appsChanged: _handleAppsOrFail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SidebarRootViewsEvent with _$SidebarRootViewsEvent {
|
||||||
|
const factory SidebarRootViewsEvent.initial(
|
||||||
|
UserProfilePB userProfile,
|
||||||
|
String workspaceId,
|
||||||
|
) = _Initial;
|
||||||
|
const factory SidebarRootViewsEvent.reset(
|
||||||
|
UserProfilePB userProfile,
|
||||||
|
String workspaceId,
|
||||||
|
) = _Reset;
|
||||||
|
const factory SidebarRootViewsEvent.createRootView(
|
||||||
|
String name, {
|
||||||
|
String? desc,
|
||||||
|
int? index,
|
||||||
|
}) = _createRootView;
|
||||||
|
const factory SidebarRootViewsEvent.moveRootView(int fromIndex, int toIndex) =
|
||||||
|
_MoveRootView;
|
||||||
|
const factory SidebarRootViewsEvent.didReceiveViews(
|
||||||
|
FlowyResult<List<ViewPB>, FlowyError> appsOrFail,
|
||||||
|
) = _ReceiveApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SidebarRootViewState with _$SidebarRootViewState {
|
||||||
|
const factory SidebarRootViewState({
|
||||||
|
required List<ViewPB> views,
|
||||||
|
required FlowyResult<void, FlowyError> successOrFailure,
|
||||||
|
@Default(null) ViewPB? lastCreatedRootView,
|
||||||
|
}) = _SidebarRootViewState;
|
||||||
|
|
||||||
|
factory SidebarRootViewState.initial() => SidebarRootViewState(
|
||||||
|
views: [],
|
||||||
|
successOrFailure: FlowyResult.success(null),
|
||||||
|
);
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
part 'user_workspace_bloc.freezed.dart';
|
part 'user_workspace_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -33,43 +36,207 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
},
|
},
|
||||||
createWorkspace: (name, desc) async {
|
createWorkspace: (name, desc) async {
|
||||||
final result = await _userService.createUserWorkspace(name);
|
final result = await _userService.createUserWorkspace(name);
|
||||||
|
final (workspaces, createWorkspaceResult) = result.fold(
|
||||||
|
(s) {
|
||||||
|
final workspaces = [...state.workspaces, s];
|
||||||
|
return (
|
||||||
|
workspaces,
|
||||||
|
FlowyResult<void, FlowyError>.success(null)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) {
|
||||||
|
Log.error(e);
|
||||||
|
return (state.workspaces, FlowyResult.failure(e));
|
||||||
|
},
|
||||||
|
);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
openWorkspaceResult: null,
|
openWorkspaceResult: null,
|
||||||
deleteWorkspaceResult: null,
|
deleteWorkspaceResult: null,
|
||||||
createWorkspaceResult:
|
updateWorkspaceIconResult: null,
|
||||||
result.fold((s) => FlowyResult.success(null), (e) {
|
createWorkspaceResult: createWorkspaceResult,
|
||||||
Log.error(e);
|
workspaces: workspaces,
|
||||||
return FlowyResult.failure(e);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
deleteWorkspace: (workspaceId) async {
|
deleteWorkspace: (workspaceId) async {
|
||||||
|
if (state.workspaces.length <= 1) {
|
||||||
|
// do not allow to delete the last workspace
|
||||||
|
return emit(
|
||||||
|
state.copyWith(
|
||||||
|
openWorkspaceResult: null,
|
||||||
|
createWorkspaceResult: null,
|
||||||
|
updateWorkspaceIconResult: null,
|
||||||
|
renameWorkspaceResult: null,
|
||||||
|
deleteWorkspaceResult: FlowyResult.failure(
|
||||||
|
FlowyError(
|
||||||
|
code: ErrorCode.Internal,
|
||||||
|
msg: 'Cannot delete the last workspace',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final result = await _userService.deleteWorkspaceById(workspaceId);
|
final result = await _userService.deleteWorkspaceById(workspaceId);
|
||||||
|
final (workspaces, deleteWorkspaceResult) = result.fold(
|
||||||
|
(s) {
|
||||||
|
// if the current workspace is deleted, open the first workspace
|
||||||
|
if (state.currentWorkspace?.workspaceId == workspaceId) {
|
||||||
|
add(OpenWorkspace(state.workspaces.first.workspaceId));
|
||||||
|
}
|
||||||
|
// remove the deleted workspace from the list instead of fetching
|
||||||
|
// the workspaces again
|
||||||
|
final workspaces = [...state.workspaces]..removeWhere(
|
||||||
|
(e) => e.workspaceId == workspaceId,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
workspaces,
|
||||||
|
FlowyResult<void, FlowyError>.success(null)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) {
|
||||||
|
Log.error(e);
|
||||||
|
return (state.workspaces, FlowyResult.failure(e));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
openWorkspaceResult: null,
|
openWorkspaceResult: null,
|
||||||
createWorkspaceResult: null,
|
createWorkspaceResult: null,
|
||||||
deleteWorkspaceResult:
|
updateWorkspaceIconResult: null,
|
||||||
result.fold((s) => FlowyResult.success(null), (e) {
|
renameWorkspaceResult: null,
|
||||||
Log.error(e);
|
deleteWorkspaceResult: deleteWorkspaceResult,
|
||||||
return FlowyResult.failure(e);
|
workspaces: workspaces,
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
openWorkspace: (workspaceId) async {
|
openWorkspace: (workspaceId) async {
|
||||||
final result = await _userService.openWorkspace(workspaceId);
|
final (currentWorkspace, openWorkspaceResult) =
|
||||||
|
await _userService.openWorkspace(workspaceId).fold(
|
||||||
|
(s) {
|
||||||
|
final openedWorkspace = state.workspaces.firstWhere(
|
||||||
|
(e) => e.workspaceId == workspaceId,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
openedWorkspace,
|
||||||
|
FlowyResult<void, FlowyError>.success(null)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(f) {
|
||||||
|
Log.error(f);
|
||||||
|
return (state.currentWorkspace, FlowyResult.failure(f));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
createWorkspaceResult: null,
|
createWorkspaceResult: null,
|
||||||
deleteWorkspaceResult: null,
|
deleteWorkspaceResult: null,
|
||||||
openWorkspaceResult:
|
updateWorkspaceIconResult: null,
|
||||||
result.fold((s) => FlowyResult.success(null), (e) {
|
openWorkspaceResult: openWorkspaceResult,
|
||||||
Log.error(e);
|
currentWorkspace: currentWorkspace,
|
||||||
return FlowyResult.failure(e);
|
),
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
renameWorkspace: (workspaceId, name) async {
|
||||||
|
final result = await _userService.renameWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
final (workspaces, currentWorkspace, renameWorkspaceResult) =
|
||||||
|
result.fold(
|
||||||
|
(s) {
|
||||||
|
final workspaces = state.workspaces.map((e) {
|
||||||
|
if (e.workspaceId == workspaceId) {
|
||||||
|
e.freeze();
|
||||||
|
return e.rebuild((p0) {
|
||||||
|
p0.name = name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final currentWorkspace = workspaces.firstWhere(
|
||||||
|
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
workspaces,
|
||||||
|
currentWorkspace,
|
||||||
|
FlowyResult<void, FlowyError>.success(null),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) {
|
||||||
|
Log.error(e);
|
||||||
|
return (
|
||||||
|
state.workspaces,
|
||||||
|
state.currentWorkspace,
|
||||||
|
FlowyResult.failure(e),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
createWorkspaceResult: null,
|
||||||
|
deleteWorkspaceResult: null,
|
||||||
|
openWorkspaceResult: null,
|
||||||
|
updateWorkspaceIconResult: null,
|
||||||
|
workspaces: workspaces,
|
||||||
|
currentWorkspace: currentWorkspace,
|
||||||
|
renameWorkspaceResult: renameWorkspaceResult,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateWorkspaceIcon: (workspaceId, icon) async {
|
||||||
|
final result = await _userService.updateWorkspaceIcon(
|
||||||
|
workspaceId,
|
||||||
|
icon,
|
||||||
|
);
|
||||||
|
|
||||||
|
final (workspaces, currentWorkspace, updateWorkspaceIconResult) =
|
||||||
|
result.fold(
|
||||||
|
(s) {
|
||||||
|
final workspaces = state.workspaces.map((e) {
|
||||||
|
if (e.workspaceId == workspaceId) {
|
||||||
|
e.freeze();
|
||||||
|
return e.rebuild((p0) {
|
||||||
|
// TODO(Lucas): the icon is not ready in the backend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final currentWorkspace = workspaces.firstWhere(
|
||||||
|
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
workspaces,
|
||||||
|
currentWorkspace,
|
||||||
|
FlowyResult<void, FlowyError>.success(null),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) {
|
||||||
|
Log.error(e);
|
||||||
|
return (
|
||||||
|
state.workspaces,
|
||||||
|
state.currentWorkspace,
|
||||||
|
FlowyResult.failure(e),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
createWorkspaceResult: null,
|
||||||
|
deleteWorkspaceResult: null,
|
||||||
|
openWorkspaceResult: null,
|
||||||
|
renameWorkspaceResult: null,
|
||||||
|
updateWorkspaceIconResult: updateWorkspaceIconResult,
|
||||||
|
workspaces: workspaces,
|
||||||
|
currentWorkspace: currentWorkspace,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -83,24 +250,17 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
|
|
||||||
Future<(UserWorkspacePB currentWorkspace, List<UserWorkspacePB> workspaces)?>
|
Future<(UserWorkspacePB currentWorkspace, List<UserWorkspacePB> workspaces)?>
|
||||||
_fetchWorkspaces() async {
|
_fetchWorkspaces() async {
|
||||||
final result = await _userService.getCurrentWorkspace();
|
try {
|
||||||
return result.fold((currentWorkspace) async {
|
final currentWorkspace =
|
||||||
final result = await _userService.getWorkspaces();
|
await _userService.getCurrentWorkspace().getOrThrow();
|
||||||
return result.fold((workspaces) {
|
final workspaces = await _userService.getWorkspaces().getOrThrow();
|
||||||
return (
|
final currentWorkspaceInList =
|
||||||
workspaces.firstWhere(
|
workspaces.firstWhere((e) => e.workspaceId == currentWorkspace.id);
|
||||||
(e) => e.workspaceId == currentWorkspace.id,
|
return (currentWorkspaceInList, workspaces);
|
||||||
),
|
} catch (e) {
|
||||||
workspaces
|
|
||||||
);
|
|
||||||
}, (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}, (e) {
|
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
return null;
|
return null;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +274,14 @@ class UserWorkspaceEvent with _$UserWorkspaceEvent {
|
|||||||
DeleteWorkspace;
|
DeleteWorkspace;
|
||||||
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
|
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
|
||||||
OpenWorkspace;
|
OpenWorkspace;
|
||||||
|
const factory UserWorkspaceEvent.renameWorkspace(
|
||||||
|
String workspaceId,
|
||||||
|
String name,
|
||||||
|
) = _RenameWorkspace;
|
||||||
|
const factory UserWorkspaceEvent.updateWorkspaceIcon(
|
||||||
|
String workspaceId,
|
||||||
|
String icon,
|
||||||
|
) = _UpdateWorkspaceIcon;
|
||||||
const factory UserWorkspaceEvent.workspacesReceived(
|
const factory UserWorkspaceEvent.workspacesReceived(
|
||||||
FlowyResult<List<UserWorkspacePB>, FlowyError> workspacesOrFail,
|
FlowyResult<List<UserWorkspacePB>, FlowyError> workspacesOrFail,
|
||||||
) = WorkspacesReceived;
|
) = WorkspacesReceived;
|
||||||
@ -127,8 +295,10 @@ class UserWorkspaceState with _$UserWorkspaceState {
|
|||||||
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
|
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
|
||||||
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
|
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
|
||||||
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
|
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
|
||||||
|
@Default(null) FlowyResult<void, FlowyError>? renameWorkspaceResult,
|
||||||
|
@Default(null) FlowyResult<void, FlowyError>? updateWorkspaceIconResult,
|
||||||
}) = _UserWorkspaceState;
|
}) = _UserWorkspaceState;
|
||||||
|
|
||||||
factory UserWorkspaceState.initial() =>
|
factory UserWorkspaceState.initial() =>
|
||||||
const UserWorkspaceState(currentWorkspace: null, workspaces: []);
|
const UserWorkspaceState(currentWorkspace: null, workspaces: []);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_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/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||||
@ -125,8 +125,8 @@ class _PersonalFolderHeaderState extends State<PersonalFolderHeader> {
|
|||||||
LocaleKeys.newPageText.tr(),
|
LocaleKeys.newPageText.tr(),
|
||||||
(viewName, _) {
|
(viewName, _) {
|
||||||
if (viewName.isNotEmpty) {
|
if (viewName.isNotEmpty) {
|
||||||
context.read<MenuBloc>().add(
|
context.read<SidebarRootViewsBloc>().add(
|
||||||
MenuEvent.createApp(
|
SidebarRootViewsEvent.createRootView(
|
||||||
viewName,
|
viewName,
|
||||||
index: 0,
|
index: 0,
|
||||||
),
|
),
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
|
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
|
||||||
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
|
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_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/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||||
@ -22,6 +21,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
|||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
/// Home Sidebar is the left side bar of the home page.
|
/// Home Sidebar is the left side bar of the home page.
|
||||||
@ -81,44 +81,72 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return BlocProvider<UserWorkspaceBloc>(
|
||||||
providers: [
|
create: (_) => UserWorkspaceBloc(userProfile: widget.userProfile)
|
||||||
BlocProvider(
|
..add(const UserWorkspaceEvent.fetchWorkspaces()),
|
||||||
create: (_) => getIt<NotificationActionBloc>(),
|
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
),
|
buildWhen: (previous, current) =>
|
||||||
BlocProvider(
|
previous.currentWorkspace?.workspaceId !=
|
||||||
create: (_) => MenuBloc(
|
current.currentWorkspace?.workspaceId,
|
||||||
user: widget.userProfile,
|
builder: (context, state) {
|
||||||
workspaceId: widget.workspaceSetting.workspaceId,
|
return MultiBlocProvider(
|
||||||
)..add(const MenuEvent.initial()),
|
providers: [
|
||||||
),
|
BlocProvider(
|
||||||
],
|
create: (_) => getIt<NotificationActionBloc>(),
|
||||||
child: MultiBlocListener(
|
),
|
||||||
listeners: [
|
BlocProvider(
|
||||||
BlocListener<MenuBloc, MenuState>(
|
create: (_) => SidebarRootViewsBloc()
|
||||||
listenWhen: (p, c) =>
|
..add(
|
||||||
p.lastCreatedView?.id != c.lastCreatedView?.id,
|
SidebarRootViewsEvent.initial(
|
||||||
listener: (context, state) => context.read<TabsBloc>().add(
|
widget.userProfile,
|
||||||
TabsEvent.openPlugin(plugin: state.lastCreatedView!.plugin()),
|
state.currentWorkspace?.workspaceId ??
|
||||||
|
widget.workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||||
|
listenWhen: (p, c) =>
|
||||||
|
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||||
|
listener: (context, state) => context.read<TabsBloc>().add(
|
||||||
|
TabsEvent.openPlugin(
|
||||||
|
plugin: state.lastCreatedRootView!.plugin(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
BlocListener<NotificationActionBloc, NotificationActionState>(
|
||||||
BlocListener<NotificationActionBloc, NotificationActionState>(
|
listenWhen: (_, curr) => curr.action != null,
|
||||||
listenWhen: (_, curr) => curr.action != null,
|
listener: _onNotificationAction,
|
||||||
listener: _onNotificationAction,
|
),
|
||||||
),
|
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
],
|
listener: (context, state) {
|
||||||
child: Builder(
|
context.read<SidebarRootViewsBloc>().add(
|
||||||
builder: (context) {
|
SidebarRootViewsEvent.reset(
|
||||||
final menuState = context.watch<MenuBloc>().state;
|
widget.userProfile,
|
||||||
final favoriteState = context.watch<FavoriteBloc>().state;
|
state.currentWorkspace?.workspaceId ??
|
||||||
|
widget.workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final menuState = context.watch<SidebarRootViewsBloc>().state;
|
||||||
|
final favoriteState = context.watch<FavoriteBloc>().state;
|
||||||
|
|
||||||
return _buildSidebar(
|
return _buildSidebar(
|
||||||
context,
|
context,
|
||||||
menuState.views,
|
menuState.views,
|
||||||
favoriteState.views,
|
favoriteState.views,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -195,8 +223,11 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
|||||||
final action = state.action;
|
final action = state.action;
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
if (action.type == ActionType.openView) {
|
if (action.type == ActionType.openView) {
|
||||||
final view =
|
final view = context
|
||||||
context.read<MenuBloc>().state.views.findView(action.objectId);
|
.read<SidebarRootViewsBloc>()
|
||||||
|
.state
|
||||||
|
.views
|
||||||
|
.findView(action.objectId);
|
||||||
|
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
final Map<String, dynamic> arguments = {};
|
final Map<String, dynamic> arguments = {};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
@ -25,7 +25,9 @@ class SidebarNewPageButton extends StatelessWidget {
|
|||||||
LocaleKeys.newPageText.tr(),
|
LocaleKeys.newPageText.tr(),
|
||||||
(viewName, _) {
|
(viewName, _) {
|
||||||
if (viewName.isNotEmpty) {
|
if (viewName.isNotEmpty) {
|
||||||
context.read<MenuBloc>().add(MenuEvent.createApp(viewName));
|
context
|
||||||
|
.read<SidebarRootViewsBloc>()
|
||||||
|
.add(SidebarRootViewsEvent.createRootView(viewName));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:appflowy/core/frameless_window.dart';
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
@ -24,7 +24,7 @@ class SidebarTopMenu extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<MenuBloc, MenuState>(
|
return BlocBuilder<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: HomeSizes.topBarHeight,
|
height: HomeSizes.topBarHeight,
|
||||||
|
@ -3,7 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_item_list.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
@ -13,6 +13,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SidebarWorkspace extends StatelessWidget {
|
class SidebarWorkspace extends StatelessWidget {
|
||||||
@ -27,32 +28,28 @@ class SidebarWorkspace extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<UserWorkspaceBloc>(
|
return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
listener: _showResultDialog,
|
||||||
..add(const UserWorkspaceEvent.fetchWorkspaces()),
|
builder: (context, state) {
|
||||||
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
|
final currentWorkspace = state.currentWorkspace;
|
||||||
listener: _showResultDialog,
|
// todo: show something if there is no workspace
|
||||||
builder: (context, state) {
|
if (currentWorkspace == null) {
|
||||||
final currentWorkspace = state.currentWorkspace;
|
return const SizedBox.shrink();
|
||||||
// todo: show something if there is no workspace
|
}
|
||||||
if (currentWorkspace == null) {
|
return Row(
|
||||||
return const SizedBox.shrink();
|
children: [
|
||||||
}
|
Expanded(
|
||||||
return Row(
|
child: _WorkspaceWrapper(
|
||||||
children: [
|
userProfile: userProfile,
|
||||||
Expanded(
|
currentWorkspace: currentWorkspace,
|
||||||
child: _WorkspaceWrapper(
|
|
||||||
userProfile: userProfile,
|
|
||||||
currentWorkspace: currentWorkspace,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
UserSettingButton(userProfile: userProfile),
|
),
|
||||||
const HSpace(4),
|
UserSettingButton(userProfile: userProfile),
|
||||||
NotificationButton(views: views),
|
const HSpace(4),
|
||||||
],
|
NotificationButton(views: views),
|
||||||
);
|
],
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +83,26 @@ class SidebarWorkspace extends StatelessWidget {
|
|||||||
showSnackBarMessage(context, message);
|
showSnackBarMessage(context, message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = state.updateWorkspaceIconResult;
|
||||||
|
if (result != null) {
|
||||||
|
final message = result.fold(
|
||||||
|
(s) => LocaleKeys.workspace_updateIconSuccess.tr(),
|
||||||
|
(e) => '${LocaleKeys.workspace_updateIconFailed.tr()}: ${e.msg}',
|
||||||
|
);
|
||||||
|
showSnackBarMessage(context, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = state.renameWorkspaceResult;
|
||||||
|
if (result != null) {
|
||||||
|
final message = result.fold(
|
||||||
|
(s) => LocaleKeys.workspace_renameSuccess.tr(),
|
||||||
|
(e) => '${LocaleKeys.workspace_renameFailed.tr()}: ${e.msg}',
|
||||||
|
);
|
||||||
|
showSnackBarMessage(context, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +178,7 @@ class _DesktopWorkspaceWrapperState extends State<_DesktopWorkspaceWrapper> {
|
|||||||
},
|
},
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
onTap: () => controller.show(),
|
onTap: () => controller.show(),
|
||||||
|
useIntrinsicWidth: true,
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
text: Row(
|
text: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -170,9 +188,11 @@ class _DesktopWorkspaceWrapperState extends State<_DesktopWorkspaceWrapper> {
|
|||||||
child: WorkspaceIcon(workspace: widget.currentWorkspace),
|
child: WorkspaceIcon(workspace: widget.currentWorkspace),
|
||||||
),
|
),
|
||||||
const HSpace(8),
|
const HSpace(8),
|
||||||
FlowyText.medium(
|
Expanded(
|
||||||
widget.currentWorkspace.name,
|
child: FlowyText.medium(
|
||||||
overflow: TextOverflow.ellipsis,
|
widget.currentWorkspace.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const FlowySvg(FlowySvgs.drop_menu_show_m),
|
const FlowySvg(FlowySvgs.drop_menu_show_m),
|
||||||
],
|
],
|
||||||
|
@ -64,23 +64,34 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell {
|
|||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
|
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
PopoverContainer.of(context).closeAll();
|
||||||
|
|
||||||
|
final workspaceBloc = context.read<UserWorkspaceBloc>();
|
||||||
switch (inner) {
|
switch (inner) {
|
||||||
case WorkspaceMoreAction.delete:
|
case WorkspaceMoreAction.delete:
|
||||||
await NavigatorAlertDialog(
|
await NavigatorAlertDialog(
|
||||||
title: LocaleKeys.workspace_deleteWorkspaceHintText.tr(),
|
title: LocaleKeys.workspace_deleteWorkspaceHintText.tr(),
|
||||||
confirm: () {
|
confirm: () {
|
||||||
context.read<UserWorkspaceBloc>().add(
|
workspaceBloc.add(
|
||||||
UserWorkspaceEvent.deleteWorkspace(workspace.workspaceId),
|
UserWorkspaceEvent.deleteWorkspace(workspace.workspaceId),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).show(context);
|
).show(context);
|
||||||
case WorkspaceMoreAction.rename:
|
case WorkspaceMoreAction.rename:
|
||||||
|
await NavigatorTextFieldDialog(
|
||||||
// TODO(Lucas): integrate with the backend
|
title: LocaleKeys.workspace_create.tr(),
|
||||||
}
|
value: workspace.name,
|
||||||
|
hintText: '',
|
||||||
if (context.mounted) {
|
autoSelectAllText: true,
|
||||||
PopoverContainer.of(context).closeAll();
|
onConfirm: (name, context) async {
|
||||||
|
workspaceBloc.add(
|
||||||
|
UserWorkspaceEvent.renameWorkspace(
|
||||||
|
workspace.workspaceId,
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||||
import 'package:appflowy/util/color_generator/color_generator.dart';
|
import 'package:appflowy/util/color_generator/color_generator.dart';
|
||||||
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class WorkspaceIcon extends StatelessWidget {
|
class WorkspaceIcon extends StatelessWidget {
|
||||||
const WorkspaceIcon({
|
const WorkspaceIcon({
|
||||||
@ -13,17 +17,37 @@ class WorkspaceIcon extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// TODO(Lucas): support icon later
|
return AppFlowyPopover(
|
||||||
return Container(
|
offset: const Offset(0, 8),
|
||||||
alignment: Alignment.center,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
decoration: BoxDecoration(
|
constraints: BoxConstraints.loose(const Size(360, 380)),
|
||||||
color: ColorGenerator.generateColorFromString(workspace.name),
|
clickHandler: PopoverClickHandler.gestureDetector,
|
||||||
borderRadius: BorderRadius.circular(4),
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
),
|
return FlowyIconPicker(
|
||||||
child: FlowyText(
|
onSelected: (result) {
|
||||||
workspace.name.isEmpty ? '' : workspace.name.substring(0, 1),
|
context.read<UserWorkspaceBloc>().add(
|
||||||
fontSize: 16,
|
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||||
color: Colors.black,
|
workspace.workspaceId,
|
||||||
|
result.emoji,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorGenerator.generateColorFromString(workspace.name),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: FlowyText(
|
||||||
|
workspace.name.isEmpty ? '' : workspace.name.substring(0, 1),
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,8 @@ import 'package:appflowy/shared/af_role_pb_extension.dart';
|
|||||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -15,6 +13,9 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
const createWorkspaceButtonKey = ValueKey('createWorkspaceButton');
|
||||||
|
|
||||||
class WorkspacesMenu extends StatelessWidget {
|
class WorkspacesMenu extends StatelessWidget {
|
||||||
const WorkspacesMenu({
|
const WorkspacesMenu({
|
||||||
super.key,
|
super.key,
|
||||||
@ -38,14 +39,17 @@ class WorkspacesMenu extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FlowyText.medium(
|
Expanded(
|
||||||
_getUserInfo(),
|
child: FlowyText.medium(
|
||||||
fontSize: 12.0,
|
_getUserInfo(),
|
||||||
overflow: TextOverflow.ellipsis,
|
fontSize: 12.0,
|
||||||
color: Theme.of(context).hintColor,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const HSpace(4.0),
|
||||||
FlowyButton(
|
FlowyButton(
|
||||||
|
key: createWorkspaceButtonKey,
|
||||||
useIntrinsicWidth: true,
|
useIntrinsicWidth: true,
|
||||||
text: const FlowySvg(FlowySvgs.add_m),
|
text: const FlowySvg(FlowySvgs.add_m),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -57,7 +61,7 @@ class WorkspacesMenu extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final workspace in workspaces) ...[
|
for (final workspace in workspaces) ...[
|
||||||
_WorkspaceMenuItem(
|
WorkspaceMenuItem(
|
||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
isSelected: workspace.workspaceId == currentWorkspace.workspaceId,
|
isSelected: workspace.workspaceId == currentWorkspace.workspaceId,
|
||||||
@ -82,29 +86,19 @@ class WorkspacesMenu extends StatelessWidget {
|
|||||||
|
|
||||||
Future<void> _showCreateWorkspaceDialog(BuildContext context) async {
|
Future<void> _showCreateWorkspaceDialog(BuildContext context) async {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
await NavigatorTextFieldDialog(
|
final workspaceBloc = context.read<UserWorkspaceBloc>();
|
||||||
title: LocaleKeys.workspace_create.tr(),
|
await CreateWorkspaceDialog(
|
||||||
value: '',
|
onConfirm: (name) {
|
||||||
hintText: '',
|
workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name, ''));
|
||||||
autoSelectAllText: true,
|
|
||||||
onConfirm: (name, context) async {
|
|
||||||
final request = CreateWorkspacePB.create()..name = name;
|
|
||||||
final result = await UserEventCreateWorkspace(request).send();
|
|
||||||
final message = result.fold(
|
|
||||||
(s) => LocaleKeys.workspace_createSuccess.tr(),
|
|
||||||
(e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}',
|
|
||||||
);
|
|
||||||
if (context.mounted) {
|
|
||||||
showSnackBarMessage(context, message);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WorkspaceMenuItem extends StatelessWidget {
|
class WorkspaceMenuItem extends StatelessWidget {
|
||||||
const _WorkspaceMenuItem({
|
const WorkspaceMenuItem({
|
||||||
|
super.key,
|
||||||
required this.workspace,
|
required this.workspace,
|
||||||
required this.userProfile,
|
required this.userProfile,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
@ -143,9 +137,10 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
iconPadding: 10.0,
|
iconPadding: 10.0,
|
||||||
leftIconSize: const Size.square(32),
|
leftIconSize: const Size.square(32),
|
||||||
leftIcon: WorkspaceIcon(
|
leftIcon: const SizedBox.square(
|
||||||
workspace: workspace,
|
dimension: 32,
|
||||||
),
|
),
|
||||||
|
rightIcon: const HSpace(42.0),
|
||||||
text: Column(
|
text: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -163,6 +158,15 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 12,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 32,
|
||||||
|
child: WorkspaceIcon(
|
||||||
|
workspace: workspace,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 12.0,
|
right: 12.0,
|
||||||
child: Align(child: _buildRightIcon(context)),
|
child: Align(child: _buildRightIcon(context)),
|
||||||
@ -187,9 +191,28 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
|||||||
WorkspaceMoreActionList(workspace: workspace),
|
WorkspaceMoreActionList(workspace: workspace),
|
||||||
const FlowySvg(
|
const FlowySvg(
|
||||||
FlowySvgs.blue_check_s,
|
FlowySvgs.blue_check_s,
|
||||||
blendMode: null,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CreateWorkspaceDialog extends StatelessWidget {
|
||||||
|
const CreateWorkspaceDialog({
|
||||||
|
super.key,
|
||||||
|
required this.onConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
|
final void Function(String name) onConfirm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return NavigatorTextFieldDialog(
|
||||||
|
title: LocaleKeys.workspace_create.tr(),
|
||||||
|
value: '',
|
||||||
|
hintText: '',
|
||||||
|
autoSelectAllText: true,
|
||||||
|
onConfirm: (name, _) => onConfirm(name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
library appflowy_result;
|
library appflowy_result;
|
||||||
|
|
||||||
|
export 'src/async_result.dart';
|
||||||
export 'src/result.dart';
|
export 'src/result.dart';
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
|
||||||
|
typedef FlowyAsyncResult<S, F extends Object> = Future<FlowyResult<S, F>>;
|
||||||
|
|
||||||
|
extension FlowyAsyncResultExtension<S, F extends Object>
|
||||||
|
on FlowyAsyncResult<S, F> {
|
||||||
|
Future<S> getOrElse(S Function(F f) onFailure) {
|
||||||
|
return then((result) => result.getOrElse(onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<S> getOrThrow() {
|
||||||
|
return then((result) => result.getOrThrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<W> fold<W>(
|
||||||
|
W Function(S s) onSuccess,
|
||||||
|
W Function(F f) onFailure,
|
||||||
|
) {
|
||||||
|
return then<W>((result) => result.fold(onSuccess, onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isError() {
|
||||||
|
return then((result) => result.isFailure());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isSuccess() {
|
||||||
|
return then((result) => result.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
FlowyAsyncResult<S, F> onFailure(void Function(F failure) onFailure) {
|
||||||
|
return then((result) => result..onFailure(onFailure));
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,28 @@
|
|||||||
abstract class FlowyResult<S, F> {
|
abstract class FlowyResult<S, F extends Object> {
|
||||||
const FlowyResult();
|
const FlowyResult();
|
||||||
|
|
||||||
factory FlowyResult.success(S s) => FlowySuccess(s);
|
factory FlowyResult.success(S s) => FlowySuccess(s);
|
||||||
|
|
||||||
factory FlowyResult.failure(F e) => FlowyFailure(e);
|
factory FlowyResult.failure(F f) => FlowyFailure(f);
|
||||||
|
|
||||||
T fold<T>(T Function(S s) onSuccess, T Function(F e) onFailure);
|
T fold<T>(T Function(S s) onSuccess, T Function(F f) onFailure);
|
||||||
|
|
||||||
FlowyResult<T, F> map<T>(T Function(S success) fn);
|
FlowyResult<T, F> map<T>(T Function(S success) fn);
|
||||||
FlowyResult<S, T> mapError<T>(T Function(F error) fn);
|
FlowyResult<S, T> mapError<T extends Object>(T Function(F failure) fn);
|
||||||
|
|
||||||
bool isSuccess();
|
bool isSuccess();
|
||||||
bool isFailure();
|
bool isFailure();
|
||||||
|
|
||||||
S? toNullable();
|
S? toNullable();
|
||||||
|
|
||||||
void onSuccess(
|
void onSuccess(void Function(S s) onSuccess);
|
||||||
void Function(S s) onSuccess,
|
void onFailure(void Function(F f) onFailure);
|
||||||
);
|
|
||||||
|
|
||||||
void onFailure(
|
S getOrElse(S Function(F failure) onFailure);
|
||||||
void Function(F f) onFailure,
|
S getOrThrow();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowySuccess<S, F> implements FlowyResult<S, F> {
|
class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
|
||||||
final S _value;
|
final S _value;
|
||||||
|
|
||||||
FlowySuccess(this._value);
|
FlowySuccess(this._value);
|
||||||
@ -54,7 +52,7 @@ class FlowySuccess<S, F> implements FlowyResult<S, F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FlowyResult<S, T> mapError<T>(T Function(F error) fn) {
|
FlowyResult<S, T> mapError<T extends Object>(T Function(F error) fn) {
|
||||||
return FlowySuccess(_value);
|
return FlowySuccess(_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,40 +78,50 @@ class FlowySuccess<S, F> implements FlowyResult<S, F> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onFailure(void Function(F failure) onFailure) {}
|
void onFailure(void Function(F failure) onFailure) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
S getOrElse(S Function(F failure) onFailure) {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
S getOrThrow() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowyFailure<S, F> implements FlowyResult<S, F> {
|
class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
|
||||||
final F _error;
|
final F _value;
|
||||||
|
|
||||||
FlowyFailure(this._error);
|
FlowyFailure(this._value);
|
||||||
|
|
||||||
F get error => _error;
|
F get error => _value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is FlowyFailure &&
|
other is FlowyFailure &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
_error == other._error;
|
_value == other._value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => _error.hashCode;
|
int get hashCode => _value.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'Failure(error: $_error)';
|
String toString() => 'Failure(error: $_value)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T fold<T>(T Function(S s) onSuccess, T Function(F e) onFailure) =>
|
T fold<T>(T Function(S s) onSuccess, T Function(F e) onFailure) =>
|
||||||
onFailure(_error);
|
onFailure(_value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
map<T>(T Function(S success) fn) {
|
map<T>(T Function(S success) fn) {
|
||||||
return FlowyFailure(_error);
|
return FlowyFailure(_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FlowyResult<S, T> mapError<T>(T Function(F error) fn) {
|
FlowyResult<S, T> mapError<T extends Object>(T Function(F error) fn) {
|
||||||
return FlowyFailure(fn(_error));
|
return FlowyFailure(fn(_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -136,6 +144,16 @@ class FlowyFailure<S, F> implements FlowyResult<S, F> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onFailure(void Function(F failure) onFailure) {
|
void onFailure(void Function(F failure) onFailure) {
|
||||||
onFailure(_error);
|
onFailure(_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
S getOrElse(S Function(F failure) onFailure) {
|
||||||
|
return onFailure(_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
S getOrThrow() {
|
||||||
|
throw _value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import '../../util.dart';
|
import '../../util.dart';
|
||||||
@ -10,26 +10,33 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('assert initial apps is the build-in app', () async {
|
test('assert initial apps is the build-in app', () async {
|
||||||
final menuBloc = MenuBloc(
|
final menuBloc = SidebarRootViewsBloc()
|
||||||
user: testContext.userProfile,
|
..add(
|
||||||
workspaceId: testContext.currentWorkspace.id,
|
SidebarRootViewsEvent.initial(
|
||||||
)..add(const MenuEvent.initial());
|
testContext.userProfile,
|
||||||
|
testContext.currentWorkspace.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(menuBloc.state.views.length == 1);
|
assert(menuBloc.state.views.length == 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reorder apps', () async {
|
test('reorder apps', () async {
|
||||||
final menuBloc = MenuBloc(
|
final menuBloc = SidebarRootViewsBloc()
|
||||||
user: testContext.userProfile,
|
..add(
|
||||||
workspaceId: testContext.currentWorkspace.id,
|
SidebarRootViewsEvent.initial(
|
||||||
)..add(const MenuEvent.initial());
|
testContext.userProfile,
|
||||||
|
testContext.currentWorkspace.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
menuBloc.add(const MenuEvent.createApp("App 1"));
|
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 1"));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
menuBloc.add(const MenuEvent.createApp("App 2"));
|
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 2"));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
menuBloc.add(const MenuEvent.createApp("App 3"));
|
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 3"));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(menuBloc.state.views[1].name == 'App 1');
|
assert(menuBloc.state.views[1].name == 'App 1');
|
||||||
|
@ -71,7 +71,11 @@
|
|||||||
"deleteSuccess": "Workspace deleted successfully",
|
"deleteSuccess": "Workspace deleted successfully",
|
||||||
"deleteFailed": "Failed to delete workspace",
|
"deleteFailed": "Failed to delete workspace",
|
||||||
"openSuccess": "Open workspace successfully",
|
"openSuccess": "Open workspace successfully",
|
||||||
"openFailed": "Failed to open workspace"
|
"openFailed": "Failed to open workspace",
|
||||||
|
"renameSuccess": "Workspace renamed successfully",
|
||||||
|
"renameFailed": "Failed to rename workspace",
|
||||||
|
"updateIconSuccess": "Workspace reset successfully",
|
||||||
|
"updateIconFailed": "Failed to reset workspace"
|
||||||
},
|
},
|
||||||
"shareAction": {
|
"shareAction": {
|
||||||
"buttonText": "Share",
|
"buttonText": "Share",
|
||||||
|
Loading…
Reference in New Issue
Block a user