@@ -576,16 +576,26 @@ class _StreamContentInputState extends State<_StreamContentInput> {
576
576
});
577
577
}
578
578
579
+ void _hasChosenTopicChanged () {
580
+ setState (() {
581
+ // The relevant state lives on widget.controller.hasChosenTopic itself.
582
+ });
583
+ }
584
+
579
585
void _contentFocusChanged () {
580
586
setState (() {
581
587
// The relevant state lives on widget.controller.contentFocusNode itself.
582
588
});
589
+ if (widget.controller.contentFocusNode.hasFocus){
590
+ widget.controller.hasChosenTopic.value = true ;
591
+ }
583
592
}
584
593
585
594
@override
586
595
void initState () {
587
596
super .initState ();
588
597
widget.controller.topic.addListener (_topicChanged);
598
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
589
599
widget.controller.contentFocusNode.addListener (_contentFocusChanged);
590
600
}
591
601
@@ -596,6 +606,10 @@ class _StreamContentInputState extends State<_StreamContentInput> {
596
606
oldWidget.controller.topic.removeListener (_topicChanged);
597
607
widget.controller.topic.addListener (_topicChanged);
598
608
}
609
+ if (widget.controller.hasChosenTopic != oldWidget.controller.hasChosenTopic) {
610
+ oldWidget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
611
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
612
+ }
599
613
if (widget.controller.contentFocusNode != oldWidget.controller.contentFocusNode) {
600
614
oldWidget.controller.contentFocusNode.removeListener (_contentFocusChanged);
601
615
widget.controller.contentFocusNode.addListener (_contentFocusChanged);
@@ -605,6 +619,7 @@ class _StreamContentInputState extends State<_StreamContentInput> {
605
619
@override
606
620
void dispose () {
607
621
widget.controller.topic.removeListener (_topicChanged);
622
+ widget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
608
623
widget.controller.contentFocusNode.removeListener (_contentFocusChanged);
609
624
super .dispose ();
610
625
}
@@ -616,7 +631,7 @@ class _StreamContentInputState extends State<_StreamContentInput> {
616
631
// The chosen topic can't be sent to, so don't show it.
617
632
return null ;
618
633
}
619
- if (! widget.controller.contentFocusNode.hasFocus ) {
634
+ if (! widget.controller.hasChosenTopic.value ) {
620
635
// Do not fall back to a vacuous topic unless the user explicitly chooses
621
636
// to do so (by skipping topic input and moving focus to content input),
622
637
// so that the user is not encouraged to use vacuous topic when they
@@ -656,12 +671,47 @@ class _StreamContentInputState extends State<_StreamContentInput> {
656
671
}
657
672
}
658
673
659
- class _TopicInput extends StatelessWidget {
674
+ class _TopicInput extends StatefulWidget {
660
675
const _TopicInput ({required this .streamId, required this .controller});
661
676
662
677
final int streamId;
663
678
final StreamComposeBoxController controller;
664
679
680
+ @override
681
+ State <_TopicInput > createState () => _TopicInputState ();
682
+ }
683
+
684
+ class _TopicInputState extends State <_TopicInput > {
685
+ @override
686
+ void initState () {
687
+ super .initState ();
688
+ widget.controller.topicFocusNode.addListener (_topicFocusChanged);
689
+ }
690
+
691
+ @override
692
+ void didUpdateWidget (covariant _TopicInput oldWidget) {
693
+ super .didUpdateWidget (oldWidget);
694
+ if (widget.controller.topicFocusNode != oldWidget.controller.topicFocusNode) {
695
+ oldWidget.controller.topicFocusNode.removeListener (_topicFocusChanged);
696
+ widget.controller.topicFocusNode.addListener (_topicFocusChanged);
697
+ }
698
+ }
699
+
700
+ @override
701
+ void dispose () {
702
+ widget.controller.topicFocusNode.removeListener (_topicFocusChanged);
703
+ super .dispose ();
704
+ }
705
+
706
+ void _topicFocusChanged () {
707
+ setState (() {
708
+ // The relevant state lives on widget.controller.topicFocusNode itself.
709
+ });
710
+ if (widget.controller.topicFocusNode.hasFocus) {
711
+ widget.controller.hasChosenTopic.value = false ;
712
+ }
713
+ }
714
+
665
715
@override
666
716
Widget build (BuildContext context) {
667
717
final zulipLocalizations = ZulipLocalizations .of (context);
@@ -673,18 +723,18 @@ class _TopicInput extends StatelessWidget {
673
723
).merge (weightVariableTextStyle (context, wght: 600 ));
674
724
675
725
return TopicAutocomplete (
676
- streamId: streamId,
677
- controller: controller.topic,
678
- focusNode: controller.topicFocusNode,
679
- contentFocusNode: controller.contentFocusNode,
726
+ streamId: widget. streamId,
727
+ controller: widget. controller.topic,
728
+ focusNode: widget. controller.topicFocusNode,
729
+ contentFocusNode: widget. controller.contentFocusNode,
680
730
fieldViewBuilder: (context) => Container (
681
731
padding: const EdgeInsets .only (top: 10 , bottom: 9 ),
682
732
decoration: BoxDecoration (border: Border (bottom: BorderSide (
683
733
width: 1 ,
684
734
color: designVariables.foreground.withFadedAlpha (0.2 )))),
685
735
child: TextField (
686
- controller: controller.topic,
687
- focusNode: controller.topicFocusNode,
736
+ controller: widget. controller.topic,
737
+ focusNode: widget. controller.topicFocusNode,
688
738
textInputAction: TextInputAction .next,
689
739
style: topicTextStyle,
690
740
decoration: InputDecoration (
@@ -1368,10 +1418,18 @@ class StreamComposeBoxController extends ComposeBoxController {
1368
1418
final ComposeTopicController topic;
1369
1419
final topicFocusNode = FocusNode ();
1370
1420
1421
+ /// Whether the user has made up their mind choosing a topic.
1422
+ ///
1423
+ /// Empirically, this should be set to `false` whenever the user focuses on
1424
+ /// the topic input, and set to `true` whenever the user focuses on the
1425
+ /// content input.
1426
+ ValueNotifier <bool > hasChosenTopic = ValueNotifier (false );
1427
+
1371
1428
@override
1372
1429
void dispose () {
1373
1430
topic.dispose ();
1374
1431
topicFocusNode.dispose ();
1432
+ hasChosenTopic.dispose ();
1375
1433
super .dispose ();
1376
1434
}
1377
1435
}
0 commit comments