Skip to content

Commit 8e9f428

Browse files
committed
compose [nfc]: Extract typing-notifier-state logic into a mixin
1 parent 8cf9c37 commit 8e9f428

File tree

1 file changed

+75
-73
lines changed

1 file changed

+75
-73
lines changed

lib/widgets/compose_box.dart

Lines changed: 75 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
562564
class _StreamContentInput extends StatefulWidget {
563565
const _StreamContentInput({required this.narrow, required this.controller});

0 commit comments

Comments
 (0)