| // Copyright 2019 The Flutter team. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/material.dart'; |
| |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/gestures.dart' show DragStartBehavior; |
| |
| import 'package:gallery/l10n/gallery_localizations.dart'; |
| |
| // BEGIN textFieldDemo |
| |
| class TextFieldDemo extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| appBar: AppBar( |
| title: Text(GalleryLocalizations.of(context).demoTextFieldTitle), |
| ), |
| body: TextFormFieldDemo(), |
| ); |
| } |
| } |
| |
| class TextFormFieldDemo extends StatefulWidget { |
| const TextFormFieldDemo({Key key}) : super(key: key); |
| |
| @override |
| TextFormFieldDemoState createState() => TextFormFieldDemoState(); |
| } |
| |
| class PersonData { |
| String name = ''; |
| String phoneNumber = ''; |
| String email = ''; |
| String password = ''; |
| } |
| |
| class PasswordField extends StatefulWidget { |
| const PasswordField({ |
| this.fieldKey, |
| this.hintText, |
| this.labelText, |
| this.helperText, |
| this.onSaved, |
| this.validator, |
| this.onFieldSubmitted, |
| }); |
| |
| final Key fieldKey; |
| final String hintText; |
| final String labelText; |
| final String helperText; |
| final FormFieldSetter<String> onSaved; |
| final FormFieldValidator<String> validator; |
| final ValueChanged<String> onFieldSubmitted; |
| |
| @override |
| _PasswordFieldState createState() => _PasswordFieldState(); |
| } |
| |
| class _PasswordFieldState extends State<PasswordField> { |
| bool _obscureText = true; |
| |
| @override |
| Widget build(BuildContext context) { |
| return TextFormField( |
| key: widget.fieldKey, |
| obscureText: _obscureText, |
| cursorColor: Theme.of(context).cursorColor, |
| maxLength: 8, |
| onSaved: widget.onSaved, |
| validator: widget.validator, |
| onFieldSubmitted: widget.onFieldSubmitted, |
| decoration: InputDecoration( |
| filled: true, |
| hintText: widget.hintText, |
| labelText: widget.labelText, |
| helperText: widget.helperText, |
| suffixIcon: GestureDetector( |
| dragStartBehavior: DragStartBehavior.down, |
| onTap: () { |
| setState(() { |
| _obscureText = !_obscureText; |
| }); |
| }, |
| child: Icon( |
| _obscureText ? Icons.visibility : Icons.visibility_off, |
| semanticLabel: _obscureText |
| ? GalleryLocalizations.of(context) |
| .demoTextFieldShowPasswordLabel |
| : GalleryLocalizations.of(context) |
| .demoTextFieldHidePasswordLabel, |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class TextFormFieldDemoState extends State<TextFormFieldDemo> { |
| final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); |
| |
| PersonData person = PersonData(); |
| |
| void showInSnackBar(String value) { |
| _scaffoldKey.currentState.showSnackBar(SnackBar( |
| content: Text(value), |
| )); |
| } |
| |
| bool _autoValidate = false; |
| |
| final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); |
| final GlobalKey<FormFieldState<String>> _passwordFieldKey = |
| GlobalKey<FormFieldState<String>>(); |
| final _UsNumberTextInputFormatter _phoneNumberFormatter = |
| _UsNumberTextInputFormatter(); |
| |
| void _handleSubmitted() { |
| final form = _formKey.currentState; |
| if (!form.validate()) { |
| _autoValidate = true; // Start validating on every change. |
| showInSnackBar( |
| GalleryLocalizations.of(context).demoTextFieldFormErrors, |
| ); |
| } else { |
| form.save(); |
| showInSnackBar(GalleryLocalizations.of(context) |
| .demoTextFieldNameHasPhoneNumber(person.name, person.phoneNumber)); |
| } |
| } |
| |
| String _validateName(String value) { |
| if (value.isEmpty) { |
| return GalleryLocalizations.of(context).demoTextFieldNameRequired; |
| } |
| final nameExp = RegExp(r'^[A-Za-z ]+$'); |
| if (!nameExp.hasMatch(value)) { |
| return GalleryLocalizations.of(context) |
| .demoTextFieldOnlyAlphabeticalChars; |
| } |
| return null; |
| } |
| |
| String _validatePhoneNumber(String value) { |
| final phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$'); |
| if (!phoneExp.hasMatch(value)) { |
| return GalleryLocalizations.of(context).demoTextFieldEnterUSPhoneNumber; |
| } |
| return null; |
| } |
| |
| String _validatePassword(String value) { |
| final passwordField = _passwordFieldKey.currentState; |
| if (passwordField.value == null || passwordField.value.isEmpty) { |
| return GalleryLocalizations.of(context).demoTextFieldEnterPassword; |
| } |
| if (passwordField.value != value) { |
| return GalleryLocalizations.of(context).demoTextFieldPasswordsDoNotMatch; |
| } |
| return null; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final cursorColor = Theme.of(context).cursorColor; |
| const sizedBoxSpace = SizedBox(height: 24); |
| |
| return Scaffold( |
| key: _scaffoldKey, |
| body: Form( |
| key: _formKey, |
| autovalidate: _autoValidate, |
| child: Scrollbar( |
| child: SingleChildScrollView( |
| dragStartBehavior: DragStartBehavior.down, |
| padding: const EdgeInsets.symmetric(horizontal: 16), |
| child: Column( |
| crossAxisAlignment: CrossAxisAlignment.stretch, |
| children: [ |
| sizedBoxSpace, |
| TextFormField( |
| textCapitalization: TextCapitalization.words, |
| cursorColor: cursorColor, |
| decoration: InputDecoration( |
| filled: true, |
| icon: Icon(Icons.person), |
| hintText: GalleryLocalizations.of(context) |
| .demoTextFieldWhatDoPeopleCallYou, |
| labelText: |
| GalleryLocalizations.of(context).demoTextFieldNameField, |
| ), |
| onSaved: (value) { |
| person.name = value; |
| }, |
| validator: _validateName, |
| ), |
| sizedBoxSpace, |
| TextFormField( |
| cursorColor: cursorColor, |
| decoration: InputDecoration( |
| filled: true, |
| icon: Icon(Icons.phone), |
| hintText: GalleryLocalizations.of(context) |
| .demoTextFieldWhereCanWeReachYou, |
| labelText: GalleryLocalizations.of(context) |
| .demoTextFieldPhoneNumber, |
| prefixText: '+1', |
| ), |
| keyboardType: TextInputType.phone, |
| onSaved: (value) { |
| person.phoneNumber = value; |
| }, |
| validator: _validatePhoneNumber, |
| // TextInputFormatters are applied in sequence. |
| inputFormatters: <TextInputFormatter>[ |
| WhitelistingTextInputFormatter.digitsOnly, |
| // Fit the validating format. |
| _phoneNumberFormatter, |
| ], |
| ), |
| sizedBoxSpace, |
| TextFormField( |
| cursorColor: cursorColor, |
| decoration: InputDecoration( |
| filled: true, |
| icon: Icon(Icons.email), |
| hintText: GalleryLocalizations.of(context) |
| .demoTextFieldYourEmailAddress, |
| labelText: |
| GalleryLocalizations.of(context).demoTextFieldEmail, |
| ), |
| keyboardType: TextInputType.emailAddress, |
| onSaved: (value) { |
| person.email = value; |
| }, |
| ), |
| sizedBoxSpace, |
| TextFormField( |
| cursorColor: cursorColor, |
| decoration: InputDecoration( |
| border: OutlineInputBorder(), |
| hintText: GalleryLocalizations.of(context) |
| .demoTextFieldTellUsAboutYourself, |
| helperText: GalleryLocalizations.of(context) |
| .demoTextFieldKeepItShort, |
| labelText: |
| GalleryLocalizations.of(context).demoTextFieldLifeStory, |
| ), |
| maxLines: 3, |
| ), |
| sizedBoxSpace, |
| TextFormField( |
| cursorColor: cursorColor, |
| keyboardType: TextInputType.number, |
| decoration: InputDecoration( |
| border: OutlineInputBorder(), |
| labelText: |
| GalleryLocalizations.of(context).demoTextFieldSalary, |
| suffixText: |
| GalleryLocalizations.of(context).demoTextFieldUSD, |
| suffixStyle: TextStyle(color: Colors.green), |
| ), |
| maxLines: 1, |
| ), |
| sizedBoxSpace, |
| PasswordField( |
| fieldKey: _passwordFieldKey, |
| helperText: |
| GalleryLocalizations.of(context).demoTextFieldNoMoreThan, |
| labelText: |
| GalleryLocalizations.of(context).demoTextFieldPassword, |
| onFieldSubmitted: (value) { |
| setState(() { |
| person.password = value; |
| }); |
| }, |
| ), |
| sizedBoxSpace, |
| TextFormField( |
| cursorColor: cursorColor, |
| decoration: InputDecoration( |
| filled: true, |
| labelText: GalleryLocalizations.of(context) |
| .demoTextFieldRetypePassword, |
| ), |
| maxLength: 8, |
| obscureText: true, |
| validator: _validatePassword, |
| ), |
| sizedBoxSpace, |
| Center( |
| child: RaisedButton( |
| child: Text( |
| GalleryLocalizations.of(context).demoTextFieldSubmit), |
| onPressed: _handleSubmitted, |
| ), |
| ), |
| sizedBoxSpace, |
| Text( |
| GalleryLocalizations.of(context).demoTextFieldRequiredField, |
| style: Theme.of(context).textTheme.caption, |
| ), |
| sizedBoxSpace, |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| /// Format incoming numeric text to fit the format of (###) ###-#### ## |
| class _UsNumberTextInputFormatter extends TextInputFormatter { |
| @override |
| TextEditingValue formatEditUpdate( |
| TextEditingValue oldValue, |
| TextEditingValue newValue, |
| ) { |
| final newTextLength = newValue.text.length; |
| final newText = StringBuffer(); |
| int selectionIndex = newValue.selection.end; |
| int usedSubstringIndex = 0; |
| if (newTextLength >= 1) { |
| newText.write('('); |
| if (newValue.selection.end >= 1) selectionIndex++; |
| } |
| if (newTextLength >= 4) { |
| newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') '); |
| if (newValue.selection.end >= 3) selectionIndex += 2; |
| } |
| if (newTextLength >= 7) { |
| newText.write(newValue.text.substring(3, usedSubstringIndex = 6) + '-'); |
| if (newValue.selection.end >= 6) selectionIndex++; |
| } |
| if (newTextLength >= 11) { |
| newText.write(newValue.text.substring(6, usedSubstringIndex = 10) + ' '); |
| if (newValue.selection.end >= 10) selectionIndex++; |
| } |
| // Dump the rest. |
| if (newTextLength >= usedSubstringIndex) { |
| newText.write(newValue.text.substring(usedSubstringIndex)); |
| } |
| return TextEditingValue( |
| text: newText.toString(), |
| selection: TextSelection.collapsed(offset: selectionIndex), |
| ); |
| } |
| } |
| |
| // END |