Skip to content

Commit 88a3e9e

Browse files
authored
feat: implement section breadcrumb and new share menu design (#8012)
* feat: support secion breadcrumb * chore: update layout * feat: observe space permission change * feat: new share menu design * fix: widget test
1 parent d3566e3 commit 88a3e9e

37 files changed

+457
-75
lines changed

frontend/appflowy_flutter/lib/features/page_access_level/data/repositories/page_access_level_repository.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:appflowy/features/share_tab/data/models/share_access_level.dart';
1+
import 'package:appflowy/features/share_tab/data/models/models.dart';
22
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
33
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
44
import 'package:appflowy_result/appflowy_result.dart';
@@ -21,4 +21,9 @@ abstract class PageAccessLevelRepository {
2121
Future<FlowyResult<ShareAccessLevel, FlowyError>> getAccessLevel(
2222
String pageId,
2323
);
24+
25+
/// Gets the section type of the shared section.
26+
Future<FlowyResult<SharedSectionType, FlowyError>> getSectionType(
27+
String pageId,
28+
);
2429
}

frontend/appflowy_flutter/lib/features/page_access_level/data/repositories/rust_page_access_level_repository_impl.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'package:appflowy/features/page_access_level/data/repositories/page_access_level_repository.dart';
2-
import 'package:appflowy/features/share_tab/data/models/share_access_level.dart';
2+
import 'package:appflowy/features/share_tab/data/models/models.dart';
33
import 'package:appflowy/features/util/extensions.dart';
44
import 'package:appflowy/user/application/user_service.dart';
55
import 'package:appflowy/workspace/application/view/view_service.dart';
@@ -102,16 +102,40 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
102102
.shareAccessLevel ??
103103
ShareAccessLevel.readAndWrite;
104104

105-
Log.debug('current user access level: $accessLevel');
105+
Log.debug('current user access level: $accessLevel, in page: $pageId');
106106

107107
return FlowyResult.success(accessLevel);
108108
},
109109
(failure) {
110-
Log.error('failed to get user access level: $failure');
110+
Log.error(
111+
'failed to get user access level: $failure, in page: $pageId',
112+
);
111113

112114
// return the read and write access level if the user is not found
113115
return FlowyResult.success(ShareAccessLevel.readAndWrite);
114116
},
115117
);
116118
}
119+
120+
@override
121+
Future<FlowyResult<SharedSectionType, FlowyError>> getSectionType(
122+
String pageId,
123+
) async {
124+
final request = ViewIdPB(value: pageId);
125+
final result = await FolderEventGetSharedViewSection(request).send();
126+
return result.fold(
127+
(success) {
128+
final sectionType = success.section.sharedSectionType;
129+
Log.debug('shared section type: $sectionType, in page: $pageId');
130+
return FlowyResult.success(sectionType);
131+
},
132+
(failure) {
133+
Log.error(
134+
'failed to get shared section type: $failure, in page: $pageId',
135+
);
136+
137+
return FlowyResult.failure(failure);
138+
},
139+
);
140+
}
117141
}

frontend/appflowy_flutter/lib/features/page_access_level/logic/page_access_level_bloc.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:appflowy/features/page_access_level/data/repositories/page_acces
44
import 'package:appflowy/features/page_access_level/data/repositories/rust_page_access_level_repository_impl.dart';
55
import 'package:appflowy/features/page_access_level/logic/page_access_level_event.dart';
66
import 'package:appflowy/features/page_access_level/logic/page_access_level_state.dart';
7-
import 'package:appflowy/features/share_tab/data/models/share_access_level.dart';
7+
import 'package:appflowy/features/share_tab/data/models/models.dart';
88
import 'package:appflowy/shared/feature_flags.dart';
99
import 'package:appflowy/workspace/application/view/view_listener.dart';
1010
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@@ -18,6 +18,7 @@ class PageAccessLevelBloc
1818
extends Bloc<PageAccessLevelEvent, PageAccessLevelState> {
1919
PageAccessLevelBloc({
2020
required this.view,
21+
this.ignorePageAccessLevel = false,
2122
PageAccessLevelRepository? repository,
2223
}) : repository = repository ?? RustPageAccessLevelRepositoryImpl(),
2324
listener = ViewListener(viewId: view.id),
@@ -26,6 +27,7 @@ class PageAccessLevelBloc
2627
on<PageAccessLevelLockEvent>(_onLock);
2728
on<PageAccessLevelUnlockEvent>(_onUnlock);
2829
on<PageAccessLevelUpdateLockStatusEvent>(_onUpdateLockStatus);
30+
on<PageAccessLevelUpdateSectionTypeEvent>(_onUpdateSectionType);
2931
}
3032

