Групповой чат с Flutter и Firebase

#firebase #flutter #dart #google-cloud-firestore

#firebase #flutter #dart #google-облако-firestore

Вопрос:

Я новичок в flutter, и я создал приложение для чата с Firebase. Мое приложение состоит в том, чтобы позволить пользователю создавать групповой чат и добавлять других пользователей, а затем запускать чат. Но чего я не могу сделать, так это указать имя отправителя в его сообщении. Вот мой код

Привет, я новичок в flutter, и я создал приложение для чата с Firebase. Мое приложение состоит в том, чтобы позволить пользователю создавать групповой чат и добавлять других пользователей, а затем запускать чат. Но чего я не могу сделать, так это указать имя отправителя в его сообщении. Вот мой код

 if (type == 'text') {
                   return MessageTile(
                     senderName: snapshot.data.documents[index].data["sendBy"],
                     message: snapshot.data.documents[index].data["message"],
                     messageType: 'text',
                     sendByMe: Constants.myName ==
                         snapshot.data.documents[index].data["sendBy"],
                   );
                 } 

  
 import 'dart:io';
import 'dart:isolate';

import 'package:chatapp/models/connection.dart';
import 'package:chatapp/models/user_connection.dart';
import 'package:chatapp/helper/constants.dart';
import 'package:chatapp/helper/encoding_provider.dart';
import 'package:chatapp/helper/helperfunctions.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:path_provider/path_provider.dart';

/*// This function happens in the isolate.
void entryPoint(SendPort context) {
  // Calling initialize from the entry point with the context is
  // required if communication is desired. It returns a messenger which
  // allows listening and sending information to the main isolate.
  final messenger = HandledIsolate.initialize(context);

  // Triggered every time data is received from the main isolate.
  messenger.listen((msg) async {
    // Use a plugin to get some new value to send back to the main isolate.
    final dir = await getApplicationDocumentsDirectory();
    messenger.send(msg   dir.path);
  });
}

*/

class DatabaseMethods {
  Future<void> addUserInfo(userData) async {
    Firestore.instance.collection("users").add(userData).catchError((e) {
      print(e.toString());
    });
  }

  getUserInfoByEmail(String email) async {
    QuerySnapshot snapshot = await Firestore.instance
        .collection("users")
        .where("userEmail", isEqualTo: email.trim())
        .where("type", isEqualTo: "parentprofile")
        .limit(1)
        .getDocuments()
        .catchError((e) {
      print(e.toString());
    });
    return UserConnection.fromDocumentSnapshot(snapshot.documents[0]);
  }

  getUserInfoByRef(DocumentReference reference) async {
    return Firestore.instance.document(reference.path).get().catchError((e) {
      print(e.toString());
    });
  }

  searchByName(String searchField) {
    return Firestore.instance
        .collection("users")
        .where('userName', isEqualTo: searchField)
        .getDocuments();
  }

  String getNewConversationId() {
    return Firestore.instance.collection("conversations").document().documentID;
  }

  addChatRoom(chatRoom, chatRoomId) {
    Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .setData(chatRoom)
        .catchError((e) {
      print(e);
    }).then((_) {
      return chatRoomId;
    });
  }

  addUserToConversation({
    chatRoomId,
    userEmail,
    adminName,
    Map<String, bool> members,
  }) async {
    UserConnection user = await getUserInfoByEmail(userEmail);
    print(members.entries.toString());
    print("User Name: ${user.userName}");
    members.addAll({"${user.userName}": true});
    print(members.entries.toString());
    Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .updateData({"users": members});
  }

  /*Future<UserConnection> getUserNameByEmail(String email) async {
    QuerySnapshot userSnapshot = await Firestore.instance
        .collection("users")
        .where('userEmail', isEqualTo: email)
        .where("type", isEqualTo: "parentprofile")
        .limit(1)
        .getDocuments();
    return UserConnection.fromDocumentSnapshot(userSnapshot.documents[0]);
  }*/

  getChats(String chatRoomId) async {
    return Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .orderBy('time')
        .snapshots();
  }

  Future<void> addMessage(String chatRoomId, chatMessageData) {
    Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .add(chatMessageData)
        .catchError((e) {
      print(e.toString());
    });
  }

