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