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..f447eed 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,8 +11,11 @@
+
+
+
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..ffc6400 100644
--- a/lib/components/message_panel.dart
+++ b/lib/components/message_panel.dart
@@ -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';
@@ -12,10 +13,14 @@ 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();
+ return micStatus.isGranted;
+}
class MessagePanel extends StatefulWidget {
const MessagePanel({Key? key, required this.converser}) : super(key: key);
@@ -27,20 +32,192 @@ class MessagePanel extends StatefulWidget {
class _MessagePanelState extends State {
TextEditingController myController = TextEditingController();
+ 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();
+ _audioPlayer.initPlayer();
+ }
+
+ void _startRecording() async {
+ if (await requestPermissions()) {
+ 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({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() {
+ if (_recordingFilePath != null) {
+ _audioPlayer.playAudio(_recordingFilePath!);
+ }
+ }
+
+ void _sendAudioMessage() async {
+ if (_recordingFilePath == null) return;
+
+ // 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;
+
+ // Create the encrypted message payload
+ String myData = jsonEncode({
+ "sender": Global.myName,
+ "type": "audio",
+ "fileName": fileName,
+ "data": base64Encode(encryptedAudio),
+ });
+
+ String date = DateTime.now().toUtc().toString();
+
+ // Add the message to the local cache
+ Global.cache[msgId] = Payload(
+ msgId,
+ Global.myName,
+ widget.converser,
+ myData,
+ date,
+ );
+
+ // Save the message to the database
+ insertIntoMessageTable(
+ Payload(
+ msgId,
+ Global.myName,
+ widget.converser,
+ myData,
+ date,
+ ),
+ );
+
+ // Update the conversations in the UI
+ Provider.of(context, listen: false).sentToConversations(
+ Msg(myData, "sent", date, msgId),
+ widget.converser,
+ );
+
+ // Provide feedback to the user
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Audio message sent!')),
+ );
+
+ // Reset the recording state
+ setState(() {
+ _recordingFilePath = null;
+ _recordingDuration = Duration.zero;
+ });
+}
+
+
@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 ',
+ labelText: 'Send Message',
suffixIcon: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
@@ -49,11 +226,18 @@ class _MessagePanelState extends State {
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,
- ),
+ icon: const Icon(Icons.send),
),
],
),
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/chat_page.dart b/lib/pages/chat_page.dart
index 33a2518..d7c2bdc 100644
--- a/lib/pages/chat_page.dart
+++ b/lib/pages/chat_page.dart
@@ -11,6 +11,44 @@ import 'dart:convert';
import 'package:pointycastle/asymmetric/api.dart';
import '../components/view_file.dart';
import '../encyption/rsa.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+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 ChatPage extends StatefulWidget {
const ChatPage({Key? key, required this.converser}) : super(key: key);
@@ -24,10 +62,14 @@ class ChatPage extends StatefulWidget {
class ChatPageState extends State {
List messageList = [];
TextEditingController myController = TextEditingController();
+
+ //initial theme of the system
+ ThemeMode _themeMode = ThemeMode.system;
@override
void initState() {
super.initState();
+ _loadTheme();
}
@override
@@ -35,6 +77,28 @@ class ChatPageState extends State {
super.didChangeDependencies();
}
+ 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(bool value) {
+ setState(() {
+ _themeMode = value ? ThemeMode.dark : ThemeMode.light;
+ });
+ _saveTheme(_themeMode);
+ }
+
final ScrollController _scrollController = ScrollController();
@override
@@ -64,86 +128,101 @@ class ChatPageState extends State {
}
groupedMessages[date]!.add(msg);
}
-
- return Scaffold(
- appBar: AppBar(
- title: Text(widget.converser),
- ),
- body: Column(
- children: [
- Expanded(
- child: messageList.isEmpty
- ? const Center(
- child: Text('No messages yet'),
- )
- : ListView.builder(
- controller: _scrollController,
- padding: const EdgeInsets.all(8),
- itemCount: groupedMessages.keys.length,
- itemBuilder: (BuildContext context, int index) {
- String date = groupedMessages.keys.elementAt(index);
- return Column(
- children: [
- Center(
- child: Padding(
- padding: const EdgeInsets.only(top: 10),
- child: Text(
- date,
- style: const TextStyle(fontWeight: FontWeight.bold),
+ return Theme(
+ data: ThemeData(
+ brightness: _themeMode == ThemeMode.dark
+ ? Brightness.dark
+ : Brightness.light,
+ ),
+ child:
+ Scaffold(
+ appBar: AppBar(
+ title: Text(widget.converser),
+ actions: [
+ Switch(
+ value: _themeMode == ThemeMode.dark,
+ onChanged: _toggleTheme,
+ activeColor: Colors.blueAccent,
+ inactiveThumbColor: Colors.grey,
+ ),
+ ],
+ ),
+ body: Column(
+ children: [
+ Expanded(
+ child: messageList.isEmpty
+ ? const Center(
+ child: Text('No messages yet'),
+ )
+ : ListView.builder(
+ controller: _scrollController,
+ padding: const EdgeInsets.all(8),
+ itemCount: groupedMessages.keys.length,
+ itemBuilder: (BuildContext context, int index) {
+ String date = groupedMessages.keys.elementAt(index);
+ return Column(
+ children: [
+ Center(
+ child: Padding(
+ padding: const EdgeInsets.only(top: 10),
+ child: Text(
+ date,
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
),
),
- ),
- ...groupedMessages[date]!.map((msg) {
- String displayMessage = msg.message;
- if (Global.myPrivateKey != null) {
- RSAPrivateKey privateKey = Global.myPrivateKey!;
- dynamic data = jsonDecode(msg.message);
- if (data['type'] == 'text') {
- Uint8List encryptedBytes = base64Decode(data['data']);
- Uint8List decryptedBytes = rsaDecrypt(privateKey, encryptedBytes);
- displayMessage = utf8.decode(decryptedBytes);
+ ...groupedMessages[date]!.map((msg) {
+ String displayMessage = msg.message;
+ if (Global.myPrivateKey != null) {
+ RSAPrivateKey privateKey = Global.myPrivateKey!;
+ dynamic data = jsonDecode(msg.message);
+ if (data['type'] == 'text') {
+ Uint8List encryptedBytes = base64Decode(data['data']);
+ Uint8List decryptedBytes = rsaDecrypt(privateKey, encryptedBytes);
+ displayMessage = utf8.decode(decryptedBytes);
+ }
}
- }
- return Column(
- crossAxisAlignment: msg.msgtype == 'sent' ? CrossAxisAlignment.end : CrossAxisAlignment.start,
- children: [
- Align(
- alignment: msg.msgtype == 'sent' ? Alignment.centerRight : Alignment.centerLeft,
- child: Bubble(
- padding: const BubbleEdges.all(12),
- margin: const BubbleEdges.only(top: 10),
- //add shadow
- style: BubbleStyle(
- elevation: 3,
- shadowColor: Colors.black.withOpacity(0.5),
- ),
- // nip: msg.msgtype == 'sent' ? BubbleNip.rightTop : BubbleNip.leftTop,
- radius: const Radius.circular(10),
- color: msg.msgtype == 'sent' ? const Color(0xffd1c4e9) : const Color(0xff80DEEA),
- child: msg.message.contains('file') ? _buildFileBubble(msg) : Text(
- displayMessage,
- style: const TextStyle(color: Colors.black87),
+ return Column(
+ crossAxisAlignment: msg.msgtype == 'sent' ? CrossAxisAlignment.end : CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: msg.msgtype == 'sent' ? Alignment.centerRight : Alignment.centerLeft,
+ child: Bubble(
+ padding: const BubbleEdges.all(12),
+ margin: const BubbleEdges.only(top: 10),
+ //add shadow
+ style: BubbleStyle(
+ elevation: 3,
+ shadowColor: Colors.black.withOpacity(0.5),
+ ),
+ // nip: msg.msgtype == 'sent' ? BubbleNip.rightTop : BubbleNip.leftTop,
+ radius: const Radius.circular(10),
+ color: msg.msgtype == 'sent' ? const Color(0xffd1c4e9) : const Color(0xff80DEEA),
+ child: msg.message.contains('file') ? _buildFileBubble(msg) : Text(
+ displayMessage,
+ style: const TextStyle(color: Colors.black87),
+ ),
),
),
- ),
- Padding(
- padding: const EdgeInsets.only(top: 2, bottom: 10),
- child: Text(
- dateFormatter(timeStamp: msg.timestamp),
- style: const TextStyle(color: Colors.black54, fontSize: 10),
+ Padding(
+ padding: const EdgeInsets.only(top: 2, bottom: 10),
+ child: Text(
+ dateFormatter(timeStamp: msg.timestamp),
+ style: const TextStyle(color: Colors.black54, fontSize: 10),
+ ),
),
- ),
- ],
- );
- }),
- ],
- );
- },
+ ],
+ );
+ }),
+ ],
+ );
+ },
+ ),
),
- ),
- MessagePanel(converser: widget.converser),
- ],
- ),
+ MessagePanel(converser: widget.converser),
+ ],
+ ),
+ )
);
}
diff --git a/lib/pages/home_screen.dart b/lib/pages/home_screen.dart
index ea87b16..73cd1f1 100644
--- a/lib/pages/home_screen.dart
+++ b/lib/pages/home_screen.dart
@@ -4,14 +4,52 @@ 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';
/// This the home screen. This can also be considered as the
-/// main screen of the application.
+/// main screen of the application.
/// 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,14 @@ 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,48 +94,85 @@ 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(bool value) {
+ setState(() {
+ _themeMode = value ? 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,
+ ),
+ child: Scaffold(
+ key: Global.scaffoldKey,
+ appBar: AppBar(
+ title: const Text("AOSSIE"),
+ actions: [
+ // Slider toggle button for light and dark themes
+ //added switch inplace of icon button
+ Switch(
+ value: _themeMode == ThemeMode.dark,
+ onChanged: _toggleTheme,
+ activeColor: Colors.blueAccent,
+ inactiveThumbColor: Colors.grey,
+ ),
+ 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(),
- ],
- ),
),
);
}
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..f189bf4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -32,6 +32,8 @@ dependencies:
open_filex: ^4.5.0
permission_handler: ^11.3.1
path_provider: ^2.1.4
+ flutter_sound:
+ lottie:
dev_dependencies:
flutter_lints:
@@ -39,4 +41,6 @@ dev_dependencies:
sdk: flutter
flutter:
+ assets:
+ - assets/audioAnimation.json
uses-material-design: true
\ No newline at end of file