simple_chat 0.2.7 copy "simple_chat: ^0.2.7" to clipboard
simple_chat: ^0.2.7 copied to clipboard

A simple UI solution for quick integration of IM chat & AI bot chat. Supports customised Message Cell, message grouping, image preview and more.

example/lib/main.dart

import 'dart:math'; // Added import for Random

import 'package:example/custom_message.dart';
import 'package:example/custom_message_view.dart';
import 'package:flutter/material.dart';
import 'package:simple_chat/simple_chat.dart';
import 'package:uuid/uuid.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({
    super.key,
  });
  @override
  State<HomePage> createState() {
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  late final ChatController controller;
  var sequence = 0;
  final userId1 = const Uuid().v4();
  final userId2 = const Uuid().v4();
  @override
  void initState() {
    super.initState();
    controller = ChatController(
      config: ChatConfig(
        loadingIndicatorType: LoadingIndicatorType.noBlocking,
        showUnreadCount: true,
        imageMaxCount: 1,
      ),
      actionHandler: ChatActionHandler(
        onSendMessage: _handleSendingMessage,
        // onSendMessage: _handleSendingMessageWithStatus,
      ),
    );
    controller.viewFactory.register<CustomMessage>(
      (
        BuildContext context, {
        required CustomMessage message,
        required bool isMessageFromCurrentUser,
      }) =>
          CustomMessageView(
        message: message,
        isMessageFromCurrentUser: isMessageFromCurrentUser,
      ),
    );
    setupTests();
  }

  @override
  void dispose() {
    super.dispose();
  }

  Future<void> _handleSendingMessage(ChatMessageSendOutput output) async {
    if (output.message.isNotEmpty) {
      await controller.store.addMessage(
        message: ModelTextMessage(
          id: const Uuid().v4(),
          text: output.message,
          userId: userId1,
          sequence: sequence++,
          displayDatetime: DateTime.now(),
        ),
      );
    }
    if (output.imageFiles.isNotEmpty) {
      // in reality, it's usually better to upload images to server first
      await controller.store.addMessage(
        message: ModelImageMessage(
          id: const Uuid().v4(),
          userId: userId1,
          sequence: sequence++,
          displayDatetime: DateTime.now(),
          imageUrls: output.imageFiles.map((e) => e.path).toList(),
        ),
      );
    }
    await controller.store.showReplyGeneratingIndicator();
    await Future.delayed(const Duration(seconds: 3));
    await controller.store.hideReplyGeneratingIndicator();
    await controller.store.addMessage(
      message: ModelTextMessage(
        id: const Uuid().v4(),
        userId: userId2,
        sequence: sequence++,
        displayDatetime: DateTime.now(),
        text: 'Example reply~',
      ),
    );
  }

  // ignore: unused_element
  Future<void> _handleSendingMessageWithStatus(
      ChatMessageSendOutput output) async {
    if (output.message.isNotEmpty) {
      final messageId = const Uuid().v4();
      await controller.store.addMessage(
        message: ModelTextMessage(
          id: messageId,
          text: output.message,
          userId: userId1,
          sequence: sequence++,
          displayDatetime: DateTime.now(),
        ),
      );
      await Future.delayed(const Duration(seconds: 1));
      await controller.store.updateSendStatus(
        messageId: messageId,
        status: ModelBaseMessageStatus.sending,
      );
      await Future.delayed(const Duration(seconds: 1));
      await controller.store.updateSendStatus(
        messageId: messageId,
        status: ModelBaseMessageStatus.failedToSend,
      );
    }
  }

  Future<void> setupTests() async {
    await injectUsers();
  }

  Future<void> injectUsers() async {
    await controller.store.addUsers(users: [
      ModelUser(
        id: userId1,
        name: 'Lawrence',
        avatarUrl:
            'https://lh3.googleusercontent.com/ogw/AF2bZyj1OQs6QwRQMGfY0H5g_VOdijzbC7Ea3XE3Z8eDYTrOZQ=s64-c-mo',
        isCurrentUser: true,
      ),
      ModelUser(
        id: userId2,
        name: 'Ciel',
        avatarUrl:
            'https://media.karousell.com/media/photos/profiles/2018/01/09/imwithye_1515485479.jpg',
        isCurrentUser: false,
      ),
    ]);
  }

  Future<void> injectMessages({required bool withDelay}) async {
    final random = Random();
    final totalCount = withDelay ? 1 : 100;
    for (var i = 0; i < totalCount; i++) {
      if (withDelay) {
        await Future.delayed(const Duration(milliseconds: 1000));
      }
      final userId = random.nextBool() ? userId1 : userId2;
      final textLength =
          random.nextInt(50) + 10; // Random length between 10 and 59
      await controller.store.addMessage(
        isInitial: !withDelay,
        message: ModelTextMessage(
          id: const Uuid().v4(),
          text: generateRandomText(textLength),
          userId: userId,
          sequence: sequence++,
          displayDatetime: DateTime.now(),
        ),
      );
    }
  }

  Future<void> injectCustomMessage() async {
    await controller.store.addMessage(
      message: CustomMessage(
        id: const Uuid().v4(),
        userId: userId1,
        sequence: sequence++,
        displayDatetime: DateTime.now(),
        forceNewBlock: true,
        data: 'this is a custom message',
      ),
    );
  }

  String generateRandomText(int length) {
    const chars = 'abcdefghijklmnopqrstuvwxyz0123456789 ';
    final random = Random();
    return String.fromCharCodes(Iterable.generate(
        length, (_) => chars.codeUnitAt(random.nextInt(chars.length))));
  }

  @override
  Widget build(BuildContext context) {
    final coloredThemeData = ChatColorThemeData();
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
        actions: [
          PopupMenuButton<String>(
            icon: const Icon(Icons.more_vert),
            onSelected: (value) {
              switch (value) {
                case 'scroll_top':
                  controller.chatScrollController.scrollToTop();
                  break;
                case 'scroll_bottom':
                  controller.chatScrollController.scrollToBottom();
                  break;
                case 'inject_messages':
                  injectMessages(withDelay: false);
                  break;
                case 'inject_messages_delay':
                  injectMessages(withDelay: true);
                  break;
                case 'clear_all':
                  controller.store.clearAll();
                  break;
                case 'inject_custom_message':
                  injectCustomMessage();
                  break;
              }
            },
            itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
              const PopupMenuItem<String>(
                value: 'scroll_top',
                child: ListTile(
                  leading: Icon(Icons.arrow_upward),
                  title: Text('Scroll to Top'),
                ),
              ),
              const PopupMenuItem<String>(
                value: 'scroll_bottom',
                child: ListTile(
                  leading: Icon(Icons.arrow_downward),
                  title: Text('Scroll to Bottom'),
                ),
              ),
              const PopupMenuItem<String>(
                value: 'inject_messages',
                child: ListTile(
                  leading: Icon(Icons.message),
                  title: Text('Inject Messages'),
                ),
              ),
              const PopupMenuItem<String>(
                value: 'inject_messages_delay',
                child: ListTile(
                  leading: Icon(Icons.auto_awesome),
                  title: Text('Inject Messages with Delay'),
                ),
              ),
              const PopupMenuItem<String>(
                value: 'clear_all',
                child: ListTile(
                  leading: Icon(Icons.clear),
                  title: Text('Clear All'),
                ),
              ),
              const PopupMenuItem<String>(
                value: 'inject_custom_message',
                child: ListTile(
                  leading: Icon(Icons.message),
                  title: Text('Inject Custom Message'),
                ),
              ),
            ],
          ),
        ],
      ),
      body: ChatView(
        controller: controller,
        theme: ChatThemeData(
          dark: coloredThemeData,
          light: coloredThemeData,
        ),
        toolbar: Container(
          color: Colors.white,
          height: 50,
          padding: const EdgeInsets.symmetric(
            horizontal: 16,
            vertical: 8,
          ),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              CompositedTransformTarget(
                link: layerLink,
                child: ElevatedButton(
                  onPressed: () {
                    _showOverlay(context);
                  },
                  style: ElevatedButton.styleFrom(
                    shape: RoundedRectangleBorder(
                      borderRadius:
                          BorderRadius.circular(12), // Rounded corners
                    ),
                  ),
                  child: const Text("Filter"),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  final layerLink = LayerLink();
  OverlayEntry? overlayEntry;

  void _showOverlay(BuildContext context) {
    if (overlayEntry == null) {
      const overlayHeight = 100.0;
      const overlayWidth = 120.0;
      final overlay = Overlay.of(context);

      overlayEntry = OverlayEntry(builder: (context) {
        return Positioned(
          left: 0,
          top: 0,
          child: CompositedTransformFollower(
            showWhenUnlinked: false,
            link: layerLink,
            offset: const Offset(0, -overlayHeight - 4),
            child: Material(
              color: Colors.transparent,
              child: SizedBox(
                width: overlayWidth,
                height: overlayHeight,
                child: TapRegion(
                  consumeOutsideTaps: true,
                  onTapOutside: (event) => _hideOverlay(),
                  child: Container(
                    height: overlayHeight,
                    width: overlayWidth,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(12),
                      color: Colors.red,
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      });
      overlay.insert(overlayEntry!);
    } else {
      _hideOverlay();
    }
  }

  void _hideOverlay() {
    overlayEntry?.remove();
    overlayEntry = null;
  }
}
17
likes
160
points
188
downloads

Publisher

verified publishertealseed.com

Weekly Downloads

A simple UI solution for quick integration of IM chat & AI bot chat. Supports customised Message Cell, message grouping, image preview and more.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

easy_asset_picker, flutter, flutter_mobx, flutter_svg, image_picker, json_annotation, logger, lottie, mobx, uuid, visibility_detector

More

Packages that depend on simple_chat