Skip to content

Commit 5598689

Browse files
authored
feat: control access level in share menu (#8017)
* feat: use auto_confirm in share with user api * feat: reduce share api calls * feat: add team space section in title bar * feat: only user with full access can invite guest/member * feat: customize popup style * feat: support section type * feat: update access level based on public or private space * test: add access level tests * chore: update i18n * chore: disable bloc talker * fix: tests * fix: general access test * fix: view title bar test * chore: i18n
1 parent 88a3e9e commit 5598689

34 files changed

+707
-210
lines changed

frontend/appflowy_flutter/lib/features/share_tab/data/models/share_access_level.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ enum ShareAccessLevel {
3232
String get subtitle {
3333
switch (this) {
3434
case ShareAccessLevel.readOnly:
35-
return 'Can\'t make changes';
35+
return LocaleKeys.shareTab_cantMakeChanges.tr();
3636
case ShareAccessLevel.readAndComment:
37-
return 'Can make any changes';
37+
return LocaleKeys.shareTab_canMakeAnyChanges.tr();
3838
case ShareAccessLevel.readAndWrite:
39-
return 'Can make any changes';
39+
return LocaleKeys.shareTab_canMakeAnyChanges.tr();
4040
case ShareAccessLevel.fullAccess:
41-
return 'Can make any changes';
41+
return LocaleKeys.shareTab_canMakeAnyChanges.tr();
4242
}
4343
}
4444

frontend/appflowy_flutter/lib/features/share_tab/data/models/share_section_type.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/// - shared: the shared section is shared, anyone in the shared section can view/edit it.
55
/// - private: the shared section is private, only the users in the shared section can view/edit it.
66
enum SharedSectionType {
7+
unknown,
78
public,
89
shared,
910
private;

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/local_share_with_user_repository_impl.dart

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:math';
22

33
import 'package:appflowy/features/share_tab/data/models/models.dart';
44
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
5+
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
56
import 'package:appflowy_result/appflowy_result.dart';
67

78
import 'share_with_user_repository.dart';
@@ -11,41 +12,47 @@ class LocalShareWithUserRepositoryImpl extends ShareWithUserRepository {
1112
LocalShareWithUserRepositoryImpl();
1213

1314
final SharedUsers _sharedUsers = [
15+
// current user has full access
1416
SharedUser(
1517
email: 'lucas.xu@appflowy.io',
1618
name: 'Lucas Xu - Long long long long long name',
17-
accessLevel: ShareAccessLevel.fullAccess,
19+
accessLevel: ShareAccessLevel.readOnly,
1820
role: ShareRole.guest,
1921
avatarUrl: 'https://avatar.iran.liara.run/public',
2022
),
23+
// member user has read and write access
2124
SharedUser(
2225
email: 'vivian@appflowy.io',
2326
name: 'Vivian Wang',
2427
accessLevel: ShareAccessLevel.readAndWrite,
25-
role: ShareRole.guest,
28+
role: ShareRole.member,
2629
avatarUrl: 'https://avatar.iran.liara.run/public/girl',
2730
),
31+
// member user has read access
2832
SharedUser(
2933
email: 'shuheng@appflowy.io',
3034
name: 'Shuheng',
31-
accessLevel: ShareAccessLevel.fullAccess,
32-
role: ShareRole.owner,
35+
accessLevel: ShareAccessLevel.readOnly,
36+
role: ShareRole.member,
3337
avatarUrl: 'https://avatar.iran.liara.run/public/boy',
3438
),
39+
// guest user has read access
3540
SharedUser(
3641
email: 'guest_user_1@appflowy.io',
37-
name: 'Guest User 1 - Long long long long long name',
42+
name: 'Read Only Guest',
3843
accessLevel: ShareAccessLevel.readOnly,
3944
role: ShareRole.guest,
4045
avatarUrl: 'https://avatar.iran.liara.run/public/boy/10',
4146
),
47+
// guest user has read and write access
4248
SharedUser(
4349
email: 'guest_user_2@appflowy.io',
44-
name: 'Guest User 2',
45-
accessLevel: ShareAccessLevel.readOnly,
46-
role: ShareRole.owner,
50+
name: 'Read And Write Guest',
51+
accessLevel: ShareAccessLevel.readAndWrite,
52+
role: ShareRole.guest,
4753
avatarUrl: 'https://avatar.iran.liara.run/public/boy/11',
4854
),
55+
// Others
4956
SharedUser(
5057
email: 'member_user_1@appflowy.io',
5158
name: 'Member User 1',
@@ -157,4 +164,21 @@ class LocalShareWithUserRepositoryImpl extends ShareWithUserRepository {
157164

158165
return FlowySuccess(null);
159166
}
167+
168+
@override
169+
Future<FlowyResult<UserProfilePB, FlowyError>> getCurrentUserProfile() async {
170+
// Simulate fetching current user profile
171+
return FlowySuccess(
172+
UserProfilePB()
173+
..email = 'lucas.xu@appflowy.io'
174+
..name = 'Lucas Xu',
175+
);
176+
}
177+
178+
@override
179+
Future<FlowyResult<SharedSectionType, FlowyError>> getCurrentPageSectionType({
180+
required String pageId,
181+
}) async {
182+
return FlowySuccess(SharedSectionType.private);
183+
}
160184
}

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/rust_share_with_user_repository_impl.dart

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import 'package:appflowy/features/share_tab/data/models/models.dart';
22
import 'package:appflowy/features/util/extensions.dart';
3+
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
4+
import 'package:appflowy/workspace/application/view/view_ext.dart';
35
import 'package:appflowy_backend/dispatch/dispatch.dart';
46
import 'package:appflowy_backend/log.dart';
57
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
68
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
9+
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
710
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
811
import 'package:appflowy_result/appflowy_result.dart';
12+
import 'package:collection/collection.dart';
913

1014
import 'share_with_user_repository.dart';
1115

@@ -70,6 +74,7 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
7074
viewId: pageId,
7175
emails: emails,
7276
accessLevel: accessLevel.accessLevel,
77+
autoConfirm: true, // TODO: remove this after the backend is ready
7378
);
7479
final result = await FolderEventSharePageWithUser(request).send();
7580

@@ -82,7 +87,9 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
8287
return FlowySuccess(success);
8388
},
8489
(failure) {
85-
Log.error('sharePageWithUser: $failure');
90+
Log.error(
91+
'share page($pageId) with users($emails) with access level($accessLevel): $failure',
92+
);
8693

8794
return FlowyFailure(failure);
8895
},
@@ -124,4 +131,34 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
124131
},
125132
);
126133
}
134+
135+
@override
136+
Future<FlowyResult<UserProfilePB, FlowyError>> getCurrentUserProfile() async {
137+
final result = await UserEventGetUserProfile().send();
138+
return result;
139+
}
140+
141+
@override
142+
Future<FlowyResult<SharedSectionType, FlowyError>> getCurrentPageSectionType({
143+
required String pageId,
144+
}) async {
145+
final request = ViewIdPB.create()..value = pageId;
146+
final result = await FolderEventGetViewAncestors(request).send();
147+
final ancestors = result.fold(
148+
(s) => s.items,
149+
(f) => <ViewPB>[],
150+
);
151+
final space = ancestors.firstWhereOrNull((e) => e.isSpace);
152+
153+
if (space == null) {
154+
return FlowySuccess(SharedSectionType.unknown);
155+
}
156+
157+
final sectionType = switch (space.spacePermission) {
158+
SpacePermission.publicToAll => SharedSectionType.public,
159+
SpacePermission.private => SharedSectionType.private,
160+
};
161+
162+
return FlowySuccess(sectionType);
163+
}
127164
}

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/share_with_user_repository.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:appflowy/features/share_tab/data/models/models.dart';
22
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
3+
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
34
import 'package:appflowy_result/appflowy_result.dart';
45

