From 947ee076b1e1c67e7a35d7cd154a800a868df7d5 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 13 Jun 2022 21:09:54 +0530 Subject: [PATCH 01/36] Starting Code of Application --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 6 +- .../kotlin/com/aossie/p2p/MainActivity.kt | 3 - lib/encyption/rsa.dart | 19 ++- lib/main.dart | 13 +- lib/p2p/AdhocHousekeeping.dart | 25 ++-- lib/p2p/MatrixServerModel.dart | 122 +++++++++--------- lib/pages/ChatListScreen.dart | 6 +- lib/pages/ChatPage.dart | 93 ++++++------- lib/pages/DeviceListScreen.dart | 8 +- lib/pages/Profile.dart | 19 +-- 11 files changed, 154 insertions(+), 162 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 21d8ad5..1c12b94 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 59d3fa1..ee958ca 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,12 +5,10 @@ In most cases you can leave this as-is, but you if you want to provide additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> - - futureKeyPair; +Future? futureKeyPair; //to store the KeyPair once we get data from our future -crypto.AsymmetricKeyPair keyPair; +crypto.AsymmetricKeyPair? keyPair; -Future> getKeyPair() -{ +Future> + getKeyPair() { var helper = RsaKeyHelper(); return helper.computeRSAKeyPair(helper.getSecureRandom()); } -void createPair (){ - futureKeyPair=getKeyPair(); - futureKeyPair.then((value) => keyPair=value); - var x=encodePrivateKeyToPemPKCS1(keyPair.privateKey); - -} \ No newline at end of file +// void createPair() { +// futureKeyPair = getKeyPair(); +// futureKeyPair.then((value) => keyPair = value); +// var x = encodePrivateKeyToPemPKCS1(keyPair.privateKey); +// } diff --git a/lib/main.dart b/lib/main.dart index e5a5e40..9934642 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,21 +1,16 @@ - import 'package:flutter/material.dart'; -import 'package:p2p/pages/ChatListScreen.dart'; import 'pages/Profile.dart'; + void main() { runApp(MyApp()); } - Route generateRoute(RouteSettings settings) { - return MaterialPageRoute( - builder: (_) => Profile()); + return MaterialPageRoute(builder: (_) => Profile()); } class MyApp extends StatelessWidget { - void initState(){ - - } + void initState() {} @override Widget build(BuildContext context) { return MaterialApp( @@ -24,5 +19,3 @@ class MyApp extends StatelessWidget { ); } } - - diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 2becdb4..48960cc 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -1,31 +1,30 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; +import 'dart:developer'; // import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_nearby_connections_example/classes/Payload.dart'; -import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import '../classes/Global.dart'; -import '../classes/Msg.dart'; String getStateName(SessionState state) { switch (state) { case SessionState.notConnected: - return "disconnected"; + return "Disconnected"; case SessionState.connecting: - return "waiting"; + return "Waiting"; default: - return "connected"; + return "Connected"; } } String getButtonStateName(SessionState state) { switch (state) { case SessionState.notConnected: - case SessionState.connecting: return "Connect"; + case SessionState.connecting: + return "Connecting"; default: return "Disconnect"; } @@ -45,8 +44,9 @@ Color getStateColor(SessionState state) { Color getButtonColor(SessionState state) { switch (state) { case SessionState.notConnected: - case SessionState.connecting: return Colors.green; + case SessionState.connecting: + return Colors.yellow; default: return Colors.red; } @@ -57,12 +57,10 @@ int getItemCount() { } bool search(String sender, String id) { - if(Global.conversations[sender]==null) - return false; - - if (Global.conversations[sender]!.containsKey(id)) { - return true; + if (Global.conversations[sender] == null) return false; + if (Global.conversations[sender]!.containsKey(id)) { + return true; } return false; @@ -75,6 +73,7 @@ void connectToDevice(Device device) { deviceID: device.deviceId, deviceName: device.deviceName, ); + log("Want to connect"); break; case SessionState.connected: Global.nearbyService!.disconnectPeer(deviceID: device.deviceId); diff --git a/lib/p2p/MatrixServerModel.dart b/lib/p2p/MatrixServerModel.dart index cef1387..f8068c5 100644 --- a/lib/p2p/MatrixServerModel.dart +++ b/lib/p2p/MatrixServerModel.dart @@ -1,61 +1,61 @@ -import 'package:flutter/foundation.dart'; -// ignore: import_of_legacy_library_into_null_safe -import 'package:p2p_client_dart/p2p_client_dart.dart'; - -class Contact { - String displayName; - String? roomId; - String? userId; - - Contact(this.displayName); - - @override - String toString() { - return displayName; - } - - Contact.fromJson(Map json) - : displayName = json['displayName']!, - roomId = json['roomId'], - userId = json['userId']; -} - -class MatrixServer extends ChangeNotifier { - Server _server = Server(); - Server get server => _server; - - @override - String toString() { - return 'Matrix Server - ${server.toString()}'; - } - - set server(Server newServer) { - this._server = newServer; - notifyListeners(); - } - - Future setServerConfig( - String url, String name, String? username, String? password) async { - this._server = server = Server.init(url, name); - if (username != null && password != null) { - await this._server.login(username, password); - print(this._server.isAuthenticated); - } - } - - Future> getContactsList() async { - var roomData = await server.getJoinedRooms(); - List contacts = roomData.map((e) { - Map contact = {}; - e.retainWhere((e) => e.senderId != server.userId); - - if (e.length == 0) return Contact("None"); - contact["roomId"] = e[0].roomId; - contact["userId"] = e[0].senderId; - contact["displayName"] = e[0].content['displayname']; - return Contact.fromJson(contact); - }).toList(); - contacts.retainWhere((element) => element.displayName != "None"); - return contacts; - } -} +// import 'package:flutter/foundation.dart'; +// // ignore: import_of_legacy_library_into_null_safe +// import 'package:p2p_client_dart/p2p_client_dart.dart'; + +// class Contact { +// String displayName; +// String? roomId; +// String? userId; + +// Contact(this.displayName); + +// @override +// String toString() { +// return displayName; +// } + +// Contact.fromJson(Map json) +// : displayName = json['displayName']!, +// roomId = json['roomId'], +// userId = json['userId']; +// } + +// class MatrixServer extends ChangeNotifier { +// Server _server = Server(); +// Server get server => _server; + +// @override +// String toString() { +// return 'Matrix Server - ${server.toString()}'; +// } + +// set server(Server newServer) { +// this._server = newServer; +// notifyListeners(); +// } + +// Future setServerConfig( +// String url, String name, String? username, String? password) async { +// this._server = server = Server.init(url, name); +// if (username != null && password != null) { +// await this._server.login(username, password); +// print(this._server.isAuthenticated); +// } +// } + +// Future> getContactsList() async { +// var roomData = await server.getJoinedRooms(); +// List contacts = roomData.map((e) { +// Map contact = {}; +// e.retainWhere((e) => e.senderId != server.userId); + +// if (e.length == 0) return Contact("None"); +// contact["roomId"] = e[0].roomId; +// contact["userId"] = e[0].senderId; +// contact["displayName"] = e[0].content['displayname']; +// return Contact.fromJson(contact); +// }).toList(); +// contacts.retainWhere((element) => element.displayName != "None"); +// return contacts; +// } +// } diff --git a/lib/pages/ChatListScreen.dart b/lib/pages/ChatListScreen.dart index 582ebc5..0de4390 100644 --- a/lib/pages/ChatListScreen.dart +++ b/lib/pages/ChatListScreen.dart @@ -58,15 +58,15 @@ class _ChatListScreenState extends State { items: [ BottomNavigationBarItem( icon: Icon(Icons.message), - title: Text("Chats"), + label: "Chats", ), BottomNavigationBarItem( icon: Icon(Icons.group_work), - title: Text("Available"), + label: "Available", ), BottomNavigationBarItem( icon: Icon(Icons.account_box), - title: Text("Profile"), + label: "Profile", ), ], ), diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index 87f9eac..5480f02 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -21,11 +21,12 @@ class ChatPage extends StatefulWidget { class ChatPageState extends State { List messageList = []; -void refresh(){ - setState(() { - ; - }); -} + void refresh() { + setState(() { + ; + }); + } + void initState() { Global.conversations[widget.converser]!.forEach((key, value) { messageList.add(value); @@ -35,7 +36,7 @@ void refresh(){ ScrollController _scrollController = new ScrollController(); Widget build(BuildContext context) { - final myController = TextEditingController(); + TextEditingController myController = TextEditingController(); return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -50,40 +51,43 @@ void refresh(){ children: [ Container( height: MediaQuery.of(context).size.height * .8, - child: messageList == null? Text("no messages"): ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - // reverse: true, - controller: _scrollController, - padding: const EdgeInsets.all(8), - itemCount: messageList == null ? 0 : messageList.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 55, - child: messageList[index].msgtype == 'sent' - ? Bubble( - margin: BubbleEdges.only(top: 10), - nip: BubbleNip.rightTop, - color: Color(0xffd1c4e9), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - textAlign: TextAlign.right), - ) - : Bubble( - nip: BubbleNip.leftTop, - color: Color(0xff80DEEA), - margin: BubbleEdges.only(top: 10), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - ), - ), - ); - }, - ), + child: messageList == null + ? Text("no messages") + : ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + // reverse: true, + controller: _scrollController, + padding: const EdgeInsets.all(8), + itemCount: + messageList == null ? 0 : messageList.length, + itemBuilder: (BuildContext context, int index) { + return Container( + height: 55, + child: messageList[index].msgtype == 'sent' + ? Bubble( + margin: BubbleEdges.only(top: 10), + nip: BubbleNip.rightTop, + color: Color(0xffd1c4e9), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + textAlign: TextAlign.right), + ) + : Bubble( + nip: BubbleNip.leftTop, + color: Color(0xff80DEEA), + margin: BubbleEdges.only(top: 10), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + ), + ), + ); + }, + ), ), TextFormField( controller: myController, @@ -127,10 +131,11 @@ void refresh(){ // Global // .conversations[widget.device.deviceName][msgId](new Msg(widget.device.deviceId, // myController.text, "sent")); - Global.conversations[widget.converser]![msgId]= - Msg(myController.text, "sent", - data["Timestamp"]!, msgId) - ; + Global.conversations[widget.converser]![msgId] = Msg( + myController.text, + "sent", + data["Timestamp"]!, + msgId); insertIntoConversationsTable( Msg(myController.text, "sent", data["Timestamp"]!, msgId), diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index db8c3c0..0f66e21 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -91,15 +91,15 @@ class _DevicesListScreenState extends State { items: [ BottomNavigationBarItem( icon: Icon(Icons.message), - title: Text("Chats"), + label: "Chats", ), BottomNavigationBarItem( icon: Icon(Icons.group_work), - title: Text("Available"), + label: "Available", ), BottomNavigationBarItem( icon: Icon(Icons.account_box), - title: Text("Profile"), + label: "Profile", ), ], currentIndex: _selectedIndex, @@ -190,7 +190,6 @@ class _DevicesListScreenState extends State { height: 1, color: Colors.grey, ), - Text("hello"), ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, @@ -288,7 +287,6 @@ class _DevicesListScreenState extends State { // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore Global.cache.remove(temp2["id"]); deleteFromMessageTable(temp2["id"]); - ; } print("350|" + temp2['type'].toString() + diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index 6b4348d..a03f860 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -3,6 +3,7 @@ import 'package:flutter_nearby_connections_example/pages/DeviceListScreen.dart'; import 'package:nanoid/nanoid.dart'; import '../classes/Global.dart'; import 'DeviceListScreen.dart'; + class Profile extends StatelessWidget { TextEditingController myName = TextEditingController(); var custom_length_id = nanoid(6); @@ -36,14 +37,16 @@ class Profile extends StatelessWidget { SizedBox( height: 20, ), - ElevatedButton(onPressed: () { - // Global.myName = myName.text+custom_length_id; - Global.myName = myName.text; - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => DevicesListScreen(deviceType: DeviceType.browser))); - - }, child: Text("Save")) + ElevatedButton( + onPressed: () { + // Global.myName = myName.text+custom_length_id; + Global.myName = myName.text; + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => + DevicesListScreen(deviceType: DeviceType.browser))); + }, + child: Text("Save"), + ) ], ), ), From 693ffb2494017e114b2cd4d0205f9a9bdb034a6c Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Tue, 14 Jun 2022 16:04:40 +0530 Subject: [PATCH 02/36] Chat Message refresh fix --- lib/pages/ChatPage.dart | 230 ++++++++++++++++++++-------------------- 1 file changed, 116 insertions(+), 114 deletions(-) diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index 5480f02..d6cb8da 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -21,16 +21,18 @@ class ChatPage extends StatefulWidget { class ChatPageState extends State { List messageList = []; - void refresh() { - setState(() { - ; - }); + void refreshMessages() { + if (Global.conversations[widget.converser] != null) { + Global.conversations[widget.converser]!.forEach((key, value) { + messageList.add(value); + }); + } } + @override void initState() { - Global.conversations[widget.converser]!.forEach((key, value) { - messageList.add(value); - }); + super.initState(); + refreshMessages(); } ScrollController _scrollController = new ScrollController(); @@ -38,113 +40,113 @@ class ChatPageState extends State { Widget build(BuildContext context) { TextEditingController myController = TextEditingController(); return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: Text('Chat with ' + widget.converser), - ), - body: SingleChildScrollView( - reverse: true, - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom), - child: Column( - children: [ - Container( - height: MediaQuery.of(context).size.height * .8, - child: messageList == null - ? Text("no messages") - : ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - // reverse: true, - controller: _scrollController, - padding: const EdgeInsets.all(8), - itemCount: - messageList == null ? 0 : messageList.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 55, - child: messageList[index].msgtype == 'sent' - ? Bubble( - margin: BubbleEdges.only(top: 10), - nip: BubbleNip.rightTop, - color: Color(0xffd1c4e9), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - textAlign: TextAlign.right), - ) - : Bubble( - nip: BubbleNip.leftTop, - color: Color(0xff80DEEA), - margin: BubbleEdges.only(top: 10), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - ), - ), - ); - }, - ), - ), - TextFormField( - controller: myController, - decoration: const InputDecoration( - icon: Icon(Icons.person), - hintText: 'Send Message?', - labelText: 'Send Message ', - ), - ), - ElevatedButton( - onPressed: () { - var msgId = nanoid(21); - var data = { - "sender": "$Global.myName", - "receiver": "$widget.device.deviceName", - "message": "$myController.text", - "id": "$msgId", - "Timestamp": "${DateTime.now().toUtc().toString()}", - "type": "Payload" - }; - var Mesagedata = data.toString(); - Global.cache[msgId] = Payload( - msgId, - Global.myName, - widget.converser, - myController.text, - DateTime.now().toUtc().toString()); - insertIntoMessageTable(Payload( - msgId, - Global.myName, - widget.converser, - myController.text, - DateTime.now().toUtc().toString())); - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, Mesagedata); - // }); - // Global.nearbyService! - // .sendMessage(widget.device.deviceId, myController.text); - setState(() { - // Global - // .conversations[widget.device.deviceName][msgId](new Msg(widget.device.deviceId, - // myController.text, "sent")); - Global.conversations[widget.converser]![msgId] = Msg( - myController.text, - "sent", - data["Timestamp"]!, - msgId); - insertIntoConversationsTable( - Msg(myController.text, "sent", data["Timestamp"]!, - msgId), - widget.converser); - }); - }, - child: Text("send")), - ], + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text('Chat with ' + widget.converser), + ), + body: SingleChildScrollView( + reverse: true, + child: Padding( + padding: + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + children: [ + Container( + height: MediaQuery.of(context).size.height * .8, + child: messageList == null + ? Text("no messages") + : ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + // reverse: true, + controller: _scrollController, + padding: const EdgeInsets.all(8), + itemCount: messageList == null ? 0 : messageList.length, + itemBuilder: (BuildContext context, int index) { + return Container( + height: 55, + child: messageList[index].msgtype == 'sent' + ? Bubble( + margin: BubbleEdges.only(top: 10), + nip: BubbleNip.rightTop, + color: Color(0xffd1c4e9), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + textAlign: TextAlign.right), + ) + : Bubble( + nip: BubbleNip.leftTop, + color: Color(0xff80DEEA), + margin: BubbleEdges.only(top: 10), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + ), + ), + ); + }, + ), + ), + TextFormField( + controller: myController, + decoration: const InputDecoration( + icon: Icon(Icons.person), + hintText: 'Send Message?', + labelText: 'Send Message ', + ), ), - ))); + ElevatedButton( + onPressed: () { + var msgId = nanoid(21); + var data = { + "sender": "$Global.myName", + "receiver": "$widget.device.deviceName", + "message": "$myController.text", + "id": "$msgId", + "Timestamp": "${DateTime.now().toUtc().toString()}", + "type": "Payload" + }; + var Mesagedata = data.toString(); + Global.cache[msgId] = Payload( + msgId, + Global.myName, + widget.converser, + myController.text, + DateTime.now().toUtc().toString()); + insertIntoMessageTable(Payload( + msgId, + Global.myName, + widget.converser, + myController.text, + DateTime.now().toUtc().toString())); + // Global.devices.forEach((element) { + // Global.nearbyService! + // .sendMessage(element.deviceId, Mesagedata); + // }); + // Global.nearbyService! + // .sendMessage(widget.device.deviceId, myController.text); + setState(() { + // Global + // .conversations[widget.device.deviceName][msgId](new Msg(widget.device.deviceId, + // myController.text, "sent")); + Global.conversations[widget.converser]![msgId] = Msg( + myController.text, "sent", data["Timestamp"]!, msgId); + insertIntoConversationsTable( + Msg(myController.text, "sent", data["Timestamp"]!, + msgId), + widget.converser); + }); + refreshMessages(); + }, + child: Text("send"), + ), + ], + ), + ), + ), + ); } } From d19b8d44e43d09289c84e1f4f78c127b41d77af9 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Tue, 21 Jun 2022 15:35:00 +0530 Subject: [PATCH 03/36] //WIP Reformatting to Stateful Widget --- lib/pages/Profile.dart | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index a03f860..a7fb047 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -4,9 +4,16 @@ import 'package:nanoid/nanoid.dart'; import '../classes/Global.dart'; import 'DeviceListScreen.dart'; -class Profile extends StatelessWidget { +class Profile extends StatefulWidget { + @override + State createState() => _ProfileState(); +} + +class _ProfileState extends State { TextEditingController myName = TextEditingController(); - var custom_length_id = nanoid(6); + + var customLengthId = nanoid(6); + @override Widget build(BuildContext context) { return Scaffold( @@ -16,7 +23,7 @@ class Profile extends StatelessWidget { SizedBox( height: 100, ), - Text("Your Username will be your name+$custom_length_id"), + Text("Your Username will be your name+$customLengthId"), TextFormField( controller: myName, decoration: const InputDecoration( @@ -41,9 +48,12 @@ class Profile extends StatelessWidget { onPressed: () { // Global.myName = myName.text+custom_length_id; Global.myName = myName.text; - Navigator.of(context).push(MaterialPageRoute( + Navigator.of(context).push( + MaterialPageRoute( builder: (_) => - DevicesListScreen(deviceType: DeviceType.browser))); + DevicesListScreen(deviceType: DeviceType.browser), + ), + ); }, child: Text("Save"), ) From fcaae6ed3850343e8d6456fb955c39950ea7610e Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Tue, 21 Jun 2022 15:37:06 +0530 Subject: [PATCH 04/36] // WIP Send the last received message id Send the last received message id by implementing the broadcastLastMessageId. --- lib/p2p/AdhocHousekeeping.dart | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 48960cc..338bc97 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -6,6 +6,7 @@ import 'dart:developer'; import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:flutter_nearby_connections_example/database/MessageDB.dart'; import '../classes/Global.dart'; String getStateName(SessionState state) { @@ -126,6 +127,30 @@ void broadcast() async { } } +// Sending request message to the connected devices to recieve fresh messages that are yet to be recieved +void broadcastLastMessageID() async { + // From Database get the last message. + String id = await MessageDB.instance.getLastMessageId(type: "received"); + log("Last message id: " + id); + + Global.devices.forEach((element) async { + var data = { + "sender": Global.myName, + "receiver": element.deviceName, + "message": "__update__", + "id": id, + "Timestamp": DateTime.now().toString(), + "type": "Update" + }; + var toSend = jsonEncode(data); + + log("270" + toSend); + await Global.nearbyService! + .sendMessage(element.deviceId, toSend); //make this async + }); +} + +// Initiating NearbyService to start the connection void initiateNearbyService() async { Global.nearbyService = NearbyService(); await Global.nearbyService!.init( From 8f2f319c3623fdc1856ca69cb3b70a181b67d500 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:11:43 +0530 Subject: [PATCH 05/36] Change the compile version The compile version for android was changed because the android sdk was updated. --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1c12b94..f3cf035 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 32 sourceSets { main.java.srcDirs += 'src/main/kotlin' From 791c09ec2023edb0cf55b28465cef8ba52fc6a57 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:13:53 +0530 Subject: [PATCH 06/36] Rename subscription and conversations The subscription name was misleading and its use for devices. Hence it is changed to deviceSubscription. The conversations initially to empty map with proper naming convention. --- lib/classes/Global.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index 0b5eb35..c4361aa 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:pointycastle/api.dart' as crypto; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; -import 'Payload.dart'; import 'Msg.dart'; @@ -9,7 +7,7 @@ class Global { static List devices = []; static List connectedDevices = []; static NearbyService? nearbyService; - static StreamSubscription? subscription; + static StreamSubscription? deviceSubscription; static StreamSubscription? receivedDataSubscription; static List messages = [ @@ -17,8 +15,9 @@ class Global { Msg("2", "test2", "sent", '4') ]; static Map publicKeys = Map(); - static Map> conversations = - Map(); //converser mapped to conversation + static Map> conversations = + {}; //converser mapped to conversation + static String myName = ''; static Map cache = Map(); } From a3a1efe735ed5370884bd74ed6c907edda044523 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:15:05 +0530 Subject: [PATCH 07/36] Changed formatting and added some documentation Also removed unnecessary packages --- lib/database/DatabaseHelper.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/database/DatabaseHelper.dart b/lib/database/DatabaseHelper.dart index f3c15c7..e03d3f4 100644 --- a/lib/database/DatabaseHelper.dart +++ b/lib/database/DatabaseHelper.dart @@ -2,11 +2,10 @@ import 'package:flutter_nearby_connections_example/classes/Global.dart'; import 'package:flutter_nearby_connections_example/classes/Msg.dart'; import 'dart:convert'; import '../classes/Payload.dart'; -import 'package:pointycastle/api.dart' as crypto; import 'model.dart'; import 'MessageDB.dart'; -Future readAllUpdateConversation() async { +Future readAllUpdateConversation() async { List conversations = [ ConversationFromDB("1", "2", "3", "5", "6", "7") ]; @@ -21,9 +20,8 @@ Future readAllUpdateConversation() async { Global.conversations[element.converser]![element.id] = Msg(element.msg, element.type, element.timestamp, element.id); - // Global.conversations[element.converser]![element.id]=Msg(element.msg,element.type,element.timestamp,element.id); }); - print("19:" + Global.conversations.toString()); + // print("19:" + Global.conversations.toString()); } void readAllUpdatePublicKey() { @@ -58,6 +56,7 @@ void readAllUpdateCache() { }); } +// Inserting message to the messages table in the database void insertIntoMessageTable(dynamic msg) { if (msg.runtimeType == Payload) MessageDB.instance.insertIntoMessagesTable(convertFromPayload(msg)); @@ -65,6 +64,7 @@ void insertIntoMessageTable(dynamic msg) { MessageDB.instance.insertIntoMessagesTable(convertFromAck(msg)); } +// Inserting message to the conversation table in the database void insertIntoConversationsTable(Msg msg, String converser) { MessageDB.instance.insertIntoConversationsTable(ConversationFromDB( msg.id, msg.msgtype, msg.message, msg.timestamp, msg.ack, converser)); @@ -89,7 +89,7 @@ Ack convertToAck(MessageFromDB msg) { Payload convertToPayload(MessageFromDB message) { String id = message.id; String payload = message.msg; - print("#61: ${payload}"); + print("#61: $payload"); var json = jsonDecode(payload); print("#63" + json.toString()); print("#62 ${json['id']}| ${json['sender']}"); From a5b6f35f10918e5ac0356ab459ed15741b3e330f Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:20:32 +0530 Subject: [PATCH 08/36] Added function to get the last message id The function gets the last message id. The function is able to get both received and sent type of message according to requirements. Other changes are related code formatting. --- lib/database/MessageDB.dart | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/database/MessageDB.dart b/lib/database/MessageDB.dart index b4af611..8dfdf8c 100644 --- a/lib/database/MessageDB.dart +++ b/lib/database/MessageDB.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; import 'model.dart'; @@ -33,14 +35,34 @@ class MessageDB { void insertIntoMessagesTable(MessageFromDB message) async { final db = await instance.database; - final id = await db.insert(messagesTableName, message.toJson()); + await db.insert(messagesTableName, message.toJson()); return; } + void insertIntoConversationsTable(ConversationFromDB message) async { final db = await instance.database; - final id = await db.insert(conversationsTableName, message.toJson()); + await db.insert(conversationsTableName, message.toJson()); return; } + + // Function to get last message id from the database + // in the conversation table + // The type can be modified based on the type of message + // that is being sent or received + Future getLastMessageId({required String type}) async { + final db = await instance.database; + final message = await db.query( + conversationsTableName, + where: '${ConversationTableFields.type}=?', + whereArgs: [type], + orderBy: '${ConversationTableFields.timestamp} DESC', + limit: 1, + ); + log(message.toString()); + if (message.isEmpty) return "-1"; // If error in database. + return MessageFromDB.fromJson(message[0]).id; + } + Future readFromMessagesTable(int id) async { final db = await instance.database; final maps = await db.query( @@ -54,6 +76,7 @@ class MessageDB { else return null; } + Future readFromConversationsTable(int id) async { final db = await instance.database; final maps = await db.query( @@ -67,6 +90,7 @@ class MessageDB { else return null; } + Future> readAllFromPublicKeyTable() async { final db = await instance.database; final result = await db.query( @@ -74,6 +98,7 @@ class MessageDB { ); return result.map((json) => PublicKeyFromDB.fromJson(json)).toList(); } + Future> readAllFromMessagesTable() async { final db = await instance.database; final result = await db.query( @@ -81,6 +106,7 @@ class MessageDB { ); return result.map((json) => MessageFromDB.fromJson(json)).toList(); } + Future> readAllFromConversationsTable() async { final db = await instance.database; final result = await db.query( @@ -88,11 +114,13 @@ class MessageDB { ); return result.map((json) => ConversationFromDB.fromJson(json)).toList(); } + Future updateMessageTable(MessageFromDB msg) async { final db = await instance.database; return db.update(messagesTableName, msg.toJson(), where: '${MessageTableFields.id}=?', whereArgs: [msg.id]); } + Future updateConversationTable(ConversationFromDB msg) async { final db = await instance.database; return db.update(conversationsTableName, msg.toJson(), @@ -104,11 +132,13 @@ class MessageDB { return db.delete(messagesTableName, where: '${MessageTableFields.id}=?', whereArgs: [id]); } + Future deleteFromConversationsTable(String id) async { final db = await instance.database; return db.delete(conversationsTableName, where: '${ConversationTableFields.id}=?', whereArgs: [id]); } + Future close() async { final db = await instance.database; db.close(); From f9e21078366558aa5f9000811751b7e93d1c0c57 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:21:27 +0530 Subject: [PATCH 09/36] Code formatting and documenting --- lib/database/model.dart | 3 ++- lib/main.dart | 5 +++-- lib/pages/Profile.dart | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/database/model.dart b/lib/database/model.dart index 3d6cef3..72a8bbb 100644 --- a/lib/database/model.dart +++ b/lib/database/model.dart @@ -1,6 +1,7 @@ final String messagesTableName = 'messages'; final String conversationsTableName = 'conversations'; -final String publicKeyTableName='publicKey'; +final String publicKeyTableName = 'publicKey'; + class MessageTableFields { static final List values = [id, type, msg]; static final String type = 'type'; diff --git a/lib/main.dart b/lib/main.dart index 9934642..3be7727 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'pages/Profile.dart'; void main() { - runApp(MyApp()); + runApp( + MyApp(), + ); } Route generateRoute(RouteSettings settings) { @@ -10,7 +12,6 @@ Route generateRoute(RouteSettings settings) { } class MyApp extends StatelessWidget { - void initState() {} @override Widget build(BuildContext context) { return MaterialApp( diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index a7fb047..244c03d 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -10,8 +10,10 @@ class Profile extends StatefulWidget { } class _ProfileState extends State { + // TextEditingController for the name of the user TextEditingController myName = TextEditingController(); + // Custom generated id for the user var customLengthId = nanoid(6); @override @@ -48,6 +50,8 @@ class _ProfileState extends State { onPressed: () { // Global.myName = myName.text+custom_length_id; Global.myName = myName.text; + + // On pressing, move to the device list screen Navigator.of(context).push( MaterialPageRoute( builder: (_) => From e12c2d95052dfafb4ee650cc3eec132f625cd5c1 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:27:17 +0530 Subject: [PATCH 10/36] Broadcasting last message id and comparing message ids The pull protocol requires to check if new messages are available or not. So we need to compare there last message ids. Hence the broadcastLastMessageId() first fetches the last message id that is being received and then the message of type update is being broadcasted to connected devices. This update message is then processed in the received data subscription. The compareMessageId compares the last received message id from the connected device with the last message id sent to the device. If they are not same, then messages in the cache are broadcasted. --- lib/p2p/AdhocHousekeeping.dart | 158 ++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 338bc97..f766328 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -9,6 +9,7 @@ import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:flutter_nearby_connections_example/database/MessageDB.dart'; import '../classes/Global.dart'; +// Get the state name of the connection String getStateName(SessionState state) { switch (state) { case SessionState.notConnected: @@ -20,6 +21,7 @@ String getStateName(SessionState state) { } } +// Get the button state name of the connection String getButtonStateName(SessionState state) { switch (state) { case SessionState.notConnected: @@ -31,6 +33,7 @@ String getButtonStateName(SessionState state) { } } +// Get the state colour of the connection Color getStateColor(SessionState state) { switch (state) { case SessionState.notConnected: @@ -42,6 +45,7 @@ Color getStateColor(SessionState state) { } } +// Get the button state colour of the connection Color getButtonColor(SessionState state) { switch (state) { case SessionState.notConnected: @@ -53,20 +57,22 @@ Color getButtonColor(SessionState state) { } } +// Get the number of devices int getItemCount() { return Global.devices.length; } -bool search(String sender, String id) { +// Check if the id exists in the conversation list +bool search(String sender, String id, BuildContext context) { if (Global.conversations[sender] == null) return false; if (Global.conversations[sender]!.containsKey(id)) { return true; } - return false; } +// Function to connect to a device void connectToDevice(Device device) { switch (device.state) { case SessionState.notConnected: @@ -94,73 +100,105 @@ void startAdvertising() async { await Global.nearbyService!.startAdvertisingPeer(); } -// this function is supposed to broadcast all messages in the cache which is set to broadcast=true +// this function is supposed to broadcast all messages in the cache when the message ids don't match void broadcast() async { - while (true) { - Global.cache.forEach((key, value) { - // if a message is supposed to be broadcasted to all devices in proximity then - if (value.runtimeType == Payload && value.broadcast) { - Payload payload = value; - var data = { - "sender": value.sender, - "receiver": payload.receiver, - "message": value.message, - "id": key, - "Timestamp": value.timestamp, - "type": "Payload" - }; - var toSend = jsonEncode(data); - Global.devices.forEach((element) { - print("270" + toSend); - Global.nearbyService! - .sendMessage(element.deviceId, toSend); //make this async - }); - } else if (value.runtimeType == Ack) { - Global.devices.forEach((element) { - var data = {"id": "$key", "type": "Ack"}; - Global.nearbyService!.sendMessage(element.deviceId, jsonEncode(data)); - }); - } - }); - print("current cache:" + Global.cache.toString()); - await Future.delayed(Duration(seconds: 10)); - } + Global.cache.forEach((key, value) { + // if a message is supposed to be broadcasted to all devices in proximity then + if (value.runtimeType == Payload && value.broadcast) { + Payload payload = value; + var data = { + "sender": value.sender, + "receiver": payload.receiver, + "message": value.message, + "id": key, + "Timestamp": value.timestamp, + "type": "Payload" + }; + var toSend = jsonEncode(data); + Global.devices.forEach((element) { + print("270" + toSend); + Global.nearbyService! + .sendMessage(element.deviceId, toSend); //make this async + }); + } else if (value.runtimeType == Ack) { + Global.devices.forEach((element) { + var data = {"id": "$key", "type": "Ack"}; + Global.nearbyService!.sendMessage(element.deviceId, jsonEncode(data)); + }); + } + }); + print("current cache:" + Global.cache.toString()); } -// Sending request message to the connected devices to recieve fresh messages that are yet to be recieved +// Broadcasting update request message to the connected devices to recieve fresh messages that are yet to be recieved void broadcastLastMessageID() async { - // From Database get the last message. - String id = await MessageDB.instance.getLastMessageId(type: "received"); - log("Last message id: " + id); - - Global.devices.forEach((element) async { - var data = { - "sender": Global.myName, - "receiver": element.deviceName, - "message": "__update__", - "id": id, - "Timestamp": DateTime.now().toString(), - "type": "Update" - }; - var toSend = jsonEncode(data); - - log("270" + toSend); - await Global.nearbyService! - .sendMessage(element.deviceId, toSend); //make this async + // Fetch from Database the last message. + Timer.periodic(Duration(seconds: 3), (timer) async { + String id = await MessageDB.instance.getLastMessageId(type: "received"); + log("Last message id: " + id); + + Global.devices.forEach((element) async { + var data = { + "sender": Global.myName, + "receiver": element.deviceName, + "message": "__update__", + "id": id, + "Timestamp": DateTime.now().toString(), + "type": "Update" + }; + var toSend = jsonEncode(data); + + log("270" + toSend); + await Global.nearbyService!.sendMessage( + element.deviceId, + toSend, + ); + }); }); } +// void checkForMessageUpdates() async { +// broadcastLastMessageID(); +// Global.receivedDataSubscription!.onData((data) { +// // print("dataReceivedSubscription: ${jsonEncode(data)}"); +// var decodedMessage = jsonDecode(data["message"]); + +// // checking if successfully recieving the update or not +// if (decodedMessage["type"] == "Update") { +// log("Update Message ${decodedMessage["id"]}"); +// String sentDeviceName = decodedMessage["sender"]; +// compareMessageId( +// receivedId: decodedMessage["id"], +// sentDeviceName: sentDeviceName, +// ); +// } +// }); +// } + +// Compare message Ids +// If they are not same, the message needs to be broadcasted. +void compareMessageId({ + required String receivedId, + required String sentDeviceName, +}) async { + String sentId = await MessageDB.instance.getLastMessageId(type: "sent"); + if (sentId != receivedId) { + broadcast(); + } +} + // Initiating NearbyService to start the connection void initiateNearbyService() async { Global.nearbyService = NearbyService(); await Global.nearbyService!.init( - serviceType: 'mpconn', - deviceName: Global.myName, - strategy: Strategy.P2P_CLUSTER, - callback: (isRunning) async { - if (isRunning) { - startAdvertising(); - startBrowsing(); - } - }); + serviceType: 'mpconn', + deviceName: Global.myName, + strategy: Strategy.P2P_CLUSTER, + callback: (isRunning) async { + if (isRunning) { + startAdvertising(); + startBrowsing(); + } + }, + ); } From b898186130208ef52ff982b628b49168c9298e23 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:31:59 +0530 Subject: [PATCH 11/36] Code formatting and UI fixes The text editing controller had state issues due to placed in the build function. The refreshMessages() function is formatted to make it empty before adding messages to it again. scroll controller is used to make the controller place at the last position where the last message is lies. Code is formatted according to Flutter updates and Listview of messages modified. --- lib/pages/ChatPage.dart | 46 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index d6cb8da..e6ac5af 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -1,17 +1,14 @@ import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:nanoid/nanoid.dart'; -import 'package:intl/intl.dart'; import '../database/DatabaseHelper.dart'; import '../classes/Msg.dart'; import '../classes/Global.dart'; class ChatPage extends StatefulWidget { - ChatPage(this.converser); + ChatPage({Key? key, required this.converser}); final String converser; @@ -21,11 +18,21 @@ class ChatPage extends StatefulWidget { class ChatPageState extends State { List messageList = []; + TextEditingController myController = TextEditingController(); + + // Function to fetch messages every time new message is sent void refreshMessages() { if (Global.conversations[widget.converser] != null) { + messageList = []; Global.conversations[widget.converser]!.forEach((key, value) { messageList.add(value); }); + if (_scrollController.hasClients) + _scrollController.animateTo( + _scrollController.position.maxScrollExtent + 100, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut, + ); } } @@ -38,7 +45,6 @@ class ChatPageState extends State { ScrollController _scrollController = new ScrollController(); Widget build(BuildContext context) { - TextEditingController myController = TextEditingController(); return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -53,15 +59,16 @@ class ChatPageState extends State { children: [ Container( height: MediaQuery.of(context).size.height * .8, - child: messageList == null - ? Text("no messages") + child: messageList.isEmpty + ? Center( + child: Text('No messages yet'), + ) : ListView.builder( scrollDirection: Axis.vertical, shrinkWrap: true, - // reverse: true, controller: _scrollController, padding: const EdgeInsets.all(8), - itemCount: messageList == null ? 0 : messageList.length, + itemCount: messageList.length, itemBuilder: (BuildContext context, int index) { return Container( height: 55, @@ -71,10 +78,11 @@ class ChatPageState extends State { nip: BubbleNip.rightTop, color: Color(0xffd1c4e9), child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - textAlign: TextAlign.right), + messageList[index].msgtype + + ": " + + messageList[index].message, + textAlign: TextAlign.right, + ), ) : Bubble( nip: BubbleNip.leftTop, @@ -109,7 +117,6 @@ class ChatPageState extends State { "Timestamp": "${DateTime.now().toUtc().toString()}", "type": "Payload" }; - var Mesagedata = data.toString(); Global.cache[msgId] = Payload( msgId, Global.myName, @@ -129,17 +136,22 @@ class ChatPageState extends State { // Global.nearbyService! // .sendMessage(widget.device.deviceId, myController.text); setState(() { - // Global - // .conversations[widget.device.deviceName][msgId](new Msg(widget.device.deviceId, - // myController.text, "sent")); + // Global.conversations[widget.device.deviceName]![msgId]( + // new Msg( + // widget.device.deviceId, myController.text, "sent")); + if (Global.conversations[widget.converser] == null) { + Global.conversations[widget.converser] = {}; + } Global.conversations[widget.converser]![msgId] = Msg( myController.text, "sent", data["Timestamp"]!, msgId); + insertIntoConversationsTable( Msg(myController.text, "sent", data["Timestamp"]!, msgId), widget.converser); }); refreshMessages(); + myController.clear(); }, child: Text("send"), ), From 1c156b5861595e5b25ec2fcc4bc4dda73dc4577a Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:32:16 +0530 Subject: [PATCH 12/36] Code formatting --- lib/pages/ChatListScreen.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pages/ChatListScreen.dart b/lib/pages/ChatListScreen.dart index 0de4390..fd2f66c 100644 --- a/lib/pages/ChatListScreen.dart +++ b/lib/pages/ChatListScreen.dart @@ -20,11 +20,12 @@ class _ChatListScreenState extends State { super.initState(); readAllUpdateCache(); readAllUpdateConversation().then((value) { - print("34" + Global.conversations.toString()); + // print("34" + Global.conversations.toString()); Global.conversations.forEach((key, value) { conversers.add(key); }); + print(" 37 reloaded:" + Global.cache.toString()); }); // print("34" + Global.conversations.toString()); @@ -109,7 +110,9 @@ class _ChatListScreenState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) { - return ChatPage(conversers[index]); + return ChatPage( + converser: conversers[index], + ); }, ), ); From bd2869653d0c719f8adc3a8f13fd0eee31dd8bb4 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:33:17 +0530 Subject: [PATCH 13/36] New plugins added The provider plugin will be used for state management shared_preferences will be used to save users profile in the device itself. --- pubspec.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 81deefd..065e636 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,8 +21,9 @@ dependencies: intl: ^0.17.0 rsa_encrypt: ^2.0.0 pointycastle: ^3.0.1 - - + shared_preferences: ^2.0.15 + provider: ^6.0.3 + dev_dependencies: flutter_test: sdk: flutter From 56834692f57a3e43543346bff0e36c5cc3991082 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sat, 2 Jul 2022 21:58:50 +0530 Subject: [PATCH 14/36] Code formatting, check for updates and UI fixed The ListView is modified in the UI to fix render overflow. checkDevices() function is separated from the init function to modularize the code. Also the devices auto connected. It is fixed by commenting the if statement at line 202. init() calls the checkDevices() and calls broadcastLastMessageId(). It checks whether the message is type of update or not. If yes, then the compareMessageId function is called. --- lib/pages/DeviceListScreen.dart | 325 ++++++++++++++++---------------- 1 file changed, 164 insertions(+), 161 deletions(-) diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index 0f66e21..b11154a 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -1,4 +1,6 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; // import 'package:device_info_plus/device_info_plus.dart'; @@ -34,7 +36,8 @@ class _DevicesListScreenState extends State { super.initState(); init(); refreshMessages(); - print(" 37 reloaded:" + Global.cache.toString()); + // print(" 37 reloaded:" + Global.cache.toString()); + // checkForMessageUpdates(); } Future refreshMessages() async { @@ -47,7 +50,7 @@ class _DevicesListScreenState extends State { @override void dispose() { - Global.subscription!.cancel(); + Global.deviceSubscription!.cancel(); Global.receivedDataSubscription!.cancel(); Global.nearbyService!.stopBrowsingForPeers(); Global.nearbyService!.stopAdvertisingPeer(); @@ -56,18 +59,18 @@ class _DevicesListScreenState extends State { var _selectedIndex = 0; - Widget getBody(BuildContext context) { - switch (_selectedIndex) { - case 0: - // return showTrips(context); - case 1: - // return search(widget.account); - case 2: - return Text('Not yet implemented!'); - default: - throw UnimplementedError(); - } - } + // Widget getBody(BuildContext context) { + // switch (_selectedIndex) { + // case 0: + // // return showTrips(context); + // case 1: + // // return search(widget.account); + // case 2: + // return Text('Not yet implemented!'); + // default: + // throw UnimplementedError(); + // } + // } void _onItemTapped(int index) { setState(() { @@ -106,64 +109,46 @@ class _DevicesListScreenState extends State { onTap: _onItemTapped, ), body: Container( + child: SingleChildScrollView( child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 16, left: 16, right: 16), - child: TextField( - decoration: InputDecoration( - hintText: "Search...", - hintStyle: TextStyle(color: Colors.grey.shade600), - prefixIcon: Icon( - Icons.search, - color: Colors.grey.shade600, - size: 20, + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 16, right: 16), + child: TextField( + decoration: InputDecoration( + hintText: "Search...", + hintStyle: TextStyle(color: Colors.grey.shade600), + prefixIcon: Icon( + Icons.search, + color: Colors.grey.shade600, + size: 20, + ), + filled: true, + fillColor: Colors.grey.shade100, + contentPadding: EdgeInsets.all(8), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide(color: Colors.grey.shade100)), + ), ), - filled: true, - fillColor: Colors.grey.shade100, - contentPadding: EdgeInsets.all(8), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide(color: Colors.grey.shade100)), ), - ), - ), - ListView.builder( - itemCount: getItemCount(), - shrinkWrap: true, - itemBuilder: (context, index) { - final device = Global.devices[index]; - return Container( - margin: EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return ChatPage(device.deviceName); - }, - ), - ); - }, - child: Column( - children: [ - Text(device.deviceName), - Text( - getStateName(device.state), - style: TextStyle( - color: getStateColor(device.state)), - ), - ], - crossAxisAlignment: CrossAxisAlignment.start, - ), - )), - // Request connect - GestureDetector( + ListView.builder( + itemCount: getItemCount(), + shrinkWrap: true, + itemBuilder: (context, index) { + final device = Global.devices[index]; + return Container( + margin: EdgeInsets.all(8.0), + child: Column( + children: [ + ListTile( + title: Text(device.deviceName), + subtitle: Text( + getStateName(device.state), + style: + TextStyle(color: getStateColor(device.state)), + ), + trailing: GestureDetector( onTap: () => connectToDevice(device), child: Container( margin: EdgeInsets.symmetric(horizontal: 8.0), @@ -180,47 +165,41 @@ class _DevicesListScreenState extends State { ), ), ), - ) - ], - ), - SizedBox( - height: 8.0, - ), - Divider( - height: 1, - color: Colors.grey, - ), - ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - padding: const EdgeInsets.all(8), - itemCount: Global.messages.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 15, - // color: Colors.amber[colorCodes[index]], - child: Center( - child: Text(Global.messages[index].msgtype + - ":" + - " " + - Global.messages[index].message)), + ), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return ChatPage( + converser: device.deviceName, + ); + }, + ), ); - }), - ], - ), - ); - }) - ], - )), + }, + ), + Divider( + height: 1, + color: Colors.grey, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), ); } - void init() async { - initiateNearbyService(); - Global.subscription = + // Check for devices in proximity + void checkDevices() { + Global.deviceSubscription = Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { devicesList.forEach((element) { - if (element.state != SessionState.connected) connectToDevice(element); + // if (element.state != SessionState.connected) connectToDevice(element); print( "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); @@ -242,85 +221,109 @@ class _DevicesListScreenState extends State { .toList()); }); }); - broadcast(); + } + + // The function responsible for receiving the messages + void init() async { + initiateNearbyService(); + checkDevices(); + broadcastLastMessageID(); Global.receivedDataSubscription = Global.nearbyService!.dataReceivedSubscription(callback: (data) { - print("dataReceivedSubscription: ${jsonEncode(data)}"); - - showToast(jsonEncode(data), - context: context, - axis: Axis.horizontal, - alignment: Alignment.center, - position: StyledToastPosition.bottom); - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, data["message"].toString());}); - + var decodedMessage = jsonDecode(data['message']); + showToast( + jsonEncode(data), + context: context, + axis: Axis.horizontal, + alignment: Alignment.center, + position: StyledToastPosition.bottom, + ); + if (decodedMessage["type"] == "Update") { + log("Update Message ${decodedMessage["id"]}"); + String sentDeviceName = decodedMessage["sender"]; + compareMessageId( + receivedId: decodedMessage["id"], + sentDeviceName: sentDeviceName, + ); + } setState(() { - String temp = data['message']; - var temp2 = jsonDecode(temp); - print("331: " + temp2['receiver'].toString()); - print("332:" + temp2['type'].toString()); - print("333|" + Global.myName.toString()); - print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); - if (Global.cache.containsKey(temp2["id"]) == false) { - print("line 338 test"); - if (temp2["type"].toString() == 'Payload') { - print("line 341"); + // print("331: " + temp2['receiver'].toString()); + // print("332:" + temp2['type'].toString()); + // print("333|" + Global.myName.toString()); + // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); + if (Global.cache.containsKey(decodedMessage["id"]) == false) { + // print("line 338 test"); + + if (decodedMessage["type"].toString() == 'Payload') { + // print("line 341"); - Global.cache[temp2["id"]] = Payload(temp2["id"], temp2['sender'], - temp2['receiver'], temp2['message'], temp2['Timestamp']); - insertIntoMessageTable(Payload(temp2["id"], temp2['sender'], - temp2['receiver'], temp2['message'], temp2['Timestamp'])); - print("current cache 344" + Global.cache.toString()); + Global.cache[decodedMessage["id"]] = Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp']); + insertIntoMessageTable(Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp'])); + // print("current cache 344" + Global.cache.toString()); } else { - Global.cache[temp2["id"]] = Ack(temp2["id"]); - insertIntoMessageTable(Ack(temp2["id"])); + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + insertIntoMessageTable(Ack(decodedMessage["id"])); } - } else if (Global.cache[temp2["id"]].runtimeType == Payload) { - if (temp2["type"] == 'Ack') { + } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { + if (decodedMessage["type"] == 'Ack') { //broadcast Ack last time to neighbours - Global.cache.remove(temp2["id"]); - deleteFromMessageTable(temp2["id"]); + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); } } else { // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore - Global.cache.remove(temp2["id"]); - deleteFromMessageTable(temp2["id"]); + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); } print("350|" + - temp2['type'].toString() + + decodedMessage['type'].toString() + ":Payload |" + - temp2['receiver'].toString() + + decodedMessage['receiver'].toString() + ":" + Global.myName.toString()); - if (temp2['type'] == "Payload" && temp2['receiver'] == Global.myName) { + if (decodedMessage['type'] == "Payload" && + decodedMessage['receiver'] == Global.myName) { // Global.cache[temp2["id"]]!.broadcast = false; - if (Global.conversations[temp2['sender']] == null) { - Global.conversations[temp2['sender']] = Map(); + if (Global.conversations[decodedMessage['sender']] == null) { + Global.conversations[decodedMessage['sender']] = Map(); } - if (!search(temp2['sender'], temp2["id"])) { - Global.conversations[temp2['sender']]![temp2["id"]] = Msg( - temp2['message'], "received", temp2['Timestamp'], temp2["id"]); + // If message type Payload, then add to cache and to conversations table + // if not already present + if (!search( + decodedMessage['sender'], decodedMessage["id"], context)) { + Global.conversations[decodedMessage['sender']]![ + decodedMessage["id"]] = Msg( + decodedMessage['message'], + "received", + decodedMessage['Timestamp'], + decodedMessage["id"], + ); + insertIntoConversationsTable( - Msg(temp2['message'], "received", temp2['Timestamp'], - temp2["id"]), - temp2['sender']); + Msg(decodedMessage['message'], "received", + decodedMessage['Timestamp'], decodedMessage["id"]), + decodedMessage['sender']); } - if (Global.cache[temp2["id"]] == null) { - Global.cache[temp2["id"]] = Ack(temp2["id"]); - print("280 test"); - insertIntoMessageTable(Ack(temp2['id'])); + if (Global.cache[decodedMessage["id"]] == null) { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // print("280 test"); + insertIntoMessageTable(Ack(decodedMessage['id'])); } else { - Global.cache[temp2["id"]] = Ack(temp2["id"]); - updateMessageTable(temp2["id"], Ack(temp2['id'])); + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); } - print("355: ack added"); - // Global.messages - // .add(new Msg(data["deviceId"], data["message"], "received")); - // Global.conversations[data["deviceId"]]!.ListOfMsgs - // .add(new Msg(data["sender"], data["message"], "received")); + // print("355: ack added"); } else { // Global.devices.forEach((element) { // Global.nearbyService! From 381df4f633a6f34cf5b187a4b076a8be49c306c5 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 4 Jul 2022 16:34:03 +0530 Subject: [PATCH 15/36] Implemented Save profile feature Now the user will set the username for first time after installing and then it will navigate directly to devices list screen. --- lib/pages/Profile.dart | 112 ++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index 244c03d..4bdb780 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -1,6 +1,10 @@ +import 'dart:async'; +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections_example/pages/DeviceListScreen.dart'; import 'package:nanoid/nanoid.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../classes/Global.dart'; import 'DeviceListScreen.dart'; @@ -12,52 +16,92 @@ class Profile extends StatefulWidget { class _ProfileState extends State { // TextEditingController for the name of the user TextEditingController myName = TextEditingController(); - + bool loading = true; // Custom generated id for the user var customLengthId = nanoid(6); + // Fetching details from saved profile + // If no profile is saved, then the new values are used + // else navigate to DeviceListScreen + Future getDetails() async { + // Obtain shared preferences. + final prefs = await SharedPreferences.getInstance(); + final name = prefs.getString('p_name') ?? ''; + final id = prefs.getString('p_id') ?? ''; + setState(() { + myName.text = name; + }); + if (name.isNotEmpty && id.isNotEmpty) { + navigateToDeviceListScreen(); + } else { + setState(() { + loading = false; + }); + } + } + + void navigateToDeviceListScreen() { + Global.myName = myName.text; + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => DevicesListScreen( + deviceType: DeviceType.browser, + ), + ), + ); + } + + @override + void initState() { + super.initState(); + getDetails(); + } + @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: Column( + appBar: AppBar( + title: Text( + 'Profile', + ), + ), + body: Visibility( + visible: loading, + child: Center( + child: CircularProgressIndicator(), + ), + replacement: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - SizedBox( - height: 100, - ), Text("Your Username will be your name+$customLengthId"), - TextFormField( - controller: myName, - decoration: const InputDecoration( - icon: Icon(Icons.person), - hintText: 'What do people call you?', - labelText: 'Name *', + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: myName, + decoration: const InputDecoration( + icon: Icon(Icons.person), + hintText: 'What do people call you?', + labelText: 'Name *', + border: OutlineInputBorder(), + ), + validator: (String? value) { + return (value != null && + value.contains('@') && + value.length > 3) + ? 'Do not use the @ char and name length should be greater than 3' + : null; + }, ), - onSaved: (String? value) { - // This optional block of code can be used to run - // code when the user saves the form. - }, - validator: (String? value) { - return (value != null && value.contains('@')) - ? 'Do not use the @ char.' - : null; - }, - ), - SizedBox( - height: 20, ), ElevatedButton( - onPressed: () { - // Global.myName = myName.text+custom_length_id; - Global.myName = myName.text; - + onPressed: () async { + final prefs = await SharedPreferences.getInstance(); + // saving the name and id to shared preferences + prefs.setString('p_name', myName.text); + prefs.setString('p_id', customLengthId); // On pressing, move to the device list screen - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => - DevicesListScreen(deviceType: DeviceType.browser), - ), - ); + navigateToDeviceListScreen(); }, child: Text("Save"), ) From 31ccb9f7f6724a92547c30dd290cdbd59f261ce9 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 15:54:09 +0530 Subject: [PATCH 16/36] HomeScreen manages the Tabs and initialises nearby connections --- lib/pages/HomeScreen.dart | 226 ++++++++++++++++++++++++++++++++++++++ lib/pages/Profile.dart | 13 +-- 2 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 lib/pages/HomeScreen.dart diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart new file mode 100644 index 0000000..26fd769 --- /dev/null +++ b/lib/pages/HomeScreen.dart @@ -0,0 +1,226 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:flutter_nearby_connections_example/pages/ChatListScreen.dart'; +import 'package:flutter_styled_toast/flutter_styled_toast.dart'; +import 'package:provider/provider.dart'; +import '../classes/Global.dart'; +import '../p2p/AdhocHousekeeping.dart'; +import 'DeviceListScreen.dart'; + +import '../classes/Payload.dart'; +import '../database/DatabaseHelper.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({Key? key}) : super(key: key); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + bool isInit = false; + + bool isLoading = false; + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + init(); + refreshMessages(); + } + + void init() async { + initiateNearbyService(); + checkDevices(); + broadcastLastMessageID(context); + Global.receivedDataSubscription = + Global.nearbyService!.dataReceivedSubscription(callback: (data) { + var decodedMessage = jsonDecode(data['message']); + showToast( + jsonEncode(data), + context: context, + axis: Axis.horizontal, + alignment: Alignment.center, + position: StyledToastPosition.bottom, + ); + if (decodedMessage["type"] == "Update") { + log("Update Message ${decodedMessage["id"]}"); + String sentDeviceName = decodedMessage["sender"]; + compareMessageId( + receivedId: decodedMessage["id"], + sentDeviceName: sentDeviceName, + context: context, + ); + } + setState(() { + // print("331: " + temp2['receiver'].toString()); + // print("332:" + temp2['type'].toString()); + // print("333|" + Global.myName.toString()); + // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); + if (Global.cache.containsKey(decodedMessage["id"]) == false) { + // print("line 338 test"); + + if (decodedMessage["type"].toString() == 'Payload') { + // print("line 341"); + + Global.cache[decodedMessage["id"]] = Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp']); + insertIntoMessageTable(Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp'])); + // print("current cache 344" + Global.cache.toString()); + } else { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + insertIntoMessageTable(Ack(decodedMessage["id"])); + } + } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { + if (decodedMessage["type"] == 'Ack') { + //broadcast Ack last time to neighbours + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); + } + } else { + // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); + } + print("350|" + + decodedMessage['type'].toString() + + ":Payload |" + + decodedMessage['receiver'].toString() + + ":" + + Global.myName.toString()); + if (decodedMessage['type'] == "Payload" && + decodedMessage['receiver'] == Global.myName) { + // Global.cache[temp2["id"]]!.broadcast = false; + // if (Global.conversations[decodedMessage['sender']] == null) { + // Global.conversations[decodedMessage['sender']] = Map(); + // } + Provider.of(context, listen: false) + .receivedToConversations(decodedMessage, context); + // If message type Payload, then add to cache and to conversations table + // if not already present + // if (!search( + // decodedMessage['sender'], decodedMessage["id"], context)) { + // Global.conversations[decodedMessage['sender']]![ + // decodedMessage["id"]] = Msg( + // decodedMessage['message'], + // "received", + // decodedMessage['Timestamp'], + // decodedMessage["id"], + // ); + + // insertIntoConversationsTable( + // Msg(decodedMessage['message'], "received", + // decodedMessage['Timestamp'], decodedMessage["id"]), + // decodedMessage['sender']); + // } + if (Global.cache[decodedMessage["id"]] == null) { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // print("280 test"); + insertIntoMessageTable(Ack(decodedMessage['id'])); + } else { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); + } + + // print("355: ack added"); + } else { + // Global.devices.forEach((element) { + // Global.nearbyService! + // .sendMessage(element.deviceId, data["message"].toString()); + // }); + } + }); + }); + } + + void checkDevices() { + Global.deviceSubscription = + Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { + devicesList.forEach((element) { + // if (element.state != SessionState.connected) connectToDevice(element); + print( + "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); + + if (Platform.isAndroid) { + if (element.state == SessionState.connected) { + Global.nearbyService!.stopBrowsingForPeers(); + } else { + Global.nearbyService!.startBrowsingForPeers(); + } + } + }); + Provider.of(context, listen: false).updateDevices(devicesList); + Provider.of(context, listen: false).updateConnectedDevices( + devicesList.where((d) => d.state == SessionState.connected).toList()); + log('Devices length: ${devicesList.length}'); + }); + } + + Future refreshMessages() async { + setState(() => isLoading = true); + + readAllUpdateCache(); + setState(() => isLoading = false); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + readAllUpdateConversation(context); + init(); + } + + @override + void dispose() { + Global.deviceSubscription!.cancel(); + Global.receivedDataSubscription!.cancel(); + Global.nearbyService!.stopBrowsingForPeers(); + Global.nearbyService!.stopAdvertisingPeer(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + key: _scaffoldKey, + appBar: AppBar( + title: Text("P2P Messaging"), + bottom: TabBar( + tabs: [ + Tab( + text: "Devices", + ), + Tab( + text: "All Chats", + ), + ], + ), + ), + body: TabBarView( + children: [ + DevicesListScreen( + deviceType: DeviceType.browser, + ), + ChatListScreen(), + ], + ), + ), + ); + } +} diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index 4bdb780..62ce86b 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections_example/pages/DeviceListScreen.dart'; +import 'DeviceListScreen.dart'; +import 'HomeScreen.dart'; import 'package:nanoid/nanoid.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../classes/Global.dart'; @@ -32,7 +33,7 @@ class _ProfileState extends State { myName.text = name; }); if (name.isNotEmpty && id.isNotEmpty) { - navigateToDeviceListScreen(); + navigateToHomeScreen(); } else { setState(() { loading = false; @@ -40,14 +41,12 @@ class _ProfileState extends State { } } - void navigateToDeviceListScreen() { + void navigateToHomeScreen() { Global.myName = myName.text; Navigator.pushReplacement( context, MaterialPageRoute( - builder: (context) => DevicesListScreen( - deviceType: DeviceType.browser, - ), + builder: (context) => HomeScreen(), ), ); } @@ -101,7 +100,7 @@ class _ProfileState extends State { prefs.setString('p_name', myName.text); prefs.setString('p_id', customLengthId); // On pressing, move to the device list screen - navigateToDeviceListScreen(); + navigateToHomeScreen(); }, child: Text("Save"), ) From 33dd43a365a168e764ac72aa85d7f5335906f14c Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 15:55:00 +0530 Subject: [PATCH 17/36] Added Provider State Management for better state management and auto refresh of messages --- lib/classes/Global.dart | 62 ++++++++++++++++++++++++++++++++++++----- lib/main.dart | 11 +++++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index c4361aa..3667775 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -1,11 +1,14 @@ import 'dart:async'; -import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'dart:developer'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import '../database/DatabaseHelper.dart'; import 'Msg.dart'; -class Global { - static List devices = []; - static List connectedDevices = []; +class Global extends ChangeNotifier { + List devices = []; + List connectedDevices = []; static NearbyService? nearbyService; static StreamSubscription? deviceSubscription; @@ -15,9 +18,54 @@ class Global { Msg("2", "test2", "sent", '4') ]; static Map publicKeys = Map(); - static Map> conversations = - {}; //converser mapped to conversation - + Map> conversations = Map(); static String myName = ''; static Map cache = Map(); + + // Global({ + // this.conversations = Map, + // }); + void sentToConversations(Msg msg, String converser, + {bool addToTable = true}) { + if (conversations[converser] == null) { + conversations[converser] = {}; + } + conversations[converser]![msg.id] = msg; + if (addToTable) { + insertIntoConversationsTable(msg, converser); + } + notifyListeners(); + } + + void receivedToConversations(dynamic decodedMessage, BuildContext context) { + if (conversations[decodedMessage['sender']] == null) { + conversations[decodedMessage['sender']] = Map(); + } + if (conversations[decodedMessage['sender']] != null && + !(conversations[decodedMessage['sender']]! + .containsKey(decodedMessage['id']))) { + conversations[decodedMessage['sender']]![decodedMessage["id"]] = Msg( + decodedMessage['message'], + "received", + decodedMessage['Timestamp'], + decodedMessage["id"], + ); + insertIntoConversationsTable( + Msg(decodedMessage['message'], "received", + decodedMessage['Timestamp'], decodedMessage["id"]), + decodedMessage['sender']); + } + + notifyListeners(); + } + + void updateDevices(List devices) { + this.devices = devices; + notifyListeners(); + } + + void updateConnectedDevices(List devices) { + this.connectedDevices = devices; + notifyListeners(); + } } diff --git a/lib/main.dart b/lib/main.dart index 3be7727..6d53784 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:flutter_nearby_connections_example/classes/Global.dart'; +import 'package:provider/provider.dart'; import 'pages/Profile.dart'; void main() { runApp( - MyApp(), + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => Global(), + ), + ], + child: MyApp(), + ), ); } From e3d926a81cb9f30298063f280c709522f919d999 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 15:56:34 +0530 Subject: [PATCH 18/36] Separated MessagePanel for sending messages separately Code refactor --- lib/components/message_panel.dart | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 lib/components/message_panel.dart diff --git a/lib/components/message_panel.dart b/lib/components/message_panel.dart new file mode 100644 index 0000000..5739fb7 --- /dev/null +++ b/lib/components/message_panel.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:nanoid/nanoid.dart'; +import 'package:provider/provider.dart'; + +import '../classes/Global.dart'; +import '../classes/Msg.dart'; +import '../classes/Payload.dart'; +import '../database/DatabaseHelper.dart'; + +class MessagePanel extends StatefulWidget { + const MessagePanel({Key? key, required this.converser}) : super(key: key); + final String converser; + + @override + State createState() => _MessagePanelState(); +} + +class _MessagePanelState extends State { + TextEditingController myController = TextEditingController(); + @override + Widget build(BuildContext context) { + return TextFormField( + controller: myController, + decoration: InputDecoration( + icon: Icon(Icons.person), + hintText: 'Send Message?', + labelText: 'Send Message ', + suffixIcon: IconButton( + onPressed: () { + var msgId = nanoid(21); + var data = { + "sender": "$Global.myName", + "receiver": "$widget.device.deviceName", + "message": "$myController.text", + "id": "$msgId", + "Timestamp": "${DateTime.now().toUtc().toString()}", + "type": "Payload" + }; + Global.cache[msgId] = Payload( + msgId, + Global.myName, + widget.converser, + myController.text, + DateTime.now().toUtc().toString()); + insertIntoMessageTable(Payload( + msgId, + Global.myName, + widget.converser, + myController.text, + DateTime.now().toUtc().toString())); + // Global.devices.forEach((element) { + // Global.nearbyService! + // .sendMessage(element.deviceId, Mesagedata); + // }); + // Global.nearbyService! + // .sendMessage(widget.device.deviceId, myController.text); + setState(() { + // Global.conversations[widget.device.deviceName]![msgId]( + // new Msg( + // widget.device.deviceId, myController.text, "sent")); + Provider.of(context, listen: false).sentToConversations( + Msg(myController.text, "sent", data["Timestamp"]!, msgId), + widget.converser); + // if (Global.conversations[widget.converser] == null) { + // Global.conversations[widget.converser] = {}; + // } + // Global.conversations[widget.converser]![msgId] = + // Msg(myController.text, "sent", data["Timestamp"]!, msgId); + + // insertIntoConversationsTable( + // Msg(myController.text, "sent", data["Timestamp"]!, msgId), + // widget.converser); + }); + // refreshMessages(); + myController.clear(); + }, + icon: Icon( + Icons.send, + ), + ), + ), + ); + } +} From 18bde65fb6dd575e900df80f3be520669f375e5b Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 15:57:22 +0530 Subject: [PATCH 19/36] Added provider state management to related files --- lib/database/DatabaseHelper.dart | 30 ++--- lib/encyption/rsa.dart | 2 +- lib/p2p/AdhocHousekeeping.dart | 38 ++++-- lib/pages/ChatListScreen.dart | 162 +++++++------------------ lib/pages/ChatPage.dart | 196 +++++++++++-------------------- 5 files changed, 152 insertions(+), 276 deletions(-) diff --git a/lib/database/DatabaseHelper.dart b/lib/database/DatabaseHelper.dart index e03d3f4..b3b290d 100644 --- a/lib/database/DatabaseHelper.dart +++ b/lib/database/DatabaseHelper.dart @@ -1,25 +1,30 @@ -import 'package:flutter_nearby_connections_example/classes/Global.dart'; -import 'package:flutter_nearby_connections_example/classes/Msg.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import '../classes/Global.dart'; +import '../classes/Msg.dart'; import 'dart:convert'; import '../classes/Payload.dart'; import 'model.dart'; import 'MessageDB.dart'; -Future readAllUpdateConversation() async { +Future readAllUpdateConversation(BuildContext context) async { List conversations = [ ConversationFromDB("1", "2", "3", "5", "6", "7") ]; var value = await MessageDB.instance.readAllFromConversationsTable(); conversations = value; - print("12:" + conversations.toString()); conversations.forEach((element) { - print(element.msg + ":14:" + element.type + element.timestamp); - if (Global.conversations[element.converser] == null) { - Global.conversations[element.converser] = Map(); - } - - Global.conversations[element.converser]![element.id] = - Msg(element.msg, element.type, element.timestamp, element.id); + // if (Global.conversations[element.converser] == null) { + // Global.conversations[element.converser] = Map(); + // } + Provider.of(context, listen: false).sentToConversations( + Msg(element.msg, element.type, element.timestamp, element.id), + element.converser, + addToTable: false, + ); + // Global.conversations[element.converser]![element.id] = + // Msg(element.msg, element.type, element.timestamp, element.id); }); // print("19:" + Global.conversations.toString()); } @@ -40,11 +45,9 @@ void readAllUpdateCache() { List messages = [MessageFromDB("1", "2", "3")]; MessageDB.instance.readAllFromMessagesTable().then((value) { messages = value; - print("10 tablevalues"); value.forEach((element) { print("_id ${element.id} type ${element.type} msg: ${element.msg}\n"); }); - print(value); messages.forEach((element) { print("line 16 dbhelper"); if (element.type == 'Ack') @@ -52,7 +55,6 @@ void readAllUpdateCache() { else Global.cache[element.id] = convertToPayload(element); }); - print("reloaded cache #22 " + Global.cache.toString()); }); } diff --git a/lib/encyption/rsa.dart b/lib/encyption/rsa.dart index 356ddb7..dc53861 100644 --- a/lib/encyption/rsa.dart +++ b/lib/encyption/rsa.dart @@ -1,5 +1,5 @@ -import 'package:rsa_encrypt/rsa_encrypt.dart'; import 'package:pointycastle/api.dart' as crypto; +import 'package:rsa_encrypt/rsa_encrypt.dart'; //Future to hold our KeyPair Future? futureKeyPair; diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index f766328..7bdb625 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -7,6 +7,7 @@ import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:flutter_nearby_connections_example/database/MessageDB.dart'; +import 'package:provider/provider.dart'; import '../classes/Global.dart'; // Get the state name of the connection @@ -58,17 +59,27 @@ Color getButtonColor(SessionState state) { } // Get the number of devices -int getItemCount() { - return Global.devices.length; +int getItemCount(BuildContext context) { + return Provider.of(context, listen: false).devices.length; } // Check if the id exists in the conversation list bool search(String sender, String id, BuildContext context) { - if (Global.conversations[sender] == null) return false; - - if (Global.conversations[sender]!.containsKey(id)) { - return true; + // if (Global.conversations[sender] == null) return false; + + // if (Global.conversations[sender]!.containsKey(id)) { + // return true; + // } + // Get from Provider + if (Provider.of(context, listen: false).conversations[sender] != + null) { + if (Provider.of(context, listen: false) + .conversations[sender]! + .containsKey(id)) { + return true; + } } + return false; } @@ -101,7 +112,7 @@ void startAdvertising() async { } // this function is supposed to broadcast all messages in the cache when the message ids don't match -void broadcast() async { +void broadcast(BuildContext context) async { Global.cache.forEach((key, value) { // if a message is supposed to be broadcasted to all devices in proximity then if (value.runtimeType == Payload && value.broadcast) { @@ -115,13 +126,13 @@ void broadcast() async { "type": "Payload" }; var toSend = jsonEncode(data); - Global.devices.forEach((element) { + Provider.of(context, listen: false).devices.forEach((element) { print("270" + toSend); Global.nearbyService! .sendMessage(element.deviceId, toSend); //make this async }); } else if (value.runtimeType == Ack) { - Global.devices.forEach((element) { + Provider.of(context, listen: false).devices.forEach((element) { var data = {"id": "$key", "type": "Ack"}; Global.nearbyService!.sendMessage(element.deviceId, jsonEncode(data)); }); @@ -131,13 +142,15 @@ void broadcast() async { } // Broadcasting update request message to the connected devices to recieve fresh messages that are yet to be recieved -void broadcastLastMessageID() async { +void broadcastLastMessageID(BuildContext context) async { // Fetch from Database the last message. Timer.periodic(Duration(seconds: 3), (timer) async { String id = await MessageDB.instance.getLastMessageId(type: "received"); log("Last message id: " + id); - Global.devices.forEach((element) async { + Provider.of(context, listen: false) + .devices + .forEach((element) async { var data = { "sender": Global.myName, "receiver": element.deviceName, @@ -180,10 +193,11 @@ void broadcastLastMessageID() async { void compareMessageId({ required String receivedId, required String sentDeviceName, + required BuildContext context, }) async { String sentId = await MessageDB.instance.getLastMessageId(type: "sent"); if (sentId != receivedId) { - broadcast(); + broadcast(context); } } diff --git a/lib/pages/ChatListScreen.dart b/lib/pages/ChatListScreen.dart index fd2f66c..62f85e9 100644 --- a/lib/pages/ChatListScreen.dart +++ b/lib/pages/ChatListScreen.dart @@ -1,4 +1,6 @@ -import 'package:flutter_nearby_connections_example/database/DatabaseHelper.dart'; +import 'package:provider/provider.dart'; + +import '../database/DatabaseHelper.dart'; import 'package:flutter/material.dart'; @@ -19,15 +21,7 @@ class _ChatListScreenState extends State { void initState() { super.initState(); readAllUpdateCache(); - readAllUpdateConversation().then((value) { - // print("34" + Global.conversations.toString()); - - Global.conversations.forEach((key, value) { - conversers.add(key); - }); - print(" 37 reloaded:" + Global.cache.toString()); - }); // print("34" + Global.conversations.toString()); // // Global.conversations.forEach((key, value) { @@ -45,125 +39,53 @@ class _ChatListScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Chats"), - ), - backgroundColor: Colors.white, - bottomNavigationBar: BottomNavigationBar( - selectedItemColor: Colors.red, - unselectedItemColor: Colors.grey.shade600, - selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), - unselectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), - type: BottomNavigationBarType.fixed, - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.message), - label: "Chats", - ), - BottomNavigationBarItem( - icon: Icon(Icons.group_work), - label: "Available", - ), - BottomNavigationBarItem( - icon: Icon(Icons.account_box), - label: "Profile", - ), - ], - ), - body: Container( - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 16, left: 16, right: 16), - child: TextField( - decoration: InputDecoration( - hintText: "Search...", - hintStyle: TextStyle(color: Colors.grey.shade600), - prefixIcon: Icon( - Icons.search, - color: Colors.grey.shade600, - size: 20, - ), - filled: true, - fillColor: Colors.grey.shade100, - contentPadding: EdgeInsets.all(8), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide(color: Colors.grey.shade100)), + conversers = []; + Provider.of(context).conversations.forEach((key, value) { + conversers.add(key); + }); + return Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 16, right: 16), + child: TextField( + decoration: InputDecoration( + hintText: "Search...", + hintStyle: TextStyle(color: Colors.grey.shade600), + prefixIcon: Icon( + Icons.search, + color: Colors.grey.shade600, + size: 20, ), + filled: true, + fillColor: Colors.grey.shade100, + contentPadding: EdgeInsets.all(8), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide(color: Colors.grey.shade100)), ), ), - ListView.builder( + ), + Expanded( + child: ListView.builder( itemCount: conversers.length, shrinkWrap: true, itemBuilder: (context, index) { - return Container( - margin: EdgeInsets.all(8.0), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return ChatPage( - converser: conversers[index], - ); - }, - ), - ); - }, - child: Column( - children: [ - Text(conversers[index]), - ], - crossAxisAlignment: CrossAxisAlignment.start, - ), - )), - // Request connect - ], - ), - SizedBox( - height: 8.0, - ), - Divider( - height: 1, - color: Colors.grey, + return ListTile( + title: Text(conversers[index]), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatPage( + converser: conversers[index], + ), ), - Text("hello"), - ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - padding: const EdgeInsets.all(8), - itemCount: Global.messages.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 15, - // color: Colors.amber[colorCodes[index]], - child: Center( - child: Text(Global.messages[index].msgtype + - ":" + - " " + - Global.messages[index].message)), - ); - }), - ], - ), + ); + }, ); - }) - ], - )), - floatingActionButton: FloatingActionButton( - onPressed: () { - // Add your onPressed code here! - }, - child: const Icon(Icons.add), - backgroundColor: Colors.green, - ), + }), + ) + ], ); } } diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index e6ac5af..c7b6f0d 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -1,9 +1,9 @@ +import 'dart:developer'; + import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections_example/classes/Payload.dart'; -import 'package:nanoid/nanoid.dart'; - -import '../database/DatabaseHelper.dart'; +import 'package:flutter_nearby_connections_example/components/message_panel.dart'; +import 'package:provider/provider.dart'; import '../classes/Msg.dart'; import '../classes/Global.dart'; @@ -20,144 +20,82 @@ class ChatPageState extends State { List messageList = []; TextEditingController myController = TextEditingController(); - // Function to fetch messages every time new message is sent - void refreshMessages() { - if (Global.conversations[widget.converser] != null) { - messageList = []; - Global.conversations[widget.converser]!.forEach((key, value) { - messageList.add(value); - }); - if (_scrollController.hasClients) - _scrollController.animateTo( - _scrollController.position.maxScrollExtent + 100, - duration: Duration(milliseconds: 300), - curve: Curves.easeOut, - ); - } - } - @override void initState() { super.initState(); - refreshMessages(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); } ScrollController _scrollController = new ScrollController(); Widget build(BuildContext context) { + if (Provider.of(context).conversations[widget.converser] != null) { + messageList = []; + Provider.of(context) + .conversations[widget.converser]! + .forEach((key, value) { + messageList.add(value); + }); + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent + 50, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + } return Scaffold( - resizeToAvoidBottomInset: false, + // resizeToAvoidBottomInset: false, appBar: AppBar( title: Text('Chat with ' + widget.converser), ), - body: SingleChildScrollView( - reverse: true, - child: Padding( - padding: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: Column( - children: [ - Container( - height: MediaQuery.of(context).size.height * .8, - child: messageList.isEmpty - ? Center( - child: Text('No messages yet'), - ) - : ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - controller: _scrollController, - padding: const EdgeInsets.all(8), - itemCount: messageList.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 55, - child: messageList[index].msgtype == 'sent' - ? Bubble( - margin: BubbleEdges.only(top: 10), - nip: BubbleNip.rightTop, - color: Color(0xffd1c4e9), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - textAlign: TextAlign.right, - ), - ) - : Bubble( - nip: BubbleNip.leftTop, - color: Color(0xff80DEEA), - margin: BubbleEdges.only(top: 10), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - ), - ), - ); - }, - ), - ), - TextFormField( - controller: myController, - decoration: const InputDecoration( - icon: Icon(Icons.person), - hintText: 'Send Message?', - labelText: 'Send Message ', - ), - ), - ElevatedButton( - onPressed: () { - var msgId = nanoid(21); - var data = { - "sender": "$Global.myName", - "receiver": "$widget.device.deviceName", - "message": "$myController.text", - "id": "$msgId", - "Timestamp": "${DateTime.now().toUtc().toString()}", - "type": "Payload" - }; - Global.cache[msgId] = Payload( - msgId, - Global.myName, - widget.converser, - myController.text, - DateTime.now().toUtc().toString()); - insertIntoMessageTable(Payload( - msgId, - Global.myName, - widget.converser, - myController.text, - DateTime.now().toUtc().toString())); - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, Mesagedata); - // }); - // Global.nearbyService! - // .sendMessage(widget.device.deviceId, myController.text); - setState(() { - // Global.conversations[widget.device.deviceName]![msgId]( - // new Msg( - // widget.device.deviceId, myController.text, "sent")); - if (Global.conversations[widget.converser] == null) { - Global.conversations[widget.converser] = {}; - } - Global.conversations[widget.converser]![msgId] = Msg( - myController.text, "sent", data["Timestamp"]!, msgId); - - insertIntoConversationsTable( - Msg(myController.text, "sent", data["Timestamp"]!, - msgId), - widget.converser); - }); - refreshMessages(); - myController.clear(); - }, - child: Text("send"), - ), - ], + body: Column( + children: [ + Expanded( + child: messageList.isEmpty + ? Center( + child: Text('No messages yet'), + ) + : ListView.builder( + shrinkWrap: true, + controller: _scrollController, + padding: const EdgeInsets.all(8), + itemCount: messageList.length, + itemBuilder: (BuildContext context, int index) { + return Container( + height: 55, + child: messageList[index].msgtype == 'sent' + ? Bubble( + margin: BubbleEdges.only(top: 10), + nip: BubbleNip.rightTop, + color: Color(0xffd1c4e9), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + textAlign: TextAlign.right, + ), + ) + : Bubble( + nip: BubbleNip.leftTop, + color: Color(0xff80DEEA), + margin: BubbleEdges.only(top: 10), + child: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + ), + ), + ); + }, + ), ), - ), + MessagePanel(converser: widget.converser), + ], ), ); } From bf36880ca1ad3be81a11ac05e4f7f588e9f3377e Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 15:57:52 +0530 Subject: [PATCH 20/36] Removed the init code and moved to HomeScreen --- lib/pages/DeviceListScreen.dart | 467 +++++++++++++++----------------- 1 file changed, 218 insertions(+), 249 deletions(-) diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index b11154a..b1a014e 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -9,6 +9,7 @@ import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:provider/provider.dart'; import '../classes/Global.dart'; import '../classes/Msg.dart'; @@ -34,28 +35,24 @@ class _DevicesListScreenState extends State { @override void initState() { super.initState(); - init(); - refreshMessages(); + // init(); // print(" 37 reloaded:" + Global.cache.toString()); // checkForMessageUpdates(); } - Future refreshMessages() async { - setState(() => isLoading = true); - - readAllUpdateCache(); - readAllUpdateConversation(); - setState(() => isLoading = false); - } + // @override + // void didChangeDependencies() { + // super.didChangeDependencies(); + // refreshMessages(); + // } - @override - void dispose() { - Global.deviceSubscription!.cancel(); - Global.receivedDataSubscription!.cancel(); - Global.nearbyService!.stopBrowsingForPeers(); - Global.nearbyService!.stopAdvertisingPeer(); - super.dispose(); - } + // Future refreshMessages() async { + // setState(() => isLoading = true); + // log("refreshing messages"); + // readAllUpdateCache(); + // readAllUpdateConversation(context); + // setState(() => isLoading = false); + // } var _selectedIndex = 0; @@ -80,257 +77,229 @@ class _DevicesListScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Available Devices"), - ), - backgroundColor: Colors.white, - bottomNavigationBar: BottomNavigationBar( - selectedItemColor: Colors.red, - unselectedItemColor: Colors.grey.shade600, - selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), - unselectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), - type: BottomNavigationBarType.fixed, - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.message), - label: "Chats", - ), - BottomNavigationBarItem( - icon: Icon(Icons.group_work), - label: "Available", - ), - BottomNavigationBarItem( - icon: Icon(Icons.account_box), - label: "Profile", - ), - ], - currentIndex: _selectedIndex, - onTap: _onItemTapped, - ), - body: Container( - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 16, left: 16, right: 16), - child: TextField( - decoration: InputDecoration( - hintText: "Search...", - hintStyle: TextStyle(color: Colors.grey.shade600), - prefixIcon: Icon( - Icons.search, - color: Colors.grey.shade600, - size: 20, - ), - filled: true, - fillColor: Colors.grey.shade100, - contentPadding: EdgeInsets.all(8), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide(color: Colors.grey.shade100)), + return Container( + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16, left: 16, right: 16), + child: TextField( + decoration: InputDecoration( + hintText: "Search...", + hintStyle: TextStyle(color: Colors.grey.shade600), + prefixIcon: Icon( + Icons.search, + color: Colors.grey.shade600, + size: 20, ), + filled: true, + fillColor: Colors.grey.shade100, + contentPadding: EdgeInsets.all(8), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide(color: Colors.grey.shade100)), ), ), - ListView.builder( - itemCount: getItemCount(), - shrinkWrap: true, - itemBuilder: (context, index) { - final device = Global.devices[index]; - return Container( - margin: EdgeInsets.all(8.0), - child: Column( - children: [ - ListTile( - title: Text(device.deviceName), - subtitle: Text( - getStateName(device.state), - style: - TextStyle(color: getStateColor(device.state)), - ), - trailing: GestureDetector( - onTap: () => connectToDevice(device), - child: Container( - margin: EdgeInsets.symmetric(horizontal: 8.0), - padding: EdgeInsets.all(8.0), - height: 35, - width: 100, - color: getButtonColor(device.state), - child: Center( - child: Text( - getButtonStateName(device.state), - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold), - ), + ), + ListView.builder( + itemCount: Provider.of(context).devices.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final device = Provider.of(context).devices[index]; + return Container( + margin: EdgeInsets.all(8.0), + child: Column( + children: [ + ListTile( + title: Text(device.deviceName), + subtitle: Text( + getStateName(device.state), + style: TextStyle(color: getStateColor(device.state)), + ), + trailing: GestureDetector( + onTap: () => connectToDevice(device), + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.0), + padding: EdgeInsets.all(8.0), + height: 35, + width: 100, + color: getButtonColor(device.state), + child: Center( + child: Text( + getButtonStateName(device.state), + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), ), ), ), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return ChatPage( - converser: device.deviceName, - ); - }, - ), - ); - }, ), - Divider( - height: 1, - color: Colors.grey, - ), - ], - ), - ); - }, - ), - ], - ), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return ChatPage( + converser: device.deviceName, + ); + }, + ), + ); + }, + ), + Divider( + height: 1, + color: Colors.grey, + ), + ], + ), + ); + }, + ), + ], ), ), ); } // Check for devices in proximity - void checkDevices() { - Global.deviceSubscription = - Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { - devicesList.forEach((element) { - // if (element.state != SessionState.connected) connectToDevice(element); - print( - "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); + // void checkDevices() { + // Global.deviceSubscription = + // Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { + // devicesList.forEach((element) { + // // if (element.state != SessionState.connected) connectToDevice(element); + // print( + // "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); - if (Platform.isAndroid) { - if (element.state == SessionState.connected) { - Global.nearbyService!.stopBrowsingForPeers(); - } else { - Global.nearbyService!.startBrowsingForPeers(); - } - } - }); + // if (Platform.isAndroid) { + // if (element.state == SessionState.connected) { + // Global.nearbyService!.stopBrowsingForPeers(); + // } else { + // Global.nearbyService!.startBrowsingForPeers(); + // } + // } + // }); - setState(() { - Global.devices.clear(); - Global.devices.addAll(devicesList); - Global.connectedDevices.clear(); - Global.connectedDevices.addAll(devicesList - .where((d) => d.state == SessionState.connected) - .toList()); - }); - }); - } + // setState(() { + // Global.devices.clear(); + // Global.devices.addAll(devicesList); + // Global.connectedDevices.clear(); + // Global.connectedDevices.addAll(devicesList + // .where((d) => d.state == SessionState.connected) + // .toList()); + // }); + // }); + // } // The function responsible for receiving the messages - void init() async { - initiateNearbyService(); - checkDevices(); - broadcastLastMessageID(); - Global.receivedDataSubscription = - Global.nearbyService!.dataReceivedSubscription(callback: (data) { - var decodedMessage = jsonDecode(data['message']); - showToast( - jsonEncode(data), - context: context, - axis: Axis.horizontal, - alignment: Alignment.center, - position: StyledToastPosition.bottom, - ); - if (decodedMessage["type"] == "Update") { - log("Update Message ${decodedMessage["id"]}"); - String sentDeviceName = decodedMessage["sender"]; - compareMessageId( - receivedId: decodedMessage["id"], - sentDeviceName: sentDeviceName, - ); - } - setState(() { - // print("331: " + temp2['receiver'].toString()); - // print("332:" + temp2['type'].toString()); - // print("333|" + Global.myName.toString()); - // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); - if (Global.cache.containsKey(decodedMessage["id"]) == false) { - // print("line 338 test"); + // void init() async { + // initiateNearbyService(); + // checkDevices(); + // broadcastLastMessageID(); + // Global.receivedDataSubscription = + // Global.nearbyService!.dataReceivedSubscription(callback: (data) { + // var decodedMessage = jsonDecode(data['message']); + // showToast( + // jsonEncode(data), + // context: context, + // axis: Axis.horizontal, + // alignment: Alignment.center, + // position: StyledToastPosition.bottom, + // ); + // if (decodedMessage["type"] == "Update") { + // log("Update Message ${decodedMessage["id"]}"); + // String sentDeviceName = decodedMessage["sender"]; + // compareMessageId( + // receivedId: decodedMessage["id"], + // sentDeviceName: sentDeviceName, + // ); + // } + // setState(() { + // // print("331: " + temp2['receiver'].toString()); + // // print("332:" + temp2['type'].toString()); + // // print("333|" + Global.myName.toString()); + // // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); + // if (Global.cache.containsKey(decodedMessage["id"]) == false) { + // // print("line 338 test"); - if (decodedMessage["type"].toString() == 'Payload') { - // print("line 341"); + // if (decodedMessage["type"].toString() == 'Payload') { + // // print("line 341"); - Global.cache[decodedMessage["id"]] = Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp']); - insertIntoMessageTable(Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp'])); - // print("current cache 344" + Global.cache.toString()); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - insertIntoMessageTable(Ack(decodedMessage["id"])); - } - } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { - if (decodedMessage["type"] == 'Ack') { - //broadcast Ack last time to neighbours - Global.cache.remove(decodedMessage["id"]); - deleteFromMessageTable(decodedMessage["id"]); - } - } else { - // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore - Global.cache.remove(decodedMessage["id"]); - deleteFromMessageTable(decodedMessage["id"]); - } - print("350|" + - decodedMessage['type'].toString() + - ":Payload |" + - decodedMessage['receiver'].toString() + - ":" + - Global.myName.toString()); - if (decodedMessage['type'] == "Payload" && - decodedMessage['receiver'] == Global.myName) { - // Global.cache[temp2["id"]]!.broadcast = false; - if (Global.conversations[decodedMessage['sender']] == null) { - Global.conversations[decodedMessage['sender']] = Map(); - } - // If message type Payload, then add to cache and to conversations table - // if not already present - if (!search( - decodedMessage['sender'], decodedMessage["id"], context)) { - Global.conversations[decodedMessage['sender']]![ - decodedMessage["id"]] = Msg( - decodedMessage['message'], - "received", - decodedMessage['Timestamp'], - decodedMessage["id"], - ); + // Global.cache[decodedMessage["id"]] = Payload( + // decodedMessage["id"], + // decodedMessage['sender'], + // decodedMessage['receiver'], + // decodedMessage['message'], + // decodedMessage['Timestamp']); + // insertIntoMessageTable(Payload( + // decodedMessage["id"], + // decodedMessage['sender'], + // decodedMessage['receiver'], + // decodedMessage['message'], + // decodedMessage['Timestamp'])); + // // print("current cache 344" + Global.cache.toString()); + // } else { + // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // insertIntoMessageTable(Ack(decodedMessage["id"])); + // } + // } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { + // if (decodedMessage["type"] == 'Ack') { + // //broadcast Ack last time to neighbours + // Global.cache.remove(decodedMessage["id"]); + // deleteFromMessageTable(decodedMessage["id"]); + // } + // } else { + // // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore + // Global.cache.remove(decodedMessage["id"]); + // deleteFromMessageTable(decodedMessage["id"]); + // } + // print("350|" + + // decodedMessage['type'].toString() + + // ":Payload |" + + // decodedMessage['receiver'].toString() + + // ":" + + // Global.myName.toString()); + // if (decodedMessage['type'] == "Payload" && + // decodedMessage['receiver'] == Global.myName) { + // // Global.cache[temp2["id"]]!.broadcast = false; + // // if (Global.conversations[decodedMessage['sender']] == null) { + // // Global.conversations[decodedMessage['sender']] = Map(); + // // } + // Provider.of(context, listen: false) + // .receivedToConversations(decodedMessage, context); + // // If message type Payload, then add to cache and to conversations table + // // if not already present + // // if (!search( + // // decodedMessage['sender'], decodedMessage["id"], context)) { + // // Global.conversations[decodedMessage['sender']]![ + // // decodedMessage["id"]] = Msg( + // // decodedMessage['message'], + // // "received", + // // decodedMessage['Timestamp'], + // // decodedMessage["id"], + // // ); - insertIntoConversationsTable( - Msg(decodedMessage['message'], "received", - decodedMessage['Timestamp'], decodedMessage["id"]), - decodedMessage['sender']); - } - if (Global.cache[decodedMessage["id"]] == null) { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // print("280 test"); - insertIntoMessageTable(Ack(decodedMessage['id'])); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); - } + // // insertIntoConversationsTable( + // // Msg(decodedMessage['message'], "received", + // // decodedMessage['Timestamp'], decodedMessage["id"]), + // // decodedMessage['sender']); + // // } + // if (Global.cache[decodedMessage["id"]] == null) { + // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // // print("280 test"); + // insertIntoMessageTable(Ack(decodedMessage['id'])); + // } else { + // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); + // } - // print("355: ack added"); - } else { - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, data["message"].toString()); - // }); - } - }); - }); - } + // // print("355: ack added"); + // } else { + // // Global.devices.forEach((element) { + // // Global.nearbyService! + // // .sendMessage(element.deviceId, data["message"].toString()); + // // }); + // } + // }); + // }); + // } } From 1f3c7bb78d955b0e78b46a9bf0638d7b9b2af60e Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 19:56:20 +0530 Subject: [PATCH 21/36] Added Date, Time in message component --- lib/pages/ChatPage.dart | 64 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index c7b6f0d..11f6fae 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -1,8 +1,7 @@ -import 'dart:developer'; - import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections_example/components/message_panel.dart'; +import 'package:intl/intl.dart'; +import '../components/message_panel.dart'; import 'package:provider/provider.dart'; import '../classes/Msg.dart'; import '../classes/Global.dart'; @@ -66,30 +65,33 @@ class ChatPageState extends State { padding: const EdgeInsets.all(8), itemCount: messageList.length, itemBuilder: (BuildContext context, int index) { - return Container( - height: 55, - child: messageList[index].msgtype == 'sent' - ? Bubble( - margin: BubbleEdges.only(top: 10), - nip: BubbleNip.rightTop, - color: Color(0xffd1c4e9), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - textAlign: TextAlign.right, - ), - ) - : Bubble( - nip: BubbleNip.leftTop, - color: Color(0xff80DEEA), - margin: BubbleEdges.only(top: 10), - child: Text( - messageList[index].msgtype + - ": " + - messageList[index].message, - ), - ), + return Bubble( + margin: BubbleEdges.only(top: 10), + nip: messageList[index].msgtype == 'sent' + ? BubbleNip.rightTop + : BubbleNip.leftTop, + color: messageList[index].msgtype == 'sent' + ? Color(0xffd1c4e9) + : Color(0xff80DEEA), + child: ListTile( + dense: true, + title: Text( + messageList[index].msgtype + + ": " + + messageList[index].message, + textAlign: messageList[index].msgtype == 'sent' + ? TextAlign.right + : TextAlign.left, + ), + subtitle: Text( + dateFormatter( + timeStamp: messageList[index].timestamp, + ), + textAlign: messageList[index].msgtype == 'sent' + ? TextAlign.right + : TextAlign.left, + ), + ), ); }, ), @@ -100,3 +102,11 @@ class ChatPageState extends State { ); } } + +String dateFormatter({required String timeStamp}) { + // From timestamp to readable date and hour minutes + DateTime dateTime = DateTime.parse(timeStamp); + String formattedDate = DateFormat('dd/MM/yyyy').format(dateTime); + String formattedTime = DateFormat('HH:mm').format(dateTime); + return formattedDate + " " + formattedTime; +} From 02d45ec4530b9748ccdff12d5964e2022858d202 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 11 Jul 2022 19:56:57 +0530 Subject: [PATCH 22/36] Fixed imports and removed unused code --- lib/main.dart | 2 +- lib/p2p/AdhocHousekeeping.dart | 4 +- lib/pages/DeviceListScreen.dart | 23 ++--- lib/pages/HomeScreen.dart | 163 ++++++++++++++++---------------- 4 files changed, 89 insertions(+), 103 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6d53784..15f567f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections_example/classes/Global.dart'; +import 'classes/Global.dart'; import 'package:provider/provider.dart'; import 'pages/Profile.dart'; diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 7bdb625..6e61821 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -1,11 +1,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; - -// import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter_nearby_connections_example/database/MessageDB.dart'; import 'package:provider/provider.dart'; import '../classes/Global.dart'; diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index b1a014e..09fc320 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -1,18 +1,7 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:developer'; -import 'dart:io'; - -// import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter_nearby_connections_example/database/DatabaseHelper.dart'; -import 'package:flutter_nearby_connections_example/classes/Payload.dart'; -import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:provider/provider.dart'; import '../classes/Global.dart'; -import '../classes/Msg.dart'; import 'ChatPage.dart'; import '../p2p/AdhocHousekeeping.dart'; @@ -54,7 +43,7 @@ class _DevicesListScreenState extends State { // setState(() => isLoading = false); // } - var _selectedIndex = 0; + // var _selectedIndex = 0; // Widget getBody(BuildContext context) { // switch (_selectedIndex) { @@ -69,11 +58,11 @@ class _DevicesListScreenState extends State { // } // } - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } + // void _onItemTapped(int index) { + // setState(() { + // _selectedIndex = index; + // }); + // } @override Widget build(BuildContext context) { diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart index 26fd769..cb98969 100644 --- a/lib/pages/HomeScreen.dart +++ b/lib/pages/HomeScreen.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; -import 'package:flutter_nearby_connections_example/pages/ChatListScreen.dart'; +import 'ChatListScreen.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:provider/provider.dart'; import '../classes/Global.dart'; @@ -57,93 +57,92 @@ class _HomeScreenState extends State { context: context, ); } - setState(() { - // print("331: " + temp2['receiver'].toString()); - // print("332:" + temp2['type'].toString()); - // print("333|" + Global.myName.toString()); - // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); - if (Global.cache.containsKey(decodedMessage["id"]) == false) { - // print("line 338 test"); - - if (decodedMessage["type"].toString() == 'Payload') { - // print("line 341"); - - Global.cache[decodedMessage["id"]] = Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp']); - insertIntoMessageTable(Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp'])); - // print("current cache 344" + Global.cache.toString()); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - insertIntoMessageTable(Ack(decodedMessage["id"])); - } - } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { - if (decodedMessage["type"] == 'Ack') { - //broadcast Ack last time to neighbours - Global.cache.remove(decodedMessage["id"]); - deleteFromMessageTable(decodedMessage["id"]); - } + + // print("331: " + temp2['receiver'].toString()); + // print("332:" + temp2['type'].toString()); + // print("333|" + Global.myName.toString()); + // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); + if (Global.cache.containsKey(decodedMessage["id"]) == false) { + // print("line 338 test"); + + if (decodedMessage["type"].toString() == 'Payload') { + // print("line 341"); + + Global.cache[decodedMessage["id"]] = Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp']); + insertIntoMessageTable(Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp'])); + // print("current cache 344" + Global.cache.toString()); } else { - // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + insertIntoMessageTable(Ack(decodedMessage["id"])); + } + } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { + if (decodedMessage["type"] == 'Ack') { + //broadcast Ack last time to neighbours Global.cache.remove(decodedMessage["id"]); deleteFromMessageTable(decodedMessage["id"]); } - print("350|" + - decodedMessage['type'].toString() + - ":Payload |" + - decodedMessage['receiver'].toString() + - ":" + - Global.myName.toString()); - if (decodedMessage['type'] == "Payload" && - decodedMessage['receiver'] == Global.myName) { - // Global.cache[temp2["id"]]!.broadcast = false; - // if (Global.conversations[decodedMessage['sender']] == null) { - // Global.conversations[decodedMessage['sender']] = Map(); - // } - Provider.of(context, listen: false) - .receivedToConversations(decodedMessage, context); - // If message type Payload, then add to cache and to conversations table - // if not already present - // if (!search( - // decodedMessage['sender'], decodedMessage["id"], context)) { - // Global.conversations[decodedMessage['sender']]![ - // decodedMessage["id"]] = Msg( - // decodedMessage['message'], - // "received", - // decodedMessage['Timestamp'], - // decodedMessage["id"], - // ); - - // insertIntoConversationsTable( - // Msg(decodedMessage['message'], "received", - // decodedMessage['Timestamp'], decodedMessage["id"]), - // decodedMessage['sender']); - // } - if (Global.cache[decodedMessage["id"]] == null) { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // print("280 test"); - insertIntoMessageTable(Ack(decodedMessage['id'])); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); - } - - // print("355: ack added"); + } else { + // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); + } + print("350|" + + decodedMessage['type'].toString() + + ":Payload |" + + decodedMessage['receiver'].toString() + + ":" + + Global.myName.toString()); + if (decodedMessage['type'] == "Payload" && + decodedMessage['receiver'] == Global.myName) { + // Global.cache[temp2["id"]]!.broadcast = false; + // if (Global.conversations[decodedMessage['sender']] == null) { + // Global.conversations[decodedMessage['sender']] = Map(); + // } + Provider.of(context, listen: false) + .receivedToConversations(decodedMessage, context); + // If message type Payload, then add to cache and to conversations table + // if not already present + // if (!search( + // decodedMessage['sender'], decodedMessage["id"], context)) { + // Global.conversations[decodedMessage['sender']]![ + // decodedMessage["id"]] = Msg( + // decodedMessage['message'], + // "received", + // decodedMessage['Timestamp'], + // decodedMessage["id"], + // ); + + // insertIntoConversationsTable( + // Msg(decodedMessage['message'], "received", + // decodedMessage['Timestamp'], decodedMessage["id"]), + // decodedMessage['sender']); + // } + if (Global.cache[decodedMessage["id"]] == null) { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + // print("280 test"); + insertIntoMessageTable(Ack(decodedMessage['id'])); } else { - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, data["message"].toString()); - // }); + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); } - }); + + // print("355: ack added"); + } else { + // Global.devices.forEach((element) { + // Global.nearbyService! + // .sendMessage(element.deviceId, data["message"].toString()); + // }); + } }); } From d7d65f622625871d1b9946199435444a0139a479 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Fri, 29 Jul 2022 21:38:23 +0530 Subject: [PATCH 23/36] Adding save profile feature The profile page is called initially to fetch details on login and then again when the user wants the device name to be changed --- lib/main.dart | 7 ++++++- lib/pages/Profile.dart | 28 +++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 15f567f..061bec8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,13 +17,18 @@ void main() { } Route generateRoute(RouteSettings settings) { - return MaterialPageRoute(builder: (_) => Profile()); + return MaterialPageRoute( + builder: (_) => Profile( + onLogin: true, + ), + ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + debugShowCheckedModeBanner: false, onGenerateRoute: generateRoute, initialRoute: '/', ); diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index 62ce86b..d75703c 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -1,15 +1,14 @@ import 'dart:async'; -import 'dart:developer'; - import 'package:flutter/material.dart'; -import 'DeviceListScreen.dart'; import 'HomeScreen.dart'; import 'package:nanoid/nanoid.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../classes/Global.dart'; -import 'DeviceListScreen.dart'; class Profile extends StatefulWidget { + final bool onLogin; + + const Profile({Key? key, required this.onLogin}) : super(key: key); @override State createState() => _ProfileState(); } @@ -31,8 +30,9 @@ class _ProfileState extends State { final id = prefs.getString('p_id') ?? ''; setState(() { myName.text = name; + customLengthId = id.isNotEmpty ? id : customLengthId; }); - if (name.isNotEmpty && id.isNotEmpty) { + if (name.isNotEmpty && id.isNotEmpty && widget.onLogin) { navigateToHomeScreen(); } else { setState(() { @@ -43,12 +43,17 @@ class _ProfileState extends State { void navigateToHomeScreen() { Global.myName = myName.text; - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => HomeScreen(), - ), - ); + if (!widget.onLogin) { + Global.myName = myName.text; + Navigator.pop(context); + } else { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => HomeScreen(), + ), + ); + } } @override @@ -100,6 +105,7 @@ class _ProfileState extends State { prefs.setString('p_name', myName.text); prefs.setString('p_id', customLengthId); // On pressing, move to the device list screen + navigateToHomeScreen(); }, child: Text("Save"), From cdd7b41156245cbf6255e2e190099b2aafc0f08d Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Fri, 29 Jul 2022 21:38:31 +0530 Subject: [PATCH 24/36] Date formatting --- lib/pages/ChatPage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index 11f6fae..6e907cc 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -107,6 +107,6 @@ String dateFormatter({required String timeStamp}) { // From timestamp to readable date and hour minutes DateTime dateTime = DateTime.parse(timeStamp); String formattedDate = DateFormat('dd/MM/yyyy').format(dateTime); - String formattedTime = DateFormat('HH:mm').format(dateTime); + String formattedTime = DateFormat('hh:mm aa').format(dateTime); return formattedDate + " " + formattedTime; } From c8ef41c9083ff1284008a5f9e39a18157ffb6b72 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Fri, 29 Jul 2022 21:38:45 +0530 Subject: [PATCH 25/36] Settings button navigating to profile page --- lib/pages/HomeScreen.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart index cb98969..4d48732 100644 --- a/lib/pages/HomeScreen.dart +++ b/lib/pages/HomeScreen.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:flutter_nearby_connections_example/pages/Profile.dart'; import 'ChatListScreen.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:provider/provider.dart'; @@ -200,6 +201,21 @@ class _HomeScreenState extends State { key: _scaffoldKey, appBar: AppBar( title: Text("P2P Messaging"), + actions: [ + IconButton( + icon: Icon(Icons.settings), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Profile( + onLogin: false, + ), + ), + ); + }, + ), + ], bottom: TabBar( tabs: [ Tab( From 71a8b86d732bd561f276e077380a1f722db882e6 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sun, 7 Aug 2022 08:45:25 +0530 Subject: [PATCH 26/36] Added global key to make context available to back-end for easier state management. --- lib/classes/Global.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index 3667775..e14fe52 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import '../database/DatabaseHelper.dart'; import 'Msg.dart'; @@ -21,7 +22,8 @@ class Global extends ChangeNotifier { Map> conversations = Map(); static String myName = ''; static Map cache = Map(); - + static final GlobalKey scaffoldKey = + GlobalKey(); // Global({ // this.conversations = Map, // }); From e0d0eaeba47173d46031767df066503ed33a7a53 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sun, 7 Aug 2022 08:45:49 +0530 Subject: [PATCH 27/36] Removed unnecessary code commented. --- lib/pages/ChatListScreen.dart | 14 --- lib/pages/DeviceListScreen.dart | 180 -------------------------------- 2 files changed, 194 deletions(-) diff --git a/lib/pages/ChatListScreen.dart b/lib/pages/ChatListScreen.dart index 62f85e9..f7458a6 100644 --- a/lib/pages/ChatListScreen.dart +++ b/lib/pages/ChatListScreen.dart @@ -21,22 +21,8 @@ class _ChatListScreenState extends State { void initState() { super.initState(); readAllUpdateCache(); - - // print("34" + Global.conversations.toString()); - // - // Global.conversations.forEach((key, value) { - // conversers.add(key); - // }); - // print(" 37 reloaded:" + Global.cache.toString()); } - // void refreshMessages() { - // setState(() => isLoading = true); - // - // - // setState(() => isLoading = false); - // } - @override Widget build(BuildContext context) { conversers = []; diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index 09fc320..4b4d8b2 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -24,46 +24,8 @@ class _DevicesListScreenState extends State { @override void initState() { super.initState(); - // init(); - // print(" 37 reloaded:" + Global.cache.toString()); - // checkForMessageUpdates(); } - // @override - // void didChangeDependencies() { - // super.didChangeDependencies(); - // refreshMessages(); - // } - - // Future refreshMessages() async { - // setState(() => isLoading = true); - // log("refreshing messages"); - // readAllUpdateCache(); - // readAllUpdateConversation(context); - // setState(() => isLoading = false); - // } - - // var _selectedIndex = 0; - - // Widget getBody(BuildContext context) { - // switch (_selectedIndex) { - // case 0: - // // return showTrips(context); - // case 1: - // // return search(widget.account); - // case 2: - // return Text('Not yet implemented!'); - // default: - // throw UnimplementedError(); - // } - // } - - // void _onItemTapped(int index) { - // setState(() { - // _selectedIndex = index; - // }); - // } - @override Widget build(BuildContext context) { return Container( @@ -149,146 +111,4 @@ class _DevicesListScreenState extends State { ), ); } - - // Check for devices in proximity - // void checkDevices() { - // Global.deviceSubscription = - // Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { - // devicesList.forEach((element) { - // // if (element.state != SessionState.connected) connectToDevice(element); - // print( - // "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); - - // if (Platform.isAndroid) { - // if (element.state == SessionState.connected) { - // Global.nearbyService!.stopBrowsingForPeers(); - // } else { - // Global.nearbyService!.startBrowsingForPeers(); - // } - // } - // }); - - // setState(() { - // Global.devices.clear(); - // Global.devices.addAll(devicesList); - // Global.connectedDevices.clear(); - // Global.connectedDevices.addAll(devicesList - // .where((d) => d.state == SessionState.connected) - // .toList()); - // }); - // }); - // } - - // The function responsible for receiving the messages - // void init() async { - // initiateNearbyService(); - // checkDevices(); - // broadcastLastMessageID(); - // Global.receivedDataSubscription = - // Global.nearbyService!.dataReceivedSubscription(callback: (data) { - // var decodedMessage = jsonDecode(data['message']); - // showToast( - // jsonEncode(data), - // context: context, - // axis: Axis.horizontal, - // alignment: Alignment.center, - // position: StyledToastPosition.bottom, - // ); - // if (decodedMessage["type"] == "Update") { - // log("Update Message ${decodedMessage["id"]}"); - // String sentDeviceName = decodedMessage["sender"]; - // compareMessageId( - // receivedId: decodedMessage["id"], - // sentDeviceName: sentDeviceName, - // ); - // } - // setState(() { - // // print("331: " + temp2['receiver'].toString()); - // // print("332:" + temp2['type'].toString()); - // // print("333|" + Global.myName.toString()); - // // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); - // if (Global.cache.containsKey(decodedMessage["id"]) == false) { - // // print("line 338 test"); - - // if (decodedMessage["type"].toString() == 'Payload') { - // // print("line 341"); - - // Global.cache[decodedMessage["id"]] = Payload( - // decodedMessage["id"], - // decodedMessage['sender'], - // decodedMessage['receiver'], - // decodedMessage['message'], - // decodedMessage['Timestamp']); - // insertIntoMessageTable(Payload( - // decodedMessage["id"], - // decodedMessage['sender'], - // decodedMessage['receiver'], - // decodedMessage['message'], - // decodedMessage['Timestamp'])); - // // print("current cache 344" + Global.cache.toString()); - // } else { - // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // insertIntoMessageTable(Ack(decodedMessage["id"])); - // } - // } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { - // if (decodedMessage["type"] == 'Ack') { - // //broadcast Ack last time to neighbours - // Global.cache.remove(decodedMessage["id"]); - // deleteFromMessageTable(decodedMessage["id"]); - // } - // } else { - // // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore - // Global.cache.remove(decodedMessage["id"]); - // deleteFromMessageTable(decodedMessage["id"]); - // } - // print("350|" + - // decodedMessage['type'].toString() + - // ":Payload |" + - // decodedMessage['receiver'].toString() + - // ":" + - // Global.myName.toString()); - // if (decodedMessage['type'] == "Payload" && - // decodedMessage['receiver'] == Global.myName) { - // // Global.cache[temp2["id"]]!.broadcast = false; - // // if (Global.conversations[decodedMessage['sender']] == null) { - // // Global.conversations[decodedMessage['sender']] = Map(); - // // } - // Provider.of(context, listen: false) - // .receivedToConversations(decodedMessage, context); - // // If message type Payload, then add to cache and to conversations table - // // if not already present - // // if (!search( - // // decodedMessage['sender'], decodedMessage["id"], context)) { - // // Global.conversations[decodedMessage['sender']]![ - // // decodedMessage["id"]] = Msg( - // // decodedMessage['message'], - // // "received", - // // decodedMessage['Timestamp'], - // // decodedMessage["id"], - // // ); - - // // insertIntoConversationsTable( - // // Msg(decodedMessage['message'], "received", - // // decodedMessage['Timestamp'], decodedMessage["id"]), - // // decodedMessage['sender']); - // // } - // if (Global.cache[decodedMessage["id"]] == null) { - // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // // print("280 test"); - // insertIntoMessageTable(Ack(decodedMessage['id'])); - // } else { - // Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); - // } - - // // print("355: ack added"); - // } else { - // // Global.devices.forEach((element) { - // // Global.nearbyService! - // // .sendMessage(element.deviceId, data["message"].toString()); - // // }); - // } - // }); - // }); - // } } From a7accbd7b2683e403cadff77596e4c4689cf3adc Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sun, 7 Aug 2022 08:46:55 +0530 Subject: [PATCH 28/36] Separated Backend from frontend by moving broadcast code from homescreen to adhochousekeeping. --- lib/p2p/AdhocHousekeeping.dart | 126 ++++++++++++++++++++++------ lib/pages/HomeScreen.dart | 146 +-------------------------------- 2 files changed, 104 insertions(+), 168 deletions(-) diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 6e61821..e360c07 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -1,12 +1,15 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:flutter_nearby_connections_example/classes/Payload.dart'; import 'package:flutter_nearby_connections_example/database/MessageDB.dart'; +import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:provider/provider.dart'; import '../classes/Global.dart'; +import '../database/DatabaseHelper.dart'; // Get the state name of the connection String getStateName(SessionState state) { @@ -63,11 +66,6 @@ int getItemCount(BuildContext context) { // Check if the id exists in the conversation list bool search(String sender, String id, BuildContext context) { - // if (Global.conversations[sender] == null) return false; - - // if (Global.conversations[sender]!.containsKey(id)) { - // return true; - // } // Get from Provider if (Provider.of(context, listen: false).conversations[sender] != null) { @@ -168,24 +166,6 @@ void broadcastLastMessageID(BuildContext context) async { }); } -// void checkForMessageUpdates() async { -// broadcastLastMessageID(); -// Global.receivedDataSubscription!.onData((data) { -// // print("dataReceivedSubscription: ${jsonEncode(data)}"); -// var decodedMessage = jsonDecode(data["message"]); - -// // checking if successfully recieving the update or not -// if (decodedMessage["type"] == "Update") { -// log("Update Message ${decodedMessage["id"]}"); -// String sentDeviceName = decodedMessage["sender"]; -// compareMessageId( -// receivedId: decodedMessage["id"], -// sentDeviceName: sentDeviceName, -// ); -// } -// }); -// } - // Compare message Ids // If they are not same, the message needs to be broadcasted. void compareMessageId({ @@ -199,6 +179,106 @@ void compareMessageId({ } } +void checkDevices() { + Global.deviceSubscription = + Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { + devicesList.forEach((element) { + // if (element.state != SessionState.connected) connectToDevice(element); + print( + "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); + + if (Platform.isAndroid) { + if (element.state == SessionState.connected) { + Global.nearbyService!.stopBrowsingForPeers(); + } else { + Global.nearbyService!.startBrowsingForPeers(); + } + } + }); + Provider.of(Global.scaffoldKey.currentState!.context, listen: false) + .updateDevices(devicesList); + Provider.of(Global.scaffoldKey.currentState!.context, listen: false) + .updateConnectedDevices(devicesList + .where((d) => d.state == SessionState.connected) + .toList()); + log('Devices length: ${devicesList.length}'); + }); +} + +void init() async { + initiateNearbyService(); + checkDevices(); + broadcastLastMessageID(Global.scaffoldKey.currentState!.context); + Global.receivedDataSubscription = + Global.nearbyService!.dataReceivedSubscription(callback: (data) { + var decodedMessage = jsonDecode(data['message']); + showToast( + jsonEncode(data), + context: Global.scaffoldKey.currentState!.context, + axis: Axis.horizontal, + alignment: Alignment.center, + position: StyledToastPosition.bottom, + ); + if (decodedMessage["type"] == "Update") { + log("Update Message ${decodedMessage["id"]}"); + String sentDeviceName = decodedMessage["sender"]; + compareMessageId( + receivedId: decodedMessage["id"], + sentDeviceName: sentDeviceName, + context: Global.scaffoldKey.currentState!.context, + ); + } + + if (Global.cache.containsKey(decodedMessage["id"]) == false) { + if (decodedMessage["type"].toString() == 'Payload') { + Global.cache[decodedMessage["id"]] = Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp']); + insertIntoMessageTable(Payload( + decodedMessage["id"], + decodedMessage['sender'], + decodedMessage['receiver'], + decodedMessage['message'], + decodedMessage['Timestamp'])); + } else { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + insertIntoMessageTable(Ack(decodedMessage["id"])); + } + } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { + if (decodedMessage["type"] == 'Ack') { + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); + } + } else { + Global.cache.remove(decodedMessage["id"]); + deleteFromMessageTable(decodedMessage["id"]); + } + print("350|" + + decodedMessage['type'].toString() + + ":Payload |" + + decodedMessage['receiver'].toString() + + ":" + + Global.myName.toString()); + if (decodedMessage['type'] == "Payload" && + decodedMessage['receiver'] == Global.myName) { + Provider.of(Global.scaffoldKey.currentState!.context, + listen: false) + .receivedToConversations( + decodedMessage, Global.scaffoldKey.currentState!.context); + if (Global.cache[decodedMessage["id"]] == null) { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + insertIntoMessageTable(Ack(decodedMessage['id'])); + } else { + Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); + updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); + } + } else {} + }); +} + // Initiating NearbyService to start the connection void initiateNearbyService() async { Global.nearbyService = NearbyService(); diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart index 4d48732..7bac28b 100644 --- a/lib/pages/HomeScreen.dart +++ b/lib/pages/HomeScreen.dart @@ -1,18 +1,10 @@ -import 'dart:convert'; -import 'dart:developer'; -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:flutter_nearby_connections_example/pages/Profile.dart'; import 'ChatListScreen.dart'; -import 'package:flutter_styled_toast/flutter_styled_toast.dart'; -import 'package:provider/provider.dart'; import '../classes/Global.dart'; import '../p2p/AdhocHousekeeping.dart'; import 'DeviceListScreen.dart'; -import '../classes/Payload.dart'; import '../database/DatabaseHelper.dart'; class HomeScreen extends StatefulWidget { @@ -26,7 +18,6 @@ class _HomeScreenState extends State { bool isInit = false; bool isLoading = false; - final GlobalKey _scaffoldKey = GlobalKey(); @override void initState() { @@ -35,141 +26,6 @@ class _HomeScreenState extends State { refreshMessages(); } - void init() async { - initiateNearbyService(); - checkDevices(); - broadcastLastMessageID(context); - Global.receivedDataSubscription = - Global.nearbyService!.dataReceivedSubscription(callback: (data) { - var decodedMessage = jsonDecode(data['message']); - showToast( - jsonEncode(data), - context: context, - axis: Axis.horizontal, - alignment: Alignment.center, - position: StyledToastPosition.bottom, - ); - if (decodedMessage["type"] == "Update") { - log("Update Message ${decodedMessage["id"]}"); - String sentDeviceName = decodedMessage["sender"]; - compareMessageId( - receivedId: decodedMessage["id"], - sentDeviceName: sentDeviceName, - context: context, - ); - } - - // print("331: " + temp2['receiver'].toString()); - // print("332:" + temp2['type'].toString()); - // print("333|" + Global.myName.toString()); - // print(data['message'] + "can u hear meeeeeeeeeeeeeeeeeeeeeeeeeeeeee?"); - if (Global.cache.containsKey(decodedMessage["id"]) == false) { - // print("line 338 test"); - - if (decodedMessage["type"].toString() == 'Payload') { - // print("line 341"); - - Global.cache[decodedMessage["id"]] = Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp']); - insertIntoMessageTable(Payload( - decodedMessage["id"], - decodedMessage['sender'], - decodedMessage['receiver'], - decodedMessage['message'], - decodedMessage['Timestamp'])); - // print("current cache 344" + Global.cache.toString()); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - insertIntoMessageTable(Ack(decodedMessage["id"])); - } - } else if (Global.cache[decodedMessage["id"]].runtimeType == Payload) { - if (decodedMessage["type"] == 'Ack') { - //broadcast Ack last time to neighbours - Global.cache.remove(decodedMessage["id"]); - deleteFromMessageTable(decodedMessage["id"]); - } - } else { - // cache has a ack form the same message id so i guess can keep track of the number of times we get acks?. currently ignore - Global.cache.remove(decodedMessage["id"]); - deleteFromMessageTable(decodedMessage["id"]); - } - print("350|" + - decodedMessage['type'].toString() + - ":Payload |" + - decodedMessage['receiver'].toString() + - ":" + - Global.myName.toString()); - if (decodedMessage['type'] == "Payload" && - decodedMessage['receiver'] == Global.myName) { - // Global.cache[temp2["id"]]!.broadcast = false; - // if (Global.conversations[decodedMessage['sender']] == null) { - // Global.conversations[decodedMessage['sender']] = Map(); - // } - Provider.of(context, listen: false) - .receivedToConversations(decodedMessage, context); - // If message type Payload, then add to cache and to conversations table - // if not already present - // if (!search( - // decodedMessage['sender'], decodedMessage["id"], context)) { - // Global.conversations[decodedMessage['sender']]![ - // decodedMessage["id"]] = Msg( - // decodedMessage['message'], - // "received", - // decodedMessage['Timestamp'], - // decodedMessage["id"], - // ); - - // insertIntoConversationsTable( - // Msg(decodedMessage['message'], "received", - // decodedMessage['Timestamp'], decodedMessage["id"]), - // decodedMessage['sender']); - // } - if (Global.cache[decodedMessage["id"]] == null) { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - // print("280 test"); - insertIntoMessageTable(Ack(decodedMessage['id'])); - } else { - Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); - updateMessageTable(decodedMessage["id"], Ack(decodedMessage['id'])); - } - - // print("355: ack added"); - } else { - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, data["message"].toString()); - // }); - } - }); - } - - void checkDevices() { - Global.deviceSubscription = - Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { - devicesList.forEach((element) { - // if (element.state != SessionState.connected) connectToDevice(element); - print( - "deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}"); - - if (Platform.isAndroid) { - if (element.state == SessionState.connected) { - Global.nearbyService!.stopBrowsingForPeers(); - } else { - Global.nearbyService!.startBrowsingForPeers(); - } - } - }); - Provider.of(context, listen: false).updateDevices(devicesList); - Provider.of(context, listen: false).updateConnectedDevices( - devicesList.where((d) => d.state == SessionState.connected).toList()); - log('Devices length: ${devicesList.length}'); - }); - } - Future refreshMessages() async { setState(() => isLoading = true); @@ -198,7 +54,7 @@ class _HomeScreenState extends State { return DefaultTabController( length: 2, child: Scaffold( - key: _scaffoldKey, + key: Global.scaffoldKey, appBar: AppBar( title: Text("P2P Messaging"), actions: [ From d63bf6d91f458a546ea51145dac1c41a965b3d83 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Sun, 7 Aug 2022 09:23:08 +0530 Subject: [PATCH 29/36] Implementing First Push then Pull protocol --- lib/classes/Global.dart | 3 +++ lib/components/message_panel.dart | 45 +++++++++++-------------------- lib/p2p/AdhocHousekeeping.dart | 14 +++++++--- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index e14fe52..e0c708f 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; +import 'package:flutter_nearby_connections_example/p2p/AdhocHousekeeping.dart'; import '../database/DatabaseHelper.dart'; import 'Msg.dart'; @@ -37,6 +38,8 @@ class Global extends ChangeNotifier { insertIntoConversationsTable(msg, converser); } notifyListeners(); + // First push the new message for one time when new message is sent + broadcast(); } void receivedToConversations(dynamic decodedMessage, BuildContext context) { diff --git a/lib/components/message_panel.dart b/lib/components/message_panel.dart index 5739fb7..9fd2b94 100644 --- a/lib/components/message_panel.dart +++ b/lib/components/message_panel.dart @@ -37,40 +37,27 @@ class _MessagePanelState extends State { "type": "Payload" }; Global.cache[msgId] = Payload( + msgId, + Global.myName, + widget.converser, + myController.text, + DateTime.now().toUtc().toString(), + ); + insertIntoMessageTable( + Payload( msgId, Global.myName, widget.converser, myController.text, - DateTime.now().toUtc().toString()); - insertIntoMessageTable(Payload( - msgId, - Global.myName, - widget.converser, - myController.text, - DateTime.now().toUtc().toString())); - // Global.devices.forEach((element) { - // Global.nearbyService! - // .sendMessage(element.deviceId, Mesagedata); - // }); - // Global.nearbyService! - // .sendMessage(widget.device.deviceId, myController.text); - setState(() { - // Global.conversations[widget.device.deviceName]![msgId]( - // new Msg( - // widget.device.deviceId, myController.text, "sent")); - Provider.of(context, listen: false).sentToConversations( - Msg(myController.text, "sent", data["Timestamp"]!, msgId), - widget.converser); - // if (Global.conversations[widget.converser] == null) { - // Global.conversations[widget.converser] = {}; - // } - // Global.conversations[widget.converser]![msgId] = - // Msg(myController.text, "sent", data["Timestamp"]!, msgId); + DateTime.now().toUtc().toString(), + ), + ); + + Provider.of(context, listen: false).sentToConversations( + Msg(myController.text, "sent", data["Timestamp"]!, msgId), + widget.converser, + ); - // insertIntoConversationsTable( - // Msg(myController.text, "sent", data["Timestamp"]!, msgId), - // widget.converser); - }); // refreshMessages(); myController.clear(); }, diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index e360c07..f59eb1c 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -108,7 +108,7 @@ void startAdvertising() async { } // this function is supposed to broadcast all messages in the cache when the message ids don't match -void broadcast(BuildContext context) async { +void broadcast() async { Global.cache.forEach((key, value) { // if a message is supposed to be broadcasted to all devices in proximity then if (value.runtimeType == Payload && value.broadcast) { @@ -122,13 +122,19 @@ void broadcast(BuildContext context) async { "type": "Payload" }; var toSend = jsonEncode(data); - Provider.of(context, listen: false).devices.forEach((element) { + Provider.of( + Global.scaffoldKey.currentState!.context, + listen: false, + ).devices.forEach((element) { print("270" + toSend); Global.nearbyService! .sendMessage(element.deviceId, toSend); //make this async }); } else if (value.runtimeType == Ack) { - Provider.of(context, listen: false).devices.forEach((element) { + Provider.of(Global.scaffoldKey.currentState!.context, + listen: false) + .devices + .forEach((element) { var data = {"id": "$key", "type": "Ack"}; Global.nearbyService!.sendMessage(element.deviceId, jsonEncode(data)); }); @@ -175,7 +181,7 @@ void compareMessageId({ }) async { String sentId = await MessageDB.instance.getLastMessageId(type: "sent"); if (sentId != receivedId) { - broadcast(context); + broadcast(); } } From 5632b0dfac85971e507516b8c03d1ff88de16440 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Tue, 16 Aug 2022 13:26:31 +0530 Subject: [PATCH 30/36] Fixing hybrid protocol context problems --- lib/classes/Global.dart | 2 +- lib/database/DatabaseHelper.dart | 8 +------ lib/p2p/AdhocHousekeeping.dart | 38 +++++++++++++------------------- lib/pages/HomeScreen.dart | 4 ++-- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index e0c708f..323bbdb 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -39,7 +39,7 @@ class Global extends ChangeNotifier { } notifyListeners(); // First push the new message for one time when new message is sent - broadcast(); + broadcast(scaffoldKey.currentContext!); } void receivedToConversations(dynamic decodedMessage, BuildContext context) { diff --git a/lib/database/DatabaseHelper.dart b/lib/database/DatabaseHelper.dart index b3b290d..c695ca2 100644 --- a/lib/database/DatabaseHelper.dart +++ b/lib/database/DatabaseHelper.dart @@ -15,18 +15,13 @@ Future readAllUpdateConversation(BuildContext context) async { var value = await MessageDB.instance.readAllFromConversationsTable(); conversations = value; conversations.forEach((element) { - // if (Global.conversations[element.converser] == null) { - // Global.conversations[element.converser] = Map(); - // } Provider.of(context, listen: false).sentToConversations( Msg(element.msg, element.type, element.timestamp, element.id), element.converser, addToTable: false, ); - // Global.conversations[element.converser]![element.id] = - // Msg(element.msg, element.type, element.timestamp, element.id); + Msg(element.msg, element.type, element.timestamp, element.id); }); - // print("19:" + Global.conversations.toString()); } void readAllUpdatePublicKey() { @@ -36,7 +31,6 @@ void readAllUpdatePublicKey() { publicKey.forEach((element) { Global.publicKeys[element.converser] = element.publicKey; - // Global.conversations[element.converser]![element.id]=Msg(element.msg,element.type,element.timestamp,element.id); }); }); } diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index f59eb1c..1a47d57 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -108,7 +108,7 @@ void startAdvertising() async { } // this function is supposed to broadcast all messages in the cache when the message ids don't match -void broadcast() async { +void broadcast(BuildContext context) async { Global.cache.forEach((key, value) { // if a message is supposed to be broadcasted to all devices in proximity then if (value.runtimeType == Payload && value.broadcast) { @@ -123,7 +123,7 @@ void broadcast() async { }; var toSend = jsonEncode(data); Provider.of( - Global.scaffoldKey.currentState!.context, + context, listen: false, ).devices.forEach((element) { print("270" + toSend); @@ -131,10 +131,7 @@ void broadcast() async { .sendMessage(element.deviceId, toSend); //make this async }); } else if (value.runtimeType == Ack) { - Provider.of(Global.scaffoldKey.currentState!.context, - listen: false) - .devices - .forEach((element) { + Provider.of(context, listen: false).devices.forEach((element) { var data = {"id": "$key", "type": "Ack"}; Global.nearbyService!.sendMessage(element.deviceId, jsonEncode(data)); }); @@ -181,11 +178,11 @@ void compareMessageId({ }) async { String sentId = await MessageDB.instance.getLastMessageId(type: "sent"); if (sentId != receivedId) { - broadcast(); + broadcast(context); } } -void checkDevices() { +void checkDevices(BuildContext context) { Global.deviceSubscription = Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { devicesList.forEach((element) { @@ -201,26 +198,23 @@ void checkDevices() { } } }); - Provider.of(Global.scaffoldKey.currentState!.context, listen: false) - .updateDevices(devicesList); - Provider.of(Global.scaffoldKey.currentState!.context, listen: false) - .updateConnectedDevices(devicesList - .where((d) => d.state == SessionState.connected) - .toList()); + Provider.of(context, listen: false).updateDevices(devicesList); + Provider.of(context, listen: false).updateConnectedDevices( + devicesList.where((d) => d.state == SessionState.connected).toList()); log('Devices length: ${devicesList.length}'); }); } -void init() async { +void init(BuildContext context) async { initiateNearbyService(); - checkDevices(); - broadcastLastMessageID(Global.scaffoldKey.currentState!.context); + checkDevices(context); + broadcastLastMessageID(context); Global.receivedDataSubscription = Global.nearbyService!.dataReceivedSubscription(callback: (data) { var decodedMessage = jsonDecode(data['message']); showToast( jsonEncode(data), - context: Global.scaffoldKey.currentState!.context, + context: context, axis: Axis.horizontal, alignment: Alignment.center, position: StyledToastPosition.bottom, @@ -231,7 +225,7 @@ void init() async { compareMessageId( receivedId: decodedMessage["id"], sentDeviceName: sentDeviceName, - context: Global.scaffoldKey.currentState!.context, + context: context, ); } @@ -270,10 +264,8 @@ void init() async { Global.myName.toString()); if (decodedMessage['type'] == "Payload" && decodedMessage['receiver'] == Global.myName) { - Provider.of(Global.scaffoldKey.currentState!.context, - listen: false) - .receivedToConversations( - decodedMessage, Global.scaffoldKey.currentState!.context); + Provider.of(context, listen: false) + .receivedToConversations(decodedMessage, context); if (Global.cache[decodedMessage["id"]] == null) { Global.cache[decodedMessage["id"]] = Ack(decodedMessage["id"]); insertIntoMessageTable(Ack(decodedMessage['id'])); diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart index 7bac28b..de1adf9 100644 --- a/lib/pages/HomeScreen.dart +++ b/lib/pages/HomeScreen.dart @@ -22,7 +22,7 @@ class _HomeScreenState extends State { @override void initState() { super.initState(); - init(); + // init(context); refreshMessages(); } @@ -37,7 +37,7 @@ class _HomeScreenState extends State { void didChangeDependencies() { super.didChangeDependencies(); readAllUpdateConversation(context); - init(); + init(context); } @override From 0df2f0b48f93b6566b7c3e6854ed9391a4adf695 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Wed, 7 Sep 2022 19:52:53 +0530 Subject: [PATCH 31/36] Documenting Files --- lib/classes/Global.dart | 6 +++--- lib/classes/Msg.dart | 6 +++--- lib/classes/Payload.dart | 31 +++++++++++++++---------------- lib/components/message_panel.dart | 3 +++ lib/database/DatabaseHelper.dart | 2 ++ lib/database/MessageDB.dart | 2 +- lib/database/model.dart | 2 ++ lib/encyption/rsa.dart | 1 + lib/main.dart | 16 ++++++++++++++++ lib/p2p/AdhocHousekeeping.dart | 12 ++++++++++-- lib/pages/ChatListScreen.dart | 10 +++++++++- lib/pages/ChatPage.dart | 10 ++++++++++ lib/pages/DeviceListScreen.dart | 12 ++++++++++++ lib/pages/HomeScreen.dart | 14 +++++++++++--- lib/pages/Profile.dart | 13 ++++++++++++- 15 files changed, 110 insertions(+), 30 deletions(-) diff --git a/lib/classes/Global.dart b/lib/classes/Global.dart index 323bbdb..ceb623c 100644 --- a/lib/classes/Global.dart +++ b/lib/classes/Global.dart @@ -1,7 +1,7 @@ -import 'dart:async'; -import 'dart:developer'; +/// It the Global state management class. +/// This is used over all the application. -import 'package:flutter/cupertino.dart'; +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections/flutter_nearby_connections.dart'; import 'package:flutter_nearby_connections_example/p2p/AdhocHousekeeping.dart'; diff --git a/lib/classes/Msg.dart b/lib/classes/Msg.dart index 4308377..e560b0b 100644 --- a/lib/classes/Msg.dart +++ b/lib/classes/Msg.dart @@ -1,10 +1,10 @@ - +/// Message model class Msg { String message; String msgtype; //sent or received String timestamp; - String ack='false'; + String ack = 'false'; String id; - Msg(this.message, this.msgtype,this.timestamp,this.id); + Msg(this.message, this.msgtype, this.timestamp, this.id); } diff --git a/lib/classes/Payload.dart b/lib/classes/Payload.dart index c184da6..633b3f2 100644 --- a/lib/classes/Payload.dart +++ b/lib/classes/Payload.dart @@ -1,18 +1,17 @@ - -class Payload{ - String id=''; - String sender=''; - String receiver=''; - String message=''; - String timestamp=''; - bool broadcast=true; - String type='Payload'; - Payload(this.id,this.sender,this.receiver,this.message,this.timestamp); - +/// This is the model to transfer the message from one device to another. +class Payload { + String id = ''; + String sender = ''; + String receiver = ''; + String message = ''; + String timestamp = ''; + bool broadcast = true; + String type = 'Payload'; + Payload(this.id, this.sender, this.receiver, this.message, this.timestamp); } -class Ack{ - String id=''; - String type="Ack"; - Ack(this.id); -} \ No newline at end of file +class Ack { + String id = ''; + String type = "Ack"; + Ack(this.id); +} diff --git a/lib/components/message_panel.dart b/lib/components/message_panel.dart index 9fd2b94..d560fda 100644 --- a/lib/components/message_panel.dart +++ b/lib/components/message_panel.dart @@ -1,3 +1,6 @@ +/// This component is used in the ChatPage. +/// It is the message bar where the message is typed on and sent to +/// connected devices. import 'package:flutter/material.dart'; import 'package:nanoid/nanoid.dart'; import 'package:provider/provider.dart'; diff --git a/lib/database/DatabaseHelper.dart b/lib/database/DatabaseHelper.dart index c695ca2..797215f 100644 --- a/lib/database/DatabaseHelper.dart +++ b/lib/database/DatabaseHelper.dart @@ -1,3 +1,5 @@ +/// This file has some utility functions for the +/// retrieval and saving of messages in the database. import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; diff --git a/lib/database/MessageDB.dart b/lib/database/MessageDB.dart index 8dfdf8c..5dc9289 100644 --- a/lib/database/MessageDB.dart +++ b/lib/database/MessageDB.dart @@ -1,5 +1,5 @@ +/// It is the database for the messages. import 'dart:async'; -import 'dart:convert'; import 'dart:developer'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; diff --git a/lib/database/model.dart b/lib/database/model.dart index 72a8bbb..87c8139 100644 --- a/lib/database/model.dart +++ b/lib/database/model.dart @@ -1,3 +1,5 @@ +/// Models for different use cases and constants +/// final String messagesTableName = 'messages'; final String conversationsTableName = 'conversations'; final String publicKeyTableName = 'publicKey'; diff --git a/lib/encyption/rsa.dart b/lib/encyption/rsa.dart index dc53861..0997863 100644 --- a/lib/encyption/rsa.dart +++ b/lib/encyption/rsa.dart @@ -1,3 +1,4 @@ +// This file is used for encryption of messages. import 'package:pointycastle/api.dart' as crypto; import 'package:rsa_encrypt/rsa_encrypt.dart'; diff --git a/lib/main.dart b/lib/main.dart index 061bec8..e81a183 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,12 @@ +/// The logic for the Adhoc and the UI are separated. +/// The different section are listed as follows +/// - p2p => Backend of application where message protocols, connections, state are managed +/// - pages => This is the UI section of the application +/// - encryption => The messages are encrypted here. +/// - database => Storage for our messages and conversations +/// - classes => Different model classes for databases +/// - components => Common UI components + import 'package:flutter/material.dart'; import 'classes/Global.dart'; import 'package:provider/provider.dart'; @@ -5,9 +14,13 @@ import 'pages/Profile.dart'; void main() { runApp( + // Provider is used for state management. The state management will help us + // to know when a new message has arrived and to refresh the chat page. MultiProvider( providers: [ ChangeNotifierProvider( + // Currently we have single class for to manage which contains the + // required data and streams create: (_) => Global(), ), ], @@ -17,6 +30,9 @@ void main() { } Route generateRoute(RouteSettings settings) { + // Initially app opens the profile page where we need to either create + // new profile or + // navigate to the home screen return MaterialPageRoute( builder: (_) => Profile( onLogin: true, diff --git a/lib/p2p/AdhocHousekeeping.dart b/lib/p2p/AdhocHousekeeping.dart index 1a47d57..43d55bc 100644 --- a/lib/p2p/AdhocHousekeeping.dart +++ b/lib/p2p/AdhocHousekeeping.dart @@ -1,3 +1,5 @@ +/// This is the Adhoc part where the messages are received and sent. +/// Each and every function have there purpose mentioned above them. import 'dart:async'; import 'dart:convert'; import 'dart:developer'; @@ -97,6 +99,7 @@ void connectToDevice(Device device) { } } +// Start discovering devices void startBrowsing() async { await Global.nearbyService!.stopBrowsingForPeers(); await Global.nearbyService!.startBrowsingForPeers(); @@ -107,7 +110,8 @@ void startAdvertising() async { await Global.nearbyService!.startAdvertisingPeer(); } -// this function is supposed to broadcast all messages in the cache when the message ids don't match +// This function is supposed to broadcast all messages in the cache +// when the message ids don't match void broadcast(BuildContext context) async { Global.cache.forEach((key, value) { // if a message is supposed to be broadcasted to all devices in proximity then @@ -140,7 +144,8 @@ void broadcast(BuildContext context) async { print("current cache:" + Global.cache.toString()); } -// Broadcasting update request message to the connected devices to recieve fresh messages that are yet to be recieved +// Broadcasting update request message to the connected devices to receive +// fresh messages that are yet to be recieved void broadcastLastMessageID(BuildContext context) async { // Fetch from Database the last message. Timer.periodic(Duration(seconds: 3), (timer) async { @@ -182,6 +187,7 @@ void compareMessageId({ } } +// void checkDevices(BuildContext context) { Global.deviceSubscription = Global.nearbyService!.stateChangedSubscription(callback: (devicesList) { @@ -205,6 +211,8 @@ void checkDevices(BuildContext context) { }); } +// The the protocol service. It receives the messages from the +// dataReceivedSubscription service and decode it. void init(BuildContext context) async { initiateNearbyService(); checkDevices(context); diff --git a/lib/pages/ChatListScreen.dart b/lib/pages/ChatListScreen.dart index f7458a6..e879e8c 100644 --- a/lib/pages/ChatListScreen.dart +++ b/lib/pages/ChatListScreen.dart @@ -1,3 +1,5 @@ +/// This is ChatListScreen. This screen lists all the Devices with which the +/// device has chat with and keeps all the previous messages records. import 'package:provider/provider.dart'; import '../database/DatabaseHelper.dart'; @@ -16,7 +18,7 @@ class ChatListScreen extends StatefulWidget { class _ChatListScreenState extends State { bool isLoading = false; List conversers = []; - + // In the init state, we need to update the cache everytime. @override void initState() { super.initState(); @@ -25,6 +27,10 @@ class _ChatListScreenState extends State { @override Widget build(BuildContext context) { + // Whenever the the UI is built, each converser is added to the list + // from the conversations map that stores the key as name of the device. + // The names are inserted into the list conversers here and then displayed + // with the help of ListView.builder. conversers = []; Provider.of(context).conversations.forEach((key, value) { conversers.add(key); @@ -59,6 +65,8 @@ class _ChatListScreenState extends State { return ListTile( title: Text(conversers[index]), onTap: () { + // Whenever tapped on the Device tile, it navigates to the + // chatpage Navigator.push( context, MaterialPageRoute( diff --git a/lib/pages/ChatPage.dart b/lib/pages/ChatPage.dart index 6e907cc..d5d6bde 100644 --- a/lib/pages/ChatPage.dart +++ b/lib/pages/ChatPage.dart @@ -1,3 +1,7 @@ +/// This is the ChatPage. This screen consists of the chat with a single device +/// with whom we had chat. The messages are saved in the database and +/// retrieved from the same using the Provider state management as it allows +/// real time messaging. import 'package:bubble/bubble.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -32,6 +36,8 @@ class ChatPageState extends State { ScrollController _scrollController = new ScrollController(); Widget build(BuildContext context) { + /// If we have previously conversed with the device, it is going to store + /// the conversations in the messageList if (Provider.of(context).conversations[widget.converser] != null) { messageList = []; Provider.of(context) @@ -39,6 +45,8 @@ class ChatPageState extends State { .forEach((key, value) { messageList.add(value); }); + // Since there can be long list of message, the scroll controller + // auto scrolls to bottom of the list. if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent + 50, @@ -60,6 +68,7 @@ class ChatPageState extends State { child: Text('No messages yet'), ) : ListView.builder( + // Builder to view messages chronologically shrinkWrap: true, controller: _scrollController, padding: const EdgeInsets.all(8), @@ -103,6 +112,7 @@ class ChatPageState extends State { } } +// Function to format the date in viewable form String dateFormatter({required String timeStamp}) { // From timestamp to readable date and hour minutes DateTime dateTime = DateTime.parse(timeStamp); diff --git a/lib/pages/DeviceListScreen.dart b/lib/pages/DeviceListScreen.dart index 4b4d8b2..7f10e01 100644 --- a/lib/pages/DeviceListScreen.dart +++ b/lib/pages/DeviceListScreen.dart @@ -1,3 +1,9 @@ +/// This is the DeviceListScreen page. This page task is to display the +/// devices in the range that are active now. It is used to manage the +/// connection between devices. We can either connect or disconnect +/// with any device in the range. +/// This is the Frontend of the Devices management, the backend is managed +/// with the help of Provider and AdhocHouseKeeping. import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../classes/Global.dart'; @@ -53,9 +59,11 @@ class _DevicesListScreenState extends State { ), ), ListView.builder( + // Builds a screen with list of devices in the proximity itemCount: Provider.of(context).devices.length, shrinkWrap: true, itemBuilder: (context, index) { + // Getting a device from the provider final device = Provider.of(context).devices[index]; return Container( margin: EdgeInsets.all(8.0), @@ -68,6 +76,8 @@ class _DevicesListScreenState extends State { style: TextStyle(color: getStateColor(device.state)), ), trailing: GestureDetector( + // GestureDetector act as onPressed() and enables + // to connect/disconnect with any device onTap: () => connectToDevice(device), child: Container( margin: EdgeInsets.symmetric(horizontal: 8.0), @@ -86,6 +96,8 @@ class _DevicesListScreenState extends State { ), ), onTap: () { + // On clicking any device tile, we navigate to the + // ChatPage. Navigator.of(context).push( MaterialPageRoute( builder: (context) { diff --git a/lib/pages/HomeScreen.dart b/lib/pages/HomeScreen.dart index de1adf9..2833eca 100644 --- a/lib/pages/HomeScreen.dart +++ b/lib/pages/HomeScreen.dart @@ -1,3 +1,8 @@ +/// This the home screen. This can also be considered as the +/// 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. + import 'package:flutter/material.dart'; import 'package:flutter_nearby_connections_example/pages/Profile.dart'; import 'ChatListScreen.dart'; @@ -15,8 +20,6 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { - bool isInit = false; - bool isLoading = false; @override @@ -26,6 +29,7 @@ class _HomeScreenState extends State { refreshMessages(); } + /// After reading all the cache, the home screen becomes visible. Future refreshMessages() async { setState(() => isLoading = true); @@ -40,6 +44,10 @@ class _HomeScreenState extends State { init(context); } + /// When the messaging is done, the services + /// or the subscrption needs to be closed + /// Hence the deviceSubscription stream is cancelled. + /// Also the nearby services are stopped. @override void dispose() { Global.deviceSubscription!.cancel(); @@ -56,7 +64,7 @@ class _HomeScreenState extends State { child: Scaffold( key: Global.scaffoldKey, appBar: AppBar( - title: Text("P2P Messaging"), + title: Text("P2P Messaging - AOSSIE"), actions: [ IconButton( icon: Icon(Icons.settings), diff --git a/lib/pages/Profile.dart b/lib/pages/Profile.dart index d75703c..3d1a04a 100644 --- a/lib/pages/Profile.dart +++ b/lib/pages/Profile.dart @@ -16,7 +16,11 @@ class Profile extends StatefulWidget { class _ProfileState extends State { // TextEditingController for the name of the user TextEditingController myName = TextEditingController(); + + // loading variable is used for UI purpose when the app is fetching + // user details bool loading = true; + // Custom generated id for the user var customLengthId = nanoid(6); @@ -41,6 +45,12 @@ class _ProfileState extends State { } } + // It is a general function to navigate to home screen. + // If we are first launching the app, we need to replace the profile page + // from the context and then open the home screen + // Otherwise we need to pop out the profile screen context + // from memory of the application. This is a flutter way + // to manage different contexts and screens. void navigateToHomeScreen() { Global.myName = myName.text; if (!widget.onLogin) { @@ -59,6 +69,7 @@ class _ProfileState extends State { @override void initState() { super.initState(); + // At the launch we are fetching details using the getDetails function getDetails(); } @@ -104,8 +115,8 @@ class _ProfileState extends State { // saving the name and id to shared preferences prefs.setString('p_name', myName.text); prefs.setString('p_id', customLengthId); - // On pressing, move to the device list screen + // On pressing, move to the home screen navigateToHomeScreen(); }, child: Text("Save"), From a433efe429480801e840535ac140c032c5796eec Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Wed, 7 Sep 2022 19:53:16 +0530 Subject: [PATCH 32/36] Updating gradle for new Flutter version --- android/app/build.gradle | 2 +- android/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f3cf035..f5bac85 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 32 + compileSdkVersion 33 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/android/build.gradle b/android/build.gradle index 1aff911..b7bb463 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.5.10' + ext.kotlin_version = '1.6.10' repositories { google() jcenter() From 53a4fddd0fce9f7f94e453ba8537a895e3e884c3 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Wed, 7 Sep 2022 19:53:50 +0530 Subject: [PATCH 33/36] README updated and GSoC 2022 implementations attached --- GSoC_2022_ManavSarkar.md | 50 ++++++++++++++++++++++++++++++++++++++++ README.md | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 GSoC_2022_ManavSarkar.md diff --git a/GSoC_2022_ManavSarkar.md b/GSoC_2022_ManavSarkar.md new file mode 100644 index 0000000..a7cee84 --- /dev/null +++ b/GSoC_2022_ManavSarkar.md @@ -0,0 +1,50 @@ +# Peer to Peer Messaging application GSOC 2022 + +## Choosen Idea +1) Change the project Push Gossip Protocol to either Pull Protocol or Hybrid protocol +2) Updating the User Interface for the Application. + +## Project Description +The Peer to Peer messaging application aims to build an application that does not rely on a central server governed by laws and influenced by third party users. Currently, we are relying on applications that are based on central server messaging that can use our private data for their benefit regardless of the privacy policies without knowing the user. We came across incidents of data breaches where the personal data of a large number of users was disclosed to hackers. Hence a solution would be to move the data from the central server to a distributed network. The distributed network will be able to provide better privacy, less dependence on the network, and freedom from central network laws. +The application will be hugely beneficial in disaster-prone areas or remote areas where the cellular connections are too weak to communicate through general messaging applications. In this case, this application would communicate through device networking capabilities. Hence the app is reliable and the project is working for a good cause. Hence I am motivated to develop and contribute to this project. + +## Proposal Detailed Approach +I intend to propose an approach to transfer the messaging protocol of the application from the naive push protocol. The push protocol has many disadvantages and so we need to migrate to either pull protocol or hybrid protocol. The hybrid model that is used in this project is the First-Push-Then-Pull protocol. + +### Hybrid Protocol Implementation +Hybrid protocol comprises both the Push protocol and Pull protocol. The approach for this protocol is First Push and then Pull. It works on the following principle. +- As we know, the push protocol has better performance in the initial rounds. So we first take the use of the push protocol and propagate the message until some rounds. +- Next, we will apply the pull protocol where the devices will start asking for updates. +- In this way, we will achieve the propagation time of O(log(log N)), which is much better than O(log N) for the push protocol. + +**Determination of transition period**We cannot change from push protocol to pull protocol randomly. We need a definite round where the nodes will change from push to pull protocol. +1) First the Pull protocol works as it asks the connected devices that if there is new message with the help of method UPDATE that is being implemented. +2) The pull protocol continues until the application is on. +3) Now when a new message is being sent from a device, it pushes to the connected devices. The rest of devices receive the message with the help of Pull Protocol. +4) In this way, we achieve First Push and Then Pull protocol. + +## Implementations in GSoC 2022 +- The app previously required to enter the name every time the app launches. +Now the application saves the user name and unique id(automatically generated) into the Shared Preferences that is being removed only when the app is installed. It is a key-value form of data storing way. So when the app is launched again, the app directly opens the Home Page. +- Home Page - It contains two tabs. The tabs are created and managed by default tab controller of the Flutter. + - Tab 1: Contains List of Connected and Disconnected but in network devices. + - Tab2: List of devices conversed with previously. +- Provider State Management - Previously when viewing the chat page, if new message arrived from the person whom with we are chatting, the message was saved in Database but not displayed until relaunching the ChatPage. Hence the state management was required which ensured that when a new message arrived, it will be displayed instantly. This also improved the overall speed of the application as common details where available globally rather being transferring from one screen to other. +- Hybrid Protocol as discussed previously - First Push Then Pull (FPTP) Protocol. +- Updated the UI + +## App Flow +- ***(If first launched)*** - The app asks for the username and a random string is added to their name, to make sure its unique. +- Then the user is taken to the HomePage. +- One first tab, it contains the devices with whom he/she can connect with and chat. +- On second tab, it contains the devices with which devices it has chatted previously. +- On clicking any device name from the list, it can chat with the devices in realtime or if there is no connection, it is delivered when the app comes on network. + +## Merge Requests +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/18 +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/19 +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/20 + +## App Working Video + +https://drive.google.com/file/d/1Egv-RhbZmwLcs0yskrFm4Ym8IX44D1qg/view?usp=sharing diff --git a/README.md b/README.md index 2d36323..a8a0fc9 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,49 @@ There are 6 main parts the above idea can be broken into, - use the 'hopping message' architecture to relay messages - offline storage of undelivered messages +# Peer to Peer Messaging application GSOC 2022 + +## Choosen Idea +1) Change the project Push Gossip Protocol to either Pull Protocol or Hybrid protocol +2) Updating the User Interface for the Application. + +## Project Description +The Peer to Peer messaging application aims to build an application that does not rely on a central server governed by laws and influenced by third party users. Currently, we are relying on applications that are based on central server messaging that can use our private data for their benefit regardless of the privacy policies without knowing the user. We came across incidents of data breaches where the personal data of a large number of users was disclosed to hackers. Hence a solution would be to move the data from the central server to a distributed network. The distributed network will be able to provide better privacy, less dependence on the network, and freedom from central network laws. +The application will be hugely beneficial in disaster-prone areas or remote areas where the cellular connections are too weak to communicate through general messaging applications. In this case, this application would communicate through device networking capabilities. Hence the app is reliable and the project is working for a good cause. Hence I am motivated to develop and contribute to this project. + +## Proposal Detailed Approach +I intend to propose an approach to transfer the messaging protocol of the application from the naive push protocol. The push protocol has many disadvantages and so we need to migrate to either pull protocol or hybrid protocol. The hybrid model that is used in this project is the First-Push-Then-Pull protocol. + +### Hybrid Protocol Implementation +Hybrid protocol comprises both the Push protocol and Pull protocol. The approach for this protocol is First Push and then Pull. It works on the following principle. +- As we know, the push protocol has better performance in the initial rounds. So we first take the use of the push protocol and propagate the message until some rounds. +- Next, we will apply the pull protocol where the devices will start asking for updates. +- In this way, we will achieve the propagation time of O(log(log N)), which is much better than O(log N) for the push protocol. + +**Determination of transition period**We cannot change from push protocol to pull protocol randomly. We need a definite round where the nodes will change from push to pull protocol. +1) First the Pull protocol works as it asks the connected devices that if there is new message with the help of method UPDATE that is being implemented. +2) The pull protocol continues until the application is on. +3) Now when a new message is being sent from a device, it pushes to the connected devices. The rest of devices receive the message with the help of Pull Protocol. +4) In this way, we achieve First Push and Then Pull protocol. + +## Implementations in GSoC 2022 +- The app previously required to enter the name every time the app launches. +Now the application saves the user name and unique id(automatically generated) into the Shared Preferences that is being removed only when the app is installed. It is a key-value form of data storing way. So when the app is launched again, the app directly opens the Home Page. +- Home Page - It contains two tabs. The tabs are created and managed by default tab controller of the Flutter. + - Tab 1: Contains List of Connected and Disconnected but in network devices. + - Tab2: List of devices conversed with previously. +- Provider State Management - Previously when viewing the chat page, if new message arrived from the person whom with we are chatting, the message was saved in Database but not displayed until relaunching the ChatPage. Hence the state management was required which ensured that when a new message arrived, it will be displayed instantly. This also improved the overall speed of the application as common details where available globally rather being transferring from one screen to other. +- Hybrid Protocol as discussed previously - First Push Then Pull (FPTP) Protocol. +- Updated the UI + +## App Flow +- ***(If first launched)*** - The app asks for the username and a random string is added to their name, to make sure its unique. +- Then the user is taken to the HomePage. +- One first tab, it contains the devices with whom he/she can connect with and chat. +- On second tab, it contains the devices with which devices it has chatted previously. +- On clicking any device name from the list, it can chat with the devices in realtime or if there is no connection, it is delivered when the app comes on network. + +## Merge Requests +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/18 +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/19 +- https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/20 From 3d7e02319b60b7efc0a20014df7a2540ad046400 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Wed, 7 Sep 2022 20:00:59 +0530 Subject: [PATCH 34/36] Updated GSoC 2022 file. --- GSoC_2022_ManavSarkar.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GSoC_2022_ManavSarkar.md b/GSoC_2022_ManavSarkar.md index a7cee84..3c27a57 100644 --- a/GSoC_2022_ManavSarkar.md +++ b/GSoC_2022_ManavSarkar.md @@ -46,5 +46,6 @@ Now the application saves the user name and unique id(automatically generated) i - https://gitlab.com/aossie/p2p-messaging-flutter/-/merge_requests/20 ## App Working Video +Here is the video for the working application. https://drive.google.com/file/d/1Egv-RhbZmwLcs0yskrFm4Ym8IX44D1qg/view?usp=sharing From a9ffaac7b38e4e5d45a211302c80e2ad65150147 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 12 Sep 2022 16:26:02 +0530 Subject: [PATCH 35/36] Adding APK and Project links --- GSoC_2022_ManavSarkar.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/GSoC_2022_ManavSarkar.md b/GSoC_2022_ManavSarkar.md index 3c27a57..cb7bf64 100644 --- a/GSoC_2022_ManavSarkar.md +++ b/GSoC_2022_ManavSarkar.md @@ -49,3 +49,12 @@ Now the application saves the user name and unique id(automatically generated) i Here is the video for the working application. https://drive.google.com/file/d/1Egv-RhbZmwLcs0yskrFm4Ym8IX44D1qg/view?usp=sharing + +## APK Link +https://drive.google.com/file/d/1fBfJO6akTe_-GXhvtEcr_qd59W7Ah5YG/view?usp=sharing + +## Project Link +https://gitlab.com/aossie/p2p-messaging-flutter + +## GSOC 2022 Branch Link +https://gitlab.com/aossie/p2p-messaging-flutter/-/tree/gsoc-2022 From acfe97b2dc62f171f2282c11c0bae3a35c65b350 Mon Sep 17 00:00:00 2001 From: Manav Sarkar Date: Mon, 12 Sep 2022 16:28:19 +0530 Subject: [PATCH 36/36] Moving to appropriate destination of GSOC Contributions folder. --- GSoC_2022_ManavSarkar.md => GSOC/2022/ManavSarkar.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename GSoC_2022_ManavSarkar.md => GSOC/2022/ManavSarkar.md (100%) diff --git a/GSoC_2022_ManavSarkar.md b/GSOC/2022/ManavSarkar.md similarity index 100% rename from GSoC_2022_ManavSarkar.md rename to GSOC/2022/ManavSarkar.md