본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓52(Chat - 4) 메시지 스타일

이번에는 채팅 화면을 좀 더 실제처럼 스타일을 적용해 보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

 

화면 구현은 아래와 같습니다. 

 

 

 

 

./src/screens/chat/chat.dart - 채팅 말풍성 위젯

 

import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import '../../models/chat_model.dart';

const roundedCorner = Radius.circular(20);

class Chat extends StatelessWidget {
  final Size size;
  final bool isMine;
  final ChatModel2 chatModel;

  const Chat(
      {Key? key,
        required this.size,
        required this.isMine,
        required this.chatModel})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return isMine ? _buildMyMsg(context) : _buildOthersMsg(context);
  }

// 내 메시지 표시
  Widget _buildMyMsg(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      crossAxisAlignment: CrossAxisAlignment.end,
      children: [
        Text(DateFormat('HH:mm').format(chatModel.createdDate)),
        const SizedBox(
          width: 8,
        ),
        Container(
          child: Text(
            chatModel.msg,
            style: Theme.of(context).textTheme.bodyText1!
                .copyWith(color: Colors.white),
          ),
          // 발품선의 여백 설정
          padding: const EdgeInsets.symmetric(
            vertical: 12,
            horizontal: 16,
          ),
          // 말풍선의 최소 높이 40, 화면 폭의 60% 사이즈,
          constraints: BoxConstraints(
            minHeight: 40,
            maxWidth: size.width * 0.6,
          ),
          decoration: const BoxDecoration(
            color: Colors.redAccent,
            borderRadius: BorderRadius.only(
              topLeft: roundedCorner,
              topRight: Radius.circular(2),
              bottomRight: roundedCorner,
              bottomLeft: roundedCorner,
            ),
          ),
        ),
      ],
    );
  }

// 상대방 메시지 표시
  Widget _buildOthersMsg(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        ExtendedImage.network(
          'https://randomuser.me/api/portraits/women/26.jpg',
          width: 36,
          height: 36,
          fit: BoxFit.cover,
          borderRadius: BorderRadius.circular(10),
          shape: BoxShape.rectangle,
        ),
        const SizedBox(width: 8),
        Row(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Container(
              child: Text(
                chatModel.msg,
                style: Theme.of(context).textTheme.bodyText1!
                    .copyWith(color: Colors.white),
              ),
              padding: const EdgeInsets.symmetric(
                vertical: 12,
                horizontal: 16,
              ),
              constraints: BoxConstraints(
                minHeight: 40,
                maxWidth: size.width * 0.55,
              ),
              decoration: BoxDecoration(
                color: Colors.grey[300]!,
                borderRadius: const BorderRadius.only(
                  topLeft: Radius.circular(2),
                  topRight: roundedCorner,
                  bottomRight: roundedCorner,
                  bottomLeft: roundedCorner,
                ),
              ),
            ),
            const SizedBox(width: 8),
            Text(DateFormat('HH:mm').format(chatModel.createdDate)),
          ],
        ),
      ],
    );
  }
}

 

 

 

./src/screens/chat/chatroom_screen.dart - 말풍선 스타일 호출 및 일자 구분용 디바이더 추가

 

Widget build(BuildContext context) {
  // logger.d('${Get.parameters['chatroomKey']}');
  Size _size = MediaQuery.of(context).size;
  UserModel1 userModel = UserController.to.userModel.value!;
  List<ChatModel2> _chatList = ChatController.to.chatList;
  Rxn<ChatroomModel2> chatroomModel = ChatController.to.chatroomModel;

  return Scaffold(
    appBar: AppBar(),
    backgroundColor: Colors.grey[200],
    // 화면 하단의 메뉴바 때문에 SafeArea 로 wrapping 해야 오동작을 방지함.
    body: SafeArea(
      child: Column(
        children: [
          // 게시글 정보를 간략히 표시
          _buildItemInfo(context),
          // 채팅 메시지 표시 부분
          Expanded(
            child: Obx(
              () => Container(
                // color: Colors.yellowAccent,
                child: ListView.separated(
                  shrinkWrap: true,
                  // 최신 메시지가 아래에 위치하게 설정,
                  reverse: true,
                  padding: const EdgeInsets.all(16),
                  itemBuilder: (context, index) {
                    bool _isMine = _chatList[index].userKey == userModel.userKey;
                    // 말풍선 호출
                    return Chat(
                      size: _size,
                      isMine: _isMine,
                      chatModel: _chatList[index], //chatNotifier.chatList[index],
                    );
                  },
                  separatorBuilder: (context, index) {
                  // 일자 구분용 디바이더 추가
                    if (DateFormat('yyyy-MM-dd').format(_chatList[index].createdDate) ==
                        DateFormat('yyyy-MM-dd').format(_chatList[index + 1].createdDate)) {
                      return const SizedBox(height: 12);
                    } else {
                      return Center(
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(
                            DateFormat('yyyy-MM-dd').format(_chatList[index].createdDate),
                          ),
                        ),
                      );
                    }
                  },
                  itemCount: _chatList.length, //chatNotifier.chatList.length,
                ),
              ),
            ),
          ),
          const Padding(padding: EdgeInsets.all(4)),
          // 메시지 입력 창
          _buildInputBar(userModel)
        ],
      ),
    ),
  );
}