Flutter简易聊天插件simple_chat的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter简易聊天插件simple_chat的使用

Simple Chat 是一个简单的UI解决方案,用于快速集成IM聊天和AI机器人聊天。

支持自定义消息单元、消息分组、图像预览等功能。

支持平台

  • iOS
  • Android

截图

| 截图1 | 截图2 |

基本用法

初始化控制器与视图

ChatActionHandler 用于处理发送消息、用户头像点击、图像预览缩略图点击等事件。 ChatConfig 是可选的,你可以自定义输入框提示文本、图像最大数量等。

final controller = ChatController(
    config: ChatConfig(
      inputBoxHintText: 'Type a message...',
    ),
    actionHandler: ChatActionHandler(
      onSendMessage: (output) {},
    ),
);

ChatView(
    controller: controller,
    theme: ChatThemeData(
        dark: coloredThemeData,
        light: coloredThemeData,
    ),
)

配置用户

用户通过 idnameavatarUrlisCurrentUser 标志来配置。

  • id 用于标识用户,并在相应的用户头像下显示消息。
  • name 用于在聊天界面中显示。
  • avatarUrl 用于显示用户头像。
  • isCurrentUser 用于标识当前用户。
await controller.store.addUsers(users: [
    ModelUser(
        id: '1',
        name: 'Lawrence',
        avatarUrl: 'https://example.com/avatar/1.png',
        isCurrentUser: true,
    ),
    ModelUser(
        id: '2',
        name: 'Ciel',
        avatarUrl: 'https://example.com/avatar/2.png',
        isCurrentUser: false,
    ),
]);

添加消息

  • isInitial 用于标识消息是否为历史消息,并确定消息是否带动画显示。
  • userId 用于标识发送消息的用户。
  • sequence 用于确定消息顺序。
  • displayDatetime 用于确定消息显示时间。
await controller.store.addMessage(
    isInitial: !withDelay,
    message: ModelTextMessage(
        id: '$i',
        text: 'Hello, how are you?',
        userId: '1',
        sequence: i,
        displayDatetime: DateTime.now(),
    ),
);

加载指示器

阻塞并显示加载指示器

final controller = ChatController(
    config: ChatConfig(
        loadingIndicatorType: LoadingIndicatorType.sendBtnLoading,
    ),
);

非阻塞并显示回复生成器

await controller.store.showReplyGeneratingIndicator();
await Future.delayed(const Duration(seconds: 3));
await controller.store.hideReplyGeneratingIndicator();

未读指示器

带有未读计数的指示器

final controller = ChatController(
    config: ChatConfig(
        showUnreadCount: true,
    ),
    ...
);

不带有未读计数的指示器

final controller = ChatController(
    config: ChatConfig(
        showUnreadCount: false,
    ),
    ...
);

消息状态

对于每条消息,我们还可以设置状态,如 sendingfailed to send

await controller.store.updateSendStatus(
    messageId: messageId,
    status: ModelBaseMessageStatus.sending,
);
await controller.store.updateSendStatus(
    messageId: messageId,
    status: ModelBaseMessageStatus.failedToSend,
);

自定义

添加自定义的消息单元UI

定义自定义的消息模型

如果一个用户连续发送多条消息且没有中断,这些消息通常会被组合在一起。如果你想将它们分成不同的消息单元,可以将 forceNewBlock 设置为 true

class CustomMessage extends ModelBaseMessage {
  [@override](/user/override)
  final String id;

  [@override](/user/override)
  final String userId;

  [@override](/user/override)
  final int sequence;

  [@override](/user/override)
  final DateTime displayDatetime;

  [@override](/user/override)
  final bool forceNewBlock;

  final String data;

  CustomMessage({
    required this.id,
    required this.userId,
    required this.sequence,
    required this.displayDatetime,
    required this.forceNewBlock,
    required this.data,
  });
}

定义自定义的消息单元UI

你可以使用 MessageBubble 包裹你的自定义消息单元UI。

class CustomMessageCell extends StatelessWidget {
  final ModelLoadingIndicatorMessage message;
  final bool isMessageFromCurrentUser;
  CustomMessageCell({
    super.key,
    required this.message,
    required this.isMessageFromCurrentUser,
  });

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MessageBubble(
      isCurrentUser: isMessageFromCurrentUser,
      padding: const EdgeInsets.all(12),
      ...
    );
  }
}

注册自定义的消息单元UI

注册后,每当添加一条具有自定义消息模型的消息时,注册的UI将用于显示该消息。

controller.viewFactory.register<CustomMessage>(
    (BuildContext context, {
    required CustomMessage message,
    required bool isMessageFromCurrentUser,
}) =>
    CustomMessageCell(
        message: message,
        isMessageFromCurrentUser: isMessageFromCurrentUser,
    ),
);

示例代码

以下是完整的示例代码:

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

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](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({
    super.key,
  });
  [@override](/user/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](/user/override)
  void initState() {
    super.initState();
    controller = ChatController(
      config: ChatConfig(
        loadingIndicatorType: LoadingIndicatorType.noBlocking,
        showUnreadCount: true,
        imageMaxCount: 1,
      ),
      actionHandler: ChatActionHandler(
        onSendMessage: _handleSendingMessage,
        // onSendMessage: _handleSendingMessageWithStatus,
      ),
    );
    setupTests();
  }

  [@override](/user/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) {
      // 在实际应用中,通常最好先将图片上传到服务器
      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~',
      ),
    );
  }

  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; // 随机长度在10到59之间
      await controller.store.addMessage(
        isInitial: !withDelay,
        message: ModelTextMessage(
          id: const Uuid().v4(),
          text: generateRandomText(textLength),
          userId: userId,
          sequence: sequence++,
          displayDatetime: DateTime.now(),
        ),
      );
    }
  }

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

  [@override](/user/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;
              }
            },
            itemBuilder: (BuildContext context) => [
              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'),
                ),
              ),
            ],
          ),
        ],
      ),
      body: ChatView(
        controller: controller,
        theme: ChatThemeData(
          dark: coloredThemeData,
          light: coloredThemeData,
        ),
      ),
    );
  }
}

更多关于Flutter简易聊天插件simple_chat的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter简易聊天插件simple_chat的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,我可以为你提供一个关于如何使用Flutter中的simple_chat插件的代码示例。simple_chat是一个用于创建简易聊天应用的Flutter插件。下面是一个简单的示例,展示如何使用这个插件来创建一个基本的聊天界面。

首先,你需要在你的pubspec.yaml文件中添加simple_chat依赖:

dependencies:
  flutter:
    sdk: flutter
  simple_chat: ^x.y.z  # 请替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

接下来,你可以在你的Flutter应用中按照以下步骤使用simple_chat插件:

  1. 导入必要的包
import 'package:flutter/material.dart';
import 'package:simple_chat/simple_chat.dart';
  1. 创建聊天页面
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Simple Chat Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChatScreen(),
    );
  }
}

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  // 假设我们有一个简单的消息列表
  final List<ChatMessage> messages = [
    ChatMessage(
      user: 'User1',
      text: 'Hello!',
      timestamp: DateTime.now().subtract(Duration(minutes: 5)),
      isMe: true,
    ),
    ChatMessage(
      user: 'User2',
      text: 'Hi there!',
      timestamp: DateTime.now().subtract(Duration(minutes: 4)),
      isMe: false,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Simple Chat'),
      ),
      body: Chat(
        messages: messages,
        onSend: (String message) {
          // 这里处理发送消息的逻辑
          setState(() {
            messages.add(ChatMessage(
              user: 'Me',
              text: message,
              timestamp: DateTime.now(),
              isMe: true,
            ));
          });
          // 清空输入框
          // 假设你有一个TextField控制器用于输入消息
          // _controller.clear();
        },
        // 你可以自定义用户头像等
        userAvatar: 'https://example.com/avatar.png',
        userName: 'Me',
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 显示输入框(这通常与TextField结合使用,这里为了简单起见省略)
          // _showTextField();
        },
        tooltip: 'Send message',
        child: Icon(Icons.send),
      ),
    );
  }
}

请注意,上述代码省略了一些实际实现细节,例如显示和隐藏文本输入字段的逻辑,以及实际的消息发送机制(通常你会将其与后端服务集成)。此外,ChatMessage类是一个假设的类,你需要根据simple_chat插件的实际API来实现它。

在实际应用中,你可能还需要处理更多细节,比如:

  • 从服务器获取和发送消息。
  • 显示消息的时间戳。
  • 处理消息状态(如发送中、已发送、已读/未读)。
  • 自定义用户头像和昵称显示。

这些功能通常需要根据simple_chat插件的文档和API来实现。由于simple_chat插件的具体API和实现细节可能会随着版本更新而变化,因此请参考该插件的最新文档和示例代码以获取最准确的信息。

回到顶部