mirror of
https://github.com/AOSSIE-Org/OpenPeerChat-flutter.git
synced 2025-08-14 18:47:51 +08:00
254 lines
6.6 KiB
Dart
254 lines
6.6 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:nanoid/nanoid.dart';
|
|
import 'package:pointycastle/asymmetric/api.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../classes/global.dart';
|
|
import '../classes/msg.dart';
|
|
import '../classes/payload.dart';
|
|
import '../database/database_helper.dart';
|
|
import '../encyption/rsa.dart';
|
|
import 'view_file.dart';
|
|
|
|
/// This component is used in the ChatPage.
|
|
/// It is the message bar where the message is typed on and sent to
|
|
/// connected devices.
|
|
|
|
class MessagePanel extends StatefulWidget {
|
|
const MessagePanel({Key? key, required this.converser}) : super(key: key);
|
|
final String converser;
|
|
|
|
@override
|
|
State<MessagePanel> createState() => _MessagePanelState();
|
|
}
|
|
|
|
class _MessagePanelState extends State<MessagePanel> {
|
|
TextEditingController myController = TextEditingController();
|
|
File _selectedFile = File('');
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: TextFormField(
|
|
//multiline text field
|
|
maxLines: null,
|
|
controller: myController,
|
|
decoration: InputDecoration(
|
|
icon: const Icon(Icons.person),
|
|
hintText: 'Send Message?',
|
|
labelText: 'Send Message ',
|
|
suffixIcon: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
onPressed: () => _navigateToFilePreviewPage(context),
|
|
icon: const Icon(Icons.attach_file),
|
|
),
|
|
IconButton(
|
|
onPressed: () => _sendMessage(context),
|
|
icon: const Icon(
|
|
Icons.send,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _sendMessage(BuildContext context) {
|
|
var msgId = nanoid(21);
|
|
if (myController.text.isEmpty) {
|
|
return;
|
|
}
|
|
// Encode the message to base64
|
|
|
|
String data = jsonEncode({
|
|
"sender": Global.myName,
|
|
"type": "text",
|
|
"data": myController.text,
|
|
});
|
|
|
|
String date = DateTime.now().toUtc().toString();
|
|
|
|
Global.cache[msgId] = Payload(
|
|
msgId,
|
|
Global.myName,
|
|
widget.converser,
|
|
data,
|
|
date,
|
|
);
|
|
insertIntoMessageTable(
|
|
Payload(
|
|
msgId,
|
|
Global.myName,
|
|
widget.converser,
|
|
data,
|
|
date,
|
|
),
|
|
);
|
|
|
|
RSAPublicKey publicKey = Global.myPublicKey!;
|
|
// Encrypt the message
|
|
Uint8List encryptedMessage = rsaEncrypt(
|
|
publicKey, Uint8List.fromList(utf8.encode(myController.text)));
|
|
|
|
String myData = jsonEncode({
|
|
"sender": Global.myName,
|
|
"type": "text",
|
|
"data": base64Encode(encryptedMessage),
|
|
});
|
|
|
|
Provider.of<Global>(context, listen: false).sentToConversations(
|
|
Msg(myData, "sent", date, msgId),
|
|
widget.converser,
|
|
);
|
|
|
|
// refreshMessages();
|
|
myController.clear();
|
|
}
|
|
|
|
/// This function is used to navigate to the file preview page and check the file size.
|
|
void _navigateToFilePreviewPage(BuildContext context) async {
|
|
//max size of file is 30 MB
|
|
double sizeKbs = 0;
|
|
const int maxSizeKbs = 30 * 1024;
|
|
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
if(result != null) {
|
|
sizeKbs = result.files.single.size / 1024;
|
|
}
|
|
|
|
|
|
if (sizeKbs > maxSizeKbs) {
|
|
if (!context.mounted) return;
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: const Text('File Size Exceeded'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ListTile(
|
|
//file size in MB
|
|
title: Text('File Size: ${(sizeKbs / 1024).ceil()} MB'),
|
|
subtitle: const Text(
|
|
'File size should not exceed 30 MB'),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: const Text('Close'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
return;
|
|
}
|
|
|
|
//this function is used to open the file preview dialog
|
|
if (result != null) {
|
|
setState(() {
|
|
_selectedFile = File(result.files.single.path!);
|
|
});
|
|
if (!context.mounted) return;
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: const Text('File Preview'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ListTile(
|
|
|
|
title: Text('File Name: ${_selectedFile.path
|
|
.split('/')
|
|
.last}', overflow: TextOverflow.ellipsis,),
|
|
subtitle: Text(
|
|
'File Size: ${(sizeKbs / 1024).floor()} MB'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => FilePreview.openFile(_selectedFile.path),
|
|
child: const Text('Open File'),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: const Text('Close'),
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
_sendFileMessage(context, _selectedFile);
|
|
|
|
},
|
|
icon: const Icon(
|
|
Icons.send,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/// This function is used to send the file message.
|
|
void _sendFileMessage(BuildContext context, File file) async{
|
|
var msgId = nanoid(21);
|
|
|
|
String fileName = _selectedFile.path.split('/').last;
|
|
String filePath = file.path;
|
|
|
|
String data = jsonEncode({
|
|
"sender": Global.myName,
|
|
"type": "file",
|
|
"fileName": fileName,
|
|
"filePath": filePath,
|
|
});
|
|
|
|
String date = DateTime.now().toUtc().toString();
|
|
Global.cache[msgId] = Payload(
|
|
msgId,
|
|
Global.myName,
|
|
widget.converser,
|
|
data,
|
|
date,
|
|
);
|
|
insertIntoMessageTable(
|
|
Payload(
|
|
msgId,
|
|
Global.myName,
|
|
widget.converser,
|
|
data,
|
|
date,
|
|
),
|
|
);
|
|
|
|
Provider.of<Global>(context, listen: false).sentToConversations(
|
|
Msg(data, "sent", date, msgId),
|
|
widget.converser,
|
|
);
|
|
|
|
}
|
|
|
|
}
|