diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf index df2c4ab947..84182be3bb 100644 Binary files a/assets/icons/ZulipIcons.ttf and b/assets/icons/ZulipIcons.ttf differ diff --git a/assets/icons/remove.svg b/assets/icons/remove.svg new file mode 100644 index 0000000000..e42b64d1ec --- /dev/null +++ b/assets/icons/remove.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index ea2e10cff3..10817cb656 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -415,6 +415,21 @@ "others": {"type": "String", "example": "Alice, Bob"} } }, + "guestUserDmWarningOne": "{guestUser} is a guest in this organization.", + "@guestUserDmWarningOne": { + "description": "Warning shown when composing a DM to one guest user", + "placeholders": { + "guestUser": {"type": "String", "example": "Alice"} + } + }, + + "guestUserDmWarningMany": "{guestUsers} are guests in this organization.", + "@guestUserDmWarningMany": { + "description": "Warning shown when composing DMs to multiple guest users", + "placeholders": { + "guestUsers": {"type": "String", "example": "Alice, Bob, and Charlie"} + } + }, "messageListGroupYouWithYourself": "Messages with yourself", "@messageListGroupYouWithYourself": { "description": "Message list recipient header for a DM group that only includes yourself." diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index 0479b0428f..b357d3a3dc 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -67,6 +67,11 @@ sealed class Event { case 'submessage': return SubmessageEvent.fromJson(json); case 'typing': return TypingEvent.fromJson(json); case 'reaction': return ReactionEvent.fromJson(json); + case 'realm': + switch(json['op'] as String){ + case 'update': return RealmUpdateEvent.fromJson(json); + default: return UnexpectedEvent.fromJson(json); + } case 'heartbeat': return HeartbeatEvent.fromJson(json); // TODO add many more event types default: return UnexpectedEvent.fromJson(json); @@ -1151,6 +1156,46 @@ enum ReactionOp { remove, } +/// A Zulip event of type `realm`, with op `update`. +/// +/// This is the simpler of two possible event types sent when realm configuration changes. +/// It updates a single realm setting at a time. +/// +/// See: https://zulip.com/api/get-events#realm-update +@JsonSerializable(fieldRename: FieldRename.snake) +class RealmUpdateEvent extends Event { + @override + @JsonKey(includeToJson: true) + String get type => 'realm'; + + @JsonKey(includeToJson: true) + String get op => 'update'; + + @JsonKey(unknownEnumValue: JsonKey.nullForUndefinedEnumValue) + final RealmPropertyName? property; + + final dynamic value; + + RealmUpdateEvent({ + required super.id, + required this.property, + required this.value, + }); + + factory RealmUpdateEvent.fromJson(Map json) => + _$RealmUpdateEventFromJson(json); + + @override + Map toJson() => _$RealmUpdateEventToJson(this); +} + +/// As in [RealmUpdateEvent.property]. +@JsonEnum(fieldRename: FieldRename.snake) +enum RealmPropertyName { + @JsonValue('enable_guest_user_dm_warning') + realmEnableGuestUserDmWarning, +} + /// A Zulip event of type `heartbeat`: https://zulip.com/api/get-events#heartbeat @JsonSerializable(fieldRename: FieldRename.snake) class HeartbeatEvent extends Event { diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index 35206d77b9..720aaae449 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -704,6 +704,31 @@ const _$ReactionTypeEnumMap = { ReactionType.zulipExtraEmoji: 'zulip_extra_emoji', }; +RealmUpdateEvent _$RealmUpdateEventFromJson(Map json) => + RealmUpdateEvent( + id: (json['id'] as num).toInt(), + property: $enumDecodeNullable( + _$RealmPropertyNameEnumMap, + json['property'], + unknownValue: JsonKey.nullForUndefinedEnumValue, + ), + value: json['value'], + ); + +Map _$RealmUpdateEventToJson(RealmUpdateEvent instance) => + { + 'id': instance.id, + 'type': instance.type, + 'op': instance.op, + 'property': _$RealmPropertyNameEnumMap[instance.property], + 'value': instance.value, + }; + +const _$RealmPropertyNameEnumMap = { + RealmPropertyName.realmEnableGuestUserDmWarning: + 'enable_guest_user_dm_warning', +}; + HeartbeatEvent _$HeartbeatEventFromJson(Map json) => HeartbeatEvent(id: (json['id'] as num).toInt()); diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index 054230a256..1df8a075df 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -86,6 +86,9 @@ class InitialSnapshot { final int maxFileUploadSizeMib; + @JsonKey(defaultValue: false) // TODO(server-10): Remove default + final bool realmEnableGuestUserDmWarning; + final Uri? serverEmojiDataUrl; // TODO(server-6) final String? realmEmptyTopicDisplayName; // TODO(server-10) @@ -144,6 +147,7 @@ class InitialSnapshot { required this.realmMessageContentEditLimitSeconds, required this.realmDefaultExternalAccounts, required this.maxFileUploadSizeMib, + required this.realmEnableGuestUserDmWarning, required this.serverEmojiDataUrl, required this.realmEmptyTopicDisplayName, required this.realmUsers, diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index 570d7c2bba..37c4fdf81c 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -84,6 +84,8 @@ InitialSnapshot _$InitialSnapshotFromJson( ), ), maxFileUploadSizeMib: (json['max_file_upload_size_mib'] as num).toInt(), + realmEnableGuestUserDmWarning: + json['realm_enable_guest_user_dm_warning'] as bool? ?? false, serverEmojiDataUrl: json['server_emoji_data_url'] == null ? null @@ -109,44 +111,46 @@ InitialSnapshot _$InitialSnapshotFromJson( .toList(), ); -Map _$InitialSnapshotToJson(InitialSnapshot instance) => - { - 'queue_id': instance.queueId, - 'last_event_id': instance.lastEventId, - 'zulip_feature_level': instance.zulipFeatureLevel, - 'zulip_version': instance.zulipVersion, - 'zulip_merge_base': instance.zulipMergeBase, - 'alert_words': instance.alertWords, - 'custom_profile_fields': instance.customProfileFields, - 'email_address_visibility': - _$EmailAddressVisibilityEnumMap[instance.emailAddressVisibility], - 'server_typing_started_expiry_period_milliseconds': - instance.serverTypingStartedExpiryPeriodMilliseconds, - 'server_typing_stopped_wait_period_milliseconds': - instance.serverTypingStoppedWaitPeriodMilliseconds, - 'server_typing_started_wait_period_milliseconds': - instance.serverTypingStartedWaitPeriodMilliseconds, - 'realm_emoji': instance.realmEmoji, - 'recent_private_conversations': instance.recentPrivateConversations, - 'subscriptions': instance.subscriptions, - 'unread_msgs': instance.unreadMsgs, - 'streams': instance.streams, - 'user_settings': instance.userSettings, - 'user_topics': instance.userTopics, - 'realm_wildcard_mention_policy': instance.realmWildcardMentionPolicy, - 'realm_mandatory_topics': instance.realmMandatoryTopics, - 'realm_waiting_period_threshold': instance.realmWaitingPeriodThreshold, - 'realm_allow_message_editing': instance.realmAllowMessageEditing, - 'realm_message_content_edit_limit_seconds': - instance.realmMessageContentEditLimitSeconds, - 'realm_default_external_accounts': instance.realmDefaultExternalAccounts, - 'max_file_upload_size_mib': instance.maxFileUploadSizeMib, - 'server_emoji_data_url': instance.serverEmojiDataUrl?.toString(), - 'realm_empty_topic_display_name': instance.realmEmptyTopicDisplayName, - 'realm_users': instance.realmUsers, - 'realm_non_active_users': instance.realmNonActiveUsers, - 'cross_realm_bots': instance.crossRealmBots, - }; +Map _$InitialSnapshotToJson( + InitialSnapshot instance, +) => { + 'queue_id': instance.queueId, + 'last_event_id': instance.lastEventId, + 'zulip_feature_level': instance.zulipFeatureLevel, + 'zulip_version': instance.zulipVersion, + 'zulip_merge_base': instance.zulipMergeBase, + 'alert_words': instance.alertWords, + 'custom_profile_fields': instance.customProfileFields, + 'email_address_visibility': + _$EmailAddressVisibilityEnumMap[instance.emailAddressVisibility], + 'server_typing_started_expiry_period_milliseconds': + instance.serverTypingStartedExpiryPeriodMilliseconds, + 'server_typing_stopped_wait_period_milliseconds': + instance.serverTypingStoppedWaitPeriodMilliseconds, + 'server_typing_started_wait_period_milliseconds': + instance.serverTypingStartedWaitPeriodMilliseconds, + 'realm_emoji': instance.realmEmoji, + 'recent_private_conversations': instance.recentPrivateConversations, + 'subscriptions': instance.subscriptions, + 'unread_msgs': instance.unreadMsgs, + 'streams': instance.streams, + 'user_settings': instance.userSettings, + 'user_topics': instance.userTopics, + 'realm_wildcard_mention_policy': instance.realmWildcardMentionPolicy, + 'realm_mandatory_topics': instance.realmMandatoryTopics, + 'realm_waiting_period_threshold': instance.realmWaitingPeriodThreshold, + 'realm_allow_message_editing': instance.realmAllowMessageEditing, + 'realm_message_content_edit_limit_seconds': + instance.realmMessageContentEditLimitSeconds, + 'realm_default_external_accounts': instance.realmDefaultExternalAccounts, + 'max_file_upload_size_mib': instance.maxFileUploadSizeMib, + 'realm_enable_guest_user_dm_warning': instance.realmEnableGuestUserDmWarning, + 'server_emoji_data_url': instance.serverEmojiDataUrl?.toString(), + 'realm_empty_topic_display_name': instance.realmEmptyTopicDisplayName, + 'realm_users': instance.realmUsers, + 'realm_non_active_users': instance.realmNonActiveUsers, + 'cross_realm_bots': instance.crossRealmBots, +}; const _$EmailAddressVisibilityEnumMap = { EmailAddressVisibility.everyone: 1, diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index e8b15440e3..fc969b6238 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -647,6 +647,18 @@ abstract class ZulipLocalizations { /// **'DMs with {others}'** String dmsWithOthersPageTitle(String others); + /// Warning shown when composing a DM to one guest user + /// + /// In en, this message translates to: + /// **'{guestUser} is a guest in this organization.'** + String guestUserDmWarningOne(String guestUser); + + /// Warning shown when composing DMs to multiple guest users + /// + /// In en, this message translates to: + /// **'{guestUsers} are guests in this organization.'** + String guestUserDmWarningMany(String guestUsers); + /// Message list recipient header for a DM group that only includes yourself. /// /// In en, this message translates to: diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index c2478f4613..1ec7dd8a88 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsAr extends ZulipLocalizations { return 'DMs with $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Messages with yourself'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 289ba33af2..b1ec47d212 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsEn extends ZulipLocalizations { return 'DMs with $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Messages with yourself'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index 00537f73a2..041791b47a 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsJa extends ZulipLocalizations { return 'DMs with $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Messages with yourself'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 3c063e91da..43237cc5f3 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsNb extends ZulipLocalizations { return 'DMs with $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Messages with yourself'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index d4c3a033d9..a3c2d78290 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsPl extends ZulipLocalizations { return 'DM z $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Zapiski na własne konto'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index cb09ca516e..35ad545051 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsRu extends ZulipLocalizations { return 'ЛС с $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Сообщения с собой'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 0cb42c3a37..ca8f709b86 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -324,6 +324,16 @@ class ZulipLocalizationsSk extends ZulipLocalizations { return 'DMs with $others'; } + @override + String guestUserDmWarningOne(String guestUser) { + return '$guestUser is a guest in this organization.'; + } + + @override + String guestUserDmWarningMany(String guestUsers) { + return '$guestUsers are guests in this organization.'; + } + @override String get messageListGroupYouWithYourself => 'Messages with yourself'; diff --git a/lib/model/store.dart b/lib/model/store.dart index 939120113e..2e6e6c7ed5 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -471,6 +471,7 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor realmMandatoryTopics: initialSnapshot.realmMandatoryTopics, realmWaitingPeriodThreshold: initialSnapshot.realmWaitingPeriodThreshold, maxFileUploadSizeMib: initialSnapshot.maxFileUploadSizeMib, + realmEnableGuestUserDmWarning: initialSnapshot.realmEnableGuestUserDmWarning, realmEmptyTopicDisplayName: initialSnapshot.realmEmptyTopicDisplayName, realmAllowMessageEditing: initialSnapshot.realmAllowMessageEditing, realmMessageContentEditLimitSeconds: initialSnapshot.realmMessageContentEditLimitSeconds, @@ -510,6 +511,7 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor required this.realmMandatoryTopics, required this.realmWaitingPeriodThreshold, required this.maxFileUploadSizeMib, + required this.realmEnableGuestUserDmWarning, required String? realmEmptyTopicDisplayName, required this.realmAllowMessageEditing, required this.realmMessageContentEditLimitSeconds, @@ -571,6 +573,13 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor final int? realmMessageContentEditLimitSeconds; // TODO(#668): update this realm setting final int maxFileUploadSizeMib; // No event for this. + /// Whether to show a warning when composing a DM to a guest user. + /// + /// See: https://zulip.com/api/get-events-types + /// Changes: Added in Zulip 10.0 (feature level 348). + // TODO(server-10): Remove default + bool realmEnableGuestUserDmWarning = false; + /// The display name to use for empty topics. /// /// This should only be accessed when FL >= 334, since topics cannot @@ -899,6 +908,13 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor assert(debugLog("server event: reaction/${event.op}")); _messages.handleReactionEvent(event); + case RealmUpdateEvent(): + assert(debugLog("server event: realm/${event.op}")); + if (event.property == RealmPropertyName.realmEnableGuestUserDmWarning) { + realmEnableGuestUserDmWarning = event.value as bool; + notifyListeners(); + } + break; case UnexpectedEvent(): assert(debugLog("server event: ${jsonEncode(event.toJson())}")); // TODO log better } diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index 005321719d..480fe2446b 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -1484,6 +1484,42 @@ class _ErrorBanner extends _Banner { } } +class _WarningBanner extends _Banner { + const _WarningBanner({ + required this.label, + required this.onDismiss, + }); + + final String label; + final VoidCallback? onDismiss; + + @override + String getLabel(ZulipLocalizations zulipLocalizations) => label; + + @override + Color getLabelColor(DesignVariables designVariables) => + designVariables.btnLabelAttMediumIntWarning; + + @override + Color getBackgroundColor(DesignVariables designVariables) => + designVariables.bannerBgIntWarning; + + @override + bool get padEnd => false; + + @override + Widget? buildTrailing(BuildContext context) { + final designVariables = DesignVariables.of(context); + return InkWell( + splashFactory: NoSplash.splashFactory, + onTap: onDismiss, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon(ZulipIcons.remove, + size: 24, color: designVariables.btnLabelAttLowIntWarning))); + } +} + /// The compose box. /// /// Takes the full screen width, covering the horizontal insets with its surface. @@ -1521,6 +1557,8 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM @override ComposeBoxController get controller => _controller!; ComposeBoxController? _controller; + bool _isWarningBannerDismissed = false; + @override void onNewStore() { switch (widget.narrow) { @@ -1547,6 +1585,12 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM super.dispose(); } + void _dismissWarningBanner() { + setState(() { + _isWarningBannerDismissed = true; + }); + } + /// An [_ErrorBanner] that replaces the compose box's text inputs. Widget? _errorBannerComposingNotAllowed(BuildContext context) { final store = PerAccountStoreWidget.of(context); @@ -1576,11 +1620,62 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM return null; } + /// A [_WarningBanner] that goes at the top of the compose box. + Widget? _warningBanner(BuildContext context) { + if (_isWarningBannerDismissed) return null; + + final store = PerAccountStoreWidget.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); + + if (store.connection.zulipFeatureLevel! < 348 || + !store.realmEnableGuestUserDmWarning) { + return null; + } + + switch (widget.narrow) { + case DmNarrow(:final otherRecipientIds): + final guestUsers = otherRecipientIds + .map((id) => store.getUser(id)) + .where((user) => user?.role == UserRole.guest) + .toList(); + + if (guestUsers.isEmpty) return null; + + final guestNames = guestUsers + .map((user) => user != null + ? store.userDisplayName(user.userId) + : zulipLocalizations.unknownUserName) + .toList(); + + final String formattedNames; + if (guestUsers.length == 1) { + formattedNames = guestNames[0]; + } else { + final allButLast = + guestNames.sublist(0, guestNames.length - 1).join(', '); + formattedNames = + "$allButLast${guestUsers.length > 2 ? ',' : ''} and ${guestNames.last}"; + } + + final bannerText = guestUsers.length == 1 + ? zulipLocalizations.guestUserDmWarningOne(guestNames.first) + : zulipLocalizations.guestUserDmWarningMany(formattedNames); + + return _WarningBanner(label: bannerText, + onDismiss: _dismissWarningBanner); + + default: + return null; + } + } + @override Widget build(BuildContext context) { final Widget? body; final errorBanner = _errorBannerComposingNotAllowed(context); + final warningBanner = _warningBanner(context); + if (errorBanner != null) { return _ComposeBoxContainer(body: null, banner: errorBanner); } @@ -1603,6 +1698,6 @@ class _ComposeBoxState extends State with PerAccountStoreAwareStateM // errorBanner = _ErrorBanner(label: // ZulipLocalizations.of(context).errorSendMessageTimeout); // } - return _ComposeBoxContainer(body: body, banner: null); + return _ComposeBoxContainer(body: body, banner: warningBanner); } } diff --git a/lib/widgets/icons.dart b/lib/widgets/icons.dart index ff9b2f7794..a0963d94a5 100644 --- a/lib/widgets/icons.dart +++ b/lib/widgets/icons.dart @@ -114,38 +114,41 @@ abstract final class ZulipIcons { /// The Zulip custom icon "read_receipts". static const IconData read_receipts = IconData(0xf11e, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "remove". + static const IconData remove = IconData(0xf11f, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "send". - static const IconData send = IconData(0xf11f, fontFamily: "Zulip Icons"); + static const IconData send = IconData(0xf120, fontFamily: "Zulip Icons"); /// The Zulip custom icon "settings". - static const IconData settings = IconData(0xf120, fontFamily: "Zulip Icons"); + static const IconData settings = IconData(0xf121, fontFamily: "Zulip Icons"); /// The Zulip custom icon "share". - static const IconData share = IconData(0xf121, fontFamily: "Zulip Icons"); + static const IconData share = IconData(0xf122, fontFamily: "Zulip Icons"); /// The Zulip custom icon "share_ios". - static const IconData share_ios = IconData(0xf122, fontFamily: "Zulip Icons"); + static const IconData share_ios = IconData(0xf123, fontFamily: "Zulip Icons"); /// The Zulip custom icon "smile". - static const IconData smile = IconData(0xf123, fontFamily: "Zulip Icons"); + static const IconData smile = IconData(0xf124, fontFamily: "Zulip Icons"); /// The Zulip custom icon "star". - static const IconData star = IconData(0xf124, fontFamily: "Zulip Icons"); + static const IconData star = IconData(0xf125, fontFamily: "Zulip Icons"); /// The Zulip custom icon "star_filled". - static const IconData star_filled = IconData(0xf125, fontFamily: "Zulip Icons"); + static const IconData star_filled = IconData(0xf126, fontFamily: "Zulip Icons"); /// The Zulip custom icon "three_person". - static const IconData three_person = IconData(0xf126, fontFamily: "Zulip Icons"); + static const IconData three_person = IconData(0xf127, fontFamily: "Zulip Icons"); /// The Zulip custom icon "topic". - static const IconData topic = IconData(0xf127, fontFamily: "Zulip Icons"); + static const IconData topic = IconData(0xf128, fontFamily: "Zulip Icons"); /// The Zulip custom icon "unmute". - static const IconData unmute = IconData(0xf128, fontFamily: "Zulip Icons"); + static const IconData unmute = IconData(0xf129, fontFamily: "Zulip Icons"); /// The Zulip custom icon "user". - static const IconData user = IconData(0xf129, fontFamily: "Zulip Icons"); + static const IconData user = IconData(0xf12a, fontFamily: "Zulip Icons"); // END GENERATED ICON DATA } diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index eea9677045..12354d2fe9 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -130,6 +130,7 @@ class DesignVariables extends ThemeExtension { static final light = DesignVariables._( background: const Color(0xffffffff), bannerBgIntDanger: const Color(0xfff2e4e4), + bannerBgIntWarning: const Color(0xfffaf5dc), bgBotBar: const Color(0xfff6f6f6), bgContextMenu: const Color(0xfff2f2f2), bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.15), @@ -144,8 +145,10 @@ class DesignVariables extends ThemeExtension { btnBgAttMediumIntInfoNormal: const Color(0xff3c6bff).withValues(alpha: 0.12), btnLabelAttHigh: const Color(0xffffffff), btnLabelAttLowIntDanger: const Color(0xffc0070a), + btnLabelAttLowIntWarning: const Color(0xffa96a05), btnLabelAttMediumIntDanger: const Color(0xffac0508), btnLabelAttMediumIntInfo: const Color(0xff1027a6), + btnLabelAttMediumIntWarning: const Color(0xff764607), btnShadowAttMed: const Color(0xff000000).withValues(alpha: 0.20), composeBoxBg: const Color(0xffffffff), contextMenuCancelText: const Color(0xff222222), @@ -187,6 +190,7 @@ class DesignVariables extends ThemeExtension { static final dark = DesignVariables._( background: const Color(0xff000000), bannerBgIntDanger: const Color(0xff461616), + bannerBgIntWarning: const Color(0xff332b00), bgBotBar: const Color(0xff222222), bgContextMenu: const Color(0xff262626), bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.37), @@ -201,8 +205,10 @@ class DesignVariables extends ThemeExtension { btnBgAttMediumIntInfoNormal: const Color(0xff97b6fe).withValues(alpha: 0.12), btnLabelAttHigh: const Color(0xffffffff).withValues(alpha: 0.85), btnLabelAttLowIntDanger: const Color(0xffff8b7c), + btnLabelAttLowIntWarning: const Color(0xffeba002), btnLabelAttMediumIntDanger: const Color(0xffff8b7c), btnLabelAttMediumIntInfo: const Color(0xff97b6fe), + btnLabelAttMediumIntWarning: const Color(0xfff8b325), btnShadowAttMed: const Color(0xffffffff).withValues(alpha: 0.21), composeBoxBg: const Color(0xff0f0f0f), contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75), @@ -252,6 +258,7 @@ class DesignVariables extends ThemeExtension { DesignVariables._({ required this.background, required this.bannerBgIntDanger, + required this.bannerBgIntWarning, required this.bgBotBar, required this.bgContextMenu, required this.bgCounterUnread, @@ -266,8 +273,10 @@ class DesignVariables extends ThemeExtension { required this.btnBgAttMediumIntInfoNormal, required this.btnLabelAttHigh, required this.btnLabelAttLowIntDanger, + required this.btnLabelAttLowIntWarning, required this.btnLabelAttMediumIntDanger, required this.btnLabelAttMediumIntInfo, + required this.btnLabelAttMediumIntWarning, required this.btnShadowAttMed, required this.composeBoxBg, required this.contextMenuCancelText, @@ -318,6 +327,7 @@ class DesignVariables extends ThemeExtension { final Color background; final Color bannerBgIntDanger; + final Color bannerBgIntWarning; final Color bgBotBar; final Color bgContextMenu; final Color bgCounterUnread; @@ -332,8 +342,10 @@ class DesignVariables extends ThemeExtension { final Color btnBgAttMediumIntInfoNormal; final Color btnLabelAttHigh; final Color btnLabelAttLowIntDanger; + final Color btnLabelAttLowIntWarning; final Color btnLabelAttMediumIntDanger; final Color btnLabelAttMediumIntInfo; + final Color btnLabelAttMediumIntWarning; final Color btnShadowAttMed; final Color composeBoxBg; final Color contextMenuCancelText; @@ -379,6 +391,7 @@ class DesignVariables extends ThemeExtension { DesignVariables copyWith({ Color? background, Color? bannerBgIntDanger, + Color? bannerBgIntWarning, Color? bgBotBar, Color? bgContextMenu, Color? bgCounterUnread, @@ -393,8 +406,10 @@ class DesignVariables extends ThemeExtension { Color? btnBgAttMediumIntInfoNormal, Color? btnLabelAttHigh, Color? btnLabelAttLowIntDanger, + Color? btnLabelAttLowIntWarning, Color? btnLabelAttMediumIntDanger, Color? btnLabelAttMediumIntInfo, + Color? btnLabelAttMediumIntWarning, Color? btnShadowAttMed, Color? composeBoxBg, Color? contextMenuCancelText, @@ -435,6 +450,7 @@ class DesignVariables extends ThemeExtension { return DesignVariables._( background: background ?? this.background, bannerBgIntDanger: bannerBgIntDanger ?? this.bannerBgIntDanger, + bannerBgIntWarning: bannerBgIntWarning ?? this.bannerBgIntWarning, bgBotBar: bgBotBar ?? this.bgBotBar, bgContextMenu: bgContextMenu ?? this.bgContextMenu, bgCounterUnread: bgCounterUnread ?? this.bgCounterUnread, @@ -449,8 +465,10 @@ class DesignVariables extends ThemeExtension { btnBgAttMediumIntInfoNormal: btnBgAttMediumIntInfoNormal ?? this.btnBgAttMediumIntInfoNormal, btnLabelAttHigh: btnLabelAttHigh ?? this.btnLabelAttHigh, btnLabelAttLowIntDanger: btnLabelAttLowIntDanger ?? this.btnLabelAttLowIntDanger, + btnLabelAttLowIntWarning: btnLabelAttLowIntWarning ?? this.btnLabelAttLowIntWarning, btnLabelAttMediumIntDanger: btnLabelAttMediumIntDanger ?? this.btnLabelAttMediumIntDanger, btnLabelAttMediumIntInfo: btnLabelAttMediumIntInfo ?? this.btnLabelAttMediumIntInfo, + btnLabelAttMediumIntWarning: btnLabelAttMediumIntWarning ?? this.btnLabelAttMediumIntWarning, btnShadowAttMed: btnShadowAttMed ?? this.btnShadowAttMed, composeBoxBg: composeBoxBg ?? this.composeBoxBg, contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText, @@ -498,6 +516,7 @@ class DesignVariables extends ThemeExtension { return DesignVariables._( background: Color.lerp(background, other.background, t)!, bannerBgIntDanger: Color.lerp(bannerBgIntDanger, other.bannerBgIntDanger, t)!, + bannerBgIntWarning: Color.lerp(bannerBgIntWarning, other.bannerBgIntWarning, t)!, bgBotBar: Color.lerp(bgBotBar, other.bgBotBar, t)!, bgContextMenu: Color.lerp(bgContextMenu, other.bgContextMenu, t)!, bgCounterUnread: Color.lerp(bgCounterUnread, other.bgCounterUnread, t)!, @@ -512,8 +531,10 @@ class DesignVariables extends ThemeExtension { btnBgAttMediumIntInfoNormal: Color.lerp(btnBgAttMediumIntInfoNormal, other.btnBgAttMediumIntInfoNormal, t)!, btnLabelAttHigh: Color.lerp(btnLabelAttHigh, other.btnLabelAttHigh, t)!, btnLabelAttLowIntDanger: Color.lerp(btnLabelAttLowIntDanger, other.btnLabelAttLowIntDanger, t)!, + btnLabelAttLowIntWarning: Color.lerp(btnLabelAttLowIntWarning, other.btnLabelAttLowIntWarning, t)!, btnLabelAttMediumIntDanger: Color.lerp(btnLabelAttMediumIntDanger, other.btnLabelAttMediumIntDanger, t)!, btnLabelAttMediumIntInfo: Color.lerp(btnLabelAttMediumIntInfo, other.btnLabelAttMediumIntInfo, t)!, + btnLabelAttMediumIntWarning: Color.lerp(btnLabelAttMediumIntWarning, other.btnLabelAttMediumIntWarning, t)!, btnShadowAttMed: Color.lerp(btnShadowAttMed, other.btnShadowAttMed, t)!, composeBoxBg: Color.lerp(composeBoxBg, other.composeBoxBg, t)!, contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!, diff --git a/test/example_data.dart b/test/example_data.dart index f31337d303..17be894a30 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -920,6 +920,7 @@ InitialSnapshot initialSnapshot({ int? realmMessageContentEditLimitSeconds, Map? realmDefaultExternalAccounts, int? maxFileUploadSizeMib, + bool? realmEnableGuestUserDmWarning, Uri? serverEmojiDataUrl, String? realmEmptyTopicDisplayName, List? realmUsers, @@ -959,6 +960,7 @@ InitialSnapshot initialSnapshot({ realmMessageContentEditLimitSeconds: realmMessageContentEditLimitSeconds ?? 600, realmDefaultExternalAccounts: realmDefaultExternalAccounts ?? {}, maxFileUploadSizeMib: maxFileUploadSizeMib ?? 25, + realmEnableGuestUserDmWarning: realmEnableGuestUserDmWarning ?? false, serverEmojiDataUrl: serverEmojiDataUrl ?? realmUrl.replace(path: '/static/emoji.json'), realmEmptyTopicDisplayName: realmEmptyTopicDisplayName ?? defaultRealmEmptyTopicDisplayName,