-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add widget for delaying errors on text fields (#1043)
Adds a delay to error messages while typing in a text field. Notably, there is no delay when errors are removed. --- UDENG-5681
- Loading branch information
Showing
6 changed files
with
162 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
gui/packages/ubuntupro/lib/pages/widgets/delayed_text_field.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/scheduler.dart'; | ||
import 'package:flutter/services.dart'; | ||
|
||
/// A [TextField] that displays error messages on a delay instead of | ||
/// immediately. | ||
class DelayedTextField extends StatefulWidget { | ||
const DelayedTextField({ | ||
this.autofocus = false, | ||
this.enabled = true, | ||
this.controller, | ||
this.error, | ||
this.errorText, | ||
this.hintText, | ||
this.inputFormatters, | ||
this.label, | ||
this.onChanged, | ||
this.onSubmitted, | ||
super.key, | ||
}); | ||
|
||
final bool autofocus; | ||
final TextEditingController? controller; | ||
final bool enabled; | ||
final Widget? error; | ||
final String? errorText; | ||
final String? hintText; | ||
final List<TextInputFormatter>? inputFormatters; | ||
final Widget? label; | ||
final void Function(String)? onChanged; | ||
final void Function(String)? onSubmitted; | ||
|
||
@override | ||
State<DelayedTextField> createState() => _DelayedTextField(); | ||
} | ||
|
||
class _DelayedTextField extends State<DelayedTextField> | ||
with SingleTickerProviderStateMixin { | ||
late TimerNotifier debouncer; | ||
|
||
bool showError = false; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
debouncer = TimerNotifier(vsync: this, duration: Durations.medium4); | ||
debouncer.addListener(() { | ||
showError = mounted && widget.error != null || widget.errorText != null; | ||
}); | ||
} | ||
|
||
@override | ||
void dispose() { | ||
debouncer.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ListenableBuilder( | ||
listenable: debouncer, | ||
builder: (context, _) { | ||
return TextField( | ||
controller: widget.controller, | ||
autofocus: widget.autofocus, | ||
inputFormatters: widget.inputFormatters, | ||
onChanged: (value) { | ||
widget.onChanged?.call(value); | ||
debouncer.stop(); | ||
debouncer.resume(); | ||
}, | ||
onSubmitted: widget.onSubmitted, | ||
decoration: InputDecoration( | ||
error: showError ? widget.error : null, | ||
errorText: showError ? widget.errorText : null, | ||
label: widget.label, | ||
), | ||
); | ||
}, | ||
); | ||
} | ||
} | ||
|
||
/// A [ChangeNotifier] that notifies when the provided duration elapses. | ||
class TimerNotifier extends ChangeNotifier { | ||
TimerNotifier({required this.duration, required this.vsync}) | ||
: assert(duration > Duration.zero, 'Duration must be greater than zero') { | ||
_ticker = vsync.createTicker(_onTick); | ||
} | ||
|
||
final TickerProvider vsync; | ||
final Duration duration; | ||
|
||
Ticker? _ticker; | ||
|
||
@override | ||
void dispose() { | ||
_ticker?.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
/// Stops the timer. | ||
void stop() { | ||
_ticker?.stop(); | ||
} | ||
|
||
/// Resumes the timer from the last elapsed time. | ||
void resume() { | ||
_ticker?.start(); | ||
} | ||
|
||
/// Callback executed on each tick. | ||
void _onTick(Duration elapsed) { | ||
if (elapsed >= duration) { | ||
_ticker?.stop(); | ||
scheduleMicrotask(notifyListeners); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters