1
1
import 'package:json_annotation/json_annotation.dart' ;
2
2
3
+ import '../../model/algorithms.dart' ;
4
+ import '../route/messages.dart' ;
3
5
import 'events.dart' ;
4
6
import 'initial_snapshot.dart' ;
5
7
import 'reaction.dart' ;
@@ -531,10 +533,68 @@ String? tryParseEmojiCodeToUnicode(String emojiCode) {
531
533
}
532
534
}
533
535
536
+ /// As in [MessageBase.recipient] .
537
+ ///
538
+ /// Different from [MessageDestination] , this information comes from
539
+ /// [getMessages] or [getEvents] , identifying the conversation that contains a
540
+ /// message.
541
+ sealed class Recipient {}
542
+
543
+ /// The recipient of a stream message.
544
+ @JsonSerializable (fieldRename: FieldRename .snake, createToJson: false )
545
+ class StreamRecipient extends Recipient {
546
+ StreamRecipient (this .streamId, this .topic);
547
+
548
+ int streamId;
549
+
550
+ @JsonKey (name: 'subject' )
551
+ TopicName topic;
552
+
553
+ factory StreamRecipient .fromJson (Map <String , dynamic > json) =>
554
+ _$StreamRecipientFromJson (json);
555
+ }
556
+
557
+ /// The recipient of a DM message.
558
+ class DmRecipient extends Recipient {
559
+ DmRecipient ({required this .allRecipientIds})
560
+ : assert (isSortedWithoutDuplicates (allRecipientIds.toList ()));
561
+
562
+ /// The user IDs of all users in the thread, sorted numerically.
563
+ ///
564
+ /// This lists the sender as well as all (other) recipients, and it
565
+ /// lists each user just once. In particular the self-user is always
566
+ /// included.
567
+ ///
568
+ /// This is required to have an efficient `length` .
569
+ final Iterable <int > allRecipientIds;
570
+ }
571
+
572
+ /// A message or message-like object, for showing in a message list.
573
+ ///
574
+ /// Other than [Message] , we use this for "outbox messages",
575
+ /// representing outstanding [sendMessage] requests.
576
+ abstract class MessageBase <T extends Recipient > {
577
+ /// The Zulip message ID.
578
+ ///
579
+ /// If null, the message doesn't have an ID acknowledged by the server
580
+ /// (e.g.: a locally-echoed message).
581
+ int ? get id;
582
+
583
+ int get senderId;
584
+ int get timestamp;
585
+
586
+ /// The recipient of this message.
587
+ // When implementing this, the return type should be either [StreamRecipient]
588
+ // or [DmRecipient]; it should never be [Recipient], because we
589
+ // expect a concrete subclass of [MessageBase] to represent either
590
+ // a channel message or a DM message, not both.
591
+ T get recipient;
592
+ }
593
+
534
594
/// As in the get-messages response.
535
595
///
536
596
/// https://zulip.com/api/get-messages#response
537
- sealed class Message {
597
+ sealed class Message < T extends Recipient > implements MessageBase < T > {
538
598
// final String? avatarUrl; // Use [User.avatarUrl] instead; will live-update
539
599
final String client;
540
600
String content;
@@ -544,6 +604,7 @@ sealed class Message {
544
604
@JsonKey (readValue: MessageEditState ._readFromMessage, fromJson: Message ._messageEditStateFromJson)
545
605
MessageEditState editState;
546
606
607
+ @override
547
608
final int id;
548
609
bool isMeMessage;
549
610
int ? lastEditTimestamp;
@@ -554,13 +615,15 @@ sealed class Message {
554
615
final int recipientId;
555
616
final String senderEmail;
556
617
final String senderFullName;
618
+ @override
557
619
final int senderId;
558
620
final String senderRealmStr;
559
621
560
622
/// Poll data if "submessages" describe a poll, `null` otherwise.
561
623
@JsonKey (name: 'submessages' , readValue: _readPoll, fromJson: Poll .fromJson, toJson: Poll .toJson)
562
624
Poll ? poll;
563
625
626
+ @override
564
627
final int timestamp;
565
628
String get type;
566
629
@@ -619,6 +682,8 @@ sealed class Message {
619
682
required this .matchTopic,
620
683
});
621
684
685
+ // TODO(dart): This has to be a static method, because factories/constructors
686
+ // do not support type parameters: https://github.com/dart-lang/language/issues/647
622
687
static Message fromJson (Map <String , dynamic > json) {
623
688
final type = json['type' ] as String ;
624
689
if (type == 'stream' ) return StreamMessage .fromJson (json);
@@ -715,7 +780,7 @@ extension type const TopicName(String _value) {
715
780
}
716
781
717
782
@JsonSerializable (fieldRename: FieldRename .snake)
718
- class StreamMessage extends Message {
783
+ class StreamMessage extends Message < StreamRecipient > {
719
784
@override
720
785
@JsonKey (includeToJson: true )
721
786
String get type => 'stream' ;
@@ -726,14 +791,23 @@ class StreamMessage extends Message {
726
791
@JsonKey (required : true , disallowNullValue: true )
727
792
String ? displayRecipient;
728
793
729
- int streamId;
794
+ @JsonKey (includeToJson: true )
795
+ int get streamId => recipient.streamId;
730
796
731
797
// The topic/subject is documented to be present on DMs too, just empty.
732
798
// We ignore it on DMs; if a future server introduces distinct topics in DMs,
733
799
// that will need new UI that we'll design then as part of that feature,
734
800
// and ignoring the topics seems as good a fallback behavior as any.
735
- @JsonKey (name: 'subject' )
736
- TopicName topic;
801
+ @JsonKey (name: 'subject' , includeToJson: true )
802
+ TopicName get topic => recipient.topic;
803
+
804
+ @override
805
+ @JsonKey (readValue: _readRecipient, includeToJson: false )
806
+ StreamRecipient recipient;
807
+
808
+ static Map <String , dynamic > _readRecipient (Map <dynamic , dynamic > json, String key) {
809
+ return {'stream_id' : json['stream_id' ], 'subject' : json['subject' ]};
810
+ }
737
811
738
812
StreamMessage ({
739
813
required super .client,
@@ -754,8 +828,7 @@ class StreamMessage extends Message {
754
828
required super .matchContent,
755
829
required super .matchTopic,
756
830
required this .displayRecipient,
757
- required this .streamId,
758
- required this .topic,
831
+ required this .recipient,
759
832
});
760
833
761
834
factory StreamMessage .fromJson (Map <String , dynamic > json) =>
@@ -809,7 +882,7 @@ class DmDisplayRecipientListConverter extends JsonConverter<List<DmDisplayRecipi
809
882
}
810
883
811
884
@JsonSerializable (fieldRename: FieldRename .snake)
812
- class DmMessage extends Message {
885
+ class DmMessage extends Message < DmRecipient > {
813
886
@override
814
887
@JsonKey (includeToJson: true )
815
888
String get type => 'private' ;
@@ -829,15 +902,12 @@ class DmMessage extends Message {
829
902
@DmDisplayRecipientListConverter ()
830
903
final List <DmDisplayRecipient > displayRecipient;
831
904
832
- /// The user IDs of all users in the thread, sorted numerically.
833
- ///
834
- /// This lists the sender as well as all (other) recipients, and it
835
- /// lists each user just once. In particular the self-user is always
836
- /// included.
837
- ///
838
- /// This is a result of [List.map] , so it has an efficient `length` .
905
+ // This is a result of [List.map], so it has an efficient `length`.
839
906
Iterable <int > get allRecipientIds => displayRecipient.map ((e) => e.id);
840
907
908
+ @override
909
+ DmRecipient get recipient => DmRecipient (allRecipientIds: allRecipientIds);
910
+
841
911
DmMessage ({
842
912
required super .client,
843
913
required super .content,
0 commit comments