diff --git a/android/app/build.gradle b/android/app/build.gradle
index 57aaebe..9daee03 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -25,7 +25,7 @@ if (flutterVersionName == null) {
android {
namespace = "com.nankai.openpeerchat_flutter"
- compileSdk = flutter.compileSdkVersion
+ compileSdk 34
ndkVersion = flutter.ndkVersion
compileOptions {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index eb48cb0..a72da72 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,8 @@
+
+
initPlayer() async {
+ if(!_isInitialized){
+ await _player.openPlayer();
+ _isInitialized=true;
+ }
+ }
+
+ Future playAudio(String filePath) async {
+ await _player.startPlayer(fromURI: filePath);
+ }
+
+ Future stopAudio() async {
+ await _player.stopPlayer();
+ }
+
+ Future dispose() async {
+ if(_isInitialized){
+ await _player.closePlayer();
+ _isInitialized=false;
+ }
+ }
+}
diff --git a/lib/classes/audio_recording.dart b/lib/classes/audio_recording.dart
new file mode 100644
index 0000000..99d7ade
--- /dev/null
+++ b/lib/classes/audio_recording.dart
@@ -0,0 +1,34 @@
+import 'dart:io';
+import 'package:flutter_sound/flutter_sound.dart';
+import 'package:path_provider/path_provider.dart';
+
+class AudioRecorder {
+ final FlutterSoundRecorder _recorder = FlutterSoundRecorder();
+ bool _isRecording = false;
+
+ Future initRecorder() async {
+ await _recorder.openRecorder();
+ }
+
+ Future startRecording() async {
+ if (!_isRecording) {
+ Directory tempDir = await getTemporaryDirectory();
+ String filePath = '${tempDir.path}/audio_${DateTime.now().millisecondsSinceEpoch}.aac';
+ await _recorder.startRecorder(toFile: filePath);
+ _isRecording = true;
+ return filePath;
+ }
+ return null;
+ }
+
+ Future stopRecording() async {
+ if (_isRecording) {
+ await _recorder.stopRecorder();
+ _isRecording = false;
+ }
+ }
+
+ Future dispose() async {
+ await _recorder.closeRecorder();
+ }
+}
diff --git a/lib/components/message_panel.dart b/lib/components/message_panel.dart
index c5db219..eab3bd3 100644
--- a/lib/components/message_panel.dart
+++ b/lib/components/message_panel.dart
@@ -12,11 +12,20 @@ import '../classes/payload.dart';
import '../database/database_helper.dart';
import '../encyption/rsa.dart';
import 'view_file.dart';
+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 requestPermissions() async {
+ var micStatus = await Permission.microphone.request();
+ var storageStatus = await Permission.storage.request();
+ return micStatus.isGranted && storageStatus.isGranted;
+}
+
class MessagePanel extends StatefulWidget {
const MessagePanel({Key? key, required this.converser}) : super(key: key);
final String converser;
@@ -27,8 +36,17 @@ class MessagePanel extends StatefulWidget {
class _MessagePanelState extends State {
TextEditingController myController = TextEditingController();
+ final AudioRecorder _audioRecorder = AudioRecorder();
+ final AudioPlayer _audioPlayer = AudioPlayer();
+ String? _recordingFilePath;
+
File _selectedFile = File('');
+ void initState() {
+ super.initState();
+ _audioRecorder.initRecorder();
+ }
+
@override
Widget build(BuildContext context) {
return Padding(
@@ -55,6 +73,18 @@ class _MessagePanelState extends State {
Icons.send,
),
),
+ IconButton(
+ icon: Icon(Icons.mic),
+ onPressed: _startRecording,
+ ),
+ IconButton(
+ icon: Icon(Icons.stop),
+ onPressed: _stopRecording,
+ ),
+ IconButton(
+ icon: Icon(Icons.send),
+ onPressed: () => _sendAudioMessage(context),
+ ),
],
),
),
@@ -62,6 +92,86 @@ class _MessagePanelState extends State {
);
}
+ void _startRecording() async {
+ if (await requestPermissions()) {
+ print("***********************************");
+ String? filePath = await _audioRecorder.startRecording();
+ setState(() {
+ _recordingFilePath = filePath;
+ });
+ }
+ }
+
+ void _stopRecording() async {
+ await _audioRecorder.stopRecording();
+ }
+
+ void _playAudio() {
+ if (_recordingFilePath != null) {
+ _audioPlayer.playAudio(_recordingFilePath!);
+ }
+ }
+
+ 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;
+ }
+
+ var msgId = nanoid(21);
+
+ String fileName = _recordingFilePath!.split('/').last;
+
+ // Encode audio metadata for the message
+ String data = jsonEncode({
+ "sender": Global.myName,
+ "type": "audio",
+ "fileName": fileName,
+ "filePath": _recordingFilePath,
+ });
+
+ String date = DateTime.now().toUtc().toString();
+
+ // Save the message in cache
+ Global.cache[msgId] = Payload(
+ msgId,
+ Global.myName,
+ widget.converser,
+ data,
+ date,
+ );
+
+ // Insert the message into the database
+ insertIntoMessageTable(
+ Payload(
+ msgId,
+ Global.myName,
+ widget.converser,
+ data,
+ date,
+ ),
+ );
+
+ // Send the message to the conversation
+ Provider.of(context, listen: false).sentToConversations(
+ Msg(data, "sent", date, msgId),
+ widget.converser,
+ );
+
+ // Notify user and reset the recording state
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Audio message sent!')),
+ );
+
+ setState(() {
+ _recordingFilePath = null; // Clear the recording path after sending
+ });
+}
+
+
void _sendMessage(BuildContext context) {
var msgId = nanoid(21);
if (myController.text.isEmpty) {
diff --git a/lib/main.dart b/lib/main.dart
index 67ce4fc..41de4be 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -8,6 +8,41 @@ import 'classes/global.dart';
import 'encyption/key_storage.dart';
import 'encyption/rsa.dart';
+final ThemeData lightTheme = ThemeData(
+ brightness: Brightness.light,
+ primaryColor: Colors.blue,
+ scaffoldBackgroundColor: Colors.white,
+ textTheme: TextTheme(
+ displayLarge: TextStyle(
+ fontSize: 24.0,
+ fontWeight: FontWeight.bold,
+ color: Colors.black,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16.0,
+ color: Colors.black87,
+ ),
+ ),
+);
+
+final ThemeData darkTheme = ThemeData(
+ brightness: Brightness.dark,
+ primaryColor: Colors.grey[900],
+ hintColor: Colors.blueAccent,
+ scaffoldBackgroundColor: Colors.grey[850],
+ textTheme: TextTheme(
+ displayLarge: TextStyle(
+ fontSize: 24.0,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16.0,
+ color: Colors.white70,
+ ),
+ ),
+);
+
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -48,10 +83,13 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return const MaterialApp(
+ return MaterialApp(
debugShowCheckedModeBanner: false,
onGenerateRoute: generateRoute,
initialRoute: '/',
+ theme: lightTheme,
+ darkTheme: darkTheme,
+ themeMode: ThemeMode.system,
);
}
}
diff --git a/lib/pages/home_screen.dart b/lib/pages/home_screen.dart
index ea87b16..1bfe894 100644
--- a/lib/pages/home_screen.dart
+++ b/lib/pages/home_screen.dart
@@ -4,6 +4,7 @@ import 'chat_list_screen.dart';
import '../classes/global.dart';
import '../p2p/adhoc_housekeeping.dart';
import 'device_list_screen.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import '../database/database_helper.dart';
@@ -12,6 +13,43 @@ import '../database/database_helper.dart';
/// As the app launches and navigates to the HomeScreen from the Profile screen,
/// all the processes of message hopping are being initiated from this page.
+const String themePreferenceKey = 'themePreference';
+
+final ThemeData lightTheme = ThemeData(
+ brightness: Brightness.light,
+ primaryColor: Colors.blue,
+ scaffoldBackgroundColor: Colors.white,
+ textTheme: TextTheme(
+ displayLarge: TextStyle(
+ fontSize: 24.0,
+ fontWeight: FontWeight.bold,
+ color: Colors.black,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16.0,
+ color: Colors.black87,
+ ),
+ ),
+);
+
+final ThemeData darkTheme = ThemeData(
+ brightness: Brightness.dark,
+ primaryColor: Colors.grey[900],
+ hintColor: Colors.blueAccent,
+ scaffoldBackgroundColor: Colors.grey[850],
+ textTheme: TextTheme(
+ displayLarge: TextStyle(
+ fontSize: 24.0,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16.0,
+ color: Colors.white70,
+ ),
+ ),
+);
+
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@@ -22,11 +60,15 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State {
bool isLoading = false;
+ //initial theme of the system
+ ThemeMode _themeMode = ThemeMode.system;
+
@override
void initState() {
super.initState();
// init(context);
refreshMessages();
+ _loadTheme();
}
/// After reading all the cache, the home screen becomes visible.
@@ -53,49 +95,86 @@ class _HomeScreenState extends State {
super.dispose();
}
+ Future _loadTheme() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ int? themeIndex = prefs.getInt(themePreferenceKey);
+ if (themeIndex != null) {
+ setState(() {
+ _themeMode = ThemeMode.values[themeIndex];
+ });
+ }
+ }
+
+ Future _saveTheme(ThemeMode mode) async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.setInt(themePreferenceKey, mode.index);
+ }
+
+ void _toggleTheme() {
+ setState(() {
+ _themeMode = _themeMode == ThemeMode.light
+ ? ThemeMode.dark
+ : ThemeMode.light;
+ });
+ _saveTheme(_themeMode);
+ }
+
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
- child: Scaffold(
- key: Global.scaffoldKey,
- appBar: AppBar(
- title: const Text("AOSSIE"),
- actions: [
- IconButton(
- icon: const Icon(Icons.person),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => const Profile(
- onLogin: false,
+ child: Theme(
+ data: ThemeData(
+ brightness: _themeMode == ThemeMode.dark
+ ? Brightness.dark
+ : Brightness.light,
+ // You can further customize the theme here if needed
+ ),
+ child: Scaffold(
+ key: Global.scaffoldKey,
+ appBar: AppBar(
+ title: const Text("AOSSIE"),
+ actions: [
+ //toggle button fro light and dark themes
+ IconButton(
+ onPressed: _toggleTheme,
+ icon: Icon(Icons.toggle_off_outlined),
+ ),
+ IconButton(
+ icon: const Icon(Icons.person),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const Profile(
+ onLogin: false,
+ ),
),
- ),
- );
- },
- ),
- ],
- bottom: const TabBar(
- tabs: [
- Tab(
- text: "Devices",
+ );
+ },
),
- Tab(
- text: "All Chats",
+ ],
+ bottom: const TabBar(
+ tabs: [
+ Tab(
+ text: "Devices",
+ ),
+ Tab(
+ text: "All Chats",
+ ),
+ ],
+ ),
+ ),
+ body: const TabBarView(
+ children: [
+ DevicesListScreen(
+ deviceType: DeviceType.browser,
),
+ ChatListScreen(),
],
),
),
- body: const TabBarView(
- children: [
- DevicesListScreen(
- deviceType: DeviceType.browser,
- ),
- ChatListScreen(),
- ],
- ),
),
);
}
-}
+}
\ No newline at end of file
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 14cd431..9d4b458 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -9,7 +9,7 @@ import flutter_secure_storage_macos
import local_auth_darwin
import path_provider_foundation
import shared_preferences_foundation
-import sqflite
+import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
diff --git a/pubspec.yaml b/pubspec.yaml
index b3040f7..3d11fef 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -32,6 +32,7 @@ dependencies:
open_filex: ^4.5.0
permission_handler: ^11.3.1
path_provider: ^2.1.4
+ flutter_sound:
dev_dependencies:
flutter_lints: