@@ -411,79 +411,7 @@ class _ContentInput extends StatefulWidget {
411411 State <_ContentInput > createState () => _ContentInputState ();
412412}
413413
414- class _ContentInputState extends State <_ContentInput > with WidgetsBindingObserver {
415- @override
416- void initState () {
417- super .initState ();
418- widget.controller.content.addListener (_contentChanged);
419- widget.controller.contentFocusNode.addListener (_focusChanged);
420- WidgetsBinding .instance.addObserver (this );
421- }
422-
423- @override
424- void didUpdateWidget (covariant _ContentInput oldWidget) {
425- super .didUpdateWidget (oldWidget);
426- if (widget.controller != oldWidget.controller) {
427- oldWidget.controller.content.removeListener (_contentChanged);
428- widget.controller.content.addListener (_contentChanged);
429- oldWidget.controller.contentFocusNode.removeListener (_focusChanged);
430- widget.controller.contentFocusNode.addListener (_focusChanged);
431- }
432- }
433-
434- @override
435- void dispose () {
436- widget.controller.content.removeListener (_contentChanged);
437- widget.controller.contentFocusNode.removeListener (_focusChanged);
438- WidgetsBinding .instance.removeObserver (this );
439- super .dispose ();
440- }
441-
442- void _contentChanged () {
443- final store = PerAccountStoreWidget .of (context);
444- (widget.controller.content.text.isEmpty)
445- ? store.typingNotifier.stoppedComposing ()
446- : store.typingNotifier.keystroke (widget.destination);
447- }
448-
449- void _focusChanged () {
450- if (widget.controller.contentFocusNode.hasFocus) {
451- // Content input getting focus doesn't necessarily mean that
452- // the user started typing, so do nothing.
453- return ;
454- }
455- final store = PerAccountStoreWidget .of (context);
456- store.typingNotifier.stoppedComposing ();
457- }
458-
459- @override
460- void didChangeAppLifecycleState (AppLifecycleState state) {
461- switch (state) {
462- case AppLifecycleState .hidden:
463- case AppLifecycleState .paused:
464- case AppLifecycleState .detached:
465- // Transition to either [hidden] or [paused] signals that
466- // > [the] application is not currently visible to the user, and not
467- // > responding to user input.
468- //
469- // When transitioning to [detached], the compose box can't exist:
470- // > The application defaults to this state before it initializes, and
471- // > can be in this state (applicable on Android, iOS, and web) after
472- // > all views have been detached.
473- //
474- // For all these states, we can conclude that the user is not
475- // composing a message.
476- final store = PerAccountStoreWidget .of (context);
477- store.typingNotifier.stoppedComposing ();
478- case AppLifecycleState .inactive:
479- // > At least one view of the application is visible, but none have
480- // > input focus. The application is otherwise running normally.
481- // For example, we expect this state when the user is selecting a file
482- // to upload.
483- case AppLifecycleState .resumed:
484- }
485- }
486-
414+ class _ContentInputState extends State <_ContentInput > with WidgetsBindingObserver , _TypingNotifierMixin {
487415 static double maxHeight (BuildContext context) {
488416 final clampingTextScaler = MediaQuery .textScalerOf (context)
489417 .clamp (maxScaleFactor: 1.5 );
@@ -558,6 +486,80 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
558486 }
559487}
560488
489+ mixin _TypingNotifierMixin on State <_ContentInput >, WidgetsBindingObserver {
490+ @override
491+ void initState () {
492+ super .initState ();
493+ widget.controller.content.addListener (_contentChanged);
494+ widget.controller.contentFocusNode.addListener (_focusChanged);
495+ WidgetsBinding .instance.addObserver (this );
496+ }
497+
498+ @override
499+ void didUpdateWidget (covariant _ContentInput oldWidget) {
500+ super .didUpdateWidget (oldWidget);
501+ if (widget.controller != oldWidget.controller) {
502+ oldWidget.controller.content.removeListener (_contentChanged);
503+ widget.controller.content.addListener (_contentChanged);
504+ oldWidget.controller.contentFocusNode.removeListener (_focusChanged);
505+ widget.controller.contentFocusNode.addListener (_focusChanged);
506+ }
507+ }
508+
509+ @override
510+ void dispose () {
511+ widget.controller.content.removeListener (_contentChanged);
512+ widget.controller.contentFocusNode.removeListener (_focusChanged);
513+ WidgetsBinding .instance.removeObserver (this );
514+ super .dispose ();
515+ }
516+
517+ void _contentChanged () {
518+ final store = PerAccountStoreWidget .of (context);
519+ (widget.controller.content.text.isEmpty)
520+ ? store.typingNotifier.stoppedComposing ()
521+ : store.typingNotifier.keystroke (widget.destination);
522+ }
523+
524+ void _focusChanged () {
525+ if (widget.controller.contentFocusNode.hasFocus) {
526+ // Content input getting focus doesn't necessarily mean that
527+ // the user started typing, so do nothing.
528+ return ;
529+ }
530+ final store = PerAccountStoreWidget .of (context);
531+ store.typingNotifier.stoppedComposing ();
532+ }
533+
534+ @override
535+ void didChangeAppLifecycleState (AppLifecycleState state) {
536+ switch (state) {
537+ case AppLifecycleState .hidden:
538+ case AppLifecycleState .paused:
539+ case AppLifecycleState .detached:
540+ // Transition to either [hidden] or [paused] signals that
541+ // > [the] application is not currently visible to the user, and not
542+ // > responding to user input.
543+ //
544+ // When transitioning to [detached], the compose box can't exist:
545+ // > The application defaults to this state before it initializes, and
546+ // > can be in this state (applicable on Android, iOS, and web) after
547+ // > all views have been detached.
548+ //
549+ // For all these states, we can conclude that the user is not
550+ // composing a message.
551+ final store = PerAccountStoreWidget .of (context);
552+ store.typingNotifier.stoppedComposing ();
553+ case AppLifecycleState .inactive:
554+ // > At least one view of the application is visible, but none have
555+ // > input focus. The application is otherwise running normally.
556+ // For example, we expect this state when the user is selecting a file
557+ // to upload.
558+ case AppLifecycleState .resumed:
559+ }
560+ }
561+ }
562+
561563/// The content input for _StreamComposeBox.
562564class _StreamContentInput extends StatefulWidget {
563565 const _StreamContentInput ({required this .narrow, required this .controller});
0 commit comments