cupertino_context_menu_plus 1.0.2 copy "cupertino_context_menu_plus: ^1.0.2" to clipboard
cupertino_context_menu_plus: ^1.0.2 copied to clipboard

An enhanced version of Flutter's CupertinoContextMenu.

example/lib/main.dart

import 'package:flutter/cupertino.dart';

import 'widgets/chat_message.dart';
import 'widgets/composer_bar.dart';
import 'widgets/draggable_list_view.dart';

void main() => runApp(const ContextMenuApp());

class ContextMenuApp extends StatefulWidget {
  const ContextMenuApp({super.key});

  @override
  State<ContextMenuApp> createState() => _ContextMenuAppState();
}

class _ContextMenuAppState extends State<ContextMenuApp> {
  Brightness _brightness = Brightness.light;

  void _toggleBrightness(Brightness brightness) {
    setState(() {
      _brightness = brightness;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: CupertinoThemeData(brightness: _brightness),
      home: ChatExample(
        brightness: _brightness,
        onBrightnessChanged: _toggleBrightness,
      ),
    );
  }
}

class ChatExample extends StatefulWidget {
  const ChatExample({
    super.key,
    required this.brightness,
    required this.onBrightnessChanged,
  });

  final Brightness brightness;
  final ValueChanged<Brightness> onBrightnessChanged;

  @override
  State<ChatExample> createState() => _ChatExampleState();
}

class _ChatExampleState extends State<ChatExample> {
  final List<_ChatMessageData> _messages = <_ChatMessageData>[];
  final TextEditingController _composerController = TextEditingController();
  final ScrollController _scrollController = ScrollController();
  final FocusNode _composerFocusNode = FocusNode();

  bool get _isDark => widget.brightness == Brightness.dark;

  @override
  void initState() {
    super.initState();
    _messages.addAll(_seedMessages());
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _scrollToBottom(animated: false);
    });
  }

  @override
  void dispose() {
    _composerController.dispose();
    _scrollController.dispose();
    _composerFocusNode.dispose();
    super.dispose();
  }

  List<_ChatMessageData> _seedMessages() {
    final List<_ChatMessageData> seeded = <_ChatMessageData>[];
    final DateTime now = DateTime.now();
    for (int i = 0; i < 32; i += 1) {
      final bool isSent = i.isOdd;
      seeded.add(
        _ChatMessageData(
          id: 'seed_$i',
          isSent: isSent,
          text: switch (i % 6) {
            0 => 'Quick check-in: are we still on for lunch?',
            1 => 'Yep! 12:30 works.',
            2 => "Perfect. I'll grab a table.",
            3 => 'Want me to bring anything?',
            4 => 'Just your appetite.',
            _ => 'Deal. See you soon.',
          },
          timestamp: now.subtract(Duration(minutes: 50 - i)),
        ),
      );
    }
    return seeded;
  }

  String _formatTime(DateTime dt) {
    String two(int n) => n.toString().padLeft(2, '0');
    final int hour12 = dt.hour % 12 == 0 ? 12 : dt.hour % 12;
    final String suffix = dt.hour >= 12 ? 'PM' : 'AM';
    return '$hour12:${two(dt.minute)} $suffix';
  }

  void _scrollToBottom({bool animated = true}) {
    if (!_scrollController.hasClients) {
      return;
    }
    final double target = _scrollController.position.maxScrollExtent;
    if (animated) {
      _scrollController.animateTo(
        target,
        duration: const Duration(milliseconds: 240),
        curve: Curves.easeOut,
      );
    } else {
      _scrollController.jumpTo(target);
    }
  }

  void _sendMessage() {
    final String text = _composerController.text.trim();
    if (text.isEmpty) {
      return;
    }
    setState(() {
      _messages.add(
        _ChatMessageData(
          id: 'msg_${DateTime.now().microsecondsSinceEpoch}',
          isSent: true,
          text: text,
          timestamp: DateTime.now(),
        ),
      );
      _composerController.clear();
    });
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _scrollToBottom();
    });
  }

  @override
  Widget build(BuildContext context) {
    final CupertinoThemeData theme = CupertinoTheme.of(context);
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: const Text('Chat Messages'),
        trailing: SizedBox(
          width: 170,
          child: CupertinoSlidingSegmentedControl<Brightness>(
            groupValue: widget.brightness,
            onValueChanged: (Brightness? value) {
              if (value == null) {
                return;
              }
              widget.onBrightnessChanged(value);
            },
            children: const <Brightness, Widget>{
              Brightness.light: Padding(
                padding: EdgeInsets.symmetric(horizontal: 8),
                child: Text('Light'),
              ),
              Brightness.dark: Padding(
                padding: EdgeInsets.symmetric(horizontal: 8),
                child: Text('Dark'),
              ),
            },
          ),
        ),
      ),
      child: SafeArea(
        child: Column(
          children: <Widget>[
            Expanded(
              child: DraggableListView(
                builder: (
                  BuildContext context,
                  double dragOffset,
                  Animation<double> slideAnimation,
                ) {
                  return ListView.builder(
                    controller: _scrollController,
                    padding: const EdgeInsets.all(16.0),
                    keyboardDismissBehavior:
                        ScrollViewKeyboardDismissBehavior.onDrag,
                    itemCount: _messages.length,
                    itemBuilder: (BuildContext context, int index) {
                      final _ChatMessageData msg = _messages[index];
                      // Find the last received message
                      final int lastReceivedIndex = _messages.lastIndexWhere(
                        (_ChatMessageData m) => !m.isSent,
                      );
                      final bool isLastReceived =
                          !msg.isSent && index == lastReceivedIndex;

                      return Padding(
                        padding: const EdgeInsets.only(bottom: 12),
                        child: ChatMessage(
                          text: msg.text,
                          time: _formatTime(msg.timestamp),
                          isSent: msg.isSent,
                          isDark: _isDark,
                          bubbleColor: msg.isSent
                              ? CupertinoDynamicColor.resolve(
                                  CupertinoColors.activeBlue,
                                  context,
                                )
                              : (_isDark
                                  ? const Color(0xFF2C2C2E)
                                  : CupertinoColors.systemGrey5
                                      .resolveFrom(context)),
                          bubbleTextColor: msg.isSent
                              ? CupertinoColors.white
                              : theme.textTheme.textStyle.color ??
                                  CupertinoColors.label,
                          dragOffset: dragOffset,
                          slideAnimation: slideAnimation,
                          showEmojiChip: isLastReceived,
                        ),
                      );
                    },
                  );
                },
              ),
            ),
            ComposerBar(
              isDark: _isDark,
              controller: _composerController,
              focusNode: _composerFocusNode,
              onSend: _sendMessage,
            ),
          ],
        ),
      ),
    );
  }
}

class _ChatMessageData {
  _ChatMessageData({
    required this.id,
    required this.isSent,
    required this.text,
    required this.timestamp,
  });

  final String id;
  final bool isSent;
  final String text;
  final DateTime timestamp;
}
7
likes
160
points
280
downloads

Publisher

verified publisheresentis.dev

Weekly Downloads

An enhanced version of Flutter's CupertinoContextMenu.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on cupertino_context_menu_plus