monkey_button 1.0.3
monkey_button: ^1.0.3 copied to clipboard
An interactive, animated Floating Action Button (FAB) canvas overlay styled like a cute monkey or high-tech robot that runs, jumps, waves, sits, and climbs walls dynamically.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:monkey_button/monkey_button.dart';
void main() {
runApp(const MonkeyApp());
}
class MonkeyApp extends StatelessWidget {
const MonkeyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Interactive Companion Canvas',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF8B5E51), // Warm cocoa/banana brown tone
brightness: Brightness.light,
),
),
home: const DashboardPage(),
);
}
}
class ActiveCompanion {
final String id;
final String name;
final FloatingCharacter character;
double scale;
ActiveCompanion({
required this.id,
required this.name,
required this.character,
this.scale = 1.0,
});
}
class DashboardPage extends StatefulWidget {
const DashboardPage({super.key});
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
final List<String> _activityLogs = [];
final List<ActiveCompanion> _companions = [];
int _bananaInteractions = 0;
int _gymnasticTricks = 0;
final ScrollController _scrollController = ScrollController();
final math.Random _random = math.Random();
// Lists of cute names for random generations
final List<String> _monkeyNames = ['Chippy', 'Momo', 'Peanut', 'Bubbles', 'Coco', 'Kiki', 'Kong', 'Cheeky'];
final List<String> _robotNames = ['Sparky', 'Gearz', 'Pixel', 'Volt', 'Rusty', 'Cyber', 'Widget', 'Gizmo'];
@override
void initState() {
super.initState();
// Start with one default Monkey and one default Robot
_companions.add(ActiveCompanion(
id: 'default_monkey',
name: 'Chippy the Monkey',
character: FloatingCharacter.monkey,
));
_companions.add(ActiveCompanion(
id: 'default_robot',
name: 'Robo the Robot',
character: FloatingCharacter.robot,
));
}
void _addLog(String message) {
final time = DateTime.now().toLocal().toString().split(' ')[1].substring(0, 8);
setState(() {
_activityLogs.insert(0, '[$time] $message');
});
}
void _addRandomCompanion(FloatingCharacter type) {
setState(() {
final String id = 'comp_${DateTime.now().millisecondsSinceEpoch}_${_random.nextInt(100)}';
final String baseName = type == FloatingCharacter.monkey
? _monkeyNames[_random.nextInt(_monkeyNames.length)]
: _robotNames[_random.nextInt(_robotNames.length)];
final String fullName = '$baseName the ${type == FloatingCharacter.monkey ? 'Monkey' : 'Robot'}';
_companions.add(ActiveCompanion(
id: id,
name: fullName,
character: type,
));
_addLog('๐ Added new companion: $fullName');
});
}
void _removeCompanion(String id) {
setState(() {
final companion = _companions.firstWhere((c) => c.id == id);
_companions.removeWhere((c) => c.id == id);
_addLog('๐๏ธ Removed ${companion.name} from the canvas.');
});
}
void _clearAllCompanions() {
setState(() {
_companions.clear();
_addLog('๐งน Cleared all active companions.');
});
}
List<MonkeyMenuItem> _buildMenuItems(ActiveCompanion companion) {
return [
MonkeyMenuItem(
onTap: () {
setState(() {
_bananaInteractions++;
});
_addLog('๐ Interact: ${companion.name} enjoyed a treat!');
},
child: Tooltip(
message: companion.character == FloatingCharacter.monkey ? 'Feed Banana' : 'Recharge Core',
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.amber[100],
shape: BoxShape.circle,
border: Border.all(color: Colors.amber[700]!, width: 1.5),
),
child: Icon(Icons.restaurant, color: Colors.amber[900], size: 20),
),
),
),
MonkeyMenuItem(
onTap: () {
setState(() {
_gymnasticTricks++;
});
_addLog('๐คธ Gymnastics: ${companion.name} executed a perfect spin trick!');
},
child: Tooltip(
message: 'Perform Jump/Trick',
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.green[100],
shape: BoxShape.circle,
border: Border.all(color: Colors.green[700]!, width: 1.5),
),
child: Icon(Icons.sports_gymnastics, color: Colors.green[900], size: 20),
),
),
),
MonkeyMenuItem(
onTap: () {
_removeCompanion(companion.id);
},
child: Tooltip(
message: 'Remove Companion',
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.red[100],
shape: BoxShape.circle,
border: Border.all(color: Colors.red[700]!, width: 1.5),
),
child: Icon(Icons.delete, color: Colors.red[900], size: 20),
),
),
),
];
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
const Icon(Icons.layers, size: 28),
const SizedBox(width: 12),
Text(
'Multi-Companion Sandbox',
style: TextStyle(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: IconButton(
tooltip: 'Go to Second Screen',
icon: const Icon(Icons.arrow_forward),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondCanvasPage()),
);
},
),
),
],
elevation: 2,
backgroundColor: theme.colorScheme.surfaceContainer,
),
body: CharacterCanvas(
// Lays out character elements dynamically in separate overlays
characters: _companions.map((companion) {
int index = _companions.indexOf(companion);
// Distribute initial positions to avoid overlaps
double initialX = 50.0 + (index * 85.0);
double initialY = 100.0 + ((index % 3) * 110.0);
return CharacterFAB(
key: ValueKey(companion.id),
character: companion.character,
initialX: initialX,
initialY: initialY,
scale: companion.scale,
menuItems: _buildMenuItems(companion),
onWalkStarted: (x, y) {
_addLog('๐ถ ${companion.name} started walking to (${x.toStringAsFixed(0)}, ${y.toStringAsFixed(0)})');
},
onWalkCompleted: () {
_addLog('๐ ${companion.name} reached target coordinate.');
},
onBlink: () {
// blink callback
},
onMenuToggled: (isOpen) {
_addLog(isOpen
? '๐ Open menu for ${companion.name}'
: '๐ Close menu for ${companion.name}');
},
onIdleAction: (action) {
_addLog('๐ค ${companion.name} $action.');
},
);
}).toList(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Intro header card
Card(
elevation: 0,
color: theme.colorScheme.primaryContainer.withOpacity(0.4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: theme.colorScheme.primary.withOpacity(0.2),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Modular Companions Canvas',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onPrimaryContainer,
),
),
const SizedBox(height: 6),
Text(
'Each monkey/robot runs on its own independent physics and behavior scheduler. Double-tap any companion to force walk, or tap to open its individual Speed Dial action menu.',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(0.85),
),
),
],
),
),
],
),
),
),
const SizedBox(height: 16),
// Sandbox configuration & active companions list
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Companion Controller Card
Expanded(
flex: 4,
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Companion Spawner',
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: theme.colorScheme.onPrimary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => _addRandomCompanion(FloatingCharacter.monkey),
icon: const Icon(Icons.add),
label: const Text('Add Monkey'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.secondary,
foregroundColor: theme.colorScheme.onSecondary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => _addRandomCompanion(FloatingCharacter.robot),
icon: const Icon(Icons.add),
label: const Text('Add Robot'),
),
),
],
),
if (_companions.isNotEmpty) ...[
const SizedBox(height: 12),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red[800],
side: BorderSide(color: Colors.red[800]!),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
minimumSize: const Size.fromHeight(40),
),
onPressed: _clearAllCompanions,
icon: const Icon(Icons.clear_all),
label: const Text('Clear All Characters'),
),
],
const SizedBox(height: 16),
Divider(height: 1, color: theme.colorScheme.outlineVariant),
const SizedBox(height: 12),
Text(
'Active Companion List (${_companions.length})',
style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
if (_companions.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Text(
'No companions active. Spawn one above!',
style: theme.textTheme.bodyMedium?.copyWith(color: Colors.grey),
textAlign: TextAlign.center,
),
)
else
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _companions.length,
itemBuilder: (context, index) {
final companion = _companions[index];
return Card(
elevation: 0,
color: theme.colorScheme.surfaceContainerLow,
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: companion.character == FloatingCharacter.monkey
? const Color(0xFFF3D2C1)
: const Color(0xFFECEFF1),
child: Icon(
companion.character == FloatingCharacter.monkey
? Icons.face_retouching_natural
: Icons.smart_toy,
color: companion.character == FloatingCharacter.monkey
? const Color(0xFF6E473B)
: const Color(0xFF455A64),
),
),
title: Text(
companion.name,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
),
trailing: IconButton(
icon: const Icon(Icons.close, color: Colors.grey, size: 18),
onPressed: () => _removeCompanion(companion.id),
),
),
Row(
children: [
const Icon(Icons.photo_size_select_large, size: 16, color: Colors.grey),
const SizedBox(width: 8),
const Text('Size: ', style: TextStyle(fontSize: 11, color: Colors.grey)),
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 2.0,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6.0),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 12.0),
),
child: Slider(
min: 0.5,
max: 1.5,
divisions: 10,
value: companion.scale,
onChanged: (val) {
setState(() {
companion.scale = val;
});
},
),
),
),
Text(
'${(companion.scale * 100).toStringAsFixed(0)}%',
style: const TextStyle(fontSize: 11, color: Colors.grey, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
],
),
],
),
),
);
},
),
],
),
),
),
),
const SizedBox(width: 16),
// Sandbox Stats Card
Expanded(
flex: 3,
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Interaction Stats',
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildStatItem(
context,
icon: Icons.restaurant,
label: 'Fed Treats',
value: '$_bananaInteractions',
color: Colors.amber[800]!,
),
const SizedBox(height: 12),
_buildStatItem(
context,
icon: Icons.sports_gymnastics,
label: 'Gymnastic Flips',
value: '$_gymnasticTricks',
color: Colors.green[800]!,
),
const SizedBox(height: 12),
_buildStatItem(
context,
icon: Icons.people_outline,
label: 'Population',
value: '${_companions.length}',
color: Colors.blue[800]!,
),
],
),
),
),
),
],
),
const SizedBox(height: 16),
// Activity logs card
Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Companion Activity Log',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
),
),
if (_activityLogs.isNotEmpty)
TextButton.icon(
onPressed: () {
setState(() {
_activityLogs.clear();
});
},
icon: const Icon(Icons.clear_all, size: 16),
label: const Text('Clear Log'),
),
],
),
const SizedBox(height: 12),
Container(
height: 250,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: theme.colorScheme.outlineVariant.withOpacity(0.5),
width: 1,
),
),
child: _activityLogs.isEmpty
? const Center(
child: Text(
'Logs will populate here as companions act...',
style: TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
itemCount: _activityLogs.length,
itemBuilder: (context, index) {
final log = _activityLogs[index];
final isMovement = log.contains('walking') || log.contains('reached') || log.contains('coordinate');
final isInteraction = log.contains('Interact') || log.contains('Gymnastics');
Color textColor = theme.colorScheme.onSurface;
if (isMovement) textColor = Colors.blue[800]!;
if (isInteraction) textColor = Colors.amber[900]!;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(
log,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 12,
fontWeight: FontWeight.w500,
color: textColor,
),
),
);
},
),
),
],
),
),
),
],
),
),
),
);
}
Widget _buildStatItem(
BuildContext context, {
required IconData icon,
required String label,
required String value,
required Color color,
}) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12.0),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.2), width: 1),
),
child: Row(
children: [
Icon(icon, color: color, size: 24),
const SizedBox(width: 12),
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
}
class _MenuOption {
final IconData icon;
final String label;
final Color color;
const _MenuOption(this.icon, this.label, this.color);
}
class SecondCanvasPage extends StatefulWidget {
const SecondCanvasPage({super.key});
@override
State<SecondCanvasPage> createState() => _SecondCanvasPageState();
}
class _SecondCanvasPageState extends State<SecondCanvasPage> {
static const List<_MenuOption> _menuPool = [
_MenuOption(Icons.celebration, 'Party Time', Colors.purple),
_MenuOption(Icons.music_note, 'Play Melody', Colors.blue),
_MenuOption(Icons.pets, 'Feed Peanut', Colors.orange),
_MenuOption(Icons.star, 'Make Wish', Colors.amber),
_MenuOption(Icons.lightbulb, 'Brainstorm', Colors.orange),
_MenuOption(Icons.favorite, 'Send Heart', Colors.red),
_MenuOption(Icons.wb_sunny, 'Soak Sun', Colors.orangeAccent),
_MenuOption(Icons.ac_unit, 'Stay Cool', Colors.cyan),
_MenuOption(Icons.auto_awesome, 'Magic Trick', Colors.indigo),
_MenuOption(Icons.explore, 'Find Treasure', Colors.teal),
];
final math.Random _random = math.Random();
late _MenuOption _randomHeaderOption;
late _MenuOption _monkeyRandomAction;
late _MenuOption _robotRandomAction;
@override
void initState() {
super.initState();
_rollRandomActions();
}
void _rollRandomActions() {
setState(() {
_randomHeaderOption = _menuPool[_random.nextInt(_menuPool.length)];
_monkeyRandomAction = _menuPool[_random.nextInt(_menuPool.length)];
_robotRandomAction = _menuPool[_random.nextInt(_menuPool.length)];
});
}
void _showActionFeedback(String characterName, String actionLabel) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('โจ $characterName performed: $actionLabel!'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final monkeyMenuItems = [
MonkeyMenuItem(
onTap: () => _showActionFeedback('Chippy the Monkey', _monkeyRandomAction.label),
child: Tooltip(
message: _monkeyRandomAction.label,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: _monkeyRandomAction.color.withOpacity(0.15),
shape: BoxShape.circle,
border: Border.all(color: _monkeyRandomAction.color, width: 1.5),
),
child: Icon(_monkeyRandomAction.icon, color: _monkeyRandomAction.color, size: 20),
),
),
),
MonkeyMenuItem(
onTap: () {
setState(() {
_monkeyRandomAction = _menuPool[_random.nextInt(_menuPool.length)];
});
},
child: Tooltip(
message: 'Roll New Menu',
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
border: Border.all(color: Colors.grey[700]!, width: 1.5),
),
child: Icon(Icons.refresh, color: Colors.grey[850], size: 20),
),
),
),
];
final robotMenuItems = [
MonkeyMenuItem(
onTap: () => _showActionFeedback('Robo the Robot', _robotRandomAction.label),
child: Tooltip(
message: _robotRandomAction.label,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: _robotRandomAction.color.withOpacity(0.15),
shape: BoxShape.circle,
border: Border.all(color: _robotRandomAction.color, width: 1.5),
),
child: Icon(_robotRandomAction.icon, color: _robotRandomAction.color, size: 20),
),
),
),
MonkeyMenuItem(
onTap: () {
setState(() {
_robotRandomAction = _menuPool[_random.nextInt(_menuPool.length)];
});
},
child: Tooltip(
message: 'Roll New Menu',
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
border: Border.all(color: Colors.grey[700]!, width: 1.5),
),
child: Icon(Icons.refresh, color: Colors.grey[850], size: 20),
),
),
),
];
return Scaffold(
appBar: AppBar(
title: Row(
children: [
const Icon(Icons.auto_awesome, size: 28),
const SizedBox(width: 12),
Text(
'Secondary Canvas',
style: TextStyle(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
elevation: 2,
backgroundColor: theme.colorScheme.surfaceContainer,
),
body: CharacterCanvas(
characters: [
CharacterFAB(
key: const ValueKey('second_screen_monkey'),
character: FloatingCharacter.monkey,
initialX: 60.0,
initialY: 220.0,
scale: 1.1,
menuItems: monkeyMenuItems,
),
CharacterFAB(
key: const ValueKey('second_screen_robot'),
character: FloatingCharacter.robot,
initialX: 200.0,
initialY: 340.0,
scale: 1.1,
menuItems: robotMenuItems,
),
],
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: BorderSide(
color: theme.colorScheme.secondary.withOpacity(0.2),
width: 1.5,
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome to the Second Canvas',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
),
),
const SizedBox(height: 10),
Text(
'This screen showcases independent character positions and dynamically changing speed dial menus.',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
const SizedBox(height: 20),
Divider(color: theme.colorScheme.outlineVariant),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Random Header Icon & Label:',
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Row(
children: [
CircleAvatar(
backgroundColor: _randomHeaderOption.color.withOpacity(0.2),
child: Icon(_randomHeaderOption.icon, color: _randomHeaderOption.color),
),
const SizedBox(width: 12),
Text(
_randomHeaderOption.label,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: _randomHeaderOption.color,
),
),
],
),
],
),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: _rollRandomActions,
icon: const Icon(Icons.casino),
label: const Text('Roll All'),
),
],
),
],
),
),
),
const Spacer(),
Center(
child: Text(
'Tap either character to open its Speed Dial.',
style: TextStyle(
fontStyle: FontStyle.italic,
color: theme.colorScheme.onSurface.withOpacity(0.5),
),
),
),
const SizedBox(height: 40),
],
),
),
),
);
}
}