monkey_button 1.0.3 copy "monkey_button: ^1.0.3" to clipboard
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.

example/lib/main.dart

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),
            ],
          ),
        ),
      ),
    );
  }
}
1
likes
150
points
199
downloads

Documentation

API reference

Publisher

verified publishermindster.com

Weekly Downloads

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.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on monkey_button