3133
final ViewPB view;
@@ -37,6 +39,10 @@ class PageAccessLevelBloc
3739
// Used to listen for view updates.
3840
late final ViewListener listener;
3941

42+
// should ignore the page access level
43+
// in the row details page, we don't need to check the page access level
44+
final bool ignorePageAccessLevel;
45+
4046
@override
4147
Future<void> close() async {
4248
await listener.stop();
@@ -47,19 +53,28 @@ class PageAccessLevelBloc
4753
PageAccessLevelInitialEvent event,
4854
Emitter<PageAccessLevelState> emit,
4955
) async {
56+
// lock status
5057
listener.start(
5158
onViewUpdated: (view) async {
5259
add(PageAccessLevelEvent.updateLockStatus(view.isLocked));
5360
},
5461
);
5562

56-
if (!FeatureFlag.sharedSection.isOn) {
63+
// section type
64+
final sectionTypeResult = await repository.getSectionType(view.id);
65+
final sectionType = sectionTypeResult.fold(
66+
(sectionType) => sectionType,
67+
(_) => SharedSectionType.public,
68+
);
69+
70+
if (!FeatureFlag.sharedSection.isOn || ignorePageAccessLevel) {
5771
emit(
5872
state.copyWith(
5973
view: view,
6074
isLocked: view.isLocked,
6175
isLoadingLockStatus: false,
6276
accessLevel: ShareAccessLevel.fullAccess,
77+
sectionType: sectionType,
6378
),
6479
);
6580
return;
@@ -80,6 +95,7 @@ class PageAccessLevelBloc
8095
(accessLevel) => accessLevel,
8196
(_) => ShareAccessLevel.readOnly,
8297
),
98+
sectionType: sectionType,
8399
),
84100
);
85101
}
@@ -133,4 +149,15 @@ class PageAccessLevelBloc
133149
),
134150
);
135151
}
152+
153+
void _onUpdateSectionType(
154+
PageAccessLevelUpdateSectionTypeEvent event,
155+
Emitter<PageAccessLevelState> emit,
156+
) {
157+
emit(
158+
state.copyWith(
159+
sectionType: event.sectionType,
160+
),
161+
);
162+
}
136163
}

frontend/appflowy_flutter/lib/features/page_access_level/logic/page_access_level_event.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:appflowy/features/share_tab/data/models/models.dart';
2+
13
/// Base class for all PageAccessLevel events
24
sealed class PageAccessLevelEvent {
35
const PageAccessLevelEvent();
@@ -17,6 +19,11 @@ sealed class PageAccessLevelEvent {
1719
bool isLocked, {
1820
int? lockCounter,
1921
}) = PageAccessLevelUpdateLockStatusEvent;
22+
23+
/// Update the section type in the state.
24+
const factory PageAccessLevelEvent.updateSectionType(
25+
SharedSectionType sectionType,
26+
) = PageAccessLevelUpdateSectionTypeEvent;
2027
}
2128

