-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
compose_box: Replace compose box with a banner when cannot post in a channel #886
base: main
Are you sure you want to change the base?
compose_box: Replace compose box with a banner when cannot post in a channel #886
Conversation
b0d63db
to
570d2ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @sm-sayedi, this looks great!
Moving on to the mentor review from @hackerkid.
ae0a4ff
to
2a44121
Compare
2a44121
to
c005b48
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Comments below, covering everything except the tests added in the main, last commit:
compose_box: Replace compose box with a banner when cannot post in a channel
because I've suggested a cleanup that should help me review those tests more efficiently; see below. 🙂
lib/api/model/initial_snapshot.dart
Outdated
/// Search for "realm_waiting_period_threshold" in https://zulip.com/api/register-queue. | ||
/// | ||
/// For how to determine if a user is a full member, see: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Search for "realm_waiting_period_threshold" in https://zulip.com/api/register-queue. | |
/// | |
/// For how to determine if a user is a full member, see: | |
/// Search for "realm_waiting_period_threshold" in https://zulip.com/api/register-queue. | |
/// | |
/// For how to determine if a user is a full member, see: |
lib/model/store.dart
Outdated
@@ -319,6 +321,8 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore { | |||
|
|||
String get zulipVersion => account.zulipVersion; | |||
final int maxFileUploadSizeMib; // No event for this. | |||
/// For docs, please see [InitialSnapshot.realmWaitingPeriodThreshold]. | |||
final int realmWaitingPeriodThreshold; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final int realmWaitingPeriodThreshold; | |
final int realmWaitingPeriodThreshold; // TODO(#668): update this realm setting |
lib/api/model/model.dart
Outdated
|
||
// This is determined based on: | ||
// https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member | ||
bool isFullMember(int realmWaitingPeriodThreshold) { | ||
final dateJoined = DateTime.parse(this.dateJoined); | ||
return DateTime.now().difference(dateJoined).inDays >= realmWaitingPeriodThreshold; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method's name isn't quite a match for what it does. Compare zulip-mobile:
/**
* Whether the user has passed the realm's waiting period to be a full member.
*
* See:
* https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member
*
* To determine if a user is a full member, callers must also check that the
* user's role is at least Role.Member.
*
* […]
*/
export function getHasUserPassedWaitingPeriod(state: PerAccountState, userId: UserId): boolean {
const { waitingPeriodThreshold } = getRealm(state);
const { date_joined } = getUserForId(state, userId);
const intervalLengthInDays = (Date.now() - Date.parse(date_joined)) / 86400_000;
// […]
// TODO(?): […]
return intervalLengthInDays >= waitingPeriodThreshold;
}
How about we call it hasPassedWaitingPeriod
, and give it a dartdoc along the lines of the jsdoc in zulip-mobile.
lib/api/model/model.dart
Outdated
return switch (channelPostPolicy) { | ||
ChannelPostPolicy.any => true, | ||
ChannelPostPolicy.fullMembers => role != UserRole.guest && (role == UserRole.member | ||
? user.isFullMember(realmWaitingPeriodThreshold) | ||
: true), | ||
ChannelPostPolicy.moderators => role != UserRole.guest && role != UserRole.member, | ||
ChannelPostPolicy.administrators => role == UserRole.administrator || role == UserRole.owner || role == UserRole.unknown, | ||
ChannelPostPolicy.unknown => true, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a helpful simplification here would be to add a method on UserRole
:
bool isAtLeast(UserRole threshold) {
which could be used here, but also anywhere else we need to do role checks. In zulip-mobile it's simple:
export function roleIsAtLeast(thisRole: Role, thresholdRole: Role): boolean {
return (thisRole: number) <= (thresholdRole: number); // Roles with more privilege have lower numbers.
}
I think I remember concluding that "roles with more privilege have lower numbers" is basically an API guarantee. It would be pretty odd if it weren't guaranteed, given the values in the current API.
And with that encapsulated in UserRole
, I think I'd feel pretty comfortable making an improvement over the logic here: when servers give an API value we don't recognize—say, 350, which would be between 300 "moderator" and 400 "member"—we could still store that value, and use it in the new isAtLeast
method. (I'd be less comfortable passing around apiValue
values outside the UserRole
implementation itself, but it seems very appropriate to do within it.)
test/widgets/compose_box_test.dart
Outdated
Future<GlobalKey<ComposeBoxController>> prepareComposeBox(WidgetTester tester, { | ||
required Narrow narrow, | ||
User? selfUser, | ||
int daysToBecomeFullMember = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means exactly the same thing as realmWaitingPeriodThreshold
, right? Let's just call it realmWaitingPeriodThreshold
, so we don't have to think about another name and decide if it means something subtly different.
lib/api/model/model.dart
Outdated
|
||
// This is determined based on: | ||
// https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member | ||
bool isFullMember(int realmWaitingPeriodThreshold) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling DateTime.now()
inside this API-model code, how about we compute it in UI code and pass the value down to here? A possible post-launch refinement might be to recheck, at regular time intervals, if enough time has passed that the user has become a full member. The natural place to do that will be near the UI code responsible for choosing whether to show the error banner.
(This caused me to think of a small, similar improvement we'll want to make eventually. I've filed that just now as #891.)
assets/l10n/app_en.arb
Outdated
@@ -188,6 +188,10 @@ | |||
"@errorBannerDeactivatedDmLabel": { | |||
"description": "Label text for error banner when sending a message to one or multiple deactivated users." | |||
}, | |||
"errorBannerCannotPostInChannelLabel": "You do not have permission to post in this channel.", | |||
"@errorBannerCannotPostInChannelLabel": { | |||
"description": "Label text for error banner when sending a message in a channel with no posting permission." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"description": "Label text for error banner when sending a message in a channel with no posting permission." | |
"description": "Error-banner text replacing the compose box when you do not have permission to send a message to the channel." |
lib/widgets/compose_box.dart
Outdated
@@ -999,8 +999,21 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose | |||
super.dispose(); | |||
} | |||
|
|||
Widget? _errorBanner(BuildContext context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's already an _errorBanner
method, in _FixedDestinationComposeBoxState
, and it duplicates a lot of this logic. Can we centralize the computation, perhaps in an early-return style in the build method of ComposeBox
?
await store.addUser(eg.user(userId: message.senderId)); | ||
await store.addUsers([eg.selfUser, eg.user(userId: message.senderId)]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compose_box: Replace compose box with a banner when cannot post in a channel
This seems like a reasonable change to the action-sheet tests, but why is it needed in this commit, which is about the compose box?
…Ah, I think I understand: it's a boring but necessary bit of setup so that the simulated action sheet doesn't crash in the code that decides whether to show the error banner. Is that right?
There are quite a few other changes in this commit that look like they're made for the same reason. Would you move those to a prep commit before this commit? That should make it easier to focus on the interesting changes here. 🙂
c005b48
to
41ab0e9
Compare
Thanks @chrisbobbe for the review! Revision pushed with the tests cleaned up. PTAL! |
41ab0e9
to
0837859
Compare
Thanks! Ah, it looks like this has gathered some conflicts—would you mind rebasing and resolving those please? |
0837859
to
169a790
Compare
Conflicts resolved @chrisbobbe! Please have a look! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Comments below, all small.
lib/api/model/model.dart
Outdated
bool isAtLeast(UserRole role) { | ||
return (apiValue ?? 0) <= (role.apiValue ?? 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If not implementing it now, let's add a TODO for this part that I mentioned at #886 (comment) :
[…] when servers give an API value we don't recognize—say, 350, which would be between 300 "moderator" and 400 "member"—we could still store that value, and use it in the new
isAtLeast
method. (I'd be less comfortable passing aroundapiValue
values outside theUserRole
implementation itself, but it seems very appropriate to do within it.)
In this revision, where we treat all unrecognized values as though they were 0, we'll give wrong results when the unrecognized role from the server is meant to be less privileged than the threshold role.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when servers give an API value we don't recognize—say, 350, which would be between 300 "moderator" and 400 "member"—we could still store that value, and use it in the new isAtLeast method.
I think in the current code for UserRole
we get UserRole.unknown
for all the unrecognized API values. With that being said, we cannot get that new unrecognized value to compare it with the threshold apiValue. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal is to store the unrecognized API value so that isAtLeast
can get its hands on it :). Looking closer, I think that would need some preparation, though, such as converting UserRole
from an enum
to a plain class
.
Greg reminds me that we don't actually expect any more API values to be added, though, because the "role" concept is on track to be replaced by "user groups". So the work I've described for handling unrecognized values won't be worth it.
test/widgets/compose_box_test.dart
Outdated
@@ -318,8 +336,10 @@ void main() { | |||
}); | |||
|
|||
group('attach from camera', () { | |||
testWidgets('success', (tester) async { | |||
final controllerKey = await prepareComposeBox(tester, narrow: ChannelNarrow(eg.stream().streamId)); | |||
testWidgets('succMessageListPageState.narrowess', (tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
test/widgets/compose_box_test.dart
Outdated
final testCases = [ | ||
(ChannelPostPolicy.unknown, UserRole.unknown, true), | ||
(ChannelPostPolicy.unknown, UserRole.guest, true), | ||
(ChannelPostPolicy.unknown, UserRole.member, true), | ||
(ChannelPostPolicy.unknown, UserRole.moderator, true), | ||
(ChannelPostPolicy.unknown, UserRole.administrator, true), | ||
(ChannelPostPolicy.unknown, UserRole.owner, true), | ||
(ChannelPostPolicy.any, UserRole.unknown, true), | ||
(ChannelPostPolicy.any, UserRole.guest, true), | ||
(ChannelPostPolicy.any, UserRole.member, true), | ||
(ChannelPostPolicy.any, UserRole.moderator, true), | ||
(ChannelPostPolicy.any, UserRole.administrator, true), | ||
(ChannelPostPolicy.any, UserRole.owner, true), | ||
(ChannelPostPolicy.fullMembers, UserRole.unknown, true), | ||
(ChannelPostPolicy.fullMembers, UserRole.guest, false), | ||
(ChannelPostPolicy.fullMembers, UserRole.member, true), | ||
(ChannelPostPolicy.fullMembers, UserRole.moderator, true), | ||
(ChannelPostPolicy.fullMembers, UserRole.administrator, true), | ||
(ChannelPostPolicy.fullMembers, UserRole.owner, true), | ||
(ChannelPostPolicy.moderators, UserRole.unknown, true), | ||
(ChannelPostPolicy.moderators, UserRole.guest, false), | ||
(ChannelPostPolicy.moderators, UserRole.member, false), | ||
(ChannelPostPolicy.moderators, UserRole.moderator, true), | ||
(ChannelPostPolicy.moderators, UserRole.administrator, true), | ||
(ChannelPostPolicy.moderators, UserRole.owner, true), | ||
(ChannelPostPolicy.administrators, UserRole.unknown, true), | ||
(ChannelPostPolicy.administrators, UserRole.guest, false), | ||
(ChannelPostPolicy.administrators, UserRole.member, false), | ||
(ChannelPostPolicy.administrators, UserRole.moderator, false), | ||
(ChannelPostPolicy.administrators, UserRole.administrator, true), | ||
(ChannelPostPolicy.administrators, UserRole.owner, true), | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make this a bit easier to scan:
final testCases = [ | |
(ChannelPostPolicy.unknown, UserRole.unknown, true), | |
(ChannelPostPolicy.unknown, UserRole.guest, true), | |
(ChannelPostPolicy.unknown, UserRole.member, true), | |
(ChannelPostPolicy.unknown, UserRole.moderator, true), | |
(ChannelPostPolicy.unknown, UserRole.administrator, true), | |
(ChannelPostPolicy.unknown, UserRole.owner, true), | |
(ChannelPostPolicy.any, UserRole.unknown, true), | |
(ChannelPostPolicy.any, UserRole.guest, true), | |
(ChannelPostPolicy.any, UserRole.member, true), | |
(ChannelPostPolicy.any, UserRole.moderator, true), | |
(ChannelPostPolicy.any, UserRole.administrator, true), | |
(ChannelPostPolicy.any, UserRole.owner, true), | |
(ChannelPostPolicy.fullMembers, UserRole.unknown, true), | |
(ChannelPostPolicy.fullMembers, UserRole.guest, false), | |
(ChannelPostPolicy.fullMembers, UserRole.member, true), | |
(ChannelPostPolicy.fullMembers, UserRole.moderator, true), | |
(ChannelPostPolicy.fullMembers, UserRole.administrator, true), | |
(ChannelPostPolicy.fullMembers, UserRole.owner, true), | |
(ChannelPostPolicy.moderators, UserRole.unknown, true), | |
(ChannelPostPolicy.moderators, UserRole.guest, false), | |
(ChannelPostPolicy.moderators, UserRole.member, false), | |
(ChannelPostPolicy.moderators, UserRole.moderator, true), | |
(ChannelPostPolicy.moderators, UserRole.administrator, true), | |
(ChannelPostPolicy.moderators, UserRole.owner, true), | |
(ChannelPostPolicy.administrators, UserRole.unknown, true), | |
(ChannelPostPolicy.administrators, UserRole.guest, false), | |
(ChannelPostPolicy.administrators, UserRole.member, false), | |
(ChannelPostPolicy.administrators, UserRole.moderator, false), | |
(ChannelPostPolicy.administrators, UserRole.administrator, true), | |
(ChannelPostPolicy.administrators, UserRole.owner, true), | |
]; | |
final testCases = [ | |
(ChannelPostPolicy.unknown, UserRole.unknown, true), | |
(ChannelPostPolicy.unknown, UserRole.guest, true), | |
(ChannelPostPolicy.unknown, UserRole.member, true), | |
(ChannelPostPolicy.unknown, UserRole.moderator, true), | |
(ChannelPostPolicy.unknown, UserRole.administrator, true), | |
(ChannelPostPolicy.unknown, UserRole.owner, true), | |
(ChannelPostPolicy.any, UserRole.unknown, true), | |
(ChannelPostPolicy.any, UserRole.guest, true), | |
(ChannelPostPolicy.any, UserRole.member, true), | |
(ChannelPostPolicy.any, UserRole.moderator, true), | |
(ChannelPostPolicy.any, UserRole.administrator, true), | |
(ChannelPostPolicy.any, UserRole.owner, true), | |
(ChannelPostPolicy.fullMembers, UserRole.unknown, true), | |
(ChannelPostPolicy.fullMembers, UserRole.guest, false), | |
(ChannelPostPolicy.fullMembers, UserRole.member, true), | |
(ChannelPostPolicy.fullMembers, UserRole.moderator, true), | |
(ChannelPostPolicy.fullMembers, UserRole.administrator, true), | |
(ChannelPostPolicy.fullMembers, UserRole.owner, true), | |
(ChannelPostPolicy.moderators, UserRole.unknown, true), | |
(ChannelPostPolicy.moderators, UserRole.guest, false), | |
(ChannelPostPolicy.moderators, UserRole.member, false), | |
(ChannelPostPolicy.moderators, UserRole.moderator, true), | |
(ChannelPostPolicy.moderators, UserRole.administrator, true), | |
(ChannelPostPolicy.moderators, UserRole.owner, true), | |
(ChannelPostPolicy.administrators, UserRole.unknown, true), | |
(ChannelPostPolicy.administrators, UserRole.guest, false), | |
(ChannelPostPolicy.administrators, UserRole.member, false), | |
(ChannelPostPolicy.administrators, UserRole.moderator, false), | |
(ChannelPostPolicy.administrators, UserRole.administrator, true), | |
(ChannelPostPolicy.administrators, UserRole.owner, true), | |
]; |
test/widgets/compose_box_test.dart
Outdated
|
||
} | ||
|
||
group('only "full member" user can post in channel with "fullMembers" policy', (){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
group('only "full member" user can post in channel with "fullMembers" policy', (){ | |
group('only "full member" user can post in channel with "fullMembers" policy', () { |
test/widgets/compose_box_test.dart
Outdated
}); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}); | |
} | |
}); | |
} |
test/widgets/compose_box_test.dart
Outdated
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)], | |
); | |
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)]); |
(here and in in many places later in this file)
This is necessary in the next commit(s) where wee need to determine if a a user has become a full member. For how to determine if a user is a full member, see: https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member
Also add `UserRole.isAtLeast` method.
…rams The additional params are: selfUser, streams, and realmWaitingPeriodThreshold.
Adding these is necessary for the next commit(s), otherwise these tests will fail.
169a790
to
be0d74d
Compare
Thanks @chrisbobbe for the review. Pushed the new changes. Also added #886 comment. PTAL. cc @PIG208 |
@@ -235,6 +235,7 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, ChannelStore, Mess | |||
connection: connection, | |||
realmUrl: realmUrl, | |||
maxFileUploadSizeMib: initialSnapshot.maxFileUploadSizeMib, | |||
realmWaitingPeriodThreshold: initialSnapshot.realmWaitingPeriodThreshold, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this should come before maxFileUploadSizeMib
.
/// To determine if a user is a full member, callers must also check that the | ||
/// user's role is at least Role.Member. | ||
bool hasPassedWaitingPeriod(DateTime byDate, int realmWaitingPeriodThreshold) { | ||
final dateJoined = DateTime.parse(this.dateJoined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could simplify this by parsing dateJoined
as we deserialize the User JSON object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but I'd prefer to keep that parsing deferred: converting user objects from JSON is a hot spot performance-wise, because there can be so many of them, and parsing a DateTime is the sort of thing that I feel could easily be slow.
And conversely I think there's only a few places like here where we care about the value of dateJoined
, so it's not much burden on code complexity for these to have to handle the parsing; and they're all for just one user at a time, so there's no performance concern.
Future<GlobalKey<ComposeBoxController>> prepareComposeBox(WidgetTester tester, { | ||
required Narrow narrow, | ||
User? selfUser, | ||
int realmWaitingPeriodThreshold = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
int realmWaitingPeriodThreshold = 0, | |
int? realmWaitingPeriodThreshold, |
which allows us to reuse the default on eg.initialSnapshot
.
final channelId = narrow is ChannelNarrow ? narrow.streamId : (narrow as TopicNarrow).streamId; | ||
assert(streams.any((stream) => stream.streamId == channelId), | ||
'Add a channel with "streamId" the same as of $narrow.streamId to the store.'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this belongs to the previous commit.
|
||
bool isAtLeast(UserRole threshold) { | ||
// Roles with more privilege have lower [apiValue]. | ||
return (apiValue ?? 0) <= (threshold.apiValue ?? 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this, I was thinking about why 0
is chosen as the default value for both operands. Considering the context this helper is used, I suppose that we want to fallback to the behavior that, when the role has an unknown value, we allow the user to post.
This consideration is specific to the posting permission feature we are implementing here. I think the best thing to do is to assert that both api values are non-null; then we handle the case when either of them is null in hasPostingPermission
.
case TopicNarrow narrow: | ||
final channel = store.streams[narrow.streamId]!; | ||
return channel.hasPostingPermission(selfUser, byDate: DateTime.now(), realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold) | ||
? null : _ErrorBanner(label: ZulipLocalizations.of(context).errorBannerCannotPostInChannelLabel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's extract ZulipLocalizations.of(context)
as a variable
case ChannelNarrow narrow: | ||
final channel = store.streams[narrow.streamId]!; | ||
return channel.hasPostingPermission(selfUser, byDate: DateTime.now(), realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold) | ||
? null : _ErrorBanner(label: ZulipLocalizations.of(context).errorBannerCannotPostInChannelLabel); | ||
case TopicNarrow narrow: | ||
final channel = store.streams[narrow.streamId]!; | ||
return channel.hasPostingPermission(selfUser, byDate: DateTime.now(), realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold) | ||
? null : _ErrorBanner(label: ZulipLocalizations.of(context).errorBannerCannotPostInChannelLabel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we can simplify this to:
case ChannelNarrow narrow: | |
final channel = store.streams[narrow.streamId]!; | |
return channel.hasPostingPermission(selfUser, byDate: DateTime.now(), realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold) | |
? null : _ErrorBanner(label: ZulipLocalizations.of(context).errorBannerCannotPostInChannelLabel); | |
case TopicNarrow narrow: | |
final channel = store.streams[narrow.streamId]!; | |
return channel.hasPostingPermission(selfUser, byDate: DateTime.now(), realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold) | |
? null : _ErrorBanner(label: ZulipLocalizations.of(context).errorBannerCannotPostInChannelLabel); | |
case ChannelNarrow(:final streamId): | |
case TopicNarrow(:final streamId): | |
final channel = store.streams[streamId]!; | |
if (channel.hasPostingPermission(selfUser, byDate: DateTime.now(), | |
realmWaitingPeriodThreshold: store.realmWaitingPeriodThreshold)) { | |
return _ErrorBanner( | |
label: localizations.errorBannerCannotPostInChannelLabel); | |
} |
return hasDeactivatedUser ? _ErrorBanner(label: ZulipLocalizations.of(context) | ||
.errorBannerDeactivatedDmLabel) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: to make the lines shorter:
return hasDeactivatedUser ? _ErrorBanner(label: ZulipLocalizations.of(context) | |
.errorBannerDeactivatedDmLabel) : null; | |
if (hasDeactivatedUser) { | |
return _ErrorBanner( | |
label: localizations.errorBannerDeactivatedDmLabel); | |
} |
|
||
group('1:1 DMs', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we are both moving tests to groups and adding the new tests.
The grouping part can be done in a prep commit so the diffs will be easier to read.
for (final testCase in testCases) { | ||
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
for (final testCase in testCases) { | |
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase; | |
for (final (ChannelPostPolicy policy, UserRole role, bool canPost) in testCases) { |
for (final testCase in testCases) { | ||
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
for (final testCase in testCases) { | |
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase; | |
for (final (ChannelPostPolicy policy, UserRole role, bool canPost) in testCases) { |
checkComposeBox(isShown: false); | ||
}); | ||
|
||
testWidgets('all deactivated users become active -> ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the deletion is a bit far apart from where it was re-added.
A goal of restructuring the change would be that we have one commit that moves the old tests, and one that only adds the new ones.
@@ -382,6 +387,19 @@ class ZulipStream { | |||
_$ZulipStreamFromJson(json); | |||
|
|||
Map<String, dynamic> toJson() => _$ZulipStreamToJson(this); | |||
|
|||
bool hasPostingPermission(User user, {required DateTime byDate, required int realmWaitingPeriodThreshold}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's break this into multiple lines
final role = user.role; | ||
return switch (channelPostPolicy) { | ||
ChannelPostPolicy.any => true, | ||
ChannelPostPolicy.fullMembers => role.isAtLeast(UserRole.member) && (role == UserRole.member |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: by turning this into ||
we can replace the ... ? ... : true
clause with ... && ...
When a user cannot post in a channel based on
ZulipStream.channelPostPolicy
, all parts of the compose box (in both channel and topic narrow) are replaced with a banner, saying: You do not have permission to post in this channel.Screenshot
Screen recording
channel-policy-based.compose.box.mp4
Fixes: #674