mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 07:18:15 +00:00
358 lines
12 KiB
Dart
358 lines
12 KiB
Dart
// 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(
|
|
automaticallyImplyLeading: false,
|
|
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.hideCurrentSnackBar();
|
|
_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;
|
|
},
|
|
maxLength: 14,
|
|
maxLengthEnforced: false,
|
|
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,
|
|
),
|
|
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
|