  Future<void> addImageMessage(
      String chatRoomId, imageMessageData, filePath) async {
    DocumentReference docRef = await Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .add(imageMessageData)
        .catchError((e) {
      print(e.toString());
    });

    final sfile = (await HelperFunctions.getLocalFile(
        imageMessageData['fileName'],
        type: imageMessageData['contentType']));
    final path = sfile.path;
    final file =
        await HelperFunctions.compressAndGetFile(filePath, descPath: path);
    final basename = HelperFunctions.getFileName(filePath);
    final StorageReference ref = FirebaseStorage.instance
        .ref()
        .child('conversations/$chatRoomId')
        .child(basename);
    StorageUploadTask uploadTask = ref.putFile(file);
    StorageTaskSnapshot snapshot = await uploadTask.onComplete;
    String url = await snapshot.ref.getDownloadURL();
    String accessToken = ((url.split('amp;'))[1].split('='))[1];
    docRef.updateData({
      'mediaURL': url,
      'uploaded': true,
      'localpath': '',
      'accessToken': accessToken,
    });
    //return url;
  }

  Future<void> addDocumentMessage(String chatRoomId, messageData) async {
    DocumentReference docRef = await Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .add(messageData)
        .catchError((e) {
      print(e.toString());
    });

    //final filePath = messageData['localpath'];
    //final file = await HelperFunctions.compressAndGetFile(File(filePath));
    //final basename =  HelperFunctions.getFileName(filePath);
    final StorageReference ref = FirebaseStorage.instance
        .ref()
        .child('conversations/$chatRoomId')
        .child(messageData['fileName']);
    print("Filename: ${messageData['fileName']}");
    String fromPath =
        await HelperFunctions.getTempPath(messageData['fileName']);
    String filePath = await HelperFunctions.copyFile(
        fromPath, messageData['fileName'], messageData['contentType']);
    print("DocumentFilePath: $filePath");
    StorageUploadTask uploadTask = ref.putFile(File(filePath));
    StorageTaskSnapshot snapshot = await uploadTask.onComplete;
    String url = await snapshot.ref.getDownloadURL();
    print(((url.split('amp;'))[1].split('='))[1]);
    String accessToken = ((url.split('amp;'))[1].split('='))[1];
    docRef.updateData({
      'docURL': url,
      'accessToken': accessToken,
      'uploaded': true,
    });
    //return url;
  }

  Future<void> addAudioMessage(String chatRoomId, messageData) async {
    DocumentReference docRef = await Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .add(messageData)
        .catchError((e) {
      print(e.toString());
    });

    //final filePath = messageData['localpath'];
    //final file = await HelperFunctions.compressAndGetFile(File(filePath));
    //final basename =  HelperFunctions.getFileName(filePath);
    final StorageReference ref = FirebaseStorage.instance
        .ref()
        .child('conversations/$chatRoomId')
        .child(messageData['fileName']);

    StorageUploadTask uploadTask = ref.putFile(
        await HelperFunctions.getLocalFile(messageData['fileName'],
            type: messageData['contentType']));
    StorageTaskSnapshot snapshot = await uploadTask.onComplete;
    String url = await snapshot.ref.getDownloadURL();
    print(((url.split('amp;'))[1].split('='))[1]);
    String accessToken = ((url.split('amp;'))[1].split('='))[1];
    docRef.updateData({
      'mediaURL': url,
      'accessToken': accessToken,
      'uploaded': true,
    });
    //return url;
  }

  Future uploadVideoThumbnail(String chatRoomId, String videopath) async {
    /*RegExp regExp = new RegExp(
      r"/^[a-zA-Z0-9_-] $/",
      caseSensitive: false,
      multiLine: false,
    );
    if (!regExp.hasMatch(videopath)) {
      String tempDir = (await getTemporaryDirectory()).path;
      String fileExtension = videopath.split('.').last;
      String newPath =
          tempDir   '/'   DateTime.now().millisecondsSinceEpoch.toString()   '.'   fileExtension;

      File tempFile =
          await File(videopath).copy(newPath);

        // you can use this new file path for making the thumbnail without error
      videopath = tempFile.path;
    }*/

    /*final sfile = (await HelperFunctions.getLocalFile(
        '${DateTime.now().millisecondsSinceEpoch.toString()}.jpg',
        type: 'video/thumbnail'));*/
    final tmpFile = (await HelperFunctions.getLocalFile(
        '${DateTime.now().millisecondsSinceEpoch.toString()}.jpg',
        type: 'video/thumbnail'));
    final data = <String>[videopath, tmpFile.path];

    /*final thumbnailFile = await VideoThumbnail.thumbnailFile(
      video: videopath,
        quality: 10, 
        imageFormat: ImageFormat.JPEG,// default(-1)
        thumbnailPath: tmpFile.path, 
        );*/
    final thumbnailFile = await EncodingProvider.getThumbnail(data);
    final sfilePath =
        thumbnailFile; //await compute(EncodingProvider.getThumbnail, data);

    print("Thumbnail String: $sfilePath");
    /*final sfile = (await HelperFunctions.getLocalFile(
        DateTime.now().toIso8601String(),
        type: 'video/thumbnail'));*/
    //await sfile.writeAsBytes(thumbnailFile.readAsBytesSync());
    File sfile = File(sfilePath);
    String fileName = HelperFunctions.getFileName(sfile.path);
    final StorageReference ref = FirebaseStorage.instance
        .ref()
        .child('conversations/$chatRoomId')
        .child(fileName);

    StorageUploadTask uploadTask = ref.putFile(sfile);
    StorageTaskSnapshot snapshot = await uploadTask.onComplete;
    String url = await snapshot.ref.getDownloadURL();
    print('Thumbnail Token: ${((url.split('amp;'))[1].split('='))[1]}');
    //String accessToken = ((url.split('amp;'))[1].split('='))[1];
    var thumbnailDetails = {'thumbnailURL': url, 'thumbnailName': fileName};
    return thumbnailDetails;
  }

  Future<void> addVideoMessage(
      String chatRoomId, Map<String, dynamic> messageData) async {
    // final video = (await HelperFunctions.getLocalFile(messageData['fileName'],
    //    type: messageData['contentType'])).path;
    final video =
        await HelperFunctions.getTempPath('Trimmer/${messageData['fileName']}');
    final thumbnailDetails = await uploadVideoThumbnail(chatRoomId, video);
    messageData.addAll(thumbnailDetails);
    DocumentReference docRef = await Firestore.instance
        .collection("conversations")
        .document(chatRoomId)
        .collection("messages")
        .add(messageData)
        .catchError((e) {
      print(e.toString());
    });

    //final filePath = messageData['localpath'];
    //final file = await HelperFunctions.compressAndGetFile(File(filePath));
    //final basename =  HelperFunctions.getFileName(filePath);
    String videoPath = video;
    print('VideoPath: $videoPath');
    //final _flutterVideoCompress = FlutterVideoCompress();
    /*VideoCompress videoCompress = VideoCompress();
    if(videoCompress.isCompressing){
      print("Video was compressing. Cancelling previous compression");
      await videoCompress.cancelCompression();
      await videoCompress.deleteAllCache();
    }*/

    /*final info = await videoCompress.compressVideo(videoPath,
        deleteOrigin: false, quality: VideoQuality.DefaultQuality);*/

    num bitrate = EncodingProvider.getBitRate(
        await EncodingProvider.getMediaInformation(videoPath));
    List data = <String>[
      videoPath,
      "${bitrate}k",
    ];
    final output = await EncodingProvider.compressVideo(data);
    print(
        "CompressedOuput: $outputnUncompressed Filename: ${messageData['fileName']}");
    //final info = await compute(EncodingProvider.compressVideo, data);
    //print('Compressed Videonsize: ${info.filesize}npath:${info.path}');
    String newPath = await HelperFunctions.copyFile(
        videoPath, messageData['fileName'], 'video');
    final StorageReference ref = FirebaseStorage.instance
        .ref()
        .child('conversations/$chatRoomId')
        .child(messageData['fileName']);

    StorageUploadTask uploadTask = ref.putFile(File(newPath));
    StorageTaskSnapshot snapshot = await uploadTask.onComplete;
    String url = await snapshot.ref.getDownloadURL();
    print(((url.split('amp;'))[1].split('='))[1]);
    String accessToken = ((url.split('amp;'))[1].split('='))[1];
    docRef.updateData({
      'mediaURL': url,
      'accessToken': accessToken,
      'uploaded': true,
    });
    //return url;
  }

  getUserConversations(String itIsMyName) async {
    return Firestore.instance
        .collection("conversations")
        .where('users.$itIsMyName', isEqualTo: true)
        .snapshots();
  }

