[Mobile]/[Flutter]

[Flutter] Chatting 모듈 정리

HiSmith 2023. 10. 8. 21:13
반응형

사용 의존성 

  firebase_core: ^2.16.0
  cloud_firestore: ^4.9.2
  chat_bubbles: ^1.5.0
  flutter_chat_bubble: ^2.0.2

chat-list

 

import 'package:get/get.dart';
import 'package:goodshot/global-provider.dart';
import 'package:goodshot/view/chat/screen/chat-list-widget.dart';
import 'package:goodshot/view/group/dto/MyGroupItem.dart';

import 'package:flutter/material.dart';
import 'package:goodshot/view/group/popup/create-group-popup.dart';
import 'package:goodshot/view/group/widget/my-group-widget.dart';

import '../../group/http/group-my-api.dart';



class ChatListScreen extends StatefulWidget {
  final GlobalProvider globalProvider = Get.find();
  @override
  _ChatListState createState() => _ChatListState(groupList: getMyGroupListApi(globalProvider.getUserId()));
}


class _ChatListState extends State<ChatListScreen> {
  final GlobalProvider globalProvider = Get.find();
  Future<List<MyGroupItem>> groupList;
  _ChatListState({required this.groupList});

  @override
  void initState() {
    super.initState();
    groupList = getMyGroupListApi(globalProvider.getUserId());
  }
  @override
  Widget build(BuildContext context) {
    _fetchData() async {
      setState((){
        groupList = getMyGroupListApi(globalProvider.getUserId());
      });
    }
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        actions:[
          //드롭 다운 지역 검색 가능하도록?
        ]
      ),
      body: Container(
          child: FutureBuilder(
              future: groupList,
              builder: (context,AsyncSnapshot snapshot){
                if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
                  return ListView.builder(
                    padding: EdgeInsets.symmetric(horizontal: 20),
                    itemCount: snapshot.data.length,
                    itemBuilder: (BuildContext context, int index) {
                      MyGroupItem item = snapshot.data[index];
                      return MyChatWidget(myGroupItem: item, refreshListFunc: _fetchData);
                    },);
                }
                else
                {
                  return Container(
                    child: Center(
                      child: Text("Loading..."),
                    ),
                  );
                }
              }
          ),
      ),
    );

  }
}

 

chat-list-widget

 

import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:goodshot/util/const.dart';
import 'package:goodshot/view/chat/screen/chat-detail.dart';

import 'package:goodshot/view/group/dto/MyGroupItem.dart';
import 'package:goodshot/view/group/http/group-join-api.dart';
import 'package:goodshot/view/group/popup/my-group-popup.dart';
import 'package:goodshot/view/group/util/chip-builder.dart';

class MyChatWidget extends StatefulWidget {

  MyGroupItem myGroupItem;
  final Function() refreshListFunc;
  MyChatWidget({Key? key,required this.myGroupItem,required this.refreshListFunc});

  @override
  _MyChatWidgetState createState() => _MyChatWidgetState(myGroupItem:  myGroupItem);
}

class _MyChatWidgetState extends State<MyChatWidget> {
  MyGroupItem myGroupItem;

  _MyChatWidgetState({required this.myGroupItem});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 1.1),
      child: InkWell(
        child: Column(

          mainAxisAlignment: MainAxisAlignment.end,
          children: <Widget>[
            ListTile(
              leading: CircleAvatar(
                radius: 25,
                backgroundImage: NetworkImage("${myGroupItem.thumbnailImageUrl}"),
              ),
              contentPadding: EdgeInsets.all(0),
              title: Text("${myGroupItem.title}",),
              subtitle: Text("${myGroupItem.nickname}",),
              isThreeLine: false,
              trailing:
                      ElevatedButton(
                        style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Constants.appColor)),
                        child: const Text('입장하기'),
                        onPressed: ()  {
                          Navigator.push(context,
                              MaterialPageRoute(
                                  builder: (context) =>  ChatScreen(groupNo: myGroupItem.groupNo))
                          );
                        },
                      ),
            ),
            Divider(thickness: 1,),
          ],
        ),
        onTap: () {

          showDialog(
              barrierDismissible: true,
              context: context, builder: (BuildContext context){
            return MyGroupDetailPopup(myGroupItem: myGroupItem);
          }
          ).then((value) => setState((){
            widget.refreshListFunc.call();
          }));
        },
      ),
    );
  }
}

 

chat-bubbles

import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_bubble/chat_bubble.dart';

class ChatBubbles extends StatelessWidget {
  const ChatBubbles(this.message,this.isMe,this.nickname,this.imgUrl,{Key? key}) : super(key: key);

  final String message;
  final bool isMe;
  final String nickname;
  final String imgUrl;
  @override
  Widget build(BuildContext context) {

    return Stack(
      children:[
        Row(
        mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
        children: [
          if(isMe)
              Padding(
                padding: const EdgeInsets.fromLTRB(0,10,45,0),
                child: ChatBubble(
                  clipper: ChatBubbleClipper8(type: BubbleType.sendBubble),
                  backGroundColor: Colors.green,
                  child: Container(
                    constraints: BoxConstraints(
                    maxWidth: MediaQuery.of(context).size.width * 0.4,
                  ),
                  child: Column(
                    children: [
                      Text(nickname),
                      Text(
                        message,
                        style: TextStyle(color: Colors.white),
                      ),
                    ],
                  ),
                  ),
                ),
              ),
        if(!isMe)
          Padding(
            padding: const EdgeInsets.fromLTRB(45,10,0,0),
            child: ChatBubble(
              clipper: ChatBubbleClipper8(type: BubbleType.receiverBubble),
              backGroundColor: Colors.grey,
              child: Container(
                constraints: BoxConstraints(
                  maxWidth: MediaQuery.of(context).size.width * 0.4,
                ),
                child: Column(
                  children: [
                    Text(nickname),
                    Text(
                      message,
                      style: TextStyle(color: Colors.black),
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
        Positioned(
            top: 0,
            left: isMe ? null : 5,
            right: isMe ?  5 : null,
            child: CircleAvatar(
              backgroundImage: NetworkImage(imgUrl),
            ),

        )
    ]
    );
  }
}

message

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';
import 'package:goodshot/global-provider.dart';
import 'package:goodshot/view/chat/widget/chat-bubble.dart';
final GlobalProvider globalProvider = Get.find();
class Messages extends StatelessWidget {
  const Messages(this.groupNo,{Key? key}) : super(key: key);

  final int groupNo;
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: FirebaseFirestore.instance.collection(groupNo.toString())
            .orderBy('time',descending: true)
            .snapshots(),
        builder: (context, AsyncSnapshot<QuerySnapshot<Map<String,dynamic>>> snapshot) {
          if(snapshot.connectionState == ConnectionState.waiting){
            return Center(
              child: CircularProgressIndicator(),
            );
          }
          final chatDoc = snapshot.data!.docs;
          return ListView.builder(
              reverse: true,
              itemCount: chatDoc.length,
              itemBuilder: (context,index){
            return ChatBubbles(
                chatDoc[index]['content'],
                chatDoc[index]['userId'] == globalProvider.getUserId(),
                chatDoc[index]['nickname'],
                chatDoc[index]['imgUrl']
            );
          });
        }
    );
  }
}

 

new-message 

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class NewMessages extends StatefulWidget {
  final String userId;
  final String nickname;
  final String imgUrl;

  const NewMessages({required this.userId,required this.nickname,required this.imgUrl});

  @override
  _NewMessagesState createState() => _NewMessagesState(userId: userId,nickname: nickname,imgUrl: imgUrl);
}

class _NewMessagesState extends State<NewMessages> {
  final String userId;
  final String nickname;
  final String imgUrl;
  _NewMessagesState({required this.userId,required this.nickname, required this.imgUrl});

  var inputMessage = '';
  final _controller = TextEditingController();

  void sendMessage(){
    FocusScope.of(context).unfocus();
    FirebaseFirestore.instance.collection('1').add({
      "content":inputMessage,
      "userId":userId,
      "imgUrl":imgUrl,
      "nickname":nickname,
      "time":Timestamp.now()
    });
    _controller.clear();
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 8),
      padding: EdgeInsets.all(8),
      child: Row(
        children: [
          Expanded(
              child: TextField(
                maxLines: null,
                controller: _controller,
                decoration: InputDecoration(
                    labelText: 'Send a message....'),
                onChanged: (value) {
                  setState(() {
                    inputMessage = value;
                  });
                },
              )
          ),
          IconButton(
              onPressed: inputMessage.trim().isEmpty ? null : sendMessage,
              icon: Icon(Icons.send),
              color: Colors.blue
          )
        ],
      ),
    );
  }
}

chat-detail.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:goodshot/global-provider.dart';
import 'package:goodshot/view/chat/widget/message.dart';
import 'package:goodshot/view/chat/widget/new-message.dart';

final GlobalProvider globalProvider = Get.find();
class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key, required this.groupNo});

  final int groupNo;

  @override
  ChatScreenState createState() => ChatScreenState(groupNo: groupNo);
}

class ChatScreenState extends State<ChatScreen> {
  final GlobalProvider globalProvider = Get.find();
  late String userId;
  final int groupNo;

  ChatScreenState({required this.groupNo});

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

  @override
  void initState() {
    userId = globalProvider.getUserId();
    super.initState();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        elevation: 0,
        title: const Text(
          'Good Shot',
        ),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back_ios_sharp,
              color: Colors.black54),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
      backgroundColor: Colors.white,
      extendBodyBehindAppBar: false,
      body: Container(
        padding: EdgeInsets.all(50),
        child: Column(
          children: [
            Expanded(child: Messages(groupNo)),
            NewMessages(userId: globalProvider.getUserId(),nickname: globalProvider.getNickName(),imgUrl: globalProvider.getImgUrl(),)
          ],
        ),
      ),
    );
  }
}

 

 

chat-list, chat-list-widget은 채팅 리스트화면을 구성하는 소스이다.

chat-bubble은 메시지 관련 말풍선 소스이다.

new-message 는 새로운메시지로직이다. 새로운 그룹에 데이터를 넣는 소스이다.

message는 시간을 역순으로 정렬하거나 isMe를 세팅해서 나라면 오른쪽 타인이라면 왼쪽으로 세팅하는 소스들이 있다.

 

 

해당 관련된 소스를 개발하면 아래와 같은 모듈을 완성시킬수 있다. 사람의 이미지나 닉네임, 메시지를 정의할수있다.

전체적인 패키지는 아래와 같이 정리했다.

 

 

 

이렇게 하면 파이어베이스를 활용한 채팅 모듈을 개발할 수 있다.

반응형