2229
class PageAccessLevelInitialEvent extends PageAccessLevelEvent {
@@ -40,3 +47,9 @@ class PageAccessLevelUpdateLockStatusEvent extends PageAccessLevelEvent {
4047
final bool isLocked;
4148
final int? lockCounter;
4249
}
50+
51+
class PageAccessLevelUpdateSectionTypeEvent extends PageAccessLevelEvent {
52+
const PageAccessLevelUpdateSectionTypeEvent(this.sectionType);
53+
54+
final SharedSectionType sectionType;
55+
}

frontend/appflowy_flutter/lib/features/page_access_level/logic/page_access_level_state.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:appflowy/features/share_tab/data/models/share_access_level.dart';
1+
import 'package:appflowy/features/share_tab/data/models/models.dart';
22
import 'package:appflowy/shared/feature_flags.dart';
33
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
44

@@ -7,6 +7,7 @@ class PageAccessLevelState {
77
view: view,
88
isLocked: false,
99
lockCounter: 0,
10+
sectionType: SharedSectionType.public,
1011
accessLevel: ShareAccessLevel
1112
.readAndWrite, // replace it with readOnly if we support offline.
1213
);
@@ -16,6 +17,8 @@ class PageAccessLevelState {
1617
required this.isLocked,
1718
required this.lockCounter,
1819
required this.accessLevel,
20+
required this.sectionType,
21+
this.myRole,
1922
this.isLoadingLockStatus = true,
2023
});
2124

