snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
This commit is contained in:
112
form_app/lib/main.dart
Normal file
112
form_app/lib/main.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. 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:go_router/go_router.dart';
|
||||
|
||||
import 'src/autofill.dart';
|
||||
import 'src/form_widgets.dart';
|
||||
import 'src/http/mock_client.dart';
|
||||
import 'src/sign_in_http.dart';
|
||||
import 'src/validation.dart';
|
||||
|
||||
void main() {
|
||||
// Removed setupWindow() call
|
||||
runApp(const FormApp());
|
||||
}
|
||||
|
||||
final demos = [
|
||||
Demo(
|
||||
name: 'Sign in with HTTP',
|
||||
route: 'signin_http',
|
||||
builder: (context) => SignInHttpDemo(
|
||||
// This sample uses a mock HTTP client.
|
||||
httpClient: mockClient,
|
||||
),
|
||||
),
|
||||
Demo(
|
||||
name: 'Autofill',
|
||||
route: 'autofill',
|
||||
builder: (context) => const AutofillDemo(),
|
||||
),
|
||||
Demo(
|
||||
name: 'Form widgets',
|
||||
route: 'form_widgets',
|
||||
builder: (context) => const FormWidgetsDemo(),
|
||||
),
|
||||
Demo(
|
||||
name: 'Validation',
|
||||
route: 'validation',
|
||||
builder: (context) => const FormValidationDemo(),
|
||||
),
|
||||
];
|
||||
|
||||
final router = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const HomePage(),
|
||||
routes: [
|
||||
for (final demo in demos)
|
||||
GoRoute(
|
||||
path: demo.route,
|
||||
builder: (context, state) => demo.builder(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
class FormApp extends StatelessWidget {
|
||||
const FormApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'Form Samples',
|
||||
theme: ThemeData(colorSchemeSeed: Colors.teal),
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Form Samples')),
|
||||
body: ListView(children: [...demos.map((d) => DemoTile(demo: d))]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DemoTile extends StatelessWidget {
|
||||
final Demo? demo;
|
||||
|
||||
const DemoTile({this.demo, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(demo!.name),
|
||||
onTap: () {
|
||||
context.go('/${demo!.route}');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Demo {
|
||||
final String name;
|
||||
final String route;
|
||||
final WidgetBuilder builder;
|
||||
|
||||
const Demo({
|
||||
required this.name,
|
||||
required this.route,
|
||||
required this.builder,
|
||||
});
|
||||
}
|
||||
113
form_app/lib/src/autofill.dart
Normal file
113
form_app/lib/src/autofill.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. 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';
|
||||
|
||||
// Demonstrates how to use autofill hints. The full list of hints is here:
|
||||
// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart
|
||||
class AutofillDemo extends StatefulWidget {
|
||||
const AutofillDemo({super.key});
|
||||
|
||||
@override
|
||||
State<AutofillDemo> createState() => _AutofillDemoState();
|
||||
}
|
||||
|
||||
class _AutofillDemoState extends State<AutofillDemo> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Autofill')),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
children: [
|
||||
...[
|
||||
const Text('This sample demonstrates autofill. '),
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Jane',
|
||||
labelText: 'First Name',
|
||||
),
|
||||
autofillHints: const [AutofillHints.givenName],
|
||||
),
|
||||
TextFormField(
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Doe',
|
||||
labelText: 'Last Name',
|
||||
),
|
||||
autofillHints: const [AutofillHints.familyName],
|
||||
),
|
||||
const TextField(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'foo@example.com',
|
||||
labelText: 'Email',
|
||||
),
|
||||
autofillHints: [AutofillHints.email],
|
||||
),
|
||||
const TextField(
|
||||
keyboardType: TextInputType.phone,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: InputDecoration(
|
||||
hintText: '(123) 456-7890',
|
||||
labelText: 'Telephone',
|
||||
),
|
||||
autofillHints: [AutofillHints.telephoneNumber],
|
||||
),
|
||||
const TextField(
|
||||
keyboardType: TextInputType.streetAddress,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: InputDecoration(
|
||||
hintText: '123 4th Ave',
|
||||
labelText: 'Street Address',
|
||||
),
|
||||
autofillHints: [AutofillHints.streetAddressLine1],
|
||||
),
|
||||
const TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: InputDecoration(
|
||||
hintText: '12345',
|
||||
labelText: 'Postal Code',
|
||||
),
|
||||
autofillHints: [AutofillHints.postalCode],
|
||||
),
|
||||
const TextField(
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'United States',
|
||||
labelText: 'Country',
|
||||
),
|
||||
autofillHints: [AutofillHints.countryName],
|
||||
),
|
||||
const TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
hintText: '1',
|
||||
labelText: 'Country Code',
|
||||
),
|
||||
autofillHints: [AutofillHints.countryCode],
|
||||
),
|
||||
].expand(
|
||||
(widget) => [widget, const SizedBox(height: 24)],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
215
form_app/lib/src/form_widgets.dart
Normal file
215
form_app/lib/src/form_widgets.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. 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:intl/intl.dart' as intl;
|
||||
|
||||
class FormWidgetsDemo extends StatefulWidget {
|
||||
const FormWidgetsDemo({super.key});
|
||||
|
||||
@override
|
||||
State<FormWidgetsDemo> createState() => _FormWidgetsDemoState();
|
||||
}
|
||||
|
||||
class _FormWidgetsDemoState extends State<FormWidgetsDemo> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
String title = '';
|
||||
String description = '';
|
||||
DateTime date = DateTime.now();
|
||||
double maxValue = 0;
|
||||
bool? brushedTeeth = false;
|
||||
bool enableFeature = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Form widgets')),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Scrollbar(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Card(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
...[
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
filled: true,
|
||||
hintText: 'Enter a title...',
|
||||
labelText: 'Title',
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
title = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
hintText: 'Enter a description...',
|
||||
labelText: 'Description',
|
||||
),
|
||||
onChanged: (value) {
|
||||
description = value;
|
||||
},
|
||||
maxLines: 5,
|
||||
),
|
||||
_FormDatePicker(
|
||||
date: date,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
date = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Estimated value',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
intl.NumberFormat.currency(
|
||||
symbol: "\$",
|
||||
decimalDigits: 0,
|
||||
).format(maxValue),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
Slider(
|
||||
min: 0,
|
||||
max: 500,
|
||||
divisions: 500,
|
||||
value: maxValue,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
maxValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: brushedTeeth,
|
||||
onChanged: (checked) {
|
||||
setState(() {
|
||||
brushedTeeth = checked;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'Brushed Teeth',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Enable feature',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Switch(
|
||||
value: enableFeature,
|
||||
onChanged: (enabled) {
|
||||
setState(() {
|
||||
enableFeature = enabled;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
].expand(
|
||||
(widget) => [widget, const SizedBox(height: 24)],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FormDatePicker extends StatefulWidget {
|
||||
final DateTime date;
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
|
||||
const _FormDatePicker({required this.date, required this.onChanged});
|
||||
|
||||
@override
|
||||
State<_FormDatePicker> createState() => _FormDatePickerState();
|
||||
}
|
||||
|
||||
class _FormDatePickerState extends State<_FormDatePicker> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text('Date', style: Theme.of(context).textTheme.bodyLarge),
|
||||
Text(
|
||||
intl.DateFormat.yMd().format(widget.date),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Edit'),
|
||||
onPressed: () async {
|
||||
var newDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: widget.date,
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100),
|
||||
);
|
||||
|
||||
// Don't change the date if the date picker returns null.
|
||||
if (newDate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.onChanged(newDate);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
20
form_app/lib/src/http/mock_client.dart
Normal file
20
form_app/lib/src/http/mock_client.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
|
||||
// Set up a mock HTTP client.
|
||||
final http.Client mockClient = MockClient(_mockHandler);
|
||||
|
||||
Future<http.Response> _mockHandler(http.Request request) async {
|
||||
var decodedJson = Map<String, dynamic>.from(
|
||||
json.decode(request.body) as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
if (decodedJson['email'] == 'root' &&
|
||||
decodedJson['password'] == 'password') {
|
||||
return http.Response('', 200);
|
||||
}
|
||||
|
||||
return http.Response('', 401);
|
||||
}
|
||||
111
form_app/lib/src/sign_in_http.dart
Normal file
111
form_app/lib/src/sign_in_http.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'sign_in_http.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class FormData {
|
||||
String? email;
|
||||
String? password;
|
||||
|
||||
FormData({this.email, this.password});
|
||||
|
||||
factory FormData.fromJson(Map<String, dynamic> json) =>
|
||||
_$FormDataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$FormDataToJson(this);
|
||||
}
|
||||
|
||||
class SignInHttpDemo extends StatefulWidget {
|
||||
final http.Client? httpClient;
|
||||
|
||||
const SignInHttpDemo({this.httpClient, super.key});
|
||||
|
||||
@override
|
||||
State<SignInHttpDemo> createState() => _SignInHttpDemoState();
|
||||
}
|
||||
|
||||
class _SignInHttpDemoState extends State<SignInHttpDemo> {
|
||||
FormData formData = FormData();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Sign in Form')),
|
||||
body: Form(
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
...[
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
decoration: const InputDecoration(
|
||||
filled: true,
|
||||
hintText: 'Your email address',
|
||||
labelText: 'Email',
|
||||
),
|
||||
onChanged: (value) {
|
||||
formData.email = value;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
filled: true,
|
||||
labelText: 'Password',
|
||||
),
|
||||
obscureText: true,
|
||||
onChanged: (value) {
|
||||
formData.password = value;
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () async {
|
||||
// Use a JSON encoded string to send
|
||||
var result = await widget.httpClient!.post(
|
||||
Uri.parse('https://example.com/signin'),
|
||||
body: json.encode(formData.toJson()),
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
|
||||
_showDialog(switch (result.statusCode) {
|
||||
200 => 'Successfully signed in.',
|
||||
401 => 'Unable to sign in.',
|
||||
_ => 'Something went wrong. Please try again.',
|
||||
});
|
||||
},
|
||||
),
|
||||
].expand((widget) => [widget, const SizedBox(height: 24)]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDialog(String message) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
form_app/lib/src/sign_in_http.g.dart
Normal file
19
form_app/lib/src/sign_in_http.g.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sign_in_http.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FormData _$FormDataFromJson(Map<String, dynamic> json) {
|
||||
return FormData(
|
||||
email: json['email'] as String?,
|
||||
password: json['password'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$FormDataToJson(FormData instance) => <String, dynamic>{
|
||||
'email': instance.email,
|
||||
'password': instance.password,
|
||||
};
|
||||
167
form_app/lib/src/validation.dart
Normal file
167
form_app/lib/src/validation.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
||||
// for details. 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:english_words/english_words.dart' as english_words;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FormValidationDemo extends StatefulWidget {
|
||||
const FormValidationDemo({super.key});
|
||||
|
||||
@override
|
||||
State<FormValidationDemo> createState() => _FormValidationDemoState();
|
||||
}
|
||||
|
||||
class _FormValidationDemoState extends State<FormValidationDemo> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
String? adjective;
|
||||
String? noun;
|
||||
bool? agreedToTerms = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('📖 Story Generator'),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextButton(
|
||||
child: const Text('Submit'),
|
||||
onPressed: () {
|
||||
// Validate the form by getting the FormState from the GlobalKey
|
||||
// and calling validate() on it.
|
||||
var valid = _formKey.currentState!.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Your story'),
|
||||
content: Text('The $adjective developer saw a $noun'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Done'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
// A text field that validates that the text is an adjective.
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter an adjective.';
|
||||
}
|
||||
if (english_words.adjectives.contains(value)) {
|
||||
return null;
|
||||
}
|
||||
return 'Not a valid adjective.';
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
filled: true,
|
||||
hintText: 'e.g. quick, beautiful, interesting',
|
||||
labelText: 'Enter an adjective',
|
||||
),
|
||||
onChanged: (value) {
|
||||
adjective = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// A text field that validates that the text is a noun.
|
||||
TextFormField(
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter a noun.';
|
||||
}
|
||||
if (english_words.nouns.contains(value)) {
|
||||
return null;
|
||||
}
|
||||
return 'Not a valid noun.';
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
filled: true,
|
||||
hintText: 'i.e. a person, place or thing',
|
||||
labelText: 'Enter a noun',
|
||||
),
|
||||
onChanged: (value) {
|
||||
noun = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// A custom form field that requires the user to check a
|
||||
// checkbox.
|
||||
FormField<bool>(
|
||||
initialValue: false,
|
||||
validator: (value) {
|
||||
if (value == false) {
|
||||
return 'You must agree to the terms of service.';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (formFieldState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: agreedToTerms,
|
||||
onChanged: (value) {
|
||||
// When the value of the checkbox changes,
|
||||
// update the FormFieldState so the form is
|
||||
// re-validated.
|
||||
formFieldState.didChange(value);
|
||||
setState(() {
|
||||
agreedToTerms = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'I agree to the terms of service.',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!formFieldState.isValid)
|
||||
Text(
|
||||
formFieldState.errorText ?? "",
|
||||
style: Theme.of(context).textTheme.bodySmall!
|
||||
.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user