  /// 1.create a chatroom, send user to the chatroom, other userdetails
  Connection createNewConversation({String userName, String title}) {
    Map<String, bool> users;
    if (userName != null) {
      users = {"${Constants.myName.trim()}": true, "${userName.trim()}": true};
    } else {
      users = {"${Constants.myName.trim()}": true};
    }
    Map<String, String> role = {"${Constants.myName.trim()}": "admin"};
    //String chatRoomId = getChatRoomId(Constants.myName.trim(),userName.trim());
    String chatRoomId = getNewConversationId();
    print("user: ${users[0]}nchatRoomId: $chatRoomIdnTitile: $title");

    Map<String, dynamic> chatRoom = {
      "users": users,
      "convId": chatRoomId,
      "title": title,
      "role": role,
    };

    addChatRoom(chatRoom, chatRoomId);
    return Connection.fromJson(chatRoom);
  }
}

 
  
 import 'package:chatapp/models/connection.dart';
import 'package:chatapp/helper/authenticate.dart';
import 'package:chatapp/helper/constants.dart';
import 'package:chatapp/helper/helperfunctions.dart';
import 'package:chatapp/helper/theme.dart';
import 'package:chatapp/services/auth.dart';
import 'package:chatapp/services/database.dart';
import 'package:chatapp/views/chat.dart';
import 'package:chatapp/views/search.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';

class ChatRoom extends StatefulWidget {
  @override
  _ChatRoomState createState() => _ChatRoomState();
}

class _ChatRoomState extends State<ChatRoom> {
  Stream chatRooms;

  //List of all users connection
  List<Connection> connectionList = List<Connection>();

  var controller = TextEditingController();
  Widget chatRoomsList() {
    return StreamBuilder(
      stream: chatRooms,
      builder: (context, snapshot) {
        return snapshot.hasData
            ? ListView.builder(
                itemCount: snapshot.data.documents.length,
                shrinkWrap: true,
                itemBuilder: (context, index) {
                  Connection connection = Connection.fromDocumentSnapshot(
                      snapshot.data.documents[index]);
                  connectionList.add(connection);
                  return ChatRoomsTile(
                    userName: snapshot.data.documents[index].data['title']
                        .toString()
                        .replaceAll("_", "")
                        .replaceAll(Constants.myName, ""),
                    // chatRoomId: snapshot.data.documents[index].data["convId"],
                    connectionInfo: connection,
                  );
                })
            : Container();
      },
    );
  }

  @override
  void initState() {
    getUserInfogetChats();
    super.initState();
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is disposed.
    controller.dispose();
    super.dispose();
  }

  getUserInfogetChats() async {
    Constants.myName = await HelperFunctions.getUserNameSharedPreference();
    DatabaseMethods().getUserConversations(Constants.myName).then((snapshots) {
      setState(() {
        chatRooms = snapshots;
        print(
            "we got the data   ${chatRooms.toString()} this is name  ${Constants.myName}");
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "RealDiagnosis",
          style: TextStyle(
            color: Colors.white,
          ),
        ),
        elevation: 0.0,
        centerTitle: false,
        actions: [
          GestureDetector(
            onTap: () async {
              return await showMaterialModalBottomSheet(
                expand: false,
                context: context,
                //backgroundColor: Colors.transparent,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(20),
                        topRight: Radius.circular(20))),
                bounce: true,
                enableDrag: true,
                builder: (context, scrollController) {
                  return Container(
                    width: MediaQuery.of(context).size.width,
                    height: (MediaQuery.of(context).size.height / 3)   150,
                    child: Column(
                      children: [
                        Padding(
                          padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
                          child: Text(
                            "Add New Connection",
                            style: Theme.of(context).textTheme.headline6,
                          ),
                        ),
                        Divider(
                          height: 1,
                        ),
                        Container(
                          height: 200,
                          margin:
                              EdgeInsets.symmetric(horizontal: 30, vertical: 8),
                          child: ListView(
                            padding: EdgeInsets.zero,
                            physics: ClampingScrollPhysics(),
                            children: [
                              TextFormField(
                                cursorColor: Colors.black,
                                controller: controller,
                                keyboardType: TextInputType.text,
                                decoration: new InputDecoration(
                                  //border: InputBorder.none,
                                  border: UnderlineInputBorder(
                                      borderSide:
                                          BorderSide(color: Colors.blue)),
                                  icon: Icon(Icons.edit),
                                  //disabledBorder: InputBorder.none,

                                  contentPadding: EdgeInsets.only(
                                    left: 15,
                                    bottom: 6,
                                    top: 11,
                                    right: 15,
                                  ),
                                  hintText:
                                      "Type connection title (subject) here...",
                                ),
                                style: TextStyle(
                                  fontSize: 24,
                                ),
                              ),
                              SizedBox(
                                height: 18,
                              ),
                              RaisedButton(
                                onPressed: () async {
                                  var _title = controller.text;
                                  debugPrint(_title);
                                  Connection connection =
                                      DatabaseMethods().createNewConversation(
                                    title: _title,
                                  );
                                  controller.text = "";
                                  Navigator.of(context).pop();
                                  Navigator.push(
                                      context,
                                      MaterialPageRoute(
                                          builder: (context) => Chat(
                                                connectionInfo: connection,
                                              )));
                                },
                                shape: RoundedRectangleBorder(
                                    borderRadius:
                                        BorderRadius.all(Radius.circular(16))),
                                color: Theme.of(context).indicatorColor,
                                textColor: Colors.white,
                                child: Container(
                                  color: Colors.transparent,
                                  height: 58,
                                  child: Center(
                                    child: Text(
                                      "Create Connection",
                                      style: TextStyle(fontSize: 18),
                                    ),
                                  ),
                                ),
                              )
                            ],
                          ),
                        )
                      ],
                    ),
                  );
                },
              );
            },
            child: Container(
                padding: EdgeInsets.symmetric(horizontal: 16),
                child: Icon(Icons.add)),
          ),
          GestureDetector(
            onTap: () async {
              AuthService().signOut();
              Navigator.pushReplacement(context,
                  MaterialPageRoute(builder: (context) => Authenticate()));
            },
            child: Container(
                padding: EdgeInsets.symmetric(horizontal: 16),
                child: Icon(Icons.exit_to_app)),
          )
        ],
      ),
      body: Container(
        child: chatRoomsList(),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.search),
        onPressed: () {
          Navigator.push(
              context, MaterialPageRoute(builder: (context) => Search()));
        },
      ),
    );
  }
}

class ChatRoomsTile extends StatelessWidget {
  final String userName;
  final Connection connectionInfo;

  ChatRoomsTile({this.userName, this.connectionInfo});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => Chat(
                      connectionInfo: connectionInfo,
                    )));
      },
      child: Container(
        color: Colors.black26,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 20),
        child: Row(
          children: [
            Container(
              height: 30,
              width: 30,
              decoration: BoxDecoration(
                  color: CustomTheme.colorAccent,
                  borderRadius: BorderRadius.circular(30)),
              child: Text(userName.substring(0, 1),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontFamily: 'OverpassRegular',
                      fontWeight: FontWeight.w300)),
            ),
            SizedBox(
              width: 12,
            ),
            Text(userName,
                textAlign: TextAlign.start,
                style: TextStyle(
                    color: Colors.white,
                    fontSize: 16,
                    fontFamily: 'OverpassRegular',
                    fontWeight: FontWeight.w300))
          ],
        ),
      ),
    );
  }
}


  
 class Constants{

  static String myName = "";
}

  
 class User {
  final String uid;
  User({this.uid});
}


  

Комментарии:

1. Здравствуйте, здесь я нашел этот пример приложения GroupChat на GitHub, который вы могли бы использовать в качестве ссылки для преодоления этого препятствия, с которым вы столкнулись. Я надеюсь, что это поможет.

Ответ №1:

Если все пользователи прослушивают одну и ту же коллекцию Firestore для группового чата, все пользователи должны иметь возможность прочитать отправленное сообщение. Проверяя предоставленный вами фрагмент, вам необходимо указать, как MessageTile они отображаются. Я предлагаю использовать StreamBuilder — прослушивание коллекции Firestore с потоком. Виджет должен автоматически перестраиваться, если в потоке обнаружены изменения, что должно привести к последующему обновлению его дочерних элементов. Я вижу, что вы уже внедрили StreamBuilder для chatRooms , вы можете применить аналогичный подход для группы в чате.