together_mobile/lib/screens/message/components/message_input_box.dart

338 lines
9.7 KiB
Dart

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<MessageInputBox> createState() => _MessageInputBoxState();
}
class _MessageInputBoxState extends State<MessageInputBox> {
final TextEditingController _controller = TextEditingController();
final ImagePicker _picker = ImagePicker();
bool _hasMsg = false;
late Box<ChatSetting> _chatSettingBox;
late Box<MessageT> _messageTBox;
List<XFile> _imageFileList = [];
@override
void initState() {
super.initState();
_chatSettingBox = Hive.box<ChatSetting>('chat_setting');
_messageTBox = Hive.box<MessageT>('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<XFile> 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<UserAccount>().id;
String text = _controller.text;
String type = 'text/multipart';
List<String> attachments = [];
Box<AttachmentProgress> 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<UserProfile>().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<int> msgIndexBox =
await Hive.openBox<int>('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<WebSocketManager>().channel.sink.add(json.encode(msg));
} else {
List<String> receiverIds = getIt
.get<ContactAccountProfile>()
.groupChats[widget.contactId]!
.members;
receiverIds.remove(senderId);
msg['event'] = 'group-chat-msg';
msg['groupChatId'] = widget.contactId;
msg['receiverIds'] = receiverIds;
msg['nickname'] = getIt.get<UserProfile>().nickname;
msg['remarkInGroupChat'] =
getIt.get<Contact>().groupChats[widget.contactId]!.remarkInGroupChat;
msg['avatar'] = getIt.get<UserProfile>().avatar;
getIt.get<WebSocketManager>().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;
}
}
}