From 41e1a3510e41964c6830c9dbdd503aacf1e36d1e Mon Sep 17 00:00:00 2001
From: chimnayajith <chinmayajith30@gmail.com>
Date: Wed, 23 Apr 2025 01:46:27 +0530
Subject: [PATCH 1/3] icons: Add remove icon

Taken from the "Icons" page in Figma:
https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=4031-32875&t=6FcBjNTASZoepny4-4
---
 assets/icons/ZulipIcons.ttf | Bin 13840 -> 13980 bytes
 assets/icons/remove.svg     |   3 +++
 lib/widgets/icons.dart      |  25 ++++++++++++++-----------
 3 files changed, 17 insertions(+), 11 deletions(-)
 create mode 100644 assets/icons/remove.svg

diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf
index df2c4ab94724dc1d2f78fde5515534a5c5dad87a..84182be3bbe0d49a1a45a6bd566855e4deac20cf 100644
GIT binary patch
delta 1818
zcmb7FOKcle6g}f_;@Gj1eyH0L0&yVqZ#*9VCbkpXNr9>oRHUG)s@j^=jtg;P(hw2}
zF^EMa5UN^D7c3$n1Zu?&H9}$$v0}vn6^RuqWC06=s&qkBRUkNT{L&V7`031jpZh)c
z&G^dVM%#n}U;yvJiPUp*FV~JAdg&uTDFDvzUYt|YuiTou1-SdDb#}g8?-YMM9tJ#X
z+>0zMt~XmpP8<gO6TtX(t5H9<`pY*rsrwU=YjLAbam{kRMMPTdm9-m3{{D-$Eyf5f
zF3p$gpa1cRhZ$^8;e&R4t)rY$ay;C}_2`9qyRql1N*YLW?Z|hQF0P#X)bs(FV_YA-
zx-}M<wJTs1rOdUDul}wnv(|{RU^WrJC_cjvxT{2zW6Foh7fRQ0%JH*f+xa})@FR(5
zaT+UdAcQzaf;%eG(2+rwvp90dqrkb71U=MvxbNnEmLrBq>Z!EL+4c&2<|76v+qDK4
zytZS*cC0b52Tx&yiRyHbvP0^0rSp86ng*VrhK>^LrznN~K8`+WYfP<1ndg3(o)fey
z+Zv+?vN$ur1WHs8l~k#k;7rFLT_l)en$M(Ee}?J>YU>;-1M8d{Jl8Nmo)X;2(YXsg
z?8noze2isiv=DkZdfiPe5lN)7hjR@@Buva;FO4*kl8~7XF>^t4jV@KzTO#ppJC!0$
zMRg+>CZ#ck7jz2eF`kR?yU_n&e}_LW?I-Bv1)M|^9VRE*5n~J$X0I2bn<<RZxL_ZM
z3Uf@sz>`cyr+SuaL8if0xF2Xm?3#Mr9Az0gby67TtQSCpj`RMUVBlrELv8{#BU5&P
z8dTB$o$J$Y6D&GgW_UsLPDlMrLJ-}%LZahdvJm6BAUDQSky6icKyoF#1el0uOoeyf
zMGGfaS<ZZjVwQXyfElEj!O`oB%HTVW1S1ET(!b9$T#J+abR(**;c?DtblYpqZX9AD
zjj8qaqC7Qt7H0|po7=Sg-t>w#1{ttuD+4aas`aO9T8UaE_m*|V?XvE<O?BITZ-L*K
zz4!*jDfwb6+jRUj*FIM7;5dWxSicjsu6xEbm4S-%oMPY%n^qvlW!{hy6BB$?>BV5m
zHNG!N-oT1=-!tcq(yPZ(JoN*#FHWZPOuj(7bV*B0SK~&uHkF*ISl@f!vqB!X>1S*>
zGvdkL3~@$6rL0TDC|{K*QqD_EQl67YQ8pxW%BF<yy&#dN<hRfUzqvSXiU+}OAYPX!
zQ!YyITZpzqiSmMkM!6)BpzKIYQ@$ZlrCgSXQ(lxXC|4xPSx(-Rs8Oy;Oi{iik)-55
zfDOUWx<qC3a{q0`cZZA%AZoomP(Avs|L)-B;NIYC!TX`pp^c&D(9Pkq!}tFG05)Cz
zVz@8njPV9zm-S=lV7BXz_`7_B1Kn;Q{K(un68P_yJf@$*_?LtHr?{<Oawvy4S2Dwj
Q*R$McFReDLOWF7T0bx50L;wH)

delta 1706
zcmb7^OKcle6o$|El_qhVXG<TD8qz>wPwes7u_tz%*bZtzR3L~b<)H?Xrb$KIm^73`
zsu;uq!7kOHuDSrx1uGV0Q&mwnR0t3YgjllZrWFemsbT|_5PWxhLtu$Vzt5d}&*MM;
zy>qWMHkVy7M5I&Rlb~29W?!tmF!y|1#BfA{x1O6#+oyh*{Z=IOJ6^BPH|ou&j=t=P
zM9zwY4=gTkoO|t$XZl5=A(7$DrG@(0OLuR7iSHedUt(gP5t?DV4UR1}*496J`maBU
zJxz`1^2&U<el_^%1sGh$;loCKy=lB^<XJq(d;CJZv9SL}#TLnYED|U*S1zvIeem1Q
zBH8bGuU*+5>YDL8po^ErZhd~`*Nida4H}E?De029T$kH&&loT&#%1FZ<Hx|sz)yiY
zK}W(8m6RNpGqNTD>5(LlDPzoK#Fnh&7$s#~@={<NM4&y7z4<WnIi7?};Flt9+>bY;
z!~Iu4+D~msNZS0wj-S|qcSr`IX_FxB7qm%avpR*J1F?u4<k`o%1(hl`&-x+aOgtxj
zkGOQxj7hx`>xy^<PuFB<ON<mIoH9NIl`t-k<6gks=1Eb?X6&$T$rI>eGBZx{kaWml
zd~JCM+7=PYZ=9?Hc&So~%6`U{6p5<I0b(pPFcmEq-n}qavQ|l%qQeqGhkYtVLKT@o
z=|jQ<<&}IYd4hFSzF*q^ncvl`jrb9|epX(QbJCPyU;YF&cBNOFmzKwfbqj>69#vYC
z0&&RL6v*+e#B}6Q$n1Iz`8~CE?<jrP_@w0+BSk5R0zG6oE~hEHD(|9Am#;|KZ_uL3
z-l=F;`nsf0JERtDIya?^jY2{R-8wRAkK0fo!MYMR!K$LvitN-{sVrR(Q5!SupP2{;
zQf;r=qJ%t&9!EF>HrX8B)wZO>RVT8QNJ;WArT^=Y<z1~D*4<X+5k^(gwzg&fsUuJ=
z>A6u>9oCXi=%P%LjZ)u9>l<-sw&?qER|J~IrWXsBjZyDpc-xCc-1LtBk&65Rbv{+-
z2O%%U4q4UJ&_U|~o;f)$8}~=On~|YRiYi6&rm32xtpc?ssak@S5~BhK2L@H79pn5>
zd$G1z(nq}BNc{-W#fh|?%@>Hulq_?ql5}#_@>p%!yWh6%#UtHrl*$quQUCmEU``{2
zt!pH(Z)g;;^BNOau0bDZ?1Bcj6{n<yEOt?YTTPZU3fS|m`UB(OO^r$HvPK5m&?sTK
zCVg1g6%7;H)R@A)rBT7IY9z52HMrNg@>(e87`&}f#a_}VW8cvj!>(&6g*G&%x30JE
z7~Zwc>WQyoXJYrdi{0P#q<WTn^S$r)S$*I9|7U9?S~|4#)$kWFuJF~|2U`zveTLVc
I|HzQP0mKdIWB>pF

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 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" fill="black"/>
+</svg>
\ No newline at end of file
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
 }

From 1ee4f1ff55eb9361bb25d63f412f50f0067a2a27 Mon Sep 17 00:00:00 2001
From: chimnayajith <chinmayajith30@gmail.com>
Date: Wed, 23 Apr 2025 02:26:32 +0530
Subject: [PATCH 2/3] api: Add RealmUpdateEvent and guest DM warning flag

- Add realmEnableGuestUserDmWarning to PerAccountStore
- Handle RealmUpdateEvent to keep setting in sync with server
- Support feature level 348+ for realm configuration
---
 lib/api/model/events.dart             | 45 +++++++++++++++
 lib/api/model/events.g.dart           | 25 +++++++++
 lib/api/model/initial_snapshot.dart   |  4 ++
 lib/api/model/initial_snapshot.g.dart | 80 ++++++++++++++-------------
 lib/model/store.dart                  | 16 ++++++
 test/example_data.dart                |  2 +
 6 files changed, 134 insertions(+), 38 deletions(-)

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<String, dynamic> json) =>
+    _$RealmUpdateEventFromJson(json);
+
+  @override
+  Map<String, dynamic> 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<String, dynamic> json) =>
+    RealmUpdateEvent(
+      id: (json['id'] as num).toInt(),
+      property: $enumDecodeNullable(
+        _$RealmPropertyNameEnumMap,
+        json['property'],
+        unknownValue: JsonKey.nullForUndefinedEnumValue,
+      ),
+      value: json['value'],
+    );
+
+Map<String, dynamic> _$RealmUpdateEventToJson(RealmUpdateEvent instance) =>
+    <String, dynamic>{
+      '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<String, dynamic> 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<String, dynamic> _$InitialSnapshotToJson(InitialSnapshot instance) =>
-    <String, dynamic>{
-      '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<String, dynamic> _$InitialSnapshotToJson(
+  InitialSnapshot instance,
+) => <String, dynamic>{
+  '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/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/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<String, RealmDefaultExternalAccount>? realmDefaultExternalAccounts,
   int? maxFileUploadSizeMib,
+  bool? realmEnableGuestUserDmWarning,
   Uri? serverEmojiDataUrl,
   String? realmEmptyTopicDisplayName,
   List<User>? 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,

From b3edf00a54d252e20dd8afae9bd9756c41f32e5f Mon Sep 17 00:00:00 2001
From: chimnayajith <chinmayajith30@gmail.com>
Date: Wed, 23 Apr 2025 02:27:32 +0530
Subject: [PATCH 3/3] compose: Implement guest user DM warning banner

---
 assets/l10n/app_en.arb                        | 15 +++
 lib/generated/l10n/zulip_localizations.dart   | 12 +++
 .../l10n/zulip_localizations_ar.dart          | 10 ++
 .../l10n/zulip_localizations_en.dart          | 10 ++
 .../l10n/zulip_localizations_ja.dart          | 10 ++
 .../l10n/zulip_localizations_nb.dart          | 10 ++
 .../l10n/zulip_localizations_pl.dart          | 10 ++
 .../l10n/zulip_localizations_ru.dart          | 10 ++
 .../l10n/zulip_localizations_sk.dart          | 10 ++
 lib/widgets/compose_box.dart                  | 97 ++++++++++++++++++-
 lib/widgets/theme.dart                        | 21 ++++
 11 files changed, 214 insertions(+), 1 deletion(-)

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/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/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<ComposeBox> 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<ComposeBox> 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<ComposeBox> 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<ComposeBox> 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/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<DesignVariables> {
   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<DesignVariables> {
     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<DesignVariables> {
   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<DesignVariables> {
     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> {
   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<DesignVariables> {
     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<DesignVariables> {
 
   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<DesignVariables> {
   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> {
   DesignVariables copyWith({
     Color? background,
     Color? bannerBgIntDanger,
+    Color? bannerBgIntWarning,
     Color? bgBotBar,
     Color? bgContextMenu,
     Color? bgCounterUnread,
@@ -393,8 +406,10 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
     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<DesignVariables> {
     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<DesignVariables> {
       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<DesignVariables> {
     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<DesignVariables> {
       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)!,