Skip to content

Instantly share code, notes, and snippets.

@100xA
Created October 7, 2025 08:36
Show Gist options
  • Select an option

  • Save 100xA/3465b26503f7c66af9018c70a6c2e097 to your computer and use it in GitHub Desktop.

Select an option

Save 100xA/3465b26503f7c66af9018c70a6c2e097 to your computer and use it in GitHub Desktop.
TextFormFields & Drawers
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