Skip to content

message-list : add a prompt to notify guest(s) when DM #1362

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -831,5 +831,13 @@
"zulipAppTitle": "Zulip",
"@zulipAppTitle": {
"description": "The name of Zulip. This should be either 'Zulip' or a transliteration."
},
"bannerText": "{guestCount, plural, =1{{guestNamesList} is a guest in this organization.} other{{guestNamesList} are guests in this organization.}}",
"@bannerText": {
"description": "Message displaying guest names in the organization.",
"placeholders": {
"guestCount": {"type": "int", "example": "3"},
"guestNamesList": {"type": "String", "example": "Alice, Bob, Charlie"}
}
}
}
3 changes: 3 additions & 0 deletions lib/api/model/initial_snapshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class InitialSnapshot {
/// https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member
final int realmWaitingPeriodThreshold;

final bool? realmEnableGuestUserDmWarning;

final Map<String, RealmDefaultExternalAccount> realmDefaultExternalAccounts;

final int maxFileUploadSizeMib;
Expand Down Expand Up @@ -135,6 +137,7 @@ class InitialSnapshot {
required this.realmWildcardMentionPolicy,
required this.realmMandatoryTopics,
required this.realmWaitingPeriodThreshold,
required this.realmEnableGuestUserDmWarning,
required this.realmDefaultExternalAccounts,
required this.maxFileUploadSizeMib,
required this.serverEmojiDataUrl,
Expand Down
4 changes: 4 additions & 0 deletions lib/api/model/initial_snapshot.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,12 @@ abstract class ZulipLocalizations {
/// In en, this message translates to:
/// **'Zulip'**
String get zulipAppTitle;

/// Message displaying guest names in the organization.
///
/// In en, this message translates to:
/// **'{guestCount, plural, =1{{guestNamesList} is a guest in this organization.} other{{guestNamesList} are guests in this organization.}}'**
String bannerText(int guestCount, String guestNamesList);
}

class _ZulipLocalizationsDelegate extends LocalizationsDelegate<ZulipLocalizations> {
Expand Down
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsAr extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsEn extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsJa extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_nb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsNb extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsPl extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsRu extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
11 changes: 11 additions & 0 deletions lib/generated/l10n/zulip_localizations_sk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,15 @@ class ZulipLocalizationsSk extends ZulipLocalizations {

@override
String get zulipAppTitle => 'Zulip';

@override
String bannerText(int guestCount, String guestNamesList) {
String _temp0 = intl.Intl.pluralLogic(
guestCount,
locale: localeName,
other: '$guestNamesList are guests in this organization.',
one: '$guestNamesList is a guest in this organization.',
);
return '$_temp0';
}
}
4 changes: 4 additions & 0 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, ChannelStore, Mess
recentDmConversationsView: RecentDmConversationsView(
initial: initialSnapshot.recentPrivateConversations, selfUserId: account.userId),
recentSenders: RecentSenders(),
realmEnableGuestUserDmWarning: initialSnapshot.realmEnableGuestUserDmWarning,
);
}

Expand All @@ -361,6 +362,7 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, ChannelStore, Mess
required this.unreads,
required this.recentDmConversationsView,
required this.recentSenders,
required this.realmEnableGuestUserDmWarning,
}) : assert(selfUserId == globalStore.getAccount(accountId)!.userId),
assert(realmUrl == globalStore.getAccount(accountId)!.realmUrl),
assert(realmUrl == connection.realmUrl),
Expand Down Expand Up @@ -567,6 +569,8 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, ChannelStore, Mess

final AutocompleteViewManager autocompleteViewManager = AutocompleteViewManager();

final bool? realmEnableGuestUserDmWarning;

// End of data.
////////////////////////////////////////////////////////////////

Expand Down
69 changes: 69 additions & 0 deletions lib/widgets/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ abstract class MessageListPageState {
///
/// This is null if [MessageList] has not mounted yet.
MessageListView? get model;

bool showDMWarningBanner = true;
}

class MessageListPage extends StatefulWidget {
Expand Down Expand Up @@ -224,10 +226,14 @@ class _MessageListPageState extends State<MessageListPage> implements MessageLis
MessageListView? get model => _messageListKey.currentState?.model;
final GlobalKey<_MessageListState> _messageListKey = GlobalKey();

@override
late bool showDMWarningBanner;

@override
void initState() {
super.initState();
narrow = widget.initNarrow;
showDMWarningBanner = narrow is DmNarrow;
}

void _narrowChanged(Narrow newNarrow) {
Expand All @@ -241,6 +247,7 @@ class _MessageListPageState extends State<MessageListPage> implements MessageLis
final store = PerAccountStoreWidget.of(context);
final messageListTheme = MessageListTheme.of(context);
final zulipLocalizations = ZulipLocalizations.of(context);
final designVariables= DesignVariables.of(context);

final Color? appBarBackgroundColor;
bool removeAppBarBottomBorder = false;
Expand Down Expand Up @@ -318,10 +325,72 @@ class _MessageListPageState extends State<MessageListPage> implements MessageLis
narrow: narrow,
onNarrowChanged: _narrowChanged,
))),
if(shouldShowGuestUserWarningPrompt(store))
showGuestUserWarningPrompt(narrow as DmNarrow, store, designVariables, zulipLocalizations),
if (ComposeBox.hasComposeBox(narrow))
ComposeBox(key: _composeBoxKey, narrow: narrow)
]))));
}

bool shouldShowGuestUserWarningPrompt(PerAccountStore store) =>
store.connection.zulipFeatureLevel! >= 348 &&
narrow is DmNarrow && (store.realmEnableGuestUserDmWarning ?? false);

Widget showGuestUserWarningPrompt(
DmNarrow narrow,
PerAccountStore store,
DesignVariables designVariables,
ZulipLocalizations zulipLocalizations,
) {
final recipients = narrow.otherRecipientIds;
final allUsersInStore = Map<int, User>.from(store.users);
allUsersInStore.removeWhere((userId, user) => !recipients.contains(userId));
final guestNames =
allUsersInStore.values
.where((user) => user.role == UserRole.guest)
.map((e) => e.fullName)
.toList();

if(guestNames.isEmpty){
return SizedBox();
}

return Visibility(
visible: showDMWarningBanner,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: designVariables.dmUserWarningBannerBorder,),
color: designVariables.dmUserWarningBanner,
),
margin: EdgeInsets.all(4),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
zulipLocalizations.bannerText(guestNames.length, guestNames.join(', ')),
style: TextStyle(color: Colors.white),
),
),
GestureDetector(
onTap: () {
setState(() {
showDMWarningBanner= false;
});
},
child: Icon(Icons.close, color: Colors.white),
),
],
),
),
),
);
}
}

class MessageListAppBarTitle extends StatelessWidget {
Expand Down
14 changes: 14 additions & 0 deletions lib/widgets/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
subscriptionListHeaderLine: const HSLColor.fromAHSL(0.2, 240, 0.1, 0.5).toColor(),
subscriptionListHeaderText: const HSLColor.fromAHSL(1.0, 240, 0.1, 0.5).toColor(),
unreadCountBadgeTextForChannel: Colors.black.withValues(alpha: 0.9),
dmUserWarningBanner: Color.fromARGB(255, 133, 118, 71),
dmUserWarningBannerBorder: Color.fromARGB(255, 127, 111, 60),
);

static final dark = DesignVariables._(
Expand Down Expand Up @@ -224,6 +226,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
subscriptionListHeaderText: const HSLColor.fromAHSL(1.0, 240, 0.1, 0.75).toColor(),
unreadCountBadgeTextForChannel: Colors.white.withValues(alpha: 0.9),
dmUserWarningBanner: Color.fromARGB(255, 133, 118, 71),
dmUserWarningBannerBorder: Color.fromARGB(255, 127, 111, 60),
);

DesignVariables._({
Expand Down Expand Up @@ -273,6 +277,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
required this.subscriptionListHeaderLine,
required this.subscriptionListHeaderText,
required this.unreadCountBadgeTextForChannel,
required this.dmUserWarningBanner,
required this.dmUserWarningBannerBorder,
});

/// The [DesignVariables] from the context's active theme.
Expand Down Expand Up @@ -335,6 +341,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
final Color subscriptionListHeaderLine;
final Color subscriptionListHeaderText;
final Color unreadCountBadgeTextForChannel;
final Color dmUserWarningBanner;
final Color dmUserWarningBannerBorder;

@override
DesignVariables copyWith({
Expand Down Expand Up @@ -384,6 +392,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
Color? subscriptionListHeaderLine,
Color? subscriptionListHeaderText,
Color? unreadCountBadgeTextForChannel,
Color? dmUserWarningBanner,
Color? dmUserWarningBannerBorder,
}) {
return DesignVariables._(
background: background ?? this.background,
Expand Down Expand Up @@ -432,6 +442,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
subscriptionListHeaderLine: subscriptionListHeaderLine ?? this.subscriptionListHeaderLine,
subscriptionListHeaderText: subscriptionListHeaderText ?? this.subscriptionListHeaderText,
unreadCountBadgeTextForChannel: unreadCountBadgeTextForChannel ?? this.unreadCountBadgeTextForChannel,
dmUserWarningBanner: dmUserWarningBanner ?? this.dmUserWarningBanner,
dmUserWarningBannerBorder: dmUserWarningBannerBorder ?? this.dmUserWarningBannerBorder,
);
}

Expand Down Expand Up @@ -487,6 +499,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
subscriptionListHeaderLine: Color.lerp(subscriptionListHeaderLine, other.subscriptionListHeaderLine, t)!,
subscriptionListHeaderText: Color.lerp(subscriptionListHeaderText, other.subscriptionListHeaderText, t)!,
unreadCountBadgeTextForChannel: Color.lerp(unreadCountBadgeTextForChannel, other.unreadCountBadgeTextForChannel, t)!,
dmUserWarningBanner: Color.lerp(dmUserWarningBanner, other.dmUserWarningBanner, t)!,
dmUserWarningBannerBorder: Color.lerp(dmUserWarningBannerBorder, other.dmUserWarningBannerBorder, t)!,
);
}
}
Expand Down
Loading