@@ -24,6 +27,13 @@ class PageAccessLevelState {
2427
final int lockCounter;
2528
final bool isLoadingLockStatus;
2629
final ShareAccessLevel accessLevel;
30+
final SharedSectionType sectionType;
31+
final ShareRole? myRole;
32+
33+
bool get isPublic => sectionType == SharedSectionType.public;
34+
bool get isPrivate => sectionType == SharedSectionType.private;
35+
bool get isShared => sectionType == SharedSectionType.shared;
36+
bool get shouldHideSpace => myRole == ShareRole.guest;
2737

2838
bool get isEditable {
2939
if (!FeatureFlag.sharedSection.isOn) {
@@ -41,13 +51,17 @@ class PageAccessLevelState {
4151
int? lockCounter,
4252
bool? isLoadingLockStatus,
4353
ShareAccessLevel? accessLevel,
54+
SharedSectionType? sectionType,
55+
ShareRole? myRole,
4456
}) {
4557
return PageAccessLevelState(
4658
view: view ?? this.view,
4759
isLocked: isLocked ?? this.isLocked,
4860
lockCounter: lockCounter ?? this.lockCounter,
4961
isLoadingLockStatus: isLoadingLockStatus ?? this.isLoadingLockStatus,
5062
accessLevel: accessLevel ?? this.accessLevel,
63+
sectionType: sectionType ?? this.sectionType,
64+
myRole: myRole ?? this.myRole,
5165
);
5266
}
5367

@@ -59,7 +73,9 @@ class PageAccessLevelState {
5973
other.isLocked == isLocked &&
6074
other.lockCounter == lockCounter &&
6175
other.isLoadingLockStatus == isLoadingLockStatus &&
62-
other.accessLevel == accessLevel;
76+
other.accessLevel == accessLevel &&
77+
other.sectionType == sectionType &&
78+
other.myRole == myRole;
6379
}
6480

6581
@override
@@ -70,11 +86,13 @@ class PageAccessLevelState {
7086
lockCounter,
7187
isLoadingLockStatus,
7288
accessLevel,
89+
sectionType,
90+
myRole,
7391
);
7492
}
7593

7694
@override
7795
String toString() {
78-
return 'PageAccessLevelState(view: $view, isLocked: $isLocked, lockCounter: $lockCounter, isLoadingLockStatus: $isLoadingLockStatus, accessLevel: $accessLevel)';
96+
return 'PageAccessLevelState(view: $view, isLocked: $isLocked, lockCounter: $lockCounter, isLoadingLockStatus: $isLoadingLockStatus, accessLevel: $accessLevel, sectionType: $sectionType, myRole: $myRole)';
7997
}
8098
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export 'share_access_level.dart';
22
export 'share_popover_group_id.dart';
33
export 'share_role.dart';
44
export 'shared_user.dart';
5+
export 'share_section_type.dart';

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:appflowy/generated/flowy_svgs.g.dart';
12
import 'package:appflowy/generated/locale_keys.g.dart';
23
import 'package:easy_localization/easy_localization.dart';
34

@@ -15,7 +16,7 @@ enum ShareAccessLevel {
1516
/// Full access (edit, share, remove, etc.) and can add new users.
1617
fullAccess;
1718

18-
String get i18n {
19+
String get title {
1920
switch (this) {
2021
case ShareAccessLevel.readOnly:
2122
return LocaleKeys.shareTab_accessLevel_view.tr();
@@ -27,5 +28,30 @@ enum ShareAccessLevel {
2728
return LocaleKeys.shareTab_accessLevel_fullAccess.tr();
2829
}
2930
}
30-
}
3131

32+
String get subtitle {
33+
switch (this) {
34+
case ShareAccessLevel.readOnly:
35+
return 'Can\'t make changes';
36+
case ShareAccessLevel.readAndComment:
37+
return 'Can make any changes';
38+
case ShareAccessLevel.readAndWrite:
39+
return 'Can make any changes';
40+
case ShareAccessLevel.fullAccess:
41+
return 'Can make any changes';
42+
}
43+
}
44+
45+
FlowySvgData get icon {
46+
switch (this) {
47+
case ShareAccessLevel.readOnly:
48+
return FlowySvgs.access_level_view_m;
49+
case ShareAccessLevel.readAndComment:
50+
return FlowySvgs.access_level_edit_m;
51+
case ShareAccessLevel.readAndWrite:
52+
return FlowySvgs.access_level_edit_m;
53+
case ShareAccessLevel.fullAccess:
54+
return FlowySvgs.access_level_edit_m;
55+
}
56+
}
57+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// The type of the shared section.
2+
///
3+
/// - public: the shared section is public, anyone in the workspace can view/edit it.
4+
/// - shared: the shared section is shared, anyone in the shared section can view/edit it.
5+
/// - private: the shared section is private, only the users in the shared section can view/edit it.
6+
enum SharedSectionType {
7+
public,
8+
shared,
9+
private;
10+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ class LocalShareWithUserRepositoryImpl extends ShareWithUserRepository {
1313
final SharedUsers _sharedUsers = [
1414
SharedUser(
1515
email: 'lucas.xu@appflowy.io',
16-
name: 'Lucas Xu',
17-
accessLevel: ShareAccessLevel.readOnly,
16+
name: 'Lucas Xu - Long long long long long name',
17+
accessLevel: ShareAccessLevel.fullAccess,
1818
role: ShareRole.guest,
1919
avatarUrl: 'https://avatar.iran.liara.run/public',
2020
),
2121
SharedUser(
2222
email: 'vivian@appflowy.io',
2323
name: 'Vivian Wang',
2424
accessLevel: ShareAccessLevel.readAndWrite,
25-
role: ShareRole.member,
25+
role: ShareRole.guest,
2626
avatarUrl: 'https://avatar.iran.liara.run/public/girl',
2727
),
2828
SharedUser(
@@ -34,7 +34,7 @@ class LocalShareWithUserRepositoryImpl extends ShareWithUserRepository {
3434
),
3535
SharedUser(
3636
email: 'guest_user_1@appflowy.io',
37-
name: 'Guest User 1',
37+
name: 'Guest User 1 - Long long long long long name',
3838
accessLevel: ShareAccessLevel.readOnly,
3939
role: ShareRole.guest,
4040
avatarUrl: 'https://avatar.iran.liara.run/public/boy/10',

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
2323

2424
return result.fold(
2525
(success) {
26-
Log.debug('get shared users: $success');
26+
Log.debug('get shared users success: $success');
2727

2828
return FlowySuccess(success.sharedUsers);
2929
},
3030
(failure) {
31-
Log.error('getUsersInSharedPage: $failure');
31+
Log.error('get shared users failed: $failure');
3232

3333
return FlowyFailure(failure);
3434
},

0 commit comments

Comments
 (0)