import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:image_picker/image_picker.dart'; import 'package:badges/badges.dart' as badges; import '/common/constants.dart'; import '/database/box_type.dart'; import '/models/contact_model.dart'; import '/models/init_get_it.dart'; import '/models/user_model.dart'; import '/models/websocket_model.dart'; import '/utils/app_dir.dart'; import '/utils/format_datetime.dart'; import '/utils/ws_receive_callback.dart'; import 'input_icon_button.dart'; class MessageInputBox extends StatefulWidget { const MessageInputBox({ super.key, required this.chatType, required this.contactId, required this.scrollController, }); final int chatType; final String contactId; final ScrollController scrollController; @override State createState() => _MessageInputBoxState(); } class _MessageInputBoxState extends State { final TextEditingController _controller = TextEditingController(); final ImagePicker _picker = ImagePicker(); bool _hasMsg = false; late Box _chatSettingBox; late Box _messageTBox; List _imageFileList = []; @override void initState() { super.initState(); _chatSettingBox = Hive.box('chat_setting'); _messageTBox = Hive.box('message_${widget.contactId}'); } @override void dispose() { super.dispose(); _controller.dispose(); } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( boxShadow: [ BoxShadow( color: kPrimaryColor.withOpacity(0.5), blurRadius: 30, // Shadow spared from the edge of the container blurStyle: BlurStyle.outer, ), ], ), padding: const EdgeInsets.only(top: 6), child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ const SizedBox( width: 10, ), Expanded( child: Container( padding: const EdgeInsets.all(10), constraints: const BoxConstraints(maxHeight: 120), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), // Container color will be imply to its child color: Theme.of(context).brightness == Brightness.dark ? const Color.fromARGB(255, 61, 61, 61) : kPrimaryColor.withOpacity(0.2), ), child: TextField( onChanged: (value) { if (value.isNotEmpty) { setState(() { _hasMsg = true; }); } else { if (_imageFileList.isEmpty) { setState(() { _hasMsg = false; }); } } }, minLines: null, maxLines: null, controller: _controller, decoration: const InputDecoration( isCollapsed: true, border: UnderlineInputBorder( borderSide: BorderSide.none, ), ), ), ), ), const SizedBox( width: 10, ), FilledButton( onPressed: () { _sendMsg(); }, style: FilledButton.styleFrom( padding: const EdgeInsets.all(0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: _hasMsg ? kPrimaryColor : kPrimaryColor.withAlpha(50), ), child: const Text( '发送', style: TextStyle( fontSize: 16, ), ), ), const SizedBox( width: 10, ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ InputIconButton( onPressed: () {}, icon: const Icon(Icons.mic), ), InputIconButton( onPressed: () {}, icon: const Icon(Icons.call), ), badges.Badge( showBadge: _imageFileList.isNotEmpty, badgeStyle: const badges.BadgeStyle( badgeColor: kSecondaryColor, elevation: 12, ), badgeContent: Text(_imageFileList.length.toString()), position: badges.BadgePosition.topEnd(top: 0, end: 0), child: InputIconButton( onPressed: () { _pickImages(context); }, icon: const Icon(Icons.insert_photo), ), ), InputIconButton( onPressed: () {}, icon: const Icon(Icons.emoji_emotions), ), InputIconButton( onPressed: () {}, icon: const Icon(Icons.add_box_rounded), ), ], ), ], ), ); } void _pickImages(BuildContext context) async { try { List pickedImages = await _picker.pickMultiImage(); if (pickedImages.isNotEmpty) { setState(() { _imageFileList = pickedImages; _hasMsg = true; }); } else { setState(() { _imageFileList = []; }); if (_controller.text.isEmpty) { setState(() { _hasMsg = false; }); } } } catch (e) { print('Error when pick image: $e'); } } void _sendMsg() async { if (!_hasMsg) { return; } DateTime now = DateTime.now(); bool isShowTime = _isShowTime(now); String senderId = getIt.get().id; String text = _controller.text; String type = 'text/multipart'; List attachments = []; Box apsBox = Hive.box('attachment_send'); var chatSetting = _chatSettingBox.get(widget.contactId); if (chatSetting == null) { _chatSettingBox.put( widget.contactId, ChatSetting( widget.contactId, widget.chatType, false, true, false, now, 0, ), ); } else { chatSetting.latestDateTime = now; chatSetting.unreadCount = 0; chatSetting.isOpen = true; _chatSettingBox.put(widget.contactId, chatSetting); } String dirTime = formatDirTime(now); String baseImageDir = getIt.get().baseImageDir; if (_imageFileList.isNotEmpty) { final dir = Directory('$baseImageDir/$dirTime'); if (!(await dir.exists())) { await dir.create(recursive: true); } for (var i = 0; i < _imageFileList.length; i++) { String filename = '$dirTime/${getRandomFilename()}'; int totalChunkNum = ((await _imageFileList[i].length()) / chunkSize).ceil(); attachments.add(filename); apsBox.put( filename, AttachmentProgress(0, totalChunkNum, 0, true, false), ); await _imageFileList[i].saveTo('$baseImageDir/${attachments[i]}'); } } final msgId = formatMsgIDFromTime(now); _messageTBox.add( MessageT(msgId, senderId, text, type, now, isShowTime, attachments), ); Box msgIndexBox = await Hive.openBox('msg_index_${widget.contactId}'); msgIndexBox.put(msgId, _messageTBox.length - 1); Future.delayed( const Duration(milliseconds: 50), () => widget.scrollController.animateTo( 0, duration: const Duration(milliseconds: 500), curve: Curves.linear, ), ); final msg = { 'type': 'text/multipart', 'msgId': msgId, 'senderId': senderId, 'text': text, 'attachments': attachments, 'dateTime': now.toString(), 'isShowTime': isShowTime, }; if (widget.chatType == 0) { msg['event'] = 'friend-chat-msg'; msg['receiverId'] = widget.contactId; getIt.get().channel.sink.add(json.encode(msg)); } else { List receiverIds = getIt .get() .groupChats[widget.contactId]! .members; receiverIds.remove(senderId); msg['event'] = 'group-chat-msg'; msg['groupChatId'] = widget.contactId; msg['receiverIds'] = receiverIds; msg['nickname'] = getIt.get().nickname; msg['remarkInGroupChat'] = getIt.get().groupChats[widget.contactId]!.remarkInGroupChat; msg['avatar'] = getIt.get().avatar; getIt.get().channel.sink.add(json.encode(msg)); } _controller.text = ''; setState(() { _imageFileList.clear(); _imageFileList = []; _hasMsg = false; }); } bool _isShowTime(DateTime now) { int messageCount = _messageTBox.length; if (messageCount == 0) { return true; } else { MessageT? message = _messageTBox.getAt(messageCount - 1); DateTime lastTime = message!.dateTime; int differenceInMinutes = now.difference(lastTime).inMinutes; return differenceInMinutes > 8 ? true : false; } } }