56
/// Abstract repository for sharing with users.
@@ -38,4 +39,12 @@ abstract class ShareWithUserRepository {
3839
required String email,
3940
required ShareRole role,
4041
});
42+
43+
/// Get current user profile.
44+
Future<FlowyResult<UserProfilePB, FlowyError>> getCurrentUserProfile();
45+
46+
/// Get current page is in public section or private section.
47+
Future<FlowyResult<SharedSectionType, FlowyError>> getCurrentPageSectionType({
48+
required String pageId,
49+
});
4150
}

frontend/appflowy_flutter/lib/features/share_tab/logic/share_tab_bloc.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
66
import 'package:appflowy/plugins/shared/share/constants.dart';
77
import 'package:appflowy/shared/feature_flags.dart';
88
import 'package:appflowy/startup/startup.dart';
9-
import 'package:appflowy/user/application/user_service.dart';
109
import 'package:appflowy_result/appflowy_result.dart';
1110
import 'package:bloc/bloc.dart';
1211

@@ -50,24 +49,33 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
5049
return;
5150
}
5251

53-
final result = await UserBackendService.getCurrentUserProfile();
52+
final result = await repository.getCurrentUserProfile();
5453
final currentUser = result.fold(
5554
(user) => user,
5655
(error) => null,
5756
);
5857

58+
final sectionTypeResult = await repository.getCurrentPageSectionType(
59+
pageId: pageId,
60+
);
61+
final sectionType = sectionTypeResult.fold(
62+
(type) => type,
63+
(error) => SharedSectionType.unknown,
64+
);
65+
5966
final shareLink = ShareConstants.buildShareUrl(
6067
workspaceId: workspaceId,
6168
viewId: pageId,
6269
);
6370

64-
final users = await _getLatestSharedUsersOrCurrentUsers();
71+
final users = await _getSharedUsers();
6572

6673
emit(
6774
state.copyWith(
6875
currentUser: currentUser,
6976
shareLink: shareLink,
7077
users: users,
78+
sectionType: sectionType,
7179
),
7280
);
7381
}
@@ -124,7 +132,7 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
124132

125133
await result.fold(
126134
(_) async {
127-
final users = await _getLatestSharedUsersOrCurrentUsers();
135+
final users = await _getSharedUsers();
128136

129137
emit(
130138
state.copyWith(
@@ -161,7 +169,7 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
161169

162170
await result.fold(
163171
(_) async {
164-
final users = await _getLatestSharedUsersOrCurrentUsers();
172+
final users = await _getSharedUsers();
165173
emit(
166174
state.copyWith(
167175
removeResult: FlowySuccess(null),
@@ -196,7 +204,7 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
196204

197205
await result.fold(
198206
(_) async {
199-
final users = await _getLatestSharedUsersOrCurrentUsers();
207+
final users = await _getSharedUsers();
200208
emit(
201209
state.copyWith(
202210
updateAccessLevelResult: FlowySuccess(null),
@@ -296,7 +304,7 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
296304

297305
await result.fold(
298306
(_) async {
299-
final users = await _getLatestSharedUsersOrCurrentUsers();
307+
final users = await _getSharedUsers();
300308
emit(
301309
state.copyWith(
302310
turnIntoMemberResult: FlowySuccess(null),
@@ -315,7 +323,7 @@ class ShareTabBloc extends Bloc<ShareTabEvent, ShareTabState> {
315323
);
316324
}
317325

318-
Future<SharedUsers> _getLatestSharedUsersOrCurrentUsers() async {
326+
Future<SharedUsers> _getSharedUsers() async {
319327
final shareResult = await repository.getSharedUsersInPage(
320328
pageId: pageId,
321329
);

frontend/appflowy_flutter/lib/features/share_tab/logic/share_tab_state.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class ShareTabState {
1515
this.shareLink = '',
1616
this.generalAccessRole,
1717
this.linkCopied = false,
18+
this.sectionType = SharedSectionType.private,
1819
this.initialResult,
1920
this.shareResult,
2021
this.removeResult,
@@ -30,6 +31,7 @@ class ShareTabState {
3031
final String shareLink;
3132
final ShareAccessLevel? generalAccessRole;
3233
final bool linkCopied;
34+
final SharedSectionType sectionType;
3335
final FlowyResult<void, FlowyError>? initialResult;
3436
final FlowyResult<void, FlowyError>? shareResult;
3537
final FlowyResult<void, FlowyError>? removeResult;
@@ -45,6 +47,7 @@ class ShareTabState {
4547
String? shareLink,
4648
ShareAccessLevel? generalAccessRole,
4749
bool? linkCopied,
50+
SharedSectionType? sectionType,
4851
FlowyResult<void, FlowyError>? initialResult,
4952
FlowyResult<void, FlowyError>? shareResult,
5053
FlowyResult<void, FlowyError>? removeResult,
@@ -60,6 +63,7 @@ class ShareTabState {
6063
shareLink: shareLink ?? this.shareLink,
6164
generalAccessRole: generalAccessRole ?? this.generalAccessRole,
6265
linkCopied: linkCopied ?? this.linkCopied,
66+
sectionType: sectionType ?? this.sectionType,
6367
initialResult: initialResult,
6468
shareResult: shareResult,
6569
removeResult: removeResult,
@@ -80,6 +84,7 @@ class ShareTabState {
8084
other.shareLink == shareLink &&
8185
other.generalAccessRole == generalAccessRole &&
8286
other.linkCopied == linkCopied &&
87+
other.sectionType == sectionType &&
8388
other.initialResult == initialResult &&
8489
other.shareResult == shareResult &&
8590
other.removeResult == removeResult &&
@@ -98,6 +103,7 @@ class ShareTabState {
98103
shareLink,
99104
generalAccessRole,
100105
linkCopied,
106+
sectionType,
101107
initialResult,
102108
shareResult,
103109
removeResult,
@@ -108,6 +114,6 @@ class ShareTabState {
108114

109115
@override
110116
String toString() {
111-
return 'ShareTabState(currentUser: $currentUser, users: $users, availableUsers: $availableUsers, isLoading: $isLoading, errorMessage: $errorMessage, shareLink: $shareLink, generalAccessRole: $generalAccessRole, linkCopied: $linkCopied, initialResult: $initialResult, shareResult: $shareResult, removeResult: $removeResult, updateAccessLevelResult: $updateAccessLevelResult, turnIntoMemberResult: $turnIntoMemberResult)';
117+
return 'ShareTabState(currentUser: $currentUser, users: $users, availableUsers: $availableUsers, isLoading: $isLoading, errorMessage: $errorMessage, shareLink: $shareLink, generalAccessRole: $generalAccessRole, shareSectionType: $SharedSectionType, linkCopied: $linkCopied, initialResult: $initialResult, shareResult: $shareResult, removeResult: $removeResult, updateAccessLevelResult: $updateAccessLevelResult, turnIntoMemberResult: $turnIntoMemberResult)';
112118
}
113119
}

0 commit comments

Comments
 (0)