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

Twilio Conversation SDK - A Flutter plugin which allows you to build engaging conversational messaging experiences for Android and iOS.

example/lib/main.dart

import 'dart:convert';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:jiffy/jiffy.dart';
import 'package:mime/mime.dart';
import 'package:twilio_conversation_sdk/twilio_conversation_sdk.dart';
import 'package:twilio_conversation_sdk_example/conversation_list.dart';

DateFormat get formatterYYYYMMddTHHMMss => DateFormat('yyyy-MM-ddTHH:mm:ss');

DateFormat get formatterYYYYMMddTHHMMssSSS => DateFormat('yyyy-MM-ddTHH:mm:ss.SSS');

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Twilio Conversation SDK',
      home: Conversation(),
    );
  }
}

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

  @override
  State<Conversation> createState() => _ConversationState();
}

class _ConversationState extends State<Conversation> {
  static var accountSid = '';
  static var apiKey = '';
  static var apiSecret = '';
  static var serviceSid = ''; // Conversation Service SID
  static var identity = 'DevChat';
  static var participantIdentity = 'DevUserTwo';
  static var pushSid = '';
  String? accessToken = "";

  final _twilioConversationSdkPlugin = TwilioConversationSdk();

  //var conversationId = "";
  //var conversationId = "";
  var conversationId = "";
  var conversationName = "";
  List messages = List.empty(growable: true);
  TextEditingController message = TextEditingController();
  final ScrollController _controller = ScrollController();
  final double _height = 100.0;
  String selectedDocPath = "";
  String fileName = "";
  bool isSendMedia = false;
  double _progress = 0;
  int totalBytes = 0;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _animateToIndex(messages.length);
    });
  }

  void getAccessToken(String accountSid, String apiKey, String apiSecret, String identity,
      String serviceSid, String pushSid) async {
    accessToken = await _twilioConversationSdkPlugin.generateToken(
        accountSid: accountSid,
        apiKey: apiKey,
        apiSecret: apiSecret,
        identity: identity,
        serviceSid: serviceSid,
        pushSid: pushSid);
    final String? resultInitialization = await _twilioConversationSdkPlugin
        .initializeConversationClient(accessToken: accessToken!);
    if (resultInitialization!.isNotEmpty) {
      _twilioConversationSdkPlugin.onClientSyncStatusChanged.listen((event) {
        print("Client Status Received ${event.toString()}");
        if (event['status'] != null) {
          if (event['status'] == 2) {
            checkOrCreateConversation();
          }
        }
      });
    }
    setState(() {});
  }

  checkOrCreateConversation() async {
    List conversationList = await _twilioConversationSdkPlugin.getConversations() ?? [];
    print("Conversation List $conversationList");
    if (conversationList.isNotEmpty) {
      bool isParticipantFound = false;
      for (Map conversation in conversationList) {
        print("Conversation $conversation");
        List participantList = await _twilioConversationSdkPlugin.getParticipants(
                conversationId: conversation["sid"]) ??
            [];
        for (Map participant in participantList) {
          print("Participant $participant");
          if (participant["identity"] == participantIdentity) {
            isParticipantFound = true;
            conversationId = conversation["sid"];
            conversationName = conversation["conversationName"];
            subscribe();
            break;
          }
        }
        if (isParticipantFound) {
          break;
        }
      }
      if (!isParticipantFound) {
        createConversation();
      }
    } else {
      createConversation();
    }
  }

  unsubscribe() {
    _twilioConversationSdkPlugin.unSubscribeToMessageUpdate(
        conversationSid: conversationId);
  }

  subscribe() {
    _twilioConversationSdkPlugin.subscribeToMessageUpdate(
        conversationSid: conversationId);
    _twilioConversationSdkPlugin.onMessageReceived.listen((event) async {
      if (event['status'] != null) {
        print("Conversation Status Received ${event.toString()}");
        if (event['status'] == 3) {
          await getAllMessages();
        }
      } else if (event['author'] != null) {
        print("Conversation Message Received ${event.toString()}");
        messages.add(event);
        _animateToIndex(messages.length);
        setState(() {});
      } else if (event['mediaStatus'] != null) {
        if (event['mediaStatus'] == "Completed" || event['mediaStatus'] == "Failed") {
          setState(() {
            _progress = 0;
            totalBytes = 0;
            isSendMedia = false;
          });
        }
        print("Conversation Message Received ${event.toString()}");
      } else if (event['bytesSent'] != null) {
        print("Conversation Message Received ${event.toString()}");
        setState(() {
          final bytesSent = event['bytesSent'];
          _progress = bytesSent / totalBytes;
        });
      } else if (event['messageStatus'] != null) {
        if (event['messageStatus'] == "Failed") {
          setState(() {
            _progress = 0;
            totalBytes = 0;
            isSendMedia = false;
          });
        }
        print("Conversation Message Received ${event.toString()}");
      }
    });
    /* _twilioConversationSdkPlugin.onMediaProgressReceived.listen((event) async {
      if (event['messageStatus'] != null) {
        print("onMediaProgressReceived ${event.toString()}");
      } else if (event['mediaStatus'] != null) {
        print("onMediaProgressReceived ${event.toString()}");
      } else if (event['bytesSent'] != null) {
        print("onMediaProgressReceived ${event.toString()}");
      }
    });*/
    setState(() {});
  }

  createConversation() async {
    //String timeStamp = DateTime.now().toString();
    //String conversationName = "Flutter - $timeStamp";
    String conversationFriendlyName = "$identity-$participantIdentity";
    conversationId = (await _twilioConversationSdkPlugin.createConversation(
        conversationName: conversationFriendlyName, identity: identity))!;
    print("Result $conversationId");
    conversationName = conversationFriendlyName;
    joinConversation();
    addParticipant();

    subscribe();
  }

  joinConversation() async {
    String? joinResult = (await _twilioConversationSdkPlugin.joinConversation(
        conversationId: conversationId))!;
    print("Result $joinResult");
  }

  sendMessage({bool isSendAttribute = false}) async {
    String timeStamp = DateTime.now().toString();
    Map<String, dynamic> attribute;
    if (isSendAttribute) {
      attribute = {
        "body": message.text.toString().trim(),
        "url": "http://www.google.com",
        "cardId": timeStamp,
      };
    } else {
      attribute = {
        "body": message.text.toString().trim(),
        "cardId": timeStamp,
      };
    }

    final String? sendMessage = await _twilioConversationSdkPlugin.sendMessage(
        conversationId: conversationId,
        message: message.text.toString().trim(),
        attribute: attribute);
    print("Result $sendMessage");
    message.text = "";
    setState(() {});
  }

  sendMessageWithMedia() async {
    Map<String, dynamic> attribute;

    attribute = {"hasMedia": true};

    final String? sendMessage = await _twilioConversationSdkPlugin.sendMessageWithMedia(
        message: "",
        conversationId: conversationId,
        attribute: attribute,
        mediaFilePath: selectedDocPath,
        mimeType: getMimeType(selectedDocPath),
        fileName: fileName);
    print("Result $sendMessage");
    _progress = 0;
    totalBytes = 0;
    isSendMedia = false;
    setState(() {});
  }

  getAllMessages() async {
    print("Get Message for $conversationId");
    messages.clear();
    var messageList =
        await _twilioConversationSdkPlugin.getMessages(conversationId: conversationId) ??
            [];
    messages.addAll(messageList);
    print("Messages $messages");
    setState(() {});
    Future.delayed(const Duration(milliseconds: 500));
    _animateToIndex(messages.length);
  }

  addParticipant() async {
    final String? addSecondParticipantConversation =
        await _twilioConversationSdkPlugin.addParticipant(
            conversationId: conversationId, participantName: participantIdentity);
    print("Result Second $addSecondParticipantConversation");
    unsubscribe();
    subscribe();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 10,
        titleSpacing: 10,
        leading: participantIdentity.isNotEmpty
            ? Container(
                padding: const EdgeInsets.all(10),
                decoration:
                    const BoxDecoration(color: Colors.black, shape: BoxShape.circle),
                alignment: Alignment.center,
                child: Text(
                  participantIdentity.substring(0, 1),
                  style: const TextStyle(
                      color: Colors.white, fontWeight: FontWeight.bold, fontSize: 25),
                ))
            : Container(),
        title: Text(identity.isEmpty
            ? "Twilio Conversation SDK"
            : "$participantIdentity - $conversationName"),
        actions: [
          accessToken!.isNotEmpty
              ? IconButton(
                  onPressed: () {
                    getAllMessages();
                  },
                  icon: const Icon(Icons.refresh),
                  iconSize: 30,
                )
              : Container(),
          accessToken!.isNotEmpty
              ? IconButton(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) {
                          return ConversationList(identity, _twilioConversationSdkPlugin);
                        },
                      ),
                    ).then((data) {
                      subscribe();
                    });
                    unsubscribe();
                  },
                  icon: const Icon(Icons.chat),
                  iconSize: 30,
                )
              : Container(),
        ],
      ),
      body: Center(
        child: Container(
          color: Colors.black12,
          child: Column(
            children: [
              accessToken!.isEmpty
                  ? Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: TextField(
                        onChanged: (value) {
                          identity = value;
                        },
                        decoration:
                            const InputDecoration(labelText: 'Enter your identity'),
                      ),
                    )
                  : Container(),
              accessToken!.isEmpty
                  ? Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: TextField(
                        onChanged: (value) {
                          participantIdentity = value;
                        },
                        decoration: const InputDecoration(
                            labelText: 'Enter participant identity'),
                      ),
                    )
                  : Container(),
              accessToken!.isEmpty
                  ? ElevatedButton(
                      onPressed: () {
                        if (identity.isNotEmpty) {
                          getAccessToken(accountSid, apiKey, apiSecret, identity,
                              serviceSid, pushSid);
                        }
                      },
                      child: Text("Get Access Token"),
                    )
                  : Container(),
              Expanded(
                  child: messages.isNotEmpty
                      ? Column(
                          children: [
                            Visibility(
                              visible: isSendMedia,
                              child: LinearProgressIndicator(
                                value: _progress,
                                minHeight: 20.0, // Set the height of the progress bar
                              ),
                            ),
                            Expanded(
                              child: Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: ListView.builder(
                                  controller: _controller,
                                  shrinkWrap: true,
                                  itemCount: messages.length,
                                  itemBuilder: (context, index) {
                                    List attachMedia = messages[index]['attachMedia'];
                                    return getMessageView(
                                        messages[index]["attributes"],
                                        messages[index]["body"],
                                        messages[index]["author"],
                                        messages[index]["dateCreated"],
                                        attachMedia);
                                  },
                                ),
                              ),
                            ),
                          ],
                        )
                      : const Center(
                          child: Text(
                            "No message yet",
                            style: TextStyle(fontSize: 16, color: Colors.blue),
                          ),
                        )),
              accessToken!.isNotEmpty
                  ? Row(
                      children: [
                        Expanded(
                          child: Container(
                            height: 50,
                            decoration: BoxDecoration(
                              color: Colors.blueGrey,
                              borderRadius: BorderRadius.circular(32),
                            ),
                            child: Row(
                              children: [
                                Flexible(
                                  child: TextField(
                                    controller: message,
                                    style: const TextStyle(color: Colors.white),
                                    decoration: const InputDecoration(
                                      border: InputBorder.none,
                                      hintText: 'Message',
                                      hintStyle: TextStyle(color: Colors.white),
                                      contentPadding: EdgeInsets.only(
                                          left: 15, bottom: 11, top: 11, right: 15),
                                    ),
                                  ),
                                ),
                                isSendMedia
                                    ? const CircularProgressIndicator()
                                    : IconButton(
                                        onPressed: () async {
                                          if (message.text.isNotEmpty) {
                                            sendMessage();
                                          } else {
                                            showAlert("Please type your message");
                                          }
                                        },
                                        icon: const Icon(
                                          Icons.send,
                                          color: Colors.white,
                                        ),
                                        iconSize: 30,
                                      ),
                              ],
                            ),
                          ),
                        ),
                        isSendMedia
                            ? const CircularProgressIndicator()
                            : Container(
                                decoration: const BoxDecoration(
                                    color: Colors.black, shape: BoxShape.circle),
                                alignment: Alignment.center,
                                child: IconButton(
                                  onPressed: () async {
                                    pickFileFromDevice();
                                    /*if (message.text.isNotEmpty) {
                                sendMessage(isSendAttribute: true);
                              } else {
                                showAlert("Please type your message");
                              }*/
                                  },
                                  icon: const Icon(
                                    Icons.attach_file,
                                    color: Colors.white,
                                  ),
                                  iconSize: 30,
                                ),
                              ),
                      ],
                    )
                  : Container(),
            ],
          ),
        ),
      ),
    );
  }

  getMessageView(String attribute, String message, String author, String date,
      List<dynamic>? attachMedia) {
    String localDate = convertUTCToLocalChat(
        utcDateTime: date,
        inputDateFormat: formatterYYYYMMddTHHMMss,
        outputDateFormat: formatterYYYYMMddTHHMMssSSS);

    DateTime dateTime = formatterYYYYMMddTHHMMssSSS.parse(localDate);
    String timeAgo;
    timeAgo = Jiffy.parseFromDateTime(dateTime).fromNow();

    if (attribute.contains("hasMedia")) {
      String? mediaUrl;
      String? contentType;
      String? filename;
      for (var media in attachMedia!) {
        filename = media['filename'];
        mediaUrl = media['mediaUrl'];
        contentType = media['contentType'];
        //String? sid = media['sid'];
      }
      return Align(
        alignment: author == identity ? Alignment.centerRight : Alignment.centerLeft,
        child: Card(
          margin: const EdgeInsets.all(10),
          color: author == identity ? Colors.blue : Colors.black,
          child: Padding(
            padding: const EdgeInsets.all(10.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                mediaUrl != null && mediaUrl.isNotEmpty
                    ? contentType == "application/pdf" || contentType == "video/mp4"
                        ? Text(
                            textAlign: TextAlign.end,
                            filename ?? "",
                            style: TextStyle(
                                color: author == identity ? Colors.white : Colors.white,
                                fontSize: 16),
                          )
                        : SizedBox(
                            height: 200,
                            width: 150,
                            child: CachedNetworkImage(
                                imageUrl: mediaUrl,
                                imageBuilder: (context, imageProvider) => Container(
                                      height: 200,
                                      width: 150,
                                      decoration: BoxDecoration(
                                        shape: BoxShape.rectangle,
                                        borderRadius: BorderRadius.circular(10),
                                        image: DecorationImage(
                                          image: imageProvider,
                                          fit: BoxFit.fill,
                                        ),
                                      ),
                                    ),
                                placeholder: (context, url) => const Center(
                                      child: CircularProgressIndicator(),
                                    )),
                          )
                    : const SizedBox(),
                message.isNotEmpty
                    ? Text(
                        textAlign: TextAlign.end,
                        message,
                        style: TextStyle(
                            color: author == identity ? Colors.white : Colors.white,
                            fontSize: 10),
                      )
                    : const SizedBox(),
                Text(
                  textAlign: TextAlign.end,
                  timeAgo,
                  style: TextStyle(
                      color: author == identity ? Colors.white : Colors.white,
                      fontSize: 10),
                ),
              ],
            ),
          ),
        ),
      );
    } else if (attribute.contains("url")) {
      Map<String, String> attributeModel = Map.castFrom(json.decode(attribute));
      if (attributeModel['url'] != null) {
        return Align(
          alignment: author == identity ? Alignment.centerRight : Alignment.centerLeft,
          child: Card(
            margin: const EdgeInsets.all(10),
            color: author == identity ? Colors.blue : Colors.black,
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: SizedBox(
                width: 100,
                height: 100,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Expanded(
                      child: Text(
                        textAlign: TextAlign.center,
                        attributeModel['url']!,
                        style: TextStyle(
                            color: author == identity ? Colors.white : Colors.white,
                            fontSize: 14),
                      ),
                    ),
                    Text(
                      textAlign: TextAlign.end,
                      timeAgo,
                      style: TextStyle(
                          color: author == identity ? Colors.white : Colors.white,
                          fontSize: 10),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      } else {
        return Align(
          alignment: author == identity ? Alignment.centerRight : Alignment.centerLeft,
          child: Card(
            margin: const EdgeInsets.all(10),
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text(
                    message,
                    style: TextStyle(
                        color: author == identity ? Colors.blue : Colors.black,
                        fontSize: 14),
                  ),
                  Text(
                    textAlign: TextAlign.end,
                    timeAgo,
                    style: TextStyle(
                        color: author == identity ? Colors.blue : Colors.black,
                        fontSize: 10),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    } else {
      return Align(
        alignment: author == identity ? Alignment.centerRight : Alignment.centerLeft,
        child: Card(
          margin: const EdgeInsets.all(10),
          child: Padding(
            padding: const EdgeInsets.all(10.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                Text(
                  message,
                  style: TextStyle(
                      color: author == identity ? Colors.blue : Colors.black,
                      fontSize: 14),
                ),
                Text(
                  timeAgo,
                  textAlign: TextAlign.end,
                  style: TextStyle(
                      color: author == identity ? Colors.blue : Colors.black,
                      fontSize: 10),
                ),
              ],
            ),
          ),
        ),
      );
    }
  }

  String convertUTCToLocalChat(
      {required String utcDateTime,
      required DateFormat inputDateFormat,
      required DateFormat outputDateFormat}) {
    try {
      // Parse the UTC date string
      DateTime utcDate = inputDateFormat.parseUtc(utcDateTime);
      String inputLocalDate = inputDateFormat.format(utcDate.toLocal());
      DateTime outputLocalDateTime = inputDateFormat.parse(inputLocalDate);
      return outputDateFormat.format(outputLocalDateTime).toString();
    } catch (e) {
      debugPrint(e.toString());
      return utcDateTime;
    }
  }

  void _animateToIndex(int index) {
    if (_controller.hasClients) {
      _controller.animateTo(
        index * _height,
        duration: const Duration(seconds: 2),
        curve: Curves.fastOutSlowIn,
      );
    }
  }

  void showAlert(String message) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(message),
    ));
  }

  Future pickFileFromDevice() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      type: FileType.custom,
      allowedExtensions: ['jpg', 'jpeg', 'pdf', 'png', 'doc', 'docx'],
    );

    if (result != null &&
            result.files.single.path!
                .isNotEmpty /*&&
            result.files.single.path!.contains(".pdf") ||
        result!.files.single.path!.contains(".jpg") ||
        result.files.single.path!.contains(".jpeg") ||
        result.files.single.path!.contains(".png") ||
        result.files.single.path!.contains(".doc") ||
        result.files.single.path!.contains(".docx")*/
        ) {
      selectedDocPath = result.files.single.path!;
      fileName = result.files.single.name;
      totalBytes = result.files.single.size;
      debugPrint("Selected file path: $selectedDocPath");
      isSendMedia = true;
      setState(() {});
      await sendMessageWithMedia();
    } else {
      debugPrint("File picking canceled.");
    }
  }

  String getMimeType(String filePath) {
    // Use the lookupMimeType function from the mime package
    String? mimeType = lookupMimeType(filePath);
    // Return the detected MIME type or default to "application/octet-stream"
    return mimeType ?? "application/octet-stream";
  }
}
0
likes
150
points
825
downloads

Publisher

unverified uploader

Weekly Downloads

Twilio Conversation SDK - A Flutter plugin which allows you to build engaging conversational messaging experiences for Android and iOS.

Repository (GitHub)
View/report issues

Documentation

API reference

License

Apache-2.0 (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on twilio_conversation_sdk