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:
@ -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 '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_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-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'user_workspace_bloc.freezed.dart';
|
||||
|
||||
@ -33,43 +36,207 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
},
|
||||
createWorkspace: (name, desc) async {
|
||||
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(
|
||||
state.copyWith(
|
||||
openWorkspaceResult: null,
|
||||
deleteWorkspaceResult: null,
|
||||
createWorkspaceResult:
|
||||
result.fold((s) => FlowyResult.success(null), (e) {
|
||||
Log.error(e);
|
||||
return FlowyResult.failure(e);
|
||||
}),
|
||||
updateWorkspaceIconResult: null,
|
||||
createWorkspaceResult: createWorkspaceResult,
|
||||
workspaces: workspaces,
|
||||
),
|
||||
);
|
||||
},
|
||||
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 (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(
|
||||
state.copyWith(
|
||||
openWorkspaceResult: null,
|
||||
createWorkspaceResult: null,
|
||||
deleteWorkspaceResult:
|
||||
result.fold((s) => FlowyResult.success(null), (e) {
|
||||
Log.error(e);
|
||||
return FlowyResult.failure(e);
|
||||
}),
|
||||
updateWorkspaceIconResult: null,
|
||||
renameWorkspaceResult: null,
|
||||
deleteWorkspaceResult: deleteWorkspaceResult,
|
||||
workspaces: workspaces,
|
||||
),
|
||||
);
|
||||
},
|
||||
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(
|
||||
state.copyWith(
|
||||
createWorkspaceResult: null,
|
||||
deleteWorkspaceResult: null,
|
||||
openWorkspaceResult:
|
||||
result.fold((s) => FlowyResult.success(null), (e) {
|
||||
Log.error(e);
|
||||
return FlowyResult.failure(e);
|
||||
}),
|
||||
updateWorkspaceIconResult: null,
|
||||
openWorkspaceResult: openWorkspaceResult,
|
||||
currentWorkspace: currentWorkspace,
|
||||
),
|
||||
);
|
||||
},
|
||||
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)?>
|
||||
_fetchWorkspaces() async {
|
||||
final result = await _userService.getCurrentWorkspace();
|
||||
return result.fold((currentWorkspace) async {
|
||||
final result = await _userService.getWorkspaces();
|
||||
return result.fold((workspaces) {
|
||||
return (
|
||||
workspaces.firstWhere(
|
||||
(e) => e.workspaceId == currentWorkspace.id,
|
||||
),
|
||||
workspaces
|
||||
);
|
||||
}, (e) {
|
||||
Log.error(e);
|
||||
return null;
|
||||
});
|
||||
}, (e) {
|
||||
try {
|
||||
final currentWorkspace =
|
||||
await _userService.getCurrentWorkspace().getOrThrow();
|
||||
final workspaces = await _userService.getWorkspaces().getOrThrow();
|
||||
final currentWorkspaceInList =
|
||||
workspaces.firstWhere((e) => e.workspaceId == currentWorkspace.id);
|
||||
return (currentWorkspaceInList, workspaces);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,6 +274,14 @@ class UserWorkspaceEvent with _$UserWorkspaceEvent {
|
||||
DeleteWorkspace;
|
||||
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
|
||||
OpenWorkspace;
|
||||
const factory UserWorkspaceEvent.renameWorkspace(
|
||||
String workspaceId,
|
||||
String name,
|
||||
) = _RenameWorkspace;
|
||||
const factory UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
String workspaceId,
|
||||
String icon,
|
||||
) = _UpdateWorkspaceIcon;
|
||||
const factory UserWorkspaceEvent.workspacesReceived(
|
||||
FlowyResult<List<UserWorkspacePB>, FlowyError> workspacesOrFail,
|
||||
) = WorkspacesReceived;
|
||||
@ -127,8 +295,10 @@ class UserWorkspaceState with _$UserWorkspaceState {
|
||||
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? renameWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? updateWorkspaceIconResult,
|
||||
}) = _UserWorkspaceState;
|
||||
|
||||
factory UserWorkspaceState.initial() =>
|
||||
factory UserWorkspaceState.initial() =>
|
||||
const UserWorkspaceState(currentWorkspace: null, workspaces: []);
|
||||
}
|
||||
|
Reference in New Issue
Block a user