Skip to content

Commit 7a81123

Browse files
committed
api: Use "channel" operator over the legacy "stream", where supported
This change affects few places in the app, for example, generating internal links and sending api request with the new "channel" operator. Fixes: #633
1 parent 6b7ba52 commit 7a81123

File tree

10 files changed

+137
-55
lines changed

10 files changed

+137
-55
lines changed

lib/api/model/narrow.dart

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,29 @@ typedef ApiNarrow = List<ApiNarrowElement>;
1414
/// reasonably be omitted will be omitted.
1515
ApiNarrow resolveApiNarrowForServer(ApiNarrow narrow, int zulipFeatureLevel) {
1616
final supportsOperatorDm = zulipFeatureLevel >= 177; // TODO(server-7)
17+
final supportsOperatorChannel = zulipFeatureLevel >= 250; // TODO(server-9)
1718
final supportsOperatorWith = zulipFeatureLevel >= 271; // TODO(server-9)
1819

1920
bool hasDmElement = false;
21+
bool hasChannelElement = false;
2022
bool hasWithElement = false;
2123
for (final element in narrow) {
2224
switch (element) {
23-
case ApiNarrowDm(): hasDmElement = true;
24-
case ApiNarrowWith(): hasWithElement = true;
25+
case ApiNarrowChannel(): hasChannelElement = true;
26+
case ApiNarrowDm(): hasDmElement = true;
27+
case ApiNarrowWith(): hasWithElement = true;
2528
default:
2629
}
2730
}
28-
if (!(hasDmElement || (hasWithElement && !supportsOperatorWith))) {
31+
if (!(hasChannelElement || hasDmElement || (hasWithElement && !supportsOperatorWith))) {
2932
return narrow;
3033
}
3134

3235
final result = <ApiNarrowElement>[];
3336
for (final element in narrow) {
3437
switch (element) {
38+
case ApiNarrowChannel():
39+
result.add(element.resolve(legacy: !supportsOperatorChannel));
3540
case ApiNarrowDm():
3641
result.add(element.resolve(legacy: !supportsOperatorDm));
3742
case ApiNarrowWith() when !supportsOperatorWith:
@@ -93,17 +98,51 @@ sealed class ApiNarrowElement {
9398
};
9499
}
95100

96-
class ApiNarrowStream extends ApiNarrowElement {
97-
@override String get operator => 'stream';
101+
class ApiNarrowChannel extends ApiNarrowElement {
102+
@override String get operator {
103+
assert(false,
104+
"The [operator] getter was called on a plain [ApiNarrowChannel]. "
105+
"Before passing to [jsonEncode] or otherwise getting [operator], "
106+
"the [ApiNarrowChannel] must be replaced by the result of [ApiNarrowChannel.resolve]."
107+
);
108+
return "channel";
109+
}
98110

99111
@override final int operand;
100112

101-
ApiNarrowStream(this.operand, {super.negated});
113+
ApiNarrowChannel(this.operand, {super.negated});
102114

103-
factory ApiNarrowStream.fromJson(Map<String, dynamic> json) => ApiNarrowStream(
104-
json['operand'] as int,
105-
negated: json['negated'] as bool? ?? false,
106-
);
115+
factory ApiNarrowChannel.fromJson(Map<String, dynamic> json) {
116+
var operand = (json['operand'] as int);
117+
var negated = json['negated'] as bool? ?? false;
118+
return json['operator'] == 'stream'
119+
? ApiNarrowStream._(operand, negated: negated)
120+
: ApiNarrowChannelModern._(operand, negated: negated);
121+
}
122+
123+
/// This element resolved, as either an [ApiNarrowChannelModern] or an [ApiNarrowStream].
124+
ApiNarrowChannel resolve({required bool legacy}) {
125+
return legacy ? ApiNarrowStream._(operand, negated: negated)
126+
: ApiNarrowChannelModern._(operand, negated: negated);
127+
}
128+
}
129+
130+
/// An [ApiNarrowElement] with the 'channel' operator (and not the legacy 'stream').
131+
///
132+
/// To construct one of these, use [ApiNarrowChannel.resolve].
133+
class ApiNarrowChannelModern extends ApiNarrowChannel {
134+
@override String get operator => 'channel';
135+
136+
ApiNarrowChannelModern._(super.operand, {super.negated});
137+
}
138+
139+
/// An [ApiNarrowElement] with the legacy 'stream' operator.
140+
///
141+
/// To construct one of these, use [ApiNarrowChannel.resolve].
142+
class ApiNarrowStream extends ApiNarrowChannel {
143+
@override String get operator => 'stream';
144+
145+
ApiNarrowStream._(super.operand, {super.negated});
107146
}
108147

109148
class ApiNarrowTopic extends ApiNarrowElement {

lib/model/internal_link.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Uri narrowLink(PerAccountStore store, Narrow narrow, {int? nearMessageId}) {
7171
fragment.write('${element.operator}/');
7272

7373
switch (element) {
74-
case ApiNarrowStream():
74+
case ApiNarrowChannel():
7575
final streamId = element.operand;
7676
final name = store.streams[streamId]?.name ?? 'unknown';
7777
final slugifiedName = _encodeHashComponent(name.replaceAll(' ', '-'));
@@ -182,7 +182,7 @@ NarrowLink? _interpretNarrowSegments(List<String> segments, PerAccountStore stor
182182
assert(segments.isNotEmpty);
183183
assert(segments.length.isEven);
184184

185-
ApiNarrowStream? streamElement;
185+
ApiNarrowChannel? channelElement;
186186
ApiNarrowTopic? topicElement;
187187
ApiNarrowDm? dmElement;
188188
ApiNarrowWith? withElement;
@@ -196,10 +196,10 @@ NarrowLink? _interpretNarrowSegments(List<String> segments, PerAccountStore stor
196196
switch (operator) {
197197
case _NarrowOperator.stream:
198198
case _NarrowOperator.channel:
199-
if (streamElement != null) return null;
199+
if (channelElement != null) return null;
200200
final streamId = _parseStreamOperand(operand, store);
201201
if (streamId == null) return null;
202-
streamElement = ApiNarrowStream(streamId, negated: negated);
202+
channelElement = ApiNarrowChannel(streamId, negated: negated);
203203

204204
case _NarrowOperator.topic:
205205
case _NarrowOperator.subject:
@@ -238,7 +238,7 @@ NarrowLink? _interpretNarrowSegments(List<String> segments, PerAccountStore stor
238238

239239
final Narrow? narrow;
240240
if (isElementOperands.isNotEmpty) {
241-
if (streamElement != null || topicElement != null || dmElement != null || withElement != null) {
241+
if (channelElement != null || topicElement != null || dmElement != null || withElement != null) {
242242
return null;
243243
}
244244
if (isElementOperands.length > 1) return null;
@@ -257,10 +257,10 @@ NarrowLink? _interpretNarrowSegments(List<String> segments, PerAccountStore stor
257257
return null;
258258
}
259259
} else if (dmElement != null) {
260-
if (streamElement != null || topicElement != null || withElement != null) return null;
260+
if (channelElement != null || topicElement != null || withElement != null) return null;
261261
narrow = DmNarrow.withUsers(dmElement.operand, selfUserId: store.selfUserId);
262-
} else if (streamElement != null) {
263-
final streamId = streamElement.operand;
262+
} else if (channelElement != null) {
263+
final streamId = channelElement.operand;
264264
if (topicElement != null) {
265265
narrow = TopicNarrow(streamId, topicElement.operand, with_: withElement?.operand);
266266
} else {

lib/model/narrow.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class ChannelNarrow extends Narrow {
8080
}
8181

8282
@override
83-
ApiNarrow apiEncode() => [ApiNarrowStream(streamId)];
83+
ApiNarrow apiEncode() => [ApiNarrowChannel(streamId)];
8484

8585
@override
8686
String toString() => 'ChannelNarrow($streamId)';
@@ -117,7 +117,7 @@ class TopicNarrow extends Narrow implements SendableNarrow {
117117

118118
@override
119119
ApiNarrow apiEncode() => [
120-
ApiNarrowStream(streamId),
120+
ApiNarrowChannel(streamId),
121121
ApiNarrowTopic(topic),
122122
if (with_ != null) ApiNarrowWith(with_!),
123123
];

test/api/route/messages_test.dart

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ void main() {
9292

9393
checkNarrow(const CombinedFeedNarrow().apiEncode(), jsonEncode([]));
9494
checkNarrow(const ChannelNarrow(12).apiEncode(), jsonEncode([
95-
{'operator': 'stream', 'operand': 12},
95+
{'operator': 'channel', 'operand': 12},
9696
]));
9797
checkNarrow(eg.topicNarrow(12, 'stuff').apiEncode(), jsonEncode([
98-
{'operator': 'stream', 'operand': 12},
98+
{'operator': 'channel', 'operand': 12},
9999
{'operator': 'topic', 'operand': 'stuff'},
100100
]));
101101
checkNarrow(eg.topicNarrow(12, 'stuff', with_: 1).apiEncode(), jsonEncode([
102-
{'operator': 'stream', 'operand': 12},
102+
{'operator': 'channel', 'operand': 12},
103103
{'operator': 'topic', 'operand': 'stuff'},
104104
{'operator': 'with', 'operand': 1},
105105
]));
@@ -113,7 +113,7 @@ void main() {
113113

114114
connection.zulipFeatureLevel = 270;
115115
checkNarrow(eg.topicNarrow(12, 'stuff', with_: 1).apiEncode(), jsonEncode([
116-
{'operator': 'stream', 'operand': 12},
116+
{'operator': 'channel', 'operand': 12},
117117
{'operator': 'topic', 'operand': 'stuff'},
118118
]));
119119
checkNarrow([ApiNarrowDm([123, 234])], jsonEncode([
@@ -123,6 +123,19 @@ void main() {
123123
{'operator': 'dm', 'operand': [123, 234]},
124124
]));
125125

126+
connection.zulipFeatureLevel = 249;
127+
checkNarrow(const ChannelNarrow(12).apiEncode(), jsonEncode([
128+
{'operator': 'stream', 'operand': 12},
129+
]));
130+
checkNarrow(eg.topicNarrow(12, 'stuff').apiEncode(), jsonEncode([
131+
{'operator': 'stream', 'operand': 12},
132+
{'operator': 'topic', 'operand': 'stuff'},
133+
]));
134+
checkNarrow(eg.topicNarrow(12, 'stuff', with_: 1).apiEncode(), jsonEncode([
135+
{'operator': 'stream', 'operand': 12},
136+
{'operator': 'topic', 'operand': 'stuff'},
137+
]));
138+
126139
connection.zulipFeatureLevel = 176;
127140
checkNarrow(eg.topicNarrow(12, 'stuff', with_: 1).apiEncode(), jsonEncode([
128141
{'operator': 'stream', 'operand': 12},

test/model/compose_test.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,19 @@ hello
349349
await store.addStream(stream);
350350
await store.addUser(sender);
351351

352+
check(quoteAndReplyPlaceholder(
353+
GlobalLocalizations.zulipLocalizations, store, message: message)).equals('''
354+
@_**Full Name|123** [said](${eg.selfAccount.realmUrl}#narrow/channel/1-test-here/topic/some.20topic/near/${message.id}): *(loading message ${message.id})*
355+
''');
356+
357+
check(quoteAndReply(store, message: message, rawContent: 'Hello world!')).equals('''
358+
@_**Full Name|123** [said](${eg.selfAccount.realmUrl}#narrow/channel/1-test-here/topic/some.20topic/near/${message.id}):
359+
```quote
360+
Hello world!
361+
```
362+
''');
363+
364+
store.connection.zulipFeatureLevel = 249;
352365
check(quoteAndReplyPlaceholder(
353366
GlobalLocalizations.zulipLocalizations, store, message: message)).equals('''
354367
@_**Full Name|123** [said](${eg.selfAccount.realmUrl}#narrow/stream/1-test-here/topic/some.20topic/near/${message.id}): *(loading message ${message.id})*

test/model/internal_link_test.dart

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,16 @@ void main() {
6060
.equals(store.realmUrl.resolve('#narrow/is/starred/near/1'));
6161
});
6262

63-
test('ChannelNarrow / TopicNarrow', () {
63+
group('ChannelNarrow / TopicNarrow', () {
6464
void checkNarrow(String expectedFragment, {
6565
required int streamId,
6666
required String name,
6767
String? topic,
6868
int? nearMessageId,
69+
int? zulipFeatureLevel = eg.futureZulipFeatureLevel,
6970
}) async {
7071
assert(expectedFragment.startsWith('#'), 'wrong-looking expectedFragment');
71-
final store = eg.store();
72+
final store = eg.store()..connection.zulipFeatureLevel = zulipFeatureLevel;
7273
await store.addStream(eg.stream(streamId: streamId, name: name));
7374
final narrow = topic == null
7475
? ChannelNarrow(streamId)
@@ -77,22 +78,32 @@ void main() {
7778
.equals(store.realmUrl.resolve(expectedFragment));
7879
}
7980

80-
checkNarrow(streamId: 1, name: 'announce', '#narrow/stream/1-announce');
81-
checkNarrow(streamId: 378, name: 'api design', '#narrow/stream/378-api-design');
82-
checkNarrow(streamId: 391, name: 'Outreachy', '#narrow/stream/391-Outreachy');
83-
checkNarrow(streamId: 415, name: 'chat.zulip.org', '#narrow/stream/415-chat.2Ezulip.2Eorg');
84-
checkNarrow(streamId: 419, name: 'français', '#narrow/stream/419-fran.C3.A7ais');
85-
checkNarrow(streamId: 403, name: 'Hshs[™~}(.', '#narrow/stream/403-Hshs.5B.E2.84.A2~.7D.28.2E');
86-
checkNarrow(streamId: 60, name: 'twitter', nearMessageId: 1570686, '#narrow/stream/60-twitter/near/1570686');
87-
88-
checkNarrow(streamId: 48, name: 'mobile', topic: 'Welcome screen UI',
89-
'#narrow/stream/48-mobile/topic/Welcome.20screen.20UI');
90-
checkNarrow(streamId: 243, name: 'mobile-team', topic: 'Podfile.lock clash #F92',
91-
'#narrow/stream/243-mobile-team/topic/Podfile.2Elock.20clash.20.23F92');
92-
checkNarrow(streamId: 377, name: 'translation/zh_tw', topic: '翻譯 "stream"',
93-
'#narrow/stream/377-translation.2Fzh_tw/topic/.E7.BF.BB.E8.AD.AF.20.22stream.22');
94-
checkNarrow(streamId: 42, name: 'Outreachy 2016-2017', topic: '2017-18 Stream?', nearMessageId: 302690,
95-
'#narrow/stream/42-Outreachy-2016-2017/topic/2017-18.20Stream.3F/near/302690');
81+
test('modern including "channel" operator', () {
82+
checkNarrow(streamId: 1, name: 'announce', '#narrow/channel/1-announce');
83+
checkNarrow(streamId: 378, name: 'api design', '#narrow/channel/378-api-design');
84+
checkNarrow(streamId: 391, name: 'Outreachy', '#narrow/channel/391-Outreachy');
85+
checkNarrow(streamId: 415, name: 'chat.zulip.org', '#narrow/channel/415-chat.2Ezulip.2Eorg');
86+
checkNarrow(streamId: 419, name: 'français', '#narrow/channel/419-fran.C3.A7ais');
87+
checkNarrow(streamId: 403, name: 'Hshs[™~}(.', '#narrow/channel/403-Hshs.5B.E2.84.A2~.7D.28.2E');
88+
checkNarrow(streamId: 60, name: 'twitter', nearMessageId: 1570686, '#narrow/channel/60-twitter/near/1570686');
89+
90+
checkNarrow(streamId: 48, name: 'mobile', topic: 'Welcome screen UI',
91+
'#narrow/channel/48-mobile/topic/Welcome.20screen.20UI');
92+
checkNarrow(streamId: 243, name: 'mobile-team', topic: 'Podfile.lock clash #F92',
93+
'#narrow/channel/243-mobile-team/topic/Podfile.2Elock.20clash.20.23F92');
94+
checkNarrow(streamId: 377, name: 'translation/zh_tw', topic: '翻譯 "stream"',
95+
'#narrow/channel/377-translation.2Fzh_tw/topic/.E7.BF.BB.E8.AD.AF.20.22stream.22');
96+
checkNarrow(streamId: 42, name: 'Outreachy 2016-2017', topic: '2017-18 Stream?', nearMessageId: 302690,
97+
'#narrow/channel/42-Outreachy-2016-2017/topic/2017-18.20Stream.3F/near/302690');
98+
});
99+
100+
test('legacy including "stream" operator', () {
101+
checkNarrow(streamId: 1, name: 'announce', zulipFeatureLevel: 249,
102+
'#narrow/stream/1-announce');
103+
checkNarrow(streamId: 48, name: 'mobile-team', topic: 'Welcome screen UI',
104+
zulipFeatureLevel: 249,
105+
'#narrow/stream/48-mobile-team/topic/Welcome.20screen.20UI');
106+
});
96107
});
97108

98109
test('DmNarrow', () {

test/model/message_list_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ void main() {
156156
..method.equals('GET')
157157
..url.path.equals('/api/v1/messages')
158158
..url.queryParameters.deepEquals({
159-
'narrow': jsonEncode(narrow),
159+
'narrow': jsonEncode(resolveApiNarrowForServer(narrow, connection.zulipFeatureLevel!)),
160160
'anchor': anchor,
161161
if (includeAnchor != null) 'include_anchor': includeAnchor.toString(),
162162
'num_before': numBefore.toString(),

test/widgets/action_sheet_test.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ void main() {
350350
'num_before': '0',
351351
'num_after': '1000',
352352
'narrow': jsonEncode([
353-
{'operator': 'stream', 'operand': channelId},
353+
{'operator': 'channel', 'operand': channelId},
354354
{'operator': 'is', 'operand': 'unread'},
355355
]),
356356
'op': 'add',
@@ -1014,7 +1014,9 @@ void main() {
10141014
check(connection.lastRequest).isA<http.Request>()
10151015
..url.path.equals('/api/v1/messages/flags/narrow')
10161016
..bodyFields['narrow'].equals(jsonEncode([
1017-
...eg.topicNarrow(someChannel.streamId, someTopic).apiEncode(),
1017+
...resolveApiNarrowForServer(
1018+
eg.topicNarrow(someChannel.streamId, someTopic).apiEncode(),
1019+
connection.zulipFeatureLevel!),
10181020
ApiNarrowIs(IsOperand.unread),
10191021
]))
10201022
..bodyFields['op'].equals('add')
@@ -1656,7 +1658,9 @@ void main() {
16561658
'include_anchor': 'true',
16571659
'num_before': '0',
16581660
'num_after': '1000',
1659-
'narrow': jsonEncode(TopicNarrow.ofMessage(message).apiEncode()),
1661+
'narrow': jsonEncode(resolveApiNarrowForServer(
1662+
TopicNarrow.ofMessage(message).apiEncode(),
1663+
connection.zulipFeatureLevel!)),
16601664
'op': 'remove',
16611665
'flag': 'read',
16621666
});
@@ -1701,7 +1705,9 @@ void main() {
17011705
..method.equals('POST')
17021706
..url.path.equals('/api/v1/messages/flags/narrow')
17031707
..bodyFields['narrow'].equals(
1704-
jsonEncode(eg.topicNarrow(newStream.streamId, newTopic).apiEncode()));
1708+
jsonEncode(resolveApiNarrowForServer(
1709+
eg.topicNarrow(newStream.streamId, newTopic).apiEncode(),
1710+
connection.zulipFeatureLevel!)));
17051711
});
17061712

17071713
testWidgets('shows error when fails', (tester) async {

test/widgets/actions_test.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void main() {
7474
'include_anchor': 'false',
7575
'num_before': '0',
7676
'num_after': '1000',
77-
'narrow': jsonEncode(apiNarrow),
77+
'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)),
7878
'op': 'add',
7979
'flag': 'read',
8080
});
@@ -155,7 +155,7 @@ void main() {
155155
'include_anchor': 'false',
156156
'num_before': '0',
157157
'num_after': '1000',
158-
'narrow': jsonEncode(apiNarrow),
158+
'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)),
159159
'op': 'add',
160160
'flag': 'read',
161161
});
@@ -180,7 +180,7 @@ void main() {
180180
'include_anchor': 'false',
181181
'num_before': '0',
182182
'num_after': '1000',
183-
'narrow': jsonEncode(apiNarrow),
183+
'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)),
184184
'op': 'add',
185185
'flag': 'read',
186186
});
@@ -199,7 +199,7 @@ void main() {
199199
'include_anchor': 'false',
200200
'num_before': '0',
201201
'num_after': '1000',
202-
'narrow': jsonEncode(apiNarrow),
202+
'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)),
203203
'op': 'add',
204204
'flag': 'read',
205205
});
@@ -223,7 +223,7 @@ void main() {
223223
'include_anchor': 'false',
224224
'num_before': '0',
225225
'num_after': '1000',
226-
'narrow': jsonEncode(apiNarrow),
226+
'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)),
227227
'op': 'add',
228228
'flag': 'read',
229229
});

0 commit comments

Comments
 (0)