import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; 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 'package:together_mobile/common/constants.dart'; import 'package:together_mobile/database/box_type.dart'; import 'package:together_mobile/models/contact_model.dart'; import 'package:together_mobile/models/init_get_it.dart'; import 'package:together_mobile/models/user_model.dart'; import 'package:together_mobile/models/websocket_model.dart'; import 'package:together_mobile/utils/app_dir.dart'; import 'package:together_mobile/utils/format_datetime.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 _chatBox; late Box _messageTBox; List _imageFileList = []; @override void initState() { super.initState(); _chatBox = 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 = []; if (_imageFileList.isNotEmpty) { String dirTime = formatDirTime(now); for (var i = 0; i < _imageFileList.length; i++) { attachments.add('$dirTime/${getRandomFilename()}'); } } _messageTBox.add( MessageT( senderId, text, type, now, isShowTime, attachments, ), ); Future.delayed( const Duration(milliseconds: 50), () => widget.scrollController.animateTo( 0, duration: const Duration(milliseconds: 500), curve: Curves.linear, ), ); final msg = { 'type': 'text/multipart', '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)); if (attachments.isNotEmpty) { String baseImageDir = getIt.get().baseImageDir; for (var i = 0; i < attachments.length; i++) { Uint8List bytes = await _imageFileList[i].readAsBytes(); File file = File('$baseImageDir/${attachments[i]}'); file.createSync(recursive: true); file.writeAsBytes(bytes); getIt.get().channel.sink.add( json.encode( { 'event': 'friend-chat-image', 'receiverId': widget.contactId, 'filename': attachments[i], 'bytes': bytes, }, ), ); } } } else { List receiverIds = getIt .get() .groupChats[widget.contactId]! .members; receiverIds.remove(senderId); msg['event'] = 'group-chat-msg'; msg['groupChatId'] = widget.contactId; msg['receiverIds'] = receiverIds; getIt.get().channel.sink.add(json.encode(msg)); if (attachments.isNotEmpty) { String baseImageDir = getIt.get().baseImageDir; for (var i = 0; i < attachments.length; i++) { Uint8List bytes = await _imageFileList[i].readAsBytes(); File file = File('$baseImageDir/${attachments[i]}'); file.createSync(recursive: true); file.writeAsBytes(bytes); getIt.get().channel.sink.add( json.encode( { 'event': 'group-chat-image', 'groupChatId': widget.contactId, 'receiverIds': receiverIds, 'filename': attachments[i], 'bytes': bytes, }, ), ); } } } _controller.text = ''; setState(() { _imageFileList = []; _hasMsg = false; }); var chatSetting = _chatBox.get(widget.contactId); if (chatSetting == null) { _chatBox.put( widget.contactId, ChatSetting( widget.contactId, widget.chatType, false, true, false, now, 0, ), ); } else { chatSetting.latestDateTime = now; chatSetting.unreadCount = 0; chatSetting.isOpen = true; _chatBox.put(widget.contactId, chatSetting); } } bool _isShowTime(DateTime now) { int messageCount = _messageTBox.length; if (messageCount == 0) { return true; } else { MessageT? message = _messageTBox.getAt(messageCount - 1); DateTime lastTime = message!.dateTime; var differenceInMinutes = now.difference(lastTime).inMinutes; return differenceInMinutes > 8 ? true : false; } } }