Created
October 7, 2025 08:36
-
-
Save 100xA/3465b26503f7c66af9018c70a6c2e097 to your computer and use it in GitHub Desktop.
TextFormFields & Drawers
This file contains hidden or 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
| import 'package:flutter/cupertino.dart'; | |
| import 'package:flutter/material.dart'; | |
| void main() => runApp(const DemoApp()); | |
| class DemoApp extends StatelessWidget { | |
| const DemoApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| title: 'Two Sites: TextFields & Drawers', | |
| debugShowCheckedModeBanner: false, | |
| theme: ThemeData( | |
| useMaterial3: true, | |
| colorSchemeSeed: Colors.red, | |
| ), | |
| home: const RootShell(), | |
| ); | |
| } | |
| } | |
| class RootShell extends StatefulWidget { | |
| const RootShell({super.key}); | |
| @override | |
| State<RootShell> createState() => _RootShellState(); | |
| } | |
| class _RootShellState extends State<RootShell> { | |
| int _index = 0; | |
| final _pages = const [ | |
| _TextFieldsSite(), | |
| _DrawersSite(), | |
| ]; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| body: IndexedStack(index: _index, children: _pages), | |
| bottomNavigationBar: NavigationBar( | |
| selectedIndex: _index, | |
| onDestinationSelected: (i) => setState(() => _index = i), | |
| destinations: const [ | |
| NavigationDestination( | |
| icon: Icon(Icons.text_fields), label: 'TextFields'), | |
| NavigationDestination( | |
| icon: Icon(Icons.view_sidebar), label: 'Drawers'), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class _TextFieldsSite extends StatefulWidget { | |
| const _TextFieldsSite(); | |
| @override | |
| State<_TextFieldsSite> createState() => _TextFieldsSiteState(); | |
| } | |
| class _TextFieldsSiteState extends State<_TextFieldsSite> { | |
| final _formKey = GlobalKey<FormState>(); | |
| final _emailCtrl = TextEditingController(); | |
| final _searchCtrl = TextEditingController(text: 'Initial value Jan'); | |
| final _focusNode = FocusNode(); | |
| bool _obscure = false; | |
| @override | |
| void dispose() { | |
| _emailCtrl.dispose(); | |
| _searchCtrl.dispose(); | |
| _focusNode.dispose(); | |
| super.dispose(); | |
| } | |
| void _validate() { | |
| if (_formKey.currentState!.validate()) { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar(content: Text('Form is valid ✅')), | |
| ); | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: const Text('Site 1: TextFields')), | |
| body: GestureDetector( | |
| onTap: () => FocusScope.of(context).unfocus(), | |
| child: SafeArea( | |
| child: ListView( | |
| padding: const EdgeInsets.all(16), | |
| children: [ | |
| Text('Material TextField (basic)', | |
| style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| const TextField( | |
| decoration: InputDecoration( | |
| labelText: 'Username', | |
| prefixIcon: Icon(Icons.person), | |
| border: OutlineInputBorder(), | |
| ), | |
| ), | |
| const SizedBox(height: 20), | |
| Text('Password (toggle visibility)', | |
| style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| TextField( | |
| obscureText: _obscure, | |
| decoration: InputDecoration( | |
| labelText: 'Password', | |
| prefixIcon: const Icon(Icons.lock), | |
| border: const OutlineInputBorder(), | |
| suffixIcon: IconButton( | |
| icon: Icon( | |
| _obscure ? Icons.visibility : Icons.visibility_off), | |
| onPressed: () { | |
| setState(() { | |
| _obscure = !_obscure; | |
| }); | |
| }, | |
| ), | |
| ), | |
| ), | |
| const SizedBox(height: 20), | |
| Text('With Controller + FocusNode', | |
| style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| TextField( | |
| controller: _searchCtrl, | |
| focusNode: _focusNode, | |
| textInputAction: TextInputAction.search, | |
| decoration: const InputDecoration( | |
| labelText: 'Search', | |
| hintText: 'Type to search…', | |
| prefixIcon: Icon(Icons.search), | |
| border: OutlineInputBorder(), | |
| ), | |
| onChanged: (value) => print(_searchCtrl.text), | |
| onSubmitted: (_) => _focusNode.unfocus(), | |
| ), | |
| const SizedBox(height: 20), | |
| Text('Multiline', style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| const TextField( | |
| maxLines: null, | |
| minLines: 2, | |
| // keyboardType: TextInputType.multiline, | |
| decoration: InputDecoration( | |
| labelText: 'Your message', | |
| alignLabelWithHint: true, | |
| border: OutlineInputBorder(), | |
| ), | |
| ), | |
| const SizedBox(height: 20), | |
| Text('TextFormField + Validation', | |
| style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| Form( | |
| key: _formKey, | |
| child: Column( | |
| children: [ | |
| TextFormField( | |
| controller: _emailCtrl, | |
| decoration: const InputDecoration( | |
| labelText: 'Email', | |
| border: OutlineInputBorder(), | |
| prefixIcon: Icon(Icons.email_outlined), | |
| ), | |
| keyboardType: TextInputType.emailAddress, | |
| ), | |
| const SizedBox(height: 12), | |
| SizedBox( | |
| width: double.infinity, | |
| child: FilledButton( | |
| onPressed: _validate, child: const Text('Validate')), | |
| ), | |
| ], | |
| ), | |
| ), | |
| const SizedBox(height: 20), | |
| Text('CupertinoTextField (iOS style)', | |
| style: Theme.of(context).textTheme.titleMedium), | |
| const SizedBox(height: 8), | |
| const _CupertinoCard(), | |
| const SizedBox(height: 8), | |
| const Text( | |
| 'Tip: You can mix Cupertino inside Material apps.', | |
| style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _CupertinoCard extends StatelessWidget { | |
| const _CupertinoCard(); | |
| @override | |
| Widget build(BuildContext context) { | |
| return DecoratedBox( | |
| decoration: BoxDecoration( | |
| color: Theme.of(context).colorScheme.surfaceContainerHighest, | |
| borderRadius: BorderRadius.all(Radius.circular(12)), | |
| ), | |
| child: const Padding( | |
| padding: EdgeInsets.all(12), | |
| child: CupertinoTextField( | |
| placeholder: 'CupertinoTextField placeholder', | |
| padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), | |
| prefix: Padding( | |
| padding: EdgeInsets.only(left: 6, right: 6), | |
| child: Icon(CupertinoIcons.person), | |
| ), | |
| clearButtonMode: OverlayVisibilityMode.editing, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _DrawersSite extends StatefulWidget { | |
| const _DrawersSite(); | |
| @override | |
| State<_DrawersSite> createState() => _DrawersSiteState(); | |
| } | |
| class _DrawersSiteState extends State<_DrawersSite> { | |
| int _selectedIndex = 0; | |
| static const _destinations = [ | |
| (Icons.home_outlined, Icons.home, 'Home'), | |
| (Icons.star_border, Icons.star, 'Favorites'), | |
| (Icons.settings_outlined, Icons.settings, 'Settings'), | |
| ]; | |
| bool showFavoritesOnly = false; | |
| bool darkTheme = false; | |
| String selectedCategory = 'All'; | |
| @override | |
| Widget build(BuildContext context) { | |
| final width = MediaQuery.sizeOf(context).width; | |
| final height = MediaQuery.sizeOf(context).height; | |
| print(height); | |
| final isCompact = width < 600; | |
| return Scaffold( | |
| drawer: _LeftDrawer( | |
| onSelect: (i) { | |
| setState(() => _selectedIndex = i); | |
| Navigator.pop(context); | |
| }, | |
| ), | |
| endDrawer: Drawer( | |
| child: _RightDrawer( | |
| showFavoritesOnly: showFavoritesOnly, | |
| darkTheme: darkTheme, | |
| selectedCategory: selectedCategory, | |
| onApply: (fav, dark, cat) { | |
| setState(() { | |
| showFavoritesOnly = fav; | |
| darkTheme = dark; | |
| selectedCategory = cat; | |
| }); | |
| Navigator.pop(context); | |
| }, | |
| onReset: () { | |
| setState(() { | |
| showFavoritesOnly = false; | |
| darkTheme = false; | |
| selectedCategory = 'All'; | |
| }); | |
| Navigator.pop(context); | |
| }, | |
| ), | |
| ), | |
| appBar: AppBar( | |
| title: Text('Site 2: ${_destinations[_selectedIndex].$3}'), | |
| leading: Builder( | |
| builder: (ctx) => IconButton( | |
| icon: const Icon(Icons.menu), | |
| tooltip: 'Open left drawer', | |
| onPressed: () => Scaffold.of(ctx).openDrawer(), | |
| ), | |
| ), | |
| actions: [ | |
| Builder( | |
| builder: (ctx) => IconButton( | |
| icon: const Icon(Icons.tune), | |
| tooltip: 'Open filters', | |
| onPressed: () => Scaffold.of(ctx).openEndDrawer(), | |
| ), | |
| ), | |
| ], | |
| ), | |
| body: Row( | |
| children: [ | |
| if (!isCompact) ...[ | |
| ConstrainedBox( | |
| constraints: const BoxConstraints(maxWidth: 320), | |
| child: NavigationDrawer( | |
| selectedIndex: _selectedIndex, | |
| onDestinationSelected: (i) => | |
| setState(() => _selectedIndex = i), | |
| children: [ | |
| const Padding( | |
| padding: EdgeInsets.fromLTRB(12, 16, 16, 10), | |
| child: Text('Navigation', style: TextStyle(fontSize: 12)), | |
| ), | |
| ..._destinations.map( | |
| (d) => NavigationDrawerDestination( | |
| icon: Icon(d.$1), | |
| selectedIcon: Icon(d.$2), | |
| label: Text(d.$3), | |
| ), | |
| ), | |
| const Padding( | |
| padding: EdgeInsets.symmetric(horizontal: 28, vertical: 12), | |
| child: Divider(), | |
| ), | |
| const Padding( | |
| padding: EdgeInsets.fromLTRB(28, 0, 16, 16), | |
| child: Text( | |
| 'Visible inline only on large screens.', | |
| style: | |
| TextStyle(fontSize: 12, fontStyle: FontStyle.italic), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| const VerticalDivider(width: 1), | |
| ], | |
| Expanded( | |
| child: _NavContent( | |
| index: _selectedIndex, | |
| showFavoritesOnly: showFavoritesOnly, | |
| darkTheme: darkTheme, | |
| category: selectedCategory, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class _LeftDrawer extends StatelessWidget { | |
| final ValueChanged<int>? onSelect; | |
| const _LeftDrawer({this.onSelect}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Drawer( | |
| child: SafeArea( | |
| child: ListView( | |
| padding: EdgeInsets.zero, | |
| children: [ | |
| const UserAccountsDrawerHeader( | |
| accountName: Text('Flutter Dev'), | |
| accountEmail: Text('dev@example.com'), | |
| currentAccountPicture: CircleAvatar(child: Icon(Icons.person)), | |
| ), | |
| Divider(), | |
| ListTile( | |
| leading: const Icon(Icons.home), | |
| title: const Text('Home'), | |
| onTap: () => onSelect?.call(0), | |
| ), | |
| ListTile( | |
| leading: const Icon(Icons.star), | |
| title: const Text('Favorites'), | |
| onTap: () => onSelect?.call(1), | |
| ), | |
| const Divider(), | |
| ListTile( | |
| leading: const Icon(Icons.settings), | |
| title: const Text('Settings'), | |
| onTap: () => onSelect?.call(2), | |
| ), | |
| const Divider(), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _RightDrawer extends StatefulWidget { | |
| final bool showFavoritesOnly; | |
| final bool darkTheme; | |
| final String selectedCategory; | |
| final void Function(bool fav, bool dark, String cat) onApply; | |
| final VoidCallback onReset; | |
| const _RightDrawer({ | |
| required this.showFavoritesOnly, | |
| required this.darkTheme, | |
| required this.selectedCategory, | |
| required this.onApply, | |
| required this.onReset, | |
| }); | |
| @override | |
| State<_RightDrawer> createState() => _RightDrawerState(); | |
| } | |
| class _RightDrawerState extends State<_RightDrawer> { | |
| late bool fav; | |
| late bool dark; | |
| late String cat; | |
| final List<String> categories = ['All', 'Tech', 'Design', 'Business']; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| fav = widget.showFavoritesOnly; | |
| dark = widget.darkTheme; | |
| cat = widget.selectedCategory; | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return SafeArea( | |
| child: ListView( | |
| padding: const EdgeInsets.all(16), | |
| children: [ | |
| const Text('Mock Filters', | |
| style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), | |
| const Divider(), | |
| SwitchListTile( | |
| value: fav, | |
| onChanged: (v) => setState(() => fav = v), | |
| title: const Text('Show favorites only'), | |
| ), | |
| SwitchListTile( | |
| value: dark, | |
| onChanged: (v) => setState(() => dark = v), | |
| title: const Text('Enable dark theme'), | |
| ), | |
| const SizedBox(height: 16), | |
| const Text('Category', style: TextStyle(fontWeight: FontWeight.w600)), | |
| const SizedBox(height: 6), | |
| DropdownButton<String>( | |
| value: cat, | |
| isExpanded: true, | |
| items: categories | |
| .map((e) => DropdownMenuItem(value: e, child: Text(e))) | |
| .toList(), | |
| onChanged: (v) => setState(() => cat = v!), | |
| ), | |
| const SizedBox(height: 24), | |
| FilledButton( | |
| onPressed: () => widget.onApply(fav, dark, cat), | |
| child: const Text('Apply Filters'), | |
| ), | |
| const SizedBox(height: 12), | |
| FilledButton.tonal( | |
| onPressed: widget.onReset, | |
| child: const Text('Reset'), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class _NavContent extends StatelessWidget { | |
| final int index; | |
| final bool showFavoritesOnly; | |
| final bool darkTheme; | |
| final String category; | |
| const _NavContent({ | |
| required this.index, | |
| required this.showFavoritesOnly, | |
| required this.darkTheme, | |
| required this.category, | |
| }); | |
| @override | |
| Widget build(BuildContext context) { | |
| final titles = ['Home', 'Favorites', 'Settings']; | |
| final icons = [Icons.home, Icons.star, Icons.settings]; | |
| final theme = Theme.of(context); | |
| return AnimatedContainer( | |
| duration: const Duration(milliseconds: 300), | |
| color: darkTheme ? theme.colorScheme.surfaceContainerHighest : null, | |
| child: Center( | |
| child: Card( | |
| elevation: 2, | |
| margin: const EdgeInsets.all(24), | |
| child: Padding( | |
| padding: const EdgeInsets.all(24), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| children: [ | |
| Icon(icons[index], size: 48), | |
| const SizedBox(height: 12), | |
| Text(titles[index], style: theme.textTheme.headlineSmall), | |
| const SizedBox(height: 8), | |
| Text( | |
| 'Filters active:\n' | |
| 'Favorites only: $showFavoritesOnly\n' | |
| 'Dark theme: $darkTheme\n' | |
| 'Category: $category', | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment