2023-08-10 19:08:46 +08:00
|
|
|
import 'dart:convert';
|
2023-08-15 10:53:30 +08:00
|
|
|
import 'dart:io';
|
2023-08-10 19:08:46 +08:00
|
|
|
|
2023-06-21 17:44:28 +08:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
2023-08-10 19:08:46 +08:00
|
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
2023-08-15 10:53:30 +08:00
|
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
import 'package:badges/badges.dart' as badges;
|
2023-08-10 19:08:46 +08:00
|
|
|
|
2024-04-09 17:23:28 +08:00
|
|
|
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';
|
2023-06-21 17:44:28 +08:00
|
|
|
import 'input_icon_button.dart';
|
|
|
|
|
|
|
|
class MessageInputBox extends StatefulWidget {
|
2023-08-10 19:08:46 +08:00
|
|
|
const MessageInputBox({
|
|
|
|
super.key,
|
2023-08-23 16:30:41 +08:00
|
|
|
required this.chatType,
|
2023-08-10 19:08:46 +08:00
|
|
|
required this.contactId,
|
|
|
|
required this.scrollController,
|
|
|
|
});
|
|
|
|
|
2023-08-23 16:30:41 +08:00
|
|
|
final int chatType;
|
2023-08-10 19:08:46 +08:00
|
|
|
final String contactId;
|
|
|
|
final ScrollController scrollController;
|
2023-06-21 17:44:28 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<MessageInputBox> createState() => _MessageInputBoxState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MessageInputBoxState extends State<MessageInputBox> {
|
2023-08-10 19:08:46 +08:00
|
|
|
final TextEditingController _controller = TextEditingController();
|
2023-08-15 10:53:30 +08:00
|
|
|
final ImagePicker _picker = ImagePicker();
|
2023-08-10 19:08:46 +08:00
|
|
|
bool _hasMsg = false;
|
|
|
|
|
2023-10-01 18:40:14 +08:00
|
|
|
late Box<ChatSetting> _chatSettingBox;
|
2023-08-10 19:08:46 +08:00
|
|
|
late Box<MessageT> _messageTBox;
|
|
|
|
|
2023-08-15 10:53:30 +08:00
|
|
|
List<XFile> _imageFileList = [];
|
|
|
|
|
2023-06-21 17:44:28 +08:00
|
|
|
@override
|
2023-08-10 19:08:46 +08:00
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2023-10-01 18:40:14 +08:00
|
|
|
_chatSettingBox = Hive.box<ChatSetting>('chat_setting');
|
2023-08-10 19:08:46 +08:00
|
|
|
_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 {
|
2023-08-15 10:53:30 +08:00
|
|
|
if (_imageFileList.isEmpty) {
|
|
|
|
setState(() {
|
|
|
|
_hasMsg = false;
|
|
|
|
});
|
|
|
|
}
|
2023-08-10 19:08:46 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
minLines: null,
|
|
|
|
maxLines: null,
|
|
|
|
controller: _controller,
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
isCollapsed: true,
|
|
|
|
border: UnderlineInputBorder(
|
|
|
|
borderSide: BorderSide.none,
|
2023-06-21 17:44:28 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2023-08-10 19:08:46 +08:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
width: 10,
|
|
|
|
),
|
|
|
|
FilledButton(
|
|
|
|
onPressed: () {
|
|
|
|
_sendMsg();
|
|
|
|
},
|
|
|
|
style: FilledButton.styleFrom(
|
|
|
|
padding: const EdgeInsets.all(0),
|
|
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
backgroundColor:
|
|
|
|
_hasMsg ? kPrimaryColor : kPrimaryColor.withAlpha(50),
|
2023-06-21 17:44:28 +08:00
|
|
|
),
|
2023-08-10 19:08:46 +08:00
|
|
|
child: const Text(
|
|
|
|
'发送',
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 16,
|
2023-06-21 17:44:28 +08:00
|
|
|
),
|
|
|
|
),
|
2023-08-10 19:08:46 +08:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
width: 10,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: [
|
|
|
|
InputIconButton(
|
|
|
|
onPressed: () {},
|
2023-08-15 10:53:30 +08:00
|
|
|
icon: const Icon(Icons.mic),
|
2023-08-10 19:08:46 +08:00
|
|
|
),
|
|
|
|
InputIconButton(
|
|
|
|
onPressed: () {},
|
|
|
|
icon: const Icon(Icons.call),
|
|
|
|
),
|
2023-08-15 10:53:30 +08:00
|
|
|
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),
|
|
|
|
),
|
2023-08-10 19:08:46 +08:00
|
|
|
),
|
|
|
|
InputIconButton(
|
|
|
|
onPressed: () {},
|
|
|
|
icon: const Icon(Icons.emoji_emotions),
|
|
|
|
),
|
|
|
|
InputIconButton(
|
|
|
|
onPressed: () {},
|
|
|
|
icon: const Icon(Icons.add_box_rounded),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-15 10:53:30 +08:00
|
|
|
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) {
|
2023-08-23 16:30:41 +08:00
|
|
|
print('Error when pick image: $e');
|
2023-08-15 10:53:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _sendMsg() async {
|
2023-08-10 19:08:46 +08:00
|
|
|
if (!_hasMsg) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
DateTime now = DateTime.now();
|
2023-08-23 16:30:41 +08:00
|
|
|
bool isShowTime = _isShowTime(now);
|
|
|
|
String senderId = getIt.get<UserAccount>().id;
|
|
|
|
String text = _controller.text;
|
|
|
|
String type = 'text/multipart';
|
2023-08-15 10:53:30 +08:00
|
|
|
List<String> attachments = [];
|
2023-10-01 18:40:14 +08:00
|
|
|
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);
|
2023-10-04 11:50:28 +08:00
|
|
|
String baseImageDir = getIt.get<UserProfile>().baseImageDir;
|
2023-08-15 10:53:30 +08:00
|
|
|
|
|
|
|
if (_imageFileList.isNotEmpty) {
|
2023-10-04 11:50:28 +08:00
|
|
|
final dir = Directory('$baseImageDir/$dirTime');
|
|
|
|
|
|
|
|
if (!(await dir.exists())) {
|
|
|
|
await dir.create(recursive: true);
|
|
|
|
}
|
|
|
|
|
2023-08-15 10:53:30 +08:00
|
|
|
for (var i = 0; i < _imageFileList.length; i++) {
|
2023-10-01 18:40:14 +08:00
|
|
|
String filename = '$dirTime/${getRandomFilename()}';
|
|
|
|
int totalChunkNum =
|
|
|
|
((await _imageFileList[i].length()) / chunkSize).ceil();
|
2023-10-04 11:50:28 +08:00
|
|
|
|
2023-10-01 18:40:14 +08:00
|
|
|
attachments.add(filename);
|
|
|
|
apsBox.put(
|
2023-10-04 11:50:28 +08:00
|
|
|
filename,
|
|
|
|
AttachmentProgress(0, totalChunkNum, 0, true, false),
|
|
|
|
);
|
|
|
|
|
|
|
|
await _imageFileList[i].saveTo('$baseImageDir/${attachments[i]}');
|
2023-08-15 10:53:30 +08:00
|
|
|
}
|
|
|
|
}
|
2023-09-16 11:33:57 +08:00
|
|
|
|
2023-10-01 18:40:14 +08:00
|
|
|
final msgId = formatMsgIDFromTime(now);
|
2023-09-16 11:33:57 +08:00
|
|
|
|
2023-09-23 17:16:31 +08:00
|
|
|
_messageTBox.add(
|
2023-10-04 11:50:28 +08:00
|
|
|
MessageT(msgId, senderId, text, type, now, isShowTime, attachments),
|
2023-08-10 19:08:46 +08:00
|
|
|
);
|
|
|
|
|
2023-10-01 18:40:14 +08:00
|
|
|
Box<int> msgIndexBox =
|
|
|
|
await Hive.openBox<int>('msg_index_${widget.contactId}');
|
|
|
|
msgIndexBox.put(msgId, _messageTBox.length - 1);
|
|
|
|
|
2023-08-10 19:08:46 +08:00
|
|
|
Future.delayed(
|
|
|
|
const Duration(milliseconds: 50),
|
|
|
|
() => widget.scrollController.animateTo(
|
2023-08-15 10:53:30 +08:00
|
|
|
0,
|
2023-08-10 19:08:46 +08:00
|
|
|
duration: const Duration(milliseconds: 500),
|
|
|
|
curve: Curves.linear,
|
|
|
|
),
|
|
|
|
);
|
2023-08-15 10:53:30 +08:00
|
|
|
|
2023-08-23 16:30:41 +08:00
|
|
|
final msg = {
|
|
|
|
'type': 'text/multipart',
|
2023-09-16 11:33:57 +08:00
|
|
|
'msgId': msgId,
|
|
|
|
'senderId': senderId,
|
|
|
|
'text': text,
|
|
|
|
'attachments': attachments,
|
|
|
|
'dateTime': now.toString(),
|
|
|
|
'isShowTime': isShowTime,
|
|
|
|
};
|
|
|
|
|
2023-08-23 16:30:41 +08:00
|
|
|
if (widget.chatType == 0) {
|
2023-10-01 18:40:14 +08:00
|
|
|
msg['event'] = 'friend-chat-msg';
|
|
|
|
msg['receiverId'] = widget.contactId;
|
|
|
|
getIt.get<WebSocketManager>().channel.sink.add(json.encode(msg));
|
2023-08-23 16:30:41 +08:00
|
|
|
} else {
|
|
|
|
List<String> receiverIds = getIt
|
|
|
|
.get<ContactAccountProfile>()
|
|
|
|
.groupChats[widget.contactId]!
|
|
|
|
.members;
|
2023-10-01 18:40:14 +08:00
|
|
|
|
2023-08-23 16:30:41 +08:00
|
|
|
receiverIds.remove(senderId);
|
|
|
|
msg['event'] = 'group-chat-msg';
|
|
|
|
msg['groupChatId'] = widget.contactId;
|
|
|
|
msg['receiverIds'] = receiverIds;
|
2023-09-18 19:32:04 +08:00
|
|
|
msg['nickname'] = getIt.get<UserProfile>().nickname;
|
|
|
|
msg['remarkInGroupChat'] =
|
|
|
|
getIt.get<Contact>().groupChats[widget.contactId]!.remarkInGroupChat;
|
|
|
|
msg['avatar'] = getIt.get<UserProfile>().avatar;
|
2023-08-23 16:30:41 +08:00
|
|
|
getIt.get<WebSocketManager>().channel.sink.add(json.encode(msg));
|
2023-08-15 10:53:30 +08:00
|
|
|
}
|
2023-08-23 16:30:41 +08:00
|
|
|
|
2023-08-15 10:53:30 +08:00
|
|
|
_controller.text = '';
|
|
|
|
setState(() {
|
2023-10-04 11:50:28 +08:00
|
|
|
_imageFileList.clear();
|
2023-08-15 10:53:30 +08:00
|
|
|
_imageFileList = [];
|
|
|
|
_hasMsg = false;
|
|
|
|
});
|
2023-08-23 16:30:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool _isShowTime(DateTime now) {
|
|
|
|
int messageCount = _messageTBox.length;
|
|
|
|
|
|
|
|
if (messageCount == 0) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
MessageT? message = _messageTBox.getAt(messageCount - 1);
|
|
|
|
DateTime lastTime = message!.dateTime;
|
2023-10-04 11:50:28 +08:00
|
|
|
int differenceInMinutes = now.difference(lastTime).inMinutes;
|
2023-08-23 16:30:41 +08:00
|
|
|
return differenceInMinutes > 8 ? true : false;
|
2023-08-15 10:53:30 +08:00
|
|
|
}
|
2023-08-10 19:08:46 +08:00
|
|
|
}
|
2023-06-21 17:44:28 +08:00
|
|
|
}
|