Skip to content

Commit 66851b0

Browse files
committed
compose [nfc]: Extract typing-notifier-state logic into a mixin
1 parent 2117fe1 commit 66851b0

File tree

1 file changed

+75
-73
lines changed

1 file changed

+75
-73
lines changed

lib/widgets/compose_box.dart

+75-73
Original file line numberDiff line numberDiff line change
@@ -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.
544546
class _StreamContentInput extends StatefulWidget {
545547
const _StreamContentInput({required this.narrow, required this.controller});

0 commit comments

Comments
 (0)