Skip to content

Commit

Permalink
Fixes issue AOSSIE-Org#28
Browse files Browse the repository at this point in the history
  • Loading branch information
khushi-hura committed Jan 11, 2025
1 parent 84171dc commit b199c98
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 147 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Optional: only required for FILE payloads -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<application
android:label="openpeerchat_flutter"
android:name="${applicationName}"
Expand Down
1 change: 1 addition & 0 deletions assets/audioAnimation.json

Large diffs are not rendered by default.

222 changes: 148 additions & 74 deletions lib/components/message_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lottie/lottie.dart';
import 'package:nanoid/nanoid.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:provider/provider.dart';
Expand All @@ -16,14 +17,9 @@ import '../classes/audio_playback.dart';
import '../classes/audio_recording.dart';
import 'package:permission_handler/permission_handler.dart';

/// This component is used in the ChatPage.
/// It is the message bar where the message is typed on and sent to
/// connected devices.
Future<bool> requestPermissions() async {
var micStatus = await Permission.microphone.request();
var storageStatus = await Permission.storage.request();
return micStatus.isGranted && storageStatus.isGranted;
return micStatus.isGranted;
}

class MessagePanel extends StatefulWidget {
Expand All @@ -39,71 +35,107 @@ class _MessagePanelState extends State<MessagePanel> {
final AudioRecorder _audioRecorder = AudioRecorder();
final AudioPlayer _audioPlayer = AudioPlayer();
String? _recordingFilePath;

bool _isRecording = false;
Duration _recordingDuration = Duration.zero;
File _selectedFile = File('');

void initState() {
super.initState();
_audioRecorder.initRecorder();
}

@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,
),
),
IconButton(
icon: Icon(Icons.mic),
onPressed: _startRecording,
),
IconButton(
icon: Icon(Icons.stop),
onPressed: _stopRecording,
),
IconButton(
icon: Icon(Icons.send),
onPressed: () => _sendAudioMessage(context),
),
],
),
),
),
);
_audioPlayer.initPlayer();
}

void _startRecording() async {
if (await requestPermissions()) {
print("***********************************");
String? filePath = await _audioRecorder.startRecording();
setState(() {
_recordingFilePath = filePath;
_isRecording = true;
_recordingDuration = Duration.zero;
});
_startRecordingTimer();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Microphone permission denied.')),
);
}
}

void _stopRecording() async {
void _stopRecording({bool cancel = false}) async {
await _audioRecorder.stopRecording();
setState(() {
_isRecording = false;
if (cancel) {
_recordingFilePath = null;
}
});

if (!cancel && _recordingFilePath != null) {
_confirmAudioMessage();
}
}

void _startRecordingTimer() {
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (_isRecording) {
setState(() {
_recordingDuration += const Duration(seconds: 1);
});
return true;
}
return false;
});
}

void _confirmAudioMessage() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Send Audio Message'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Duration: ${_recordingDuration.inSeconds} seconds'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () => _playAudio(),
),
Lottie.asset('assets/audioAnimation.json',
height: 50, width: 50),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
_recordingFilePath = null;
});
Navigator.pop(context);
},
),
],
)
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_sendAudioMessage();
},
child: const Text('Send'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
],
);
},
);
}

void _playAudio() {
Expand All @@ -112,66 +144,108 @@ class _MessagePanelState extends State<MessagePanel> {
}
}

void _sendAudioMessage(BuildContext context) async {
// Ensure a recording exists
if (_recordingFilePath == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No audio recorded to send.')),
);
return;
}
void _sendAudioMessage() async {
if (_recordingFilePath == null) return;

var msgId = nanoid(21);
// Read the audio file content
Uint8List audioBytes = await File(_recordingFilePath!).readAsBytes();

// Encrypt the audio file content
RSAPublicKey publicKey = Global.myPublicKey!;
Uint8List encryptedAudio = rsaEncrypt(publicKey, audioBytes);

// Generate a unique ID for the message
var msgId = nanoid(21);
String fileName = _recordingFilePath!.split('/').last;

// Encode audio metadata for the message
String data = jsonEncode({
// Create the encrypted message payload
String myData = jsonEncode({
"sender": Global.myName,
"type": "audio",
"fileName": fileName,
"filePath": _recordingFilePath,
"data": base64Encode(encryptedAudio),
});

String date = DateTime.now().toUtc().toString();

// Save the message in cache
// Add the message to the local cache
Global.cache[msgId] = Payload(
msgId,
Global.myName,
widget.converser,
data,
myData,
date,
);

// Insert the message into the database
// Save the message to the database
insertIntoMessageTable(
Payload(
msgId,
Global.myName,
widget.converser,
data,
myData,
date,
),
);

// Send the message to the conversation
// Update the conversations in the UI
Provider.of<Global>(context, listen: false).sentToConversations(
Msg(data, "sent", date, msgId),
Msg(myData, "sent", date, msgId),
widget.converser,
);

// Notify user and reset the recording state
// Provide feedback to the user
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Audio message sent!')),
);

// Reset the recording state
setState(() {
_recordingFilePath = null; // Clear the recording path after sending
_recordingFilePath = null;
_recordingDuration = Duration.zero;
});
}


@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
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),
),
GestureDetector(
onLongPress: _startRecording,
onLongPressUp: () => _stopRecording(cancel: false),
onTapCancel: () => _stopRecording(cancel: true),
child: Icon(
_isRecording ? Icons.mic : Icons.mic_none,
color: _isRecording ? Colors.red : Colors.black,
),
),
IconButton(
onPressed: () => _sendMessage(context),
icon: const Icon(Icons.send),
),
],
),
),
),
);
}

void _sendMessage(BuildContext context) {
var msgId = nanoid(21);
if (myController.text.isEmpty) {
Expand Down
Loading

0 comments on commit b199c98

Please sign in to comment.