Skip to content
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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

sm-sayedi
Copy link
Collaborator

@sm-sayedi sm-sayedi commented Aug 12, 2024

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

@sm-sayedi sm-sayedi added the buddy review GSoC buddy review needed. label Aug 12, 2024
@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch 3 times, most recently from b0d63db to 570d2ac Compare August 13, 2024 06:46
Copy link
Collaborator

@rajveermalviya rajveermalviya left a 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.

@sm-sayedi sm-sayedi added mentor review GSoC mentor review needed. and removed buddy review GSoC buddy review needed. labels Aug 14, 2024
@sm-sayedi sm-sayedi added the maintainer review PR ready for review by Zulip maintainers label Aug 14, 2024
@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch 2 times, most recently from ae0a4ff to 2a44121 Compare August 15, 2024 04:14
@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch from 2a44121 to c005b48 Compare August 16, 2024 15:22
Copy link
Collaborator

@chrisbobbe chrisbobbe left a 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. 🙂

Comment on lines 69 to 71
/// 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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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:

@@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final int realmWaitingPeriodThreshold;
final int realmWaitingPeriodThreshold; // TODO(#668): update this realm setting

Comment on lines 265 to 277

// 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;
}
Copy link
Collaborator

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.

Comment on lines 382 to 401
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,
};
Copy link
Collaborator

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.)

Future<GlobalKey<ComposeBoxController>> prepareComposeBox(WidgetTester tester, {
required Narrow narrow,
User? selfUser,
int daysToBecomeFullMember = 0,
Copy link
Collaborator

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.


// This is determined based on:
// https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member
bool isFullMember(int realmWaitingPeriodThreshold) {
Copy link
Collaborator

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.)

@@ -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."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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."

@@ -999,8 +999,21 @@ class _StreamComposeBoxState extends State<_StreamComposeBox> implements Compose
super.dispose();
}

Widget? _errorBanner(BuildContext context) {
Copy link
Collaborator

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)]);
Copy link
Collaborator

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. 🙂

@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch from c005b48 to 41ab0e9 Compare August 19, 2024 19:55
@sm-sayedi
Copy link
Collaborator Author

sm-sayedi commented Aug 19, 2024

Thanks @chrisbobbe for the review! Revision pushed with the tests cleaned up. PTAL!

@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch from 41ab0e9 to 0837859 Compare August 20, 2024 14:03
@chrisbobbe
Copy link
Collaborator

Thanks! Ah, it looks like this has gathered some conflicts—would you mind rebasing and resolving those please?

@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch from 0837859 to 169a790 Compare August 22, 2024 03:45
@sm-sayedi
Copy link
Collaborator Author

Conflicts resolved @chrisbobbe! Please have a look!

Copy link
Collaborator

@chrisbobbe chrisbobbe left a 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.

Comment on lines 318 to 322
bool isAtLeast(UserRole role) {
return (apiValue ?? 0) <= (role.apiValue ?? 0);
}
Copy link
Collaborator

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 around apiValue values outside the UserRole 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.

Copy link
Collaborator Author

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. 🙂

Copy link
Collaborator

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.

@@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

Comment on lines 512 to 544
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),
];
Copy link
Collaborator

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:

Suggested change
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),
];


}

group('only "full member" user can post in channel with "fullMembers" policy', (){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
group('only "full member" user can post in channel with "fullMembers" policy', (){
group('only "full member" user can post in channel with "fullMembers" policy', () {

});

}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
}
});
}

Comment on lines 553 to 554
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)],
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)],
);
streams: [eg.stream(streamId: 1, channelPostPolicy: policy)]);

(here and in in many places later in this file)

…rams

The additional params are: selfUser, streams, and realmWaitingPeriodThreshold.
Adding these is necessary for the next commit(s), otherwise these
tests will fail.
@sm-sayedi sm-sayedi force-pushed the issue-674-hide-compose-box-according-channel-posting-policy branch from 169a790 to be0d74d Compare October 7, 2024 03:21
@sm-sayedi
Copy link
Collaborator Author

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,
Copy link
Member

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);
Copy link
Member

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.

Copy link
Member

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
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.');
}
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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

Comment on lines +1117 to +1124
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);
Copy link
Member

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:

Suggested change
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);
}

Comment on lines +1128 to +1129
return hasDeactivatedUser ? _ErrorBanner(label: ZulipLocalizations.of(context)
.errorBannerDeactivatedDmLabel) : null;
Copy link
Member

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:

Suggested change
return hasDeactivatedUser ? _ErrorBanner(label: ZulipLocalizations.of(context)
.errorBannerDeactivatedDmLabel) : null;
if (hasDeactivatedUser) {
return _ErrorBanner(
label: localizations.errorBannerDeactivatedDmLabel);
}


group('1:1 DMs', () {
Copy link
Member

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.

Comment on lines +546 to +547
for (final testCase in testCases) {
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
for (final testCase in testCases) {
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase;
for (final (ChannelPostPolicy policy, UserRole role, bool canPost) in testCases) {

Comment on lines +546 to +547
for (final testCase in testCases) {
final (ChannelPostPolicy policy, UserRole role, bool canPost) = testCase;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
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 -> '
Copy link
Member

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}) {
Copy link
Member

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
Copy link
Member

@PIG208 PIG208 Oct 10, 2024

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 ... && ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR ready for review by Zulip maintainers mentor review GSoC mentor review needed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hide compose box based on stream posting policy
5 participants