maestro cases
This commit is contained in:
@@ -36,8 +36,22 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
);
|
||||
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
|
||||
|
||||
/// Hidden field for E2E: Maestro inputText sends full OTP in one call;
|
||||
/// the 6 visible boxes have maxLength:1 and would truncate.
|
||||
late final TextEditingController _hiddenController;
|
||||
late final FocusNode _hiddenFocusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hiddenController = TextEditingController();
|
||||
_hiddenFocusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hiddenController.dispose();
|
||||
_hiddenFocusNode.dispose();
|
||||
for (final TextEditingController controller in _controllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
@@ -47,6 +61,22 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Distributes full OTP from hidden field to the 6 visible boxes and notifies Bloc.
|
||||
void _syncFromHidden(BuildContext context, String value) {
|
||||
final String raw = value.replaceAll(RegExp(r'\D'), '');
|
||||
final String digits = raw.length > 6 ? raw.substring(0, 6) : raw;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final String digit = i < digits.length ? digits[i] : '';
|
||||
if (_controllers[i].text != digit) {
|
||||
_controllers[i].text = digit;
|
||||
}
|
||||
}
|
||||
BlocProvider.of<AuthBloc>(context).add(AuthOtpUpdated(digits));
|
||||
if (digits.length == 6) {
|
||||
widget.onCompleted(digits);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper getter to compute the current OTP code from all controllers.
|
||||
String get _otpCode => _controllers.map((TextEditingController c) => c.text).join();
|
||||
|
||||
@@ -70,57 +100,79 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double boxWidth = 45;
|
||||
const double boxHeight = 56;
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(6, (int index) {
|
||||
final TextField field = TextField(
|
||||
controller: _controllers[index],
|
||||
focusNode: _focusNodes[index],
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
maxLength: 1,
|
||||
style: UiTypography.headline3m,
|
||||
decoration: InputDecoration(
|
||||
counterText: '',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: widget.error.isNotEmpty
|
||||
? UiColors.textError
|
||||
: (_controllers[index].text.isNotEmpty
|
||||
? UiColors.primary
|
||||
: UiColors.border),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
borderSide: BorderSide(
|
||||
color: widget.error.isNotEmpty
|
||||
? UiColors.textError
|
||||
: (_controllers[index].text.isNotEmpty
|
||||
? UiColors.primary
|
||||
: UiColors.border),
|
||||
width: 2,
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: boxHeight,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(6, (int index) {
|
||||
final TextField field = TextField(
|
||||
controller: _controllers[index],
|
||||
focusNode: _focusNodes[index],
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
maxLength: 1,
|
||||
style: UiTypography.headline3m,
|
||||
decoration: InputDecoration(
|
||||
counterText: '',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: widget.error.isNotEmpty
|
||||
? UiColors.textError
|
||||
: (_controllers[index].text.isNotEmpty
|
||||
? UiColors.primary
|
||||
: UiColors.border),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
borderSide: BorderSide(
|
||||
color: widget.error.isNotEmpty
|
||||
? UiColors.textError
|
||||
: (_controllers[index].text.isNotEmpty
|
||||
? UiColors.primary
|
||||
: UiColors.border),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (String value) =>
|
||||
_onChanged(context: context, index: index, value: value),
|
||||
);
|
||||
return SizedBox(width: boxWidth, height: boxHeight, child: field);
|
||||
}),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Semantics(
|
||||
identifier: 'staff_otp_input',
|
||||
container: false,
|
||||
child: Opacity(
|
||||
opacity: 0.01,
|
||||
child: TextField(
|
||||
controller: _hiddenController,
|
||||
focusNode: _hiddenFocusNode,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(6),
|
||||
],
|
||||
maxLength: 6,
|
||||
onChanged: (String value) =>
|
||||
_syncFromHidden(context, value),
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (String value) =>
|
||||
_onChanged(context: context, index: index, value: value),
|
||||
);
|
||||
return SizedBox(
|
||||
width: 45,
|
||||
height: 56,
|
||||
child: index == 0
|
||||
? Semantics(
|
||||
identifier: 'staff_otp_input',
|
||||
child: field,
|
||||
)
|
||||
: field,
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.error.isNotEmpty)
|
||||
Padding(
|
||||
|
||||
@@ -84,6 +84,7 @@ class _PhoneInputFormFieldState extends State<PhoneInputFormField> {
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
identifier: 'staff_phone_input',
|
||||
container: false, // Merge with TextField so tap/input reach the actual field
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
keyboardType: TextInputType.phone,
|
||||
|
||||
@@ -152,9 +152,14 @@ class DocumentCard extends StatelessWidget {
|
||||
|
||||
Widget _buildActionButton(DocumentStatus status) {
|
||||
final bool isVerified = status == DocumentStatus.verified;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
return Semantics(
|
||||
identifier: 'staff_document_upload',
|
||||
label: isVerified
|
||||
? t.staff_documents.card.view
|
||||
: t.staff_documents.card.upload,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Row(
|
||||
@@ -174,6 +179,7 @@ class DocumentCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user