Skip to content

Commit 43049ee

Browse files
committed
compose: Show special hint text for inputs if empty topic
Signed-off-by: Zixuan James Li <[email protected]>
1 parent 290a1a0 commit 43049ee

File tree

2 files changed

+73
-17
lines changed

2 files changed

+73
-17
lines changed

lib/widgets/compose_box.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,17 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
429429
@override
430430
Widget build(BuildContext context) {
431431
final designVariables = DesignVariables.of(context);
432+
TextStyle hintStyle = TextStyle(
433+
color: designVariables.textInput.withFadedAlpha(0.5));
434+
435+
if (widget.destination.destination
436+
// ignore: constant_pattern_never_matches_value_type // null topic names soon to be enabled
437+
case StreamDestination(topic: TopicName(displayName: null))) {
438+
// TODO(#1285): This applies to the entire hint text; ideally we'd only
439+
// want to italize the "general chat" text, but this requires
440+
// special l10n support for the hint text string.
441+
hintStyle = hintStyle.copyWith(fontStyle: FontStyle.italic);
442+
}
432443

433444
return ComposeAutocomplete(
434445
narrow: widget.narrow,
@@ -470,8 +481,7 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
470481
// this and offering two lines of touchable area.
471482
contentPadding: const EdgeInsets.symmetric(vertical: _verticalPadding),
472483
hintText: widget.hintText,
473-
hintStyle: TextStyle(
474-
color: designVariables.textInput.withFadedAlpha(0.5))))))));
484+
hintStyle: hintStyle))))));
475485
}
476486
}
477487

@@ -529,7 +539,8 @@ class _StreamContentInputState extends State<_StreamContentInput> {
529539
destination: TopicNarrow(widget.narrow.streamId, topic),
530540
controller: widget.controller,
531541
hintText: zulipLocalizations.composeBoxChannelContentHint(
532-
streamName, topic.displayName));
542+
// ignore: dead_null_aware_expression // null topic names soon to be enabled
543+
streamName, topic.displayName ?? store.realmEmptyTopicDisplayName));
533544
}
534545
}
535546

@@ -548,6 +559,9 @@ class _TopicInput extends StatelessWidget {
548559
height: 22 / 20,
549560
color: designVariables.textInput.withFadedAlpha(0.9),
550561
).merge(weightVariableTextStyle(context, wght: 600));
562+
final store = PerAccountStoreWidget.of(context);
563+
final allowsEmptyTopics =
564+
store.connection.zulipFeatureLevel! >= 334 && !store.realmMandatoryTopics;
551565

552566
return TopicAutocomplete(
553567
streamId: streamId,
@@ -565,8 +579,11 @@ class _TopicInput extends StatelessWidget {
565579
textInputAction: TextInputAction.next,
566580
style: topicTextStyle,
567581
decoration: InputDecoration(
568-
hintText: zulipLocalizations.composeBoxTopicHintText,
582+
hintText: (allowsEmptyTopics)
583+
? store.realmEmptyTopicDisplayName
584+
: zulipLocalizations.composeBoxTopicHintText,
569585
hintStyle: topicTextStyle.copyWith(
586+
fontStyle: (allowsEmptyTopics) ? FontStyle.italic : null,
570587
color: designVariables.textInput.withFadedAlpha(0.5))))));
571588
}
572589
}
@@ -588,7 +605,8 @@ class _FixedDestinationContentInput extends StatelessWidget {
588605
final streamName = store.streams[streamId]?.name
589606
?? zulipLocalizations.composeBoxUnknownChannelName;
590607
return zulipLocalizations.composeBoxChannelContentHint(
591-
streamName, topic.displayName);
608+
// ignore: dead_null_aware_expression // null topic names soon to be enabled
609+
streamName, topic.displayName ?? store.realmEmptyTopicDisplayName);
592610

593611
case DmNarrow(otherRecipientIds: []): // The self-1:1 thread.
594612
return zulipLocalizations.composeBoxSelfDmContentHint;

test/widgets/compose_box_test.dart

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,18 @@ void main() {
4747
List<User> otherUsers = const [],
4848
List<ZulipStream> streams = const [],
4949
bool? mandatoryTopics,
50+
int? zulipFeatureLevel,
5051
}) async {
5152
if (narrow case ChannelNarrow(:var streamId) || TopicNarrow(: var streamId)) {
5253
assert(streams.any((stream) => stream.streamId == streamId),
5354
'Add a channel with "streamId" the same as of $narrow.streamId to the store.');
5455
}
5556
addTearDown(testBinding.reset);
5657
selfUser ??= eg.selfUser;
57-
final selfAccount = eg.account(user: selfUser);
58+
zulipFeatureLevel ??= eg.futureZulipFeatureLevel;
59+
final selfAccount = eg.account(user: selfUser, zulipFeatureLevel: zulipFeatureLevel);
5860
await testBinding.globalStore.add(selfAccount, eg.initialSnapshot(
61+
zulipFeatureLevel: zulipFeatureLevel,
5962
realmMandatoryTopics: mandatoryTopics,
6063
));
6164

@@ -317,11 +320,15 @@ void main() {
317320

318321
Future<void> prepare(WidgetTester tester, {
319322
required Narrow narrow,
323+
bool? mandatoryTopics,
324+
int? zulipFeatureLevel,
320325
}) async {
321326
await prepareComposeBox(tester,
322327
narrow: narrow,
323328
otherUsers: [eg.otherUser, eg.thirdUser],
324-
streams: [channel]);
329+
streams: [channel],
330+
mandatoryTopics: mandatoryTopics,
331+
zulipFeatureLevel: zulipFeatureLevel);
325332
}
326333

327334
void checkComposeBoxHintTexts(WidgetTester tester, {
@@ -338,48 +345,79 @@ void main() {
338345
.decoration.isNotNull().hintText.equals(contentHintText);
339346
}
340347

341-
testWidgets('to ChannelNarrow without topic', (tester) async {
342-
await prepare(tester, narrow: ChannelNarrow(channel.streamId));
348+
testWidgets('to ChannelNarrow with empty topic', (tester) async {
349+
await prepare(tester, narrow: ChannelNarrow(channel.streamId),
350+
mandatoryTopics: false);
351+
checkComposeBoxHintTexts(tester,
352+
topicHintText: eg.defaultRealmEmptyTopicDisplayName,
353+
contentHintText: 'Message #${channel.name} > ${eg.defaultRealmEmptyTopicDisplayName}');
354+
}, skip: true); // null topic names soon to be enabled
355+
356+
testWidgets('to ChannelNarrow with empty topic (mandatory topics)', (tester) async {
357+
await prepare(tester, narrow: ChannelNarrow(channel.streamId),
358+
mandatoryTopics: true);
359+
checkComposeBoxHintTexts(tester,
360+
topicHintText: 'Topic',
361+
contentHintText: 'Message #${channel.name} > ${eg.defaultRealmEmptyTopicDisplayName}');
362+
}, skip: true); // null topic names soon to be enabled
363+
364+
testWidgets('legacy: to ChannelNarrow with empty topic', (tester) async {
365+
await prepare(tester, narrow: ChannelNarrow(channel.streamId),
366+
mandatoryTopics: false,
367+
zulipFeatureLevel: 333);
343368
checkComposeBoxHintTexts(tester,
344369
topicHintText: 'Topic',
345370
contentHintText: 'Message #${channel.name} > (no topic)');
346371
});
347372

348-
testWidgets('to ChannelNarrow with topic', (tester) async {
373+
testWidgets('to ChannelNarrow with non-empty topic', (tester) async {
349374
final narrow = ChannelNarrow(channel.streamId);
350-
await prepare(tester, narrow: narrow);
375+
await prepare(tester, narrow: narrow,
376+
mandatoryTopics: false);
351377
await enterTopic(tester, narrow: narrow, topic: 'new topic');
352378
await tester.pump();
353379
checkComposeBoxHintTexts(tester,
354-
topicHintText: 'Topic',
380+
topicHintText: eg.defaultRealmEmptyTopicDisplayName,
355381
contentHintText: 'Message #${channel.name} > new topic');
356382
});
357383

358-
testWidgets('to TopicNarrow', (tester) async {
384+
testWidgets('to TopicNarrow with non-empty topic', (tester) async {
359385
await prepare(tester,
360-
narrow: TopicNarrow(channel.streamId, TopicName('topic')));
386+
narrow: TopicNarrow(channel.streamId, TopicName('topic')),
387+
mandatoryTopics: false);
361388
checkComposeBoxHintTexts(tester,
362389
contentHintText: 'Message #${channel.name} > topic');
363390
});
364391

392+
testWidgets('to TopicNarrow with empty topic', (tester) async {
393+
await prepare(tester,
394+
narrow: TopicNarrow(channel.streamId, TopicName('')),
395+
mandatoryTopics: false);
396+
checkComposeBoxHintTexts(tester, contentHintText:
397+
'Message #${channel.name} > ${eg.defaultRealmEmptyTopicDisplayName}');
398+
}, skip: true); // null topic names soon to be enabled
399+
365400
testWidgets('to DmNarrow with self', (tester) async {
366401
await prepare(tester, narrow: DmNarrow.withUser(
367-
eg.selfUser.userId, selfUserId: eg.selfUser.userId));
402+
eg.selfUser.userId, selfUserId: eg.selfUser.userId),
403+
mandatoryTopics: false);
368404
checkComposeBoxHintTexts(tester,
369405
contentHintText: 'Jot down something');
370406
});
371407

372408
testWidgets('to 1:1 DmNarrow', (tester) async {
373409
await prepare(tester, narrow: DmNarrow.withUser(
374-
eg.otherUser.userId, selfUserId: eg.selfUser.userId));
410+
eg.otherUser.userId, selfUserId: eg.selfUser.userId),
411+
mandatoryTopics: false);
375412
checkComposeBoxHintTexts(tester,
376413
contentHintText: 'Message @${eg.otherUser.fullName}');
377414
});
378415

379416
testWidgets('to group DmNarrow', (tester) async {
380417
await prepare(tester, narrow: DmNarrow.withOtherUsers(
381418
[eg.otherUser.userId, eg.thirdUser.userId],
382-
selfUserId: eg.selfUser.userId));
419+
selfUserId: eg.selfUser.userId),
420+
mandatoryTopics: false);
383421
checkComposeBoxHintTexts(tester,
384422
contentHintText: 'Message group');
385423
});

0 commit comments

Comments
 (0)