Skip to content

Commit 442d112

Browse files
committed
compose: Change content input hint text if topic is empty and mandatory
Previously, "Message #stream > (no topic)" would appear as the hint text when the topic input is empty but mandatory. Now, it is shown as "Message #stream" instead, since the "(no topic)" isn't allowed when topics are mandatory. The control flow of `String get hintText` can be simplified. However, it is structured this way to prepare for a later change to support showing "general chat" in the hint text, that adds some more advanced checks. Signed-off-by: Zixuan James Li <[email protected]>
1 parent a7ff94c commit 442d112

File tree

2 files changed

+76
-13
lines changed

2 files changed

+76
-13
lines changed

lib/widgets/compose_box.dart

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -582,23 +582,48 @@ class _StreamContentInputState extends State<_StreamContentInput> {
582582
super.dispose();
583583
}
584584

585-
@override
586-
Widget build(BuildContext context) {
585+
/// The hint text for [_ContentInput] computed from the topic input.
586+
///
587+
/// This is a string referring to the send destination as something like
588+
/// "#stream name" when topics are [ComposeTopicController.mandatory] but the
589+
/// topic input is empty.
590+
///
591+
/// Otherwise, the destination is formatted like "#stream name > topic name".
592+
// No i18n of the use of "#" and ">" strings; those are part of how
593+
// Zulip expresses channels and topics, not any normal English punctuation,
594+
// so don't make sense to translate. See:
595+
// https://github.com/zulip/zulip-flutter/pull/1148#discussion_r1941990585
596+
String get hintText {
587597
final store = PerAccountStoreWidget.of(context);
588598
final zulipLocalizations = ZulipLocalizations.of(context);
589599
final streamName = store.streams[widget.narrow.streamId]?.name
590600
?? zulipLocalizations.unknownChannelName;
591-
final topic = TopicName(widget.controller.topic.textNormalized);
601+
602+
final textTrimmed = widget.controller.topic.text.trim();
603+
if (textTrimmed.isNotEmpty) {
604+
return zulipLocalizations.composeBoxChannelContentHint(
605+
'#$streamName > $textTrimmed');
606+
}
607+
608+
assert(widget.controller.topic._isTopicVacuous);
609+
// Sending to a vacuous topic (see [ComposeTopicController._isTopicVacuous])
610+
// is not possible if topics are mandatory.
611+
if (widget.controller.topic.mandatory) {
612+
return zulipLocalizations.composeBoxChannelContentHint('#$streamName');
613+
}
614+
615+
return zulipLocalizations.composeBoxChannelContentHint(
616+
'#$streamName > $kNoTopicTopic');
617+
}
618+
619+
@override
620+
Widget build(BuildContext context) {
592621
return _ContentInput(
593622
narrow: widget.narrow,
594-
destination: TopicNarrow(widget.narrow.streamId, topic),
623+
destination: TopicNarrow(widget.narrow.streamId,
624+
TopicName(widget.controller.topic.textNormalized)),
595625
controller: widget.controller,
596-
hintText: zulipLocalizations.composeBoxChannelContentHint(
597-
// No i18n of this use of "#" and ">" string; those are part of how
598-
// Zulip expresses channels and topics, not any normal English punctuation,
599-
// so don't make sense to translate. See:
600-
// https://github.com/zulip/zulip-flutter/pull/1148#discussion_r1941990585
601-
'#$streamName > ${topic.displayName}'));
626+
hintText: hintText);
602627
}
603628
}
604629

test/widgets/compose_box_test.dart

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,18 +353,56 @@ void main() {
353353
.decoration.isNotNull().hintText.equals(contentHintText);
354354
}
355355

356-
group('to ChannelNarrow', () {
356+
group('to ChannelNarrow, topics not mandatory', () {
357357
final narrow = ChannelNarrow(channel.streamId);
358358

359359
testWidgets('with empty topic', (tester) async {
360-
await prepare(tester, narrow: narrow);
360+
await prepare(tester, narrow: narrow, mandatoryTopics: false);
361+
checkComposeBoxHintTexts(tester,
362+
topicHintText: 'Topic',
363+
contentHintText: 'Message #${channel.name} > (no topic)');
364+
});
365+
366+
testWidgets('with non-empty but vacuous topic', (tester) async {
367+
await prepare(tester, narrow: narrow, mandatoryTopics: false);
368+
await enterTopic(tester, narrow: narrow, topic: '(no topic)');
369+
await tester.pump();
370+
checkComposeBoxHintTexts(tester,
371+
topicHintText: 'Topic',
372+
contentHintText: 'Message #${channel.name} > (no topic)');
373+
});
374+
375+
testWidgets('with non-empty topic', (tester) async {
376+
await prepare(tester, narrow: narrow, mandatoryTopics: false);
377+
await enterTopic(tester, narrow: narrow, topic: 'new topic');
378+
await tester.pump();
379+
checkComposeBoxHintTexts(tester,
380+
topicHintText: 'Topic',
381+
contentHintText: 'Message #${channel.name} > new topic');
382+
});
383+
});
384+
385+
group('to ChannelNarrow, mandatory topics', () {
386+
final narrow = ChannelNarrow(channel.streamId);
387+
388+
testWidgets('with empty topic', (tester) async {
389+
await prepare(tester, narrow: narrow, mandatoryTopics: true);
390+
checkComposeBoxHintTexts(tester,
391+
topicHintText: 'Topic',
392+
contentHintText: 'Message #${channel.name}');
393+
});
394+
395+
testWidgets('with non-empty but vacuous topic', (tester) async {
396+
await prepare(tester, narrow: narrow, mandatoryTopics: true);
397+
await enterTopic(tester, narrow: narrow, topic: '(no topic)');
398+
await tester.pump();
361399
checkComposeBoxHintTexts(tester,
362400
topicHintText: 'Topic',
363401
contentHintText: 'Message #${channel.name} > (no topic)');
364402
});
365403

366404
testWidgets('with non-empty topic', (tester) async {
367-
await prepare(tester, narrow: narrow);
405+
await prepare(tester, narrow: narrow, mandatoryTopics: true);
368406
await enterTopic(tester, narrow: narrow, topic: 'new topic');
369407
await tester.pump();
370408
checkComposeBoxHintTexts(tester,

0 commit comments

Comments
 (0)