@@ -202,7 +202,7 @@ class ComposeTopicController extends ComposeController<TopicValidationError> {
202
202
// https://github.com/zulip/zulip-flutter/pull/1148#discussion_r1941990585
203
203
String getDestinationString ({
204
204
required String streamName,
205
- required bool contentHasFocus ,
205
+ required bool hasChosenTopic ,
206
206
}) {
207
207
final textTrimmed = text.trim ();
208
208
if (textTrimmed.isNotEmpty) {
@@ -217,7 +217,7 @@ class ComposeTopicController extends ComposeController<TopicValidationError> {
217
217
// Do not fall back to a vacuous topic unless the user explicitly chooses
218
218
// to do so (by skipping topic input and moving focus to content input),
219
219
// because we expect a call to action for the user to pick one first.
220
- || ! contentHasFocus
220
+ || ! hasChosenTopic
221
221
) {
222
222
return '#$streamName ' ;
223
223
}
@@ -619,16 +619,26 @@ class _StreamContentInputState extends State<_StreamContentInput> {
619
619
});
620
620
}
621
621
622
+ void _hasChosenTopicChanged () {
623
+ setState (() {
624
+ // The relevant state lives on widget.controller.hasChosenTopic itself.
625
+ });
626
+ }
627
+
622
628
void _contentFocusChanged () {
623
629
setState (() {
624
630
// The relevant state lives on widget.controller.contentFocusNode itself.
625
631
});
632
+ if (widget.controller.contentFocusNode.hasFocus){
633
+ widget.controller.hasChosenTopic.value = true ;
634
+ }
626
635
}
627
636
628
637
@override
629
638
void initState () {
630
639
super .initState ();
631
640
widget.controller.topic.addListener (_topicChanged);
641
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
632
642
widget.controller.contentFocusNode.addListener (_contentFocusChanged);
633
643
}
634
644
@@ -639,6 +649,10 @@ class _StreamContentInputState extends State<_StreamContentInput> {
639
649
oldWidget.controller.topic.removeListener (_topicChanged);
640
650
widget.controller.topic.addListener (_topicChanged);
641
651
}
652
+ if (widget.controller.hasChosenTopic != oldWidget.controller.hasChosenTopic) {
653
+ oldWidget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
654
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
655
+ }
642
656
if (widget.controller.contentFocusNode != oldWidget.controller.contentFocusNode) {
643
657
oldWidget.controller.contentFocusNode.removeListener (_contentFocusChanged);
644
658
widget.controller.contentFocusNode.addListener (_contentFocusChanged);
@@ -648,6 +662,7 @@ class _StreamContentInputState extends State<_StreamContentInput> {
648
662
@override
649
663
void dispose () {
650
664
widget.controller.topic.removeListener (_topicChanged);
665
+ widget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
651
666
widget.controller.contentFocusNode.removeListener (_contentFocusChanged);
652
667
super .dispose ();
653
668
}
@@ -666,45 +681,113 @@ class _StreamContentInputState extends State<_StreamContentInput> {
666
681
hintText: zulipLocalizations.composeBoxChannelContentHint (
667
682
widget.controller.topic.getDestinationString (
668
683
streamName: streamName,
669
- contentHasFocus : widget.controller.contentFocusNode.hasFocus )));
684
+ hasChosenTopic : widget.controller.hasChosenTopic.value )));
670
685
}
671
686
}
672
687
673
- class _TopicInput extends StatelessWidget {
688
+ class _TopicInput extends StatefulWidget {
674
689
const _TopicInput ({required this .streamId, required this .controller});
675
690
676
691
final int streamId;
677
692
final StreamComposeBoxController controller;
678
693
694
+ @override
695
+ State <_TopicInput > createState () => _TopicInputState ();
696
+ }
697
+
698
+ class _TopicInputState extends State <_TopicInput > {
699
+ @override
700
+ void initState () {
701
+ super .initState ();
702
+ widget.controller.topicFocusNode.addListener (_topicFocusChanged);
703
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
704
+ }
705
+
706
+ @override
707
+ void didUpdateWidget (covariant _TopicInput oldWidget) {
708
+ super .didUpdateWidget (oldWidget);
709
+ if (widget.controller.topicFocusNode != oldWidget.controller.topicFocusNode) {
710
+ oldWidget.controller.topicFocusNode.removeListener (_topicFocusChanged);
711
+ widget.controller.topicFocusNode.addListener (_topicFocusChanged);
712
+ }
713
+ if (widget.controller.hasChosenTopic != oldWidget.controller.hasChosenTopic) {
714
+ oldWidget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
715
+ widget.controller.hasChosenTopic.addListener (_hasChosenTopicChanged);
716
+ }
717
+ }
718
+
719
+ @override
720
+ void dispose () {
721
+ widget.controller.topicFocusNode.removeListener (_topicFocusChanged);
722
+ widget.controller.hasChosenTopic.removeListener (_hasChosenTopicChanged);
723
+ super .dispose ();
724
+ }
725
+
726
+ void _topicFocusChanged () {
727
+ setState (() {
728
+ // The relevant state lives on widget.controller.topicFocusNode itself.
729
+ });
730
+ if (widget.controller.topicFocusNode.hasFocus) {
731
+ widget.controller.hasChosenTopic.value = false ;
732
+ }
733
+ }
734
+
735
+ void _hasChosenTopicChanged () {
736
+ setState (() {
737
+ // The relevant state lives on widget.controller.hasChosenTopic itself.
738
+ });
739
+ }
740
+
679
741
@override
680
742
Widget build (BuildContext context) {
681
743
final zulipLocalizations = ZulipLocalizations .of (context);
682
744
final designVariables = DesignVariables .of (context);
745
+ final store = PerAccountStoreWidget .of (context);
683
746
TextStyle topicTextStyle = TextStyle (
684
747
fontSize: 20 ,
685
748
height: 22 / 20 ,
686
749
color: designVariables.textInput.withFadedAlpha (0.9 ),
687
750
).merge (weightVariableTextStyle (context, wght: 600 ));
751
+ final hintStyle = topicTextStyle.copyWith (
752
+ color: designVariables.textInput.withFadedAlpha (0.5 ));
753
+
754
+ final defaultTopicDisplayName = store.zulipFeatureLevel >= 334
755
+ ? store.realmEmptyTopicDisplayName : kNoTopicTopic;
756
+
757
+ final decoration = switch ((
758
+ store.realmMandatoryTopics,
759
+ widget.controller.hasChosenTopic.value,
760
+ widget.controller.topicFocusNode.hasFocus,
761
+ )) {
762
+ (false , true , _) => InputDecoration (
763
+ hintText: defaultTopicDisplayName,
764
+ hintStyle: topicTextStyle.copyWith (
765
+ fontStyle: store.zulipFeatureLevel >= 334 ? FontStyle .italic : null )),
766
+ (false , false , true ) => InputDecoration (
767
+ hintText: zulipLocalizations.composeBoxEnterTopicOrSkipHintText (
768
+ defaultTopicDisplayName),
769
+ hintStyle: hintStyle),
770
+ (_, _, _) => InputDecoration (
771
+ hintText: zulipLocalizations.composeBoxTopicHintText,
772
+ hintStyle: hintStyle),
773
+ };
688
774
689
775
return TopicAutocomplete (
690
- streamId: streamId,
691
- controller: controller.topic,
692
- focusNode: controller.topicFocusNode,
693
- contentFocusNode: controller.contentFocusNode,
776
+ streamId: widget. streamId,
777
+ controller: widget. controller.topic,
778
+ focusNode: widget. controller.topicFocusNode,
779
+ contentFocusNode: widget. controller.contentFocusNode,
694
780
fieldViewBuilder: (context) => Container (
695
781
padding: const EdgeInsets .only (top: 10 , bottom: 9 ),
696
782
decoration: BoxDecoration (border: Border (bottom: BorderSide (
697
783
width: 1 ,
698
784
color: designVariables.foreground.withFadedAlpha (0.2 )))),
699
785
child: TextField (
700
- controller: controller.topic,
701
- focusNode: controller.topicFocusNode,
786
+ controller: widget. controller.topic,
787
+ focusNode: widget. controller.topicFocusNode,
702
788
textInputAction: TextInputAction .next,
703
789
style: topicTextStyle,
704
- decoration: InputDecoration (
705
- hintText: zulipLocalizations.composeBoxTopicHintText,
706
- hintStyle: topicTextStyle.copyWith (
707
- color: designVariables.textInput.withFadedAlpha (0.5 ))))));
790
+ decoration: decoration)));
708
791
}
709
792
}
710
793
@@ -1382,6 +1465,10 @@ class StreamComposeBoxController extends ComposeBoxController {
1382
1465
final ComposeTopicController topic;
1383
1466
final topicFocusNode = FocusNode ();
1384
1467
1468
+ // True if auto-completing to "general chat", moving focus to content input
1469
+ // from topic input (or as long as focus has been on content input?)
1470
+ ValueNotifier <bool > hasChosenTopic = ValueNotifier (false );
1471
+
1385
1472
@override
1386
1473
void dispose () {
1387
1474
topic.dispose ();
0 commit comments