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/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.contactId, required this.scrollController, }); 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(e); } } void _sendMsg() async { if (!_hasMsg) { return; } DateTime now = DateTime.now(); late bool isShowTime; List attachments = []; int messageCount = _messageTBox.length; if (messageCount == 0) { isShowTime = true; } else { MessageT? message = _messageTBox.getAt(messageCount - 1); DateTime lastTime = message!.dateTime; var differenceInMinutes = now.difference(lastTime).inMinutes; isShowTime = differenceInMinutes > 8 ? true : false; } if (_imageFileList.isNotEmpty) { String dirTime = formatDirTime(now); for (var i = 0; i < _imageFileList.length; i++) { attachments.add('$dirTime/${getRandomFilename()}'); } } final msg = { 'event': 'one-to-one-chat', 'type': 'text/multipart', 'senderId': getIt.get().id, 'receiverId': widget.contactId, 'text': _controller.text, 'attachments': attachments, 'dateTime': now.toString(), 'isShowTime': isShowTime, }; _messageTBox.add( MessageT( msg['senderId']! as String, msg['text']! as String, msg['type']! as String, now, isShowTime, attachments, ), ); Future.delayed( const Duration(milliseconds: 50), () => widget.scrollController.animateTo( 0, duration: const Duration(milliseconds: 500), curve: Curves.linear, ), ); 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': 'chat-image', 'receiverId': widget.contactId, '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, false, true, false, now, 0), ); } } }