diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..5e5373b7f
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,14 @@
+# These are supported funding model platforms
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username e.g., user1
+open_collective: dekusms
+ko_fi: # Replace with a single Ko-fi username e.g., user1
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+polar: # Replace with a single Polar username e.g., user1
+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username e.g., user1
+thanks_dev: # Replace with a single thanks.dev username e.g., u/gh/user1
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username e.g., user1
+issuehunt: # Replace with a single IssueHunt username e.g., user1
+otechie: # Replace with a single Otechie username e.g., user1
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.gitignore b/.gitignore
index 779cc037e..a417aa356 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,11 @@ venv/*
gradle.properties
*.tmp.sh
*.logs
+/app/.idea/.gitignore
+/app/.idea/appInsightsSettings.xml
+/app/.idea/caches/deviceStreaming.xml
+/app/.idea/gradle.xml
+/app/.idea/migrations.xml
+/app/.idea/misc.xml
+/app/.idea/runConfigurations.xml
+/app/.idea/vcs.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b86273d94..b589d56e9 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 94dcebe24..770162664 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -14,7 +14,6 @@
-
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 22f76ec4e..adb0d8eae 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -52,6 +52,10 @@
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b9f8ce5f0..e2bb3d880 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,3 +1,4 @@
+
-
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 5e0944535..7da3b5e9e 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -3,5 +3,6 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ccc1ef377..49fe7818a 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,8 @@
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.afkanerd.deku)
-
+
+
Contents
[About](#about)
diff --git a/app/build.gradle b/app/build.gradle
index 99bb5da70..cc17c3d66 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -40,7 +40,7 @@ android {
}
}
- resourceConfigurations += ["en", "fr", "ru", "de"]
+ resourceConfigurations += ["en", "fr", "ru", "de", "pl"]
}
buildFeatures {
@@ -298,5 +298,6 @@ dependencies {
implementation(libs.autolinktext)
+ implementation libs.accompanist.permissions
}
diff --git a/app/schemas/com.afkanerd.deku.Datastore/22.json b/app/schemas/com.afkanerd.deku.Datastore/22.json
new file mode 100644
index 000000000..8ac431990
--- /dev/null
+++ b/app/schemas/com.afkanerd.deku.Datastore/22.json
@@ -0,0 +1,524 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 22,
+ "identityHash": "bf8ec85e9505a28f35b965cb2ce0b223",
+ "entities": [
+ {
+ "tableName": "Archive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `is_archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))",
+ "fields": [
+ {
+ "fieldPath": "thread_id",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_archived",
+ "columnName": "is_archived",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "thread_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GatewayServer",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`URL` TEXT, `protocol` TEXT, `tag` TEXT, `format` TEXT, `date` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `smtp_host` TEXT, `smtp_username` TEXT, `smtp_password` TEXT, `smtp_from` TEXT, `smtp_recipient` TEXT, `smtp_subject` TEXT, `smtp_port` INTEGER, `ftp_host` TEXT, `ftp_username` TEXT, `ftp_password` TEXT, `ftp_remote_path` TEXT, `ftp_working_directory` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "URL",
+ "columnName": "URL",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "protocol",
+ "columnName": "protocol",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tag",
+ "columnName": "tag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "smtp.smtp_host",
+ "columnName": "smtp_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_username",
+ "columnName": "smtp_username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_password",
+ "columnName": "smtp_password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_from",
+ "columnName": "smtp_from",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_recipient",
+ "columnName": "smtp_recipient",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_subject",
+ "columnName": "smtp_subject",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_port",
+ "columnName": "smtp_port",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_host",
+ "columnName": "ftp_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_username",
+ "columnName": "ftp_username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_password",
+ "columnName": "ftp_password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_remote_path",
+ "columnName": "ftp_remote_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_working_directory",
+ "columnName": "ftp_working_directory",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "RemoteListenersQueues",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gatewayClientId` INTEGER NOT NULL, `name` TEXT, `binding1Name` TEXT, `binding2Name` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gatewayClientId",
+ "columnName": "gatewayClientId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "binding1Name",
+ "columnName": "binding1Name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "binding2Name",
+ "columnName": "binding2Name",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conversation",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `message_id` TEXT, `thread_id` TEXT, `date` TEXT, `date_sent` TEXT, `type` INTEGER NOT NULL, `num_segments` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, `status` INTEGER NOT NULL, `error_code` INTEGER NOT NULL, `read` INTEGER NOT NULL, `is_encrypted` INTEGER NOT NULL, `is_key` INTEGER NOT NULL, `is_image` INTEGER NOT NULL, `formatted_date` TEXT, `address` TEXT, `text` TEXT, `data` TEXT, `_mk` TEXT, `isArchived` INTEGER NOT NULL DEFAULT 0, `isData` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message_id",
+ "columnName": "message_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thread_id",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date_sent",
+ "columnName": "date_sent",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "num_segments",
+ "columnName": "num_segments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subscription_id",
+ "columnName": "subscription_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error_code",
+ "columnName": "error_code",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_encrypted",
+ "columnName": "is_encrypted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_key",
+ "columnName": "is_key",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_image",
+ "columnName": "is_image",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "formatted_date",
+ "columnName": "formatted_date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "_mk",
+ "columnName": "_mk",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isArchived",
+ "columnName": "isArchived",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isData",
+ "columnName": "isData",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Conversation_message_id",
+ "unique": true,
+ "columnNames": [
+ "message_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Conversation_message_id` ON `${TABLE_NAME}` (`message_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GatewayClient",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `hostUrl` TEXT, `username` TEXT, `password` TEXT, `port` INTEGER NOT NULL, `friendlyConnectionName` TEXT, `virtualHost` TEXT, `connectionTimeout` INTEGER NOT NULL, `prefetch_count` INTEGER NOT NULL, `heartbeat` INTEGER NOT NULL, `protocol` TEXT NOT NULL, `projectName` TEXT, `projectBinding` TEXT, `projectBinding2` TEXT, `activated` INTEGER NOT NULL DEFAULT 0, `state` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hostUrl",
+ "columnName": "hostUrl",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "friendlyConnectionName",
+ "columnName": "friendlyConnectionName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "virtualHost",
+ "columnName": "virtualHost",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionTimeout",
+ "columnName": "connectionTimeout",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "prefetch_count",
+ "columnName": "prefetch_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "heartbeat",
+ "columnName": "heartbeat",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "protocol",
+ "columnName": "protocol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "projectName",
+ "columnName": "projectName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "projectBinding",
+ "columnName": "projectBinding",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "projectBinding2",
+ "columnName": "projectBinding2",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "activated",
+ "columnName": "activated",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ThreadsConfigurations",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `threadId` TEXT, `isMute` INTEGER NOT NULL, `isArchive` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "threadId",
+ "columnName": "threadId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isMute",
+ "columnName": "isMute",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isArchive",
+ "columnName": "isArchive",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_ThreadsConfigurations_threadId",
+ "unique": true,
+ "columnNames": [
+ "threadId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ThreadsConfigurations_threadId` ON `${TABLE_NAME}` (`threadId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bf8ec85e9505a28f35b965cb2ce0b223')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/com.afkanerd.deku.Datastore/23.json b/app/schemas/com.afkanerd.deku.Datastore/23.json
new file mode 100644
index 000000000..c59111071
--- /dev/null
+++ b/app/schemas/com.afkanerd.deku.Datastore/23.json
@@ -0,0 +1,524 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 23,
+ "identityHash": "d8920b8d3536e56a5a20e77da98e2819",
+ "entities": [
+ {
+ "tableName": "Archive",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `is_archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))",
+ "fields": [
+ {
+ "fieldPath": "thread_id",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_archived",
+ "columnName": "is_archived",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "thread_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "GatewayServer",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`URL` TEXT, `protocol` TEXT, `tag` TEXT, `format` TEXT, `date` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `smtp_host` TEXT, `smtp_username` TEXT, `smtp_password` TEXT, `smtp_from` TEXT, `smtp_recipient` TEXT, `smtp_subject` TEXT, `smtp_port` INTEGER, `ftp_host` TEXT, `ftp_username` TEXT, `ftp_password` TEXT, `ftp_remote_path` TEXT, `ftp_working_directory` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "URL",
+ "columnName": "URL",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "protocol",
+ "columnName": "protocol",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tag",
+ "columnName": "tag",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "smtp.smtp_host",
+ "columnName": "smtp_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_username",
+ "columnName": "smtp_username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_password",
+ "columnName": "smtp_password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_from",
+ "columnName": "smtp_from",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_recipient",
+ "columnName": "smtp_recipient",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_subject",
+ "columnName": "smtp_subject",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "smtp.smtp_port",
+ "columnName": "smtp_port",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_host",
+ "columnName": "ftp_host",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_username",
+ "columnName": "ftp_username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_password",
+ "columnName": "ftp_password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_remote_path",
+ "columnName": "ftp_remote_path",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ftp.ftp_working_directory",
+ "columnName": "ftp_working_directory",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "RemoteListenersQueues",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gatewayClientId` INTEGER NOT NULL, `name` TEXT, `binding1Name` TEXT, `binding2Name` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gatewayClientId",
+ "columnName": "gatewayClientId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "binding1Name",
+ "columnName": "binding1Name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "binding2Name",
+ "columnName": "binding2Name",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Conversation",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `message_id` TEXT, `thread_id` TEXT, `date` TEXT, `date_sent` TEXT, `type` INTEGER NOT NULL, `num_segments` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, `status` INTEGER NOT NULL, `error_code` INTEGER NOT NULL, `read` INTEGER NOT NULL, `is_encrypted` INTEGER NOT NULL, `is_key` INTEGER NOT NULL, `is_image` INTEGER NOT NULL, `formatted_date` TEXT, `address` TEXT, `text` TEXT, `data` TEXT, `_mk` TEXT, `isArchived` INTEGER NOT NULL DEFAULT 0, `isData` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message_id",
+ "columnName": "message_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thread_id",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date_sent",
+ "columnName": "date_sent",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "num_segments",
+ "columnName": "num_segments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subscription_id",
+ "columnName": "subscription_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error_code",
+ "columnName": "error_code",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isRead",
+ "columnName": "read",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_encrypted",
+ "columnName": "is_encrypted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_key",
+ "columnName": "is_key",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isIs_image",
+ "columnName": "is_image",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "formatted_date",
+ "columnName": "formatted_date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "_mk",
+ "columnName": "_mk",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isArchived",
+ "columnName": "isArchived",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isData",
+ "columnName": "isData",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Conversation_message_id",
+ "unique": true,
+ "columnNames": [
+ "message_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Conversation_message_id` ON `${TABLE_NAME}` (`message_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "RemoteListeners",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `hostUrl` TEXT, `username` TEXT, `password` TEXT, `port` INTEGER NOT NULL, `friendlyConnectionName` TEXT, `virtualHost` TEXT, `connectionTimeout` INTEGER NOT NULL, `prefetch_count` INTEGER NOT NULL, `heartbeat` INTEGER NOT NULL, `protocol` TEXT NOT NULL, `projectName` TEXT, `projectBinding` TEXT, `projectBinding2` TEXT, `activated` INTEGER NOT NULL DEFAULT 0, `state` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hostUrl",
+ "columnName": "hostUrl",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "friendlyConnectionName",
+ "columnName": "friendlyConnectionName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "virtualHost",
+ "columnName": "virtualHost",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "connectionTimeout",
+ "columnName": "connectionTimeout",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "prefetch_count",
+ "columnName": "prefetch_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "heartbeat",
+ "columnName": "heartbeat",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "protocol",
+ "columnName": "protocol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "projectName",
+ "columnName": "projectName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "projectBinding",
+ "columnName": "projectBinding",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "projectBinding2",
+ "columnName": "projectBinding2",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "activated",
+ "columnName": "activated",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ThreadsConfigurations",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `threadId` TEXT, `isMute` INTEGER NOT NULL, `isArchive` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "threadId",
+ "columnName": "threadId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isMute",
+ "columnName": "isMute",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isArchive",
+ "columnName": "isArchive",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_ThreadsConfigurations_threadId",
+ "unique": true,
+ "columnNames": [
+ "threadId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ThreadsConfigurations_threadId` ON `${TABLE_NAME}` (`threadId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd8920b8d3536e56a5a20e77da98e2819')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java
index b4364d471..08bc6fd28 100644
--- a/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java
+++ b/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java
@@ -5,33 +5,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.content.Context;
-import android.util.Log;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.afkanerd.deku.QueueListener.RMQ.RMQConnection;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.ConsumerShutdownSignalCallback;
-import com.rabbitmq.client.DeliverCallback;
-import com.rabbitmq.client.Delivery;
-import com.rabbitmq.client.ShutdownSignalException;
-import com.rabbitmq.client.impl.DefaultExceptionHandler;
-
-import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeoutException;
-
@RunWith(AndroidJUnit4.class)
public class RMQConnectionTest {
//
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 932adf012..c41cfe58b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,10 +38,15 @@
android:supportsRtl="true"
android:theme="@style/Theme.main">
+
+
+
+
+
@@ -86,11 +91,11 @@
android:name=".AboutActivity"
android:exported="false"/>
+ android:parentActivityName="com.afkanerd.deku.RemoteListeners.Models.GatewayClientListingActivity" />
-
-
-
-
-
@@ -178,7 +178,7 @@
@@ -200,7 +200,7 @@
android:exported="false"
tools:node="merge">
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
index b33bdf35f..5ce7024b4 100644
Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/afkanerd/deku/Datastore.java b/app/src/main/java/com/afkanerd/deku/Datastore.java
index 47e513d62..3ae1d3262 100644
--- a/app/src/main/java/com/afkanerd/deku/Datastore.java
+++ b/app/src/main/java/com/afkanerd/deku/Datastore.java
@@ -6,10 +6,10 @@
import androidx.room.AutoMigration;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
-import androidx.room.Delete;
import androidx.room.DeleteColumn;
import androidx.room.DeleteTable;
import androidx.room.InvalidationTracker;
+import androidx.room.RenameTable;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.AutoMigrationSpec;
@@ -19,14 +19,11 @@
import com.afkanerd.deku.DefaultSMS.Models.Archive;
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation;
import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao;
-import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations;
-import com.afkanerd.deku.DefaultSMS.Models.Conversations.ConversationsThreadsEncryption;
-import com.afkanerd.deku.DefaultSMS.DAO.ConversationsThreadsEncryptionDao;
import com.afkanerd.deku.DefaultSMS.Models.ThreadsConfigurations;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientDAO;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjectDao;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjects;
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners;
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenerDAO;
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersQueuesDao;
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues;
import com.afkanerd.deku.Router.GatewayServers.GatewayServer;
import com.afkanerd.deku.Router.GatewayServers.GatewayServerDAO;
//import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient;
@@ -40,11 +37,11 @@
@Database(entities = {
Archive.class,
GatewayServer.class,
- GatewayClientProjects.class,
+ RemoteListenersQueues.class,
Conversation.class,
- GatewayClient.class,
+ RemoteListeners.class,
ThreadsConfigurations.class},
- version = 21,
+ version = 23,
autoMigrations = {
@AutoMigration(from = 9, to = 10),
@AutoMigration(from = 10, to = 11),
@@ -57,7 +54,9 @@
@AutoMigration(from = 17, to = 18),
@AutoMigration(from = 18, to = 19),
@AutoMigration(from = 19, to = 20, spec = Datastore.Migrate19To20.class),
- @AutoMigration(from = 20, to = 21, spec = Datastore.Migrate20To21.class)
+ @AutoMigration(from = 20, to = 21, spec = Datastore.Migrate20To21.class),
+ @AutoMigration(from = 21, to = 22, spec = Datastore.Migrate21To22.class),
+ @AutoMigration(from = 22, to = 23, spec = Datastore.Migrate22To23.class)
})
@@ -81,8 +80,8 @@ private static Datastore create(final Context context) {
public abstract GatewayServerDAO gatewayServerDAO();
- public abstract GatewayClientDAO gatewayClientDAO();
- public abstract GatewayClientProjectDao gatewayClientProjectDao();
+ public abstract RemoteListenerDAO remoteListenerDAO();
+ public abstract RemoteListenersQueuesDao remoteListenersQueuesDao();
public abstract ConversationDao conversationDao();
@@ -122,4 +121,20 @@ static class Migrate19To20 implements AutoMigrationSpec { }
@DeleteTable(tableName = "ConversationsThreadsEncryption")
)
static class Migrate20To21 implements AutoMigrationSpec { }
+
+ @RenameTable.Entries(
+ @RenameTable(
+ fromTableName = "GatewayClientProjects",
+ toTableName = "RemoteListenersQueues"
+ )
+ )
+ static class Migrate21To22 implements AutoMigrationSpec { }
+
+ @RenameTable.Entries(
+ @RenameTable(
+ fromTableName = "GatewayClient",
+ toTableName = "RemoteListeners"
+ )
+ )
+ static class Migrate22To23 implements AutoMigrationSpec { }
}
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AboutActivity.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AboutActivity.kt
index 8238960de..4bcf49c2f 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AboutActivity.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AboutActivity.kt
@@ -9,6 +9,7 @@ import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
+import com.afkanerd.deku.MainActivity
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.kt
index 97e13634c..f80954eed 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.kt
@@ -1,7 +1,6 @@
package com.afkanerd.deku.DefaultSMS.BroadcastReceivers
import android.app.Activity
-import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -10,18 +9,15 @@ import android.util.Base64
import android.util.Log
import android.util.Pair
import android.widget.Toast
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
import com.afkanerd.deku.Datastore
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
import com.afkanerd.deku.DefaultSMS.BuildConfig
-import com.afkanerd.deku.DefaultSMS.MainActivity
+import com.afkanerd.deku.MainActivity
import com.afkanerd.deku.DefaultSMS.Models.Contacts
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
import com.afkanerd.deku.DefaultSMS.Models.E2EEHandler
import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB
import com.afkanerd.deku.DefaultSMS.Models.Notifications
-import com.afkanerd.deku.DefaultSMS.Models.NotificationsHandler
import com.afkanerd.deku.DefaultSMS.R
import com.afkanerd.deku.Router.GatewayServers.GatewayServer
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.libsignal.Ratchets
@@ -30,8 +26,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
class IncomingTextSMSBroadcastReceiver : BroadcastReceiver() {
/*
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyMuteActionBroadcastReceiver.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyMuteActionBroadcastReceiver.kt
index 401376128..64382a830 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyMuteActionBroadcastReceiver.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyMuteActionBroadcastReceiver.kt
@@ -12,14 +12,12 @@ import androidx.core.app.RemoteInput
import com.afkanerd.deku.Datastore
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
import com.afkanerd.deku.DefaultSMS.BuildConfig
-import com.afkanerd.deku.DefaultSMS.MainActivity
+import com.afkanerd.deku.MainActivity
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB
import com.afkanerd.deku.DefaultSMS.Models.Notifications
-import com.afkanerd.deku.DefaultSMS.Models.NotificationsHandler
import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -146,6 +144,6 @@ class IncomingTextSMSReplyMuteActionBroadcastReceiver : BroadcastReceiver() {
var REPLY_SUBSCRIPTION_ID: String = "REPLY_SUBSCRIPTION_ID"
// Key for the string that's delivered in the action's intent.
- const val KEY_TEXT_REPLY: String = "KEY_TEXT_REPLY"
+ const val KEY_TEXT_REPLY: String = "extra_remote_reply"
}
}
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java
index 8a64e8378..b2daf84b3 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Commons/Helpers.java
@@ -52,64 +52,6 @@
public class Helpers {
- public static Spannable highlightSubstringYellow(Context context, String text,
- String searchString, boolean sent) {
- // Find all occurrences of the substring in the text.
- List startIndices = new ArrayList<>();
- int index = text.toLowerCase().indexOf(searchString.toLowerCase());
- while (index >= 0) {
- startIndices.add(index);
- index = text.indexOf(searchString, index + searchString.length());
- }
-
- // Create a SpannableString object.
- SpannableString spannableString = new SpannableString(text);
-
- // Set the foreground color of the substring to yellow.
- BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(
- context.getColor(R.color.md_theme_inversePrimary));
- for (int startIndex : startIndices) {
- spannableString.setSpan(backgroundColorSpan, startIndex, startIndex + searchString.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- return spannableString;
- }
- public static long generateRandomNumber() {
- Random random = new Random();
- return random.nextInt(Integer.MAX_VALUE);
- }
-
- public static int dpToPixel(float dpValue) {
- float density = Resources.getSystem().getDisplayMetrics().density;
- return (int) (dpValue * density);
- }
-
- public static int getRandomColor() {
- Random random = new Random();
- int r = random.nextInt(256);
- int g = random.nextInt(256);
- int b = random.nextInt(256);
- int color = r << 16 | g << 8 | b;
-
- return generateColor(color);
- }
-
- public static String[] convertSetToStringArray(Set setOfString)
- {
- // Create String[] of size of setOfString
- String[] arrayOfString = new String[setOfString.size()];
-
- // Copy elements from set to string array
- // using advanced for loop
- int index = 0;
- for (String str : setOfString)
- arrayOfString[index++] = str;
-
- // return the formed String[]
- return arrayOfString;
- }
-
public static boolean isShortCode(String address) {
if(address.length() < 4)
return true;
@@ -118,34 +60,13 @@ public static boolean isShortCode(String address) {
return !PhoneNumberUtils.isWellFormedSmsAddress(address) || matcher.find();
}
- public static byte[] generateRandomBytes(int length) {
- SecureRandom random = new SecureRandom();
- byte[] bytes = new
-
- byte[length];
- random.nextBytes(bytes);
- return bytes;
- }
-
- public static String getFormatCompleteNumber(Context context, String address, String defaultRegion) {
- try(Cursor cursor = NativeSMSDB.fetchByAddress(context, address)) {
- if(cursor.moveToFirst()) {
- int recipientIdIndex = cursor.getColumnIndexOrThrow("address");
- address = cursor.getString(recipientIdIndex);
- }
- cursor.close();
- } catch(Exception e) {
- e.printStackTrace();
- }
-
- return address;
- }
-
public static String getFormatCompleteNumber(String data, String defaultRegion) {
data = data.replaceAll("%2B", "+")
.replaceAll("-", "")
.replaceAll("%20", "")
- .replaceAll(" ", "");
+ .replaceAll(" ", "")
+ .replaceFirst("^0+", "");
+
if(data.length() < 5)
return data;
PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
@@ -157,9 +78,10 @@ public static String getFormatCompleteNumber(String data, String defaultRegion)
return "+" + countryCode + nationalNumber;
} catch(NumberParseException e) {
-// e.printStackTrace();
if(e.getErrorType() == NumberParseException.ErrorType.INVALID_COUNTRY_CODE) {
- data = outputNumber.replaceAll("sms[to]*:", "");
+ data = outputNumber
+ .replaceAll("sms[to]*:", "")
+ .replaceFirst("^0+", "");
if (data.startsWith(defaultRegion)) {
outputNumber = "+" + data;
} else {
@@ -228,35 +150,6 @@ public static String getFormatNationalNumber(String data, String defaultRegion)
return data;
}
-// public static String formatDateExtended(Context context, long epochTime) {
-// long currentTime = System.currentTimeMillis();
-// long diff = currentTime - epochTime;
-//
-// Date currentDate = new Date(currentTime);
-// Date targetDate = new Date(epochTime);
-//
-// SimpleDateFormat timeFormat = new SimpleDateFormat("h:mm a", Locale.getDefault());
-// SimpleDateFormat fullDayFormat = new SimpleDateFormat("EEEE", Locale.getDefault());
-// SimpleDateFormat shortDayFormat = new SimpleDateFormat("EEE", Locale.getDefault());
-// SimpleDateFormat shortMonthDayFormat = new SimpleDateFormat("MMM d", Locale.getDefault());
-//
-//// if (diff < DateUtils.HOUR_IN_MILLIS) { // less than 1 hour
-//// return DateUtils.getRelativeTimeSpanString(epochTime, currentTime, DateUtils.MINUTE_IN_MILLIS).toString();
-//// }
-// if (diff < DateUtils.DAY_IN_MILLIS) { // less than 1 day
-// return DateUtils.formatDateTime(context, epochTime, DateUtils.FORMAT_SHOW_TIME);
-// } else if (isSameDay(currentDate, targetDate)) { // today
-// return timeFormat.format(targetDate);
-// } else if (isYesterday(currentDate, targetDate)) { // yesterday
-// return context.getString(R.string.single_message_thread_yesterday) + " • " + timeFormat.format(targetDate);
-// } else if (isSameWeek(currentDate, targetDate)) { // within the same week
-// return fullDayFormat.format(targetDate) + " • " + timeFormat.format(targetDate);
-// } else { // greater than 1 week
-// return shortDayFormat.format(targetDate) + ", " + shortMonthDayFormat.format(targetDate)
-// + " • " + timeFormat.format(targetDate);
-// }
-// }
-
public static String formatDateExtended(Context context, long epochTime) {
long currentTime = System.currentTimeMillis();
long diff = currentTime - epochTime;
@@ -281,13 +174,6 @@ public static String formatDateExtended(Context context, long epochTime) {
}
}
- private static boolean isSameDay(Date date1, Date date2) {
- SimpleDateFormat dayFormat = new SimpleDateFormat("yyyyDDD", Locale.getDefault());
- String day1 = dayFormat.format(date1);
- String day2 = dayFormat.format(date2);
- return day1.equals(day2);
- }
-
private static boolean isYesterday(Date date1, Date date2) {
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyyDDD", Locale.getDefault());
String day1 = dayFormat.format(date1);
@@ -309,21 +195,6 @@ private static boolean isSameWeek(Date date1, Date date2) {
return week1.equals(week2);
}
-// public static String formatDate(Context context, long epochTime) {
-// long currentTime = System.currentTimeMillis();
-// long diff = currentTime - epochTime;
-//
-// if (diff < DateUtils.HOUR_IN_MILLIS) { // less than 1 hour
-// return DateUtils.getRelativeTimeSpanString(epochTime, currentTime, DateUtils.MINUTE_IN_MILLIS).toString();
-// } else if (diff < DateUtils.DAY_IN_MILLIS) { // less than 1 day
-// return DateUtils.formatDateTime(context, epochTime, DateUtils.FORMAT_SHOW_TIME);
-// } else if (diff < DateUtils.WEEK_IN_MILLIS) { // less than 1 week
-// return DateUtils.formatDateTime(context, epochTime, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY);
-// } else { // greater than 1 week
-// return DateUtils.formatDateTime(context, epochTime, DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE);
-// }
-// }
-
public static String formatDate(Context context, long epochTime) {
long currentTime = System.currentTimeMillis();
long diff = currentTime - epochTime;
@@ -352,17 +223,6 @@ public static String formatDate(Context context, long epochTime) {
return null;
}
- public static String formatLongDate(long epochTime) {
- // Create a date object from the epoch time
- Date date = new Date(epochTime);
-
- // Create a SimpleDateFormat object with the desired format
- SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd, h:mm a", Locale.getDefault());
-
- // Format the date and return the string
- return formatter.format(date);
- }
-
public static String getUserCountry(Context context) {
String countryCode = null;
@@ -379,57 +239,6 @@ public static String getUserCountry(Context context) {
return String.valueOf(PhoneNumberUtil.getInstance().getCountryCodeForRegion(countryCode));
}
- public static int getColor(Context context, String input) {
- int sDefaultColor = context.getResources().getIntArray(R.array.letter_tile_colors)[0];
-// int sDefaultColor = context.getColor(defaultColor);
- if (TextUtils.isEmpty(input)) {
- return sDefaultColor;
- }
- TypedArray sColors = context.getResources().obtainTypedArray(R.array.letter_tile_colors);
- // String.hashCode() implementation is not supposed to change across java versions, so
- // this should guarantee the same email address always maps to the same color.
- // The email should already have been normalized by the ContactRequest.
- final int color = Math.abs(input.hashCode()) % sColors.length();
- return sColors.getColor(color, sDefaultColor);
- }
-
- public static int generateColor(int input) {
- int hue;
- int saturation = 100;
- int value = 60; // Reduced value component for darker colors
-
- hue = Math.abs(input * 31 % 360);
- // Convert the HSV color to RGB and return the color as an int
- float[] hsv = {hue, saturation, value};
- int color = Color.HSVToColor(hsv);
- return color;
- }
-
- public static int generateColor(String input) {
- int hue;
- int saturation = 100;
- int value = 60; // Reduced value component for darker colors
-
- if (input.length() == 0) {
- // Return a default color if the input is empty
- hue = 0;
- } else if (input.length() == 1) {
- // Use the first character of the input to generate the hue
- char firstChar = input.charAt(0);
- hue = Math.abs(firstChar * 31 % 360);
- } else {
- // Use the first and second characters of the input to generate the hue
- char firstChar = input.charAt(0);
- char secondChar = input.charAt(1);
- hue = Math.abs((firstChar + secondChar) * 31 % 360);
- }
-
- // Convert the HSV color to RGB and return the color as an int
- float[] hsv = {hue, saturation, value};
- int color = Color.HSVToColor(hsv);
- return color;
- }
-
public static boolean isBase64Encoded(String input) {
try {
byte[] decodedBytes = Base64.decode(input, Base64.DEFAULT);
@@ -444,82 +253,6 @@ public static boolean isBase64Encoded(String input) {
}
}
- public static void highlightLinks(TextView textView, String text, int color) {
- if(text == null)
- return;
- // Regular expression to find URLs in the text
-// String urlPattern = "((mailto:)?[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+)" +
-// "|((\\+?[0-9]{1,3}?)[ \\-]?)?([\\(]{1}[0-9]{3}[\\)])?[ \\-]?[0-9]{3}[ \\-]?[0-9]{4}" +
-// "|(https?://)?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}(/[\\w\\.-]+)*" +
-// "|(https?://)?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}(/[\\w\\.-]+)*(/\\S*)*(\\?[^ ]*#[^ ]*)/?";
-
- SpannableString spannableString = new SpannableString(text);
-
- String[] splitString = text.split("\\s");
- for(int i=0, length =0; i
-
- println("Moving to bring up activities")
- if (result.resultCode == RESULT_OK) {
- val sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(applicationContext)
- sharedPreferences.edit()
- .putBoolean(getString(R.string.configs_load_natives), true)
- .apply()
- startUserActivities()
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_default_check)
-
- val materialButton = findViewById(R.id.default_check_make_default_btn)
- materialButton.setOnClickListener(object : View.OnClickListener {
- override fun onClick(v: View?) {
- makeDefault()
- }
- })
-
- val defaultName = Telephony.Sms.getDefaultSmsPackage(applicationContext)
- // TODO: this is a hack because defaultName is always null in Android SDK 35 (15)
- if(defaultName.isNullOrBlank()) {
- when {
- ContextCompat.checkSelfPermission(applicationContext,
- Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED -> {
- startUserActivities()
- }
- }
- }
- else if (packageName == defaultName) {
- startUserActivities()
- }
- }
-
- fun clickPrivacyPolicy(view: View?) {
- val url = getString(R.string.privacy_policy_url)
- val shareIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
- startActivity(shareIntent)
- }
-
- fun makeDefault() {
- // TODO: replace this with checking other permissions - since this gives null in level 35
- Log.d(getLocalClassName(), "Got into make default function..")
- val roleManagerIntent: Intent = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- val roleManager = getSystemService(ROLE_SERVICE) as RoleManager
- roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS).apply {
- putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
- }
- } else {
- Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT).apply {
- putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName)
- }
- }
-
- getDefaultPermission.launch(roleManagerIntent)
- }
-
- private fun startUserActivities() {
- val intent = Intent(this, MainActivity::class.java)
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
-
- finish()
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DevelopersFragment.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DevelopersFragment.java
deleted file mode 100644
index c06e28080..000000000
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DevelopersFragment.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.afkanerd.deku.DefaultSMS.Fragments;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragmentCompat;
-
-import com.afkanerd.deku.DefaultSMS.R;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity;
-
-public class DevelopersFragment extends PreferenceFragmentCompat {
- @Override
- public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
- setPreferencesFromResource(R.xml.developer_preferences, rootKey);
-
- Preference smsListeningSMSWithoutBorders = findPreference("settings_sms_listening_gateway_clients");
- smsListeningSMSWithoutBorders.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(@NonNull Preference preference) {
- startActivity(new Intent(getContext(), GatewayClientListingActivity.class));
- return true;
- }
- });
-
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ExportImportHandlers.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ExportImportHandlers.kt
deleted file mode 100644
index 243d8c29a..000000000
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ExportImportHandlers.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.afkanerd.deku.DefaultSMS.Models
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.provider.DocumentsContract
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityCompat.startActivityForResult
-
-object ExportImportHandlers {
-
- val exportRequestCode = 777
- val importRequestCode = 666
-
- fun exportInbox(context: Context) {
- // Request code for creating a PDF document.
- val filename = "Deku_SMS_All_Backup" + System.currentTimeMillis() + ".json";
- val intent = Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("application/json");
- intent.putExtra(Intent.EXTRA_TITLE, filename);
-
- // Optionally, specify a URI for the directory that should be opened in
- // the system file picker when your app creates the document.
-// intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
-
- startActivityForResult(
- context as Activity,
- intent,
- exportRequestCode,
- null
- )
- }
-
- fun importInbox(context: Context) {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- type = "application/json"
-
- // Optionally, specify a URI for the file that should appear in the
- // system file picker when it loads.
-// putExtra(DocumentsContract.EXTRA_INITIAL_URI)
- }
-
- startActivityForResult(
- context as Activity,
- intent,
- importRequestCode,
- null
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Notifications.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Notifications.kt
index 8d87159a8..536cd90b2 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Notifications.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Notifications.kt
@@ -1,6 +1,5 @@
package com.afkanerd.deku.DefaultSMS.Models
-import android.app.Activity.RESULT_OK
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
@@ -8,41 +7,22 @@ import android.content.Intent
import android.content.LocusId
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
-import android.graphics.drawable.Icon
import android.os.Build
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.RequiresApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
-import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getString
import androidx.core.content.LocusIdCompat
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
-import androidx.core.graphics.drawable.RoundedBitmapDrawable
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
-import androidx.core.graphics.drawable.toBitmap
-import androidx.preference.PreferenceManager
-import coil3.compose.AsyncImage
+import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSReplyMuteActionBroadcastReceiver
import com.afkanerd.deku.DefaultSMS.Commons.Helpers
import com.afkanerd.deku.DefaultSMS.R
import com.afkanerd.deku.DefaultSMS.ui.Components.ConvenientMethods
-import com.google.android.material.color.MaterialColors
object Notifications {
const val KEY_TEXT_REPLY = "KEY_TEXT_REPLY"
@@ -60,12 +40,6 @@ object Notifications {
) : NotificationCompat.Builder {
val channelId = getString(context, R.string.incoming_messages_channel_id)
- var replyLabel = getString(context, R.string.notifications_reply_label)
- var remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run {
- setLabel(replyLabel)
- build()
- }
-
var pendingIntent = PendingIntent
.getActivity(
context,
@@ -99,6 +73,8 @@ object Notifications {
PendingIntent.FLAG_MUTABLE
)
+
+ val replyLabel: String? = context.resources.getString(R.string.notifications_reply_label)
var replyAction: NotificationCompat.Action? =
if(replyPendingIntent == null || Helpers.isShortCode(address)) null else
NotificationCompat.Action.Builder(
@@ -106,15 +82,15 @@ object Notifications {
getString(context, R.string.notifications_reply_label),
replyPendingIntent
)
- .addRemoteInput(RemoteInput.Builder("extra_remote_reply").setLabel("").build())
+ .addRemoteInput(
+ RemoteInput.Builder(
+ IncomingTextSMSReplyMuteActionBroadcastReceiver.KEY_TEXT_REPLY)
+ .setLabel(replyLabel)
+ .build()
+ )
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
.setShowsUserInterface(false)
.build()
-// .addRemoteInput(remoteInput)
-// .setAllowGeneratedReplies(true)
-// .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
-// .setShowsUserInterface(false)
-// .build()
var muteAction: NotificationCompat.Action? = if(mutePendingIntent == null) null else
NotificationCompat.Action.Builder(
@@ -184,9 +160,9 @@ object Notifications {
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true)
.setAllowSystemGeneratedContextualActions(true)
- .addAction(muteAction)
- .addAction(markAsReadAction)
.addAction(replyAction)
+ .addAction(markAsReadAction)
+ .addAction(muteAction)
.setShortcutId(address)
.setBubbleMetadata(bubbleMetadata)
.setLocusId(
@@ -212,6 +188,28 @@ object Notifications {
)
}
+ fun cancel(context: Context, notificationId: Int) {
+ with(NotificationManagerCompat.from(context)) {
+ if (ActivityCompat.checkSelfPermission(
+ context,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // TODO: Consider calling
+ // ActivityCompat#requestPermissions
+ // here to request the missing permissions, and then overriding
+ // public fun onRequestPermissionsResult(requestCode: Int, permissions: Array,
+ // grantResults: IntArray)
+ // to handle the case where the user grants the permission. See the documentation
+ // for ActivityCompat#requestPermissions for more details.
+
+ return@with
+ }
+ // notificationId is a unique int for each notification that you must define.
+ cancel(notificationId)
+ }
+ }
+
fun notify(
context: Context,
builder: NotificationCompat.Builder,
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java
index 03eed6b82..ec3e8e1b1 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java
@@ -20,7 +20,7 @@
import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSReplyMuteActionBroadcastReceiver;
import com.afkanerd.deku.DefaultSMS.Commons.Helpers;
-import com.afkanerd.deku.DefaultSMS.MainActivity;
+import com.afkanerd.deku.MainActivity;
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation;
import com.afkanerd.deku.DefaultSMS.R;
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.java
deleted file mode 100644
index 5ae118f46..000000000
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.afkanerd.deku.DefaultSMS.Settings;
-
-import static androidx.core.content.ContextCompat.getSystemService;
-
-import android.app.LocaleManager;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.LocaleList;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatDelegate;
-import androidx.core.os.LocaleListCompat;
-import androidx.preference.ListPreference;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragmentCompat;
-
-import com.afkanerd.deku.DefaultSMS.Fragments.DevelopersFragment;
-//import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity;
-//import com.afkanerd.deku.Router.GatewayServers.GatewayServerListingActivity;
-import com.afkanerd.deku.DefaultSMS.R;
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity;
-import com.afkanerd.deku.Router.GatewayServers.GatewayServerListingActivity;
-
-import java.util.Locale;
-
-public class SettingsFragment extends PreferenceFragmentCompat {
-
- @Override
- public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
- setPreferencesFromResource(R.xml.settings_preferences, rootKey);
-
-// Preference securityPrivacyPreference = findPreference(getString(R.string.settings_security_end_to_end));
-// securityPrivacyPreference.setFragment(EndToEndFragments.class.getCanonicalName());
-
- Preference developersPreferences = findPreference(getString(R.string.settings_advanced_developers_key));
- developersPreferences.setFragment(DevelopersFragment.class.getCanonicalName());
-
- ListPreference languagePreference = findPreference(getString(R.string.settings_locale));
- languagePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
- String languageLocale = (String) newValue;
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
- getContext().getSystemService(LocaleManager.class)
- .setApplicationLocales(
- new LocaleList(Locale.forLanguageTag(languageLocale)));
- }
- else {
- LocaleListCompat appLocale = LocaleListCompat.forLanguageTags(languageLocale);
- AppCompatDelegate.setApplicationLocales(appLocale);
- }
- return true;
- }
- });
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.kt
new file mode 100644
index 000000000..73b68c465
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Settings/SettingsFragment.kt
@@ -0,0 +1,37 @@
+package com.afkanerd.deku.DefaultSMS.Settings
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.PreferenceFragmentCompat
+
+class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(
+ savedInstanceState: android.os.Bundle?,
+ rootKey: kotlin.String?
+ ) {
+ setPreferencesFromResource(com.afkanerd.deku.DefaultSMS.R.xml.settings_preferences, rootKey)
+
+ val languagePreference =
+ findPreference(getString(com.afkanerd.deku.DefaultSMS.R.string.settings_locale))
+
+ languagePreference!!.onPreferenceChangeListener = object :
+ androidx.preference.Preference.OnPreferenceChangeListener {
+ override fun onPreferenceChange(
+ preference: androidx.preference.Preference,
+ newValue: kotlin.Any?
+ ): kotlin.Boolean {
+ val languageLocale = newValue as kotlin.String
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ context?.getSystemService(
+ android.app.LocaleManager::class.java
+ )?.applicationLocales =
+ android.os.LocaleList(java.util.Locale.forLanguageTag(languageLocale))
+ } else {
+ val appLocale =
+ androidx.core.os.LocaleListCompat.forLanguageTags(languageLocale)
+ AppCompatDelegate.setApplicationLocales(appLocale)
+ }
+ return true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/SettingsActivity.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/SettingsActivity.kt
index 3e3ecb8ef..be7b69443 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/SettingsActivity.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/SettingsActivity.kt
@@ -7,6 +7,7 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.afkanerd.deku.DefaultSMS.Settings.SettingsFragment
+import com.afkanerd.deku.MainActivity
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/Components/ConversationsComponents.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/Components/ConversationsComponents.kt
index 43954ee7f..476a2a4db 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/Components/ConversationsComponents.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/Components/ConversationsComponents.kt
@@ -1,10 +1,16 @@
package com.afkanerd.deku.DefaultSMS.ui.Components
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.ComponentName
import android.content.Context
+import android.content.Context.CLIPBOARD_SERVICE
+import android.content.Intent
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.telephony.SmsManager
import android.util.Base64
+import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
@@ -37,8 +43,11 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBackIos
import androidx.compose.material.icons.automirrored.filled.ArrowForwardIos
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.outlined.SimCard
+import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -70,6 +79,7 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardCapitalization
@@ -79,9 +89,15 @@ import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
+import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
+import com.afkanerd.deku.DefaultSMS.BuildConfig
+import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.MainActivity
import com.jakewharton.rxbinding.view.RxMenuItem.icon
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.nio.file.WatchEvent
@@ -483,4 +499,110 @@ fun SimChooser(
}
}
}
-}
\ No newline at end of file
+}
+
+@Preview
+@Composable
+fun ConversationCrudBottomBar(
+ viewModel: ConversationsViewModel = ConversationsViewModel(),
+ items: List = emptyList(),
+ onCompleted: (() -> Unit)? = null,
+ onCancel: (() -> Unit)? = null,
+) {
+ val context = LocalContext.current
+ BottomAppBar (
+ actions = {
+ Row {
+ IconButton(onClick = {
+ CoroutineScope(Dispatchers.Default).launch {
+ onCancel?.let { it() }
+ }
+ }) {
+ Icon(Icons.Default.Close, stringResource(R.string.cancel_selected_messages))
+ }
+
+ Text(
+ viewModel.selectedItems.size.toString(),
+ fontSize = 24.sp,
+ modifier = Modifier.align(Alignment.CenterVertically)
+ )
+
+ Spacer(Modifier.weight(1f))
+
+ if(viewModel.selectedItems.size < 2) {
+ IconButton(onClick = {
+ val conversation = items.firstOrNull {
+ it.message_id in viewModel.selectedItems
+ }
+ copyItem(context, conversation?.text!!)
+ onCompleted?.invoke()
+ }) {
+ Icon(Icons.Filled.ContentCopy, stringResource(R.string.copy_message))
+ }
+
+ IconButton(onClick = {
+ TODO("Implement forward message")
+ }) {
+ Icon(painter= painterResource(id= R.drawable.rounded_forward_24),
+ stringResource(R.string.forward_message)
+ )
+ }
+
+ IconButton(onClick = {
+ val conversation = items.firstOrNull {
+ it.message_id in viewModel.selectedItems
+ }
+ shareItem(context, conversation?.text!!)
+ onCompleted?.let { it() }
+ }) {
+ Icon(Icons.Filled.Share, stringResource(R.string.share_message))
+ }
+ }
+
+ IconButton(onClick = {
+ CoroutineScope(Dispatchers.Default).launch {
+ val conversations = items.filter {
+ it.message_id in viewModel.selectedItems
+ }
+ viewModel.delete(context, conversations)
+ onCompleted?.let { it() }
+ }
+ }) {
+ Icon(Icons.Filled.Delete, stringResource(R.string.delete_message))
+ }
+ }
+
+ }
+ )
+}
+
+private fun copyItem(context: Context, text: String) {
+ val clip = ClipData.newPlainText(text, text)
+ val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+ clipboard.setPrimaryClip(clip)
+
+ Toast.makeText(
+ context, context.getString(R.string.conversation_copied),
+ Toast.LENGTH_SHORT
+ ).show()
+}
+
+private fun shareItem(context: Context, text: String) {
+ val sendIntent = Intent().apply {
+ setAction(Intent.ACTION_SEND)
+ putExtra(Intent.EXTRA_TEXT, text)
+ setType("text/plain")
+ }
+
+ val shareIntent = Intent.createChooser(sendIntent, null)
+ // Only use for components you have control over
+ val excludedComponentNames = arrayOf(
+ ComponentName(
+ BuildConfig.APPLICATION_ID,
+ MainActivity::class.java.name
+ )
+ )
+ shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, excludedComponentNames)
+ context.startActivity(shareIntent)
+}
+
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ComposeNewMain.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ComposeNewMain.kt
index 22bdc19b9..57b0380a8 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ComposeNewMain.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ComposeNewMain.kt
@@ -1,6 +1,5 @@
package com.afkanerd.deku.DefaultSMS.ui
-import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -15,9 +14,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.filled.EnhancedEncryption
-import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -39,37 +35,26 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ContactsViewModel
-import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
import com.afkanerd.deku.DefaultSMS.R
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import com.afkanerd.deku.DefaultSMS.Models.Contacts
import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.Badge
-import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.TextField
-import androidx.compose.material3.TextFieldColors
-import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.room.util.TableInfo
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
-import com.afkanerd.deku.DefaultSMS.ConversationsScreen
import com.afkanerd.deku.DefaultSMS.Extensions.toHslColor
import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversationsHandler
import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
-import com.example.compose.backgroundDark
@Preview
@Composable
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ContactDetails.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ContactDetails.kt
index a6fdbd7d2..4d38fdc43 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ContactDetails.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ContactDetails.kt
@@ -73,8 +73,8 @@ import com.afkanerd.deku.DefaultSMS.Extensions.toHslColor
import com.afkanerd.deku.DefaultSMS.Models.Contacts
import com.afkanerd.deku.DefaultSMS.Models.E2EEHandler
import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.DefaultSMS.SearchThreadScreen
import com.afkanerd.deku.DefaultSMS.ui.Components.ConvenientMethods
+import com.afkanerd.deku.SearchThreadScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ConversationsMain.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ConversationsMain.kt
index 48aebf4b3..27b893aeb 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ConversationsMain.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ConversationsMain.kt
@@ -1,17 +1,11 @@
package com.afkanerd.deku.DefaultSMS.ui
import android.app.Activity
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.ComponentName
import android.content.Context
-import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent
import android.net.Uri
import android.provider.BlockedNumberContract
import android.provider.Telephony
-import android.util.Log
-import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
@@ -19,7 +13,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -34,14 +27,9 @@ import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material.icons.filled.ContentCopy
-import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.EnhancedEncryption
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.material.icons.filled.Share
-import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -75,7 +63,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -84,24 +71,20 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ContactsViewModel
+import com.afkanerd.deku.ContactDetailsScreen
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.SearchViewModel
-import com.afkanerd.deku.DefaultSMS.BuildConfig
import com.afkanerd.deku.DefaultSMS.Commons.Helpers
-import com.afkanerd.deku.DefaultSMS.ContactDetailsScreen
-import com.afkanerd.deku.DefaultSMS.HomeScreen
-import com.afkanerd.deku.DefaultSMS.MainActivity
import com.afkanerd.deku.DefaultSMS.Models.Contacts
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
import com.afkanerd.deku.DefaultSMS.Models.E2EEHandler
+import com.afkanerd.deku.DefaultSMS.Models.Notifications
import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
import com.afkanerd.deku.DefaultSMS.Models.SMSHandler.sendTextMessage
import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.DefaultSMS.SearchThreadScreen
import com.afkanerd.deku.DefaultSMS.ui.Components.ChatCompose
import com.afkanerd.deku.DefaultSMS.ui.Components.ConvenientMethods
+import com.afkanerd.deku.DefaultSMS.ui.Components.ConversationCrudBottomBar
import com.afkanerd.deku.DefaultSMS.ui.Components.ConversationPositionTypes
import com.afkanerd.deku.DefaultSMS.ui.Components.ConversationStatusTypes
import com.afkanerd.deku.DefaultSMS.ui.Components.ConversationsCard
@@ -112,27 +95,17 @@ import com.afkanerd.deku.DefaultSMS.ui.Components.SearchTopAppBarText
import com.afkanerd.deku.DefaultSMS.ui.Components.SecureRequestAcceptModal
import com.afkanerd.deku.DefaultSMS.ui.Components.ShortCodeAlert
import com.afkanerd.deku.DefaultSMS.ui.Components.SimChooser
+import com.afkanerd.deku.SearchThreadScreen
import com.example.compose.AppTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
-private fun copyItem(context: Context, text: String) {
- val clip = ClipData.newPlainText(text, text)
- val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
- clipboard.setPrimaryClip(clip)
- Toast.makeText(
- context, context.getString(R.string.conversation_copied),
- Toast.LENGTH_SHORT
- ).show()
-}
private fun sendSMS(
context: Context,
@@ -165,6 +138,24 @@ private fun sendSMS(
)
}
+enum class PredefinedTypes {
+ OUTGOING,
+ INCOMING
+}
+
+private fun getPredefinedType(type: Int) : PredefinedTypes? {
+ when(type) {
+ Telephony.Sms.MESSAGE_TYPE_OUTBOX,
+ Telephony.Sms.MESSAGE_TYPE_QUEUED,
+ Telephony.Sms.MESSAGE_TYPE_SENT -> {
+ return PredefinedTypes.OUTGOING
+ }
+ Telephony.Sms.MESSAGE_TYPE_INBOX, -> {
+ return PredefinedTypes.INCOMING
+ }
+ }
+ return null
+}
private fun getContentType(
index: Int,
@@ -175,7 +166,7 @@ private fun getContentType(
return ConversationPositionTypes.NORMAL_TIMESTAMP
}
if(index == 0) {
- if(conversation.type == conversations[1].type) {
+ if(getPredefinedType(conversation.type) == getPredefinedType(conversations[1].type)) {
if(Helpers.isSameMinute(conversation.date!!.toLong(), conversations[1].date!!.toLong())) {
return ConversationPositionTypes.END
}
@@ -185,14 +176,21 @@ private fun getContentType(
}
}
if(index == conversations.size - 1) {
- if(conversation.type == conversations.last().type && Helpers.isSameMinute(conversation.date!!.toLong(), conversations[index -1].date!!.toLong())) {
+ if(getPredefinedType(conversation.type) == getPredefinedType(conversations.last().type) &&
+ Helpers.isSameMinute(
+ conversation.date!!.toLong(),
+ conversations[index -1].date!!.toLong())
+ ) {
return ConversationPositionTypes.START_TIMESTAMP
}
return ConversationPositionTypes.NORMAL_TIMESTAMP
}
if(index + 1 < conversations.size && index - 1 > -1 ) {
- if(conversation.type == conversations[index - 1].type && conversation.type == conversations[index + 1].type) {
+ if(getPredefinedType(conversation.type) == getPredefinedType(conversations[index - 1].type)
+ &&
+ getPredefinedType(conversation.type) == getPredefinedType(conversations[index + 1].type)
+ ) {
if(Helpers.isSameHour(conversation.date!!.toLong(), conversations[index -1].date!!.toLong())) {
if(Helpers.isSameMinute(conversation.date!!.toLong(),
conversations[index -1].date!!.toLong()) &&
@@ -205,18 +203,28 @@ private fun getContentType(
}
}
- if(conversation.type == conversations[index + 1].type ) {
- if(Helpers.isSameHour(conversation.date!!.toLong(), conversations[index +1].date!!.toLong())) {
- if(Helpers.isSameMinute(conversation.date!!.toLong(), conversations[index +1].date!!.toLong())) {
+ if(getPredefinedType(conversation.type) == getPredefinedType(conversations[index + 1].type))
+ {
+ if(Helpers.isSameHour(
+ conversation.date!!.toLong(), conversations[index +1].date!!.toLong())
+ ) {
+ if(Helpers.isSameMinute(
+ conversation.date!!.toLong(), conversations[index +1].date!!.toLong())
+ ) {
return ConversationPositionTypes.END
}
}
return ConversationPositionTypes.NORMAL_TIMESTAMP
}
- if(conversation.type == conversations[index - 1].type ) {
- if(Helpers.isSameMinute(conversation.date!!.toLong(), conversations[index -1].date!!.toLong())) {
- if(Helpers.isSameHour(conversation.date!!.toLong(), conversations[index +1].date!!.toLong())) {
+ if(getPredefinedType(conversation.type) == getPredefinedType(conversations[index - 1].type))
+ {
+ if(Helpers.isSameMinute(
+ conversation.date!!.toLong(), conversations[index -1].date!!.toLong())
+ ) {
+ if(Helpers.isSameHour(
+ conversation.date!!.toLong(), conversations[index +1].date!!.toLong())
+ ) {
return ConversationPositionTypes.START_TIMESTAMP
}
return ConversationPositionTypes.START
@@ -227,101 +235,6 @@ private fun getContentType(
return ConversationPositionTypes.NORMAL
}
-@Preview
-@Composable
-private fun ConversationCrudBottomBar(
- viewModel: ConversationsViewModel = ConversationsViewModel(),
- items: List = emptyList(),
- onCompleted: (() -> Unit)? = null,
- onCancel: (() -> Unit)? = null,
-) {
- val context = LocalContext.current
- BottomAppBar (
- actions = {
- Row {
- IconButton(onClick = {
- CoroutineScope(Dispatchers.Default).launch {
- onCancel?.let { it() }
- }
- }) {
- Icon(Icons.Default.Close, stringResource(R.string.cancel_selected_messages))
- }
-
- Text(
- viewModel.selectedItems.size.toString(),
- fontSize = 24.sp,
- modifier = Modifier.align(Alignment.CenterVertically)
- )
-
- Spacer(Modifier.weight(1f))
-
- if(viewModel.selectedItems.size < 2) {
- IconButton(onClick = {
- val conversation = items.firstOrNull {
- it.message_id in viewModel.selectedItems
- }
- copyItem(context, conversation?.text!!)
- onCompleted?.invoke()
- }) {
- Icon(Icons.Filled.ContentCopy, stringResource(R.string.copy_message))
- }
-
- IconButton(onClick = {
- TODO("Implement forward message")
- }) {
- Icon(painter= painterResource(id= R.drawable.rounded_forward_24),
- stringResource(R.string.forward_message)
- )
- }
-
- IconButton(onClick = {
- val conversation = items.firstOrNull {
- it.message_id in viewModel.selectedItems
- }
- shareItem(context, conversation?.text!!)
- onCompleted?.let { it() }
- }) {
- Icon(Icons.Filled.Share, stringResource(R.string.share_message))
- }
- }
-
- IconButton(onClick = {
- CoroutineScope(Dispatchers.Default).launch {
- val conversations = items.filter {
- it.message_id in viewModel.selectedItems
- }
- viewModel.delete(context, conversations)
- onCompleted?.let { it() }
- }
- }) {
- Icon(Icons.Filled.Delete, stringResource(R.string.delete_message))
- }
- }
-
- }
- )
-}
-
-
-private fun shareItem(context: Context, text: String) {
- val sendIntent = Intent().apply {
- setAction(Intent.ACTION_SEND)
- putExtra(Intent.EXTRA_TEXT, text)
- setType("text/plain")
- }
-
- val shareIntent = Intent.createChooser(sendIntent, null)
- // Only use for components you have control over
- val excludedComponentNames = arrayOf(
- ComponentName(
- BuildConfig.APPLICATION_ID,
- MainActivity::class.java.name
- )
- )
- shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, excludedComponentNames)
- context.startActivity(shareIntent)
-}
-
private fun call(context: Context, address: String) {
val callIntent = Intent(Intent.ACTION_DIAL).apply {
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -561,6 +474,7 @@ fun Conversations(
if(searchIndexes.isNotEmpty() && searchIndex == 0)
listState.animateScrollToItem(searchIndexes.first())
+ Notifications.cancel(context, viewModel.threadId.toInt())
}
LaunchedEffect(viewModel.address){
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/DefaultCheckMain.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/DefaultCheckMain.kt
new file mode 100644
index 000000000..6858b76f5
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/DefaultCheckMain.kt
@@ -0,0 +1,113 @@
+package com.afkanerd.deku.DefaultSMS.ui
+
+import android.app.Activity.RESULT_OK
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.Context.ROLE_SERVICE
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Telephony
+import android.util.Log
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import com.afkanerd.deku.DefaultSMS.R
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.preference.PreferenceManager
+import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+
+@Preview(showBackground = true)
+@Composable
+fun DefaultCheckMain(permissionGrantedCallback: (()->Unit)? = null) {
+ val context = LocalContext.current
+ val getDefaultPermission =
+ rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context)
+ sharedPreferences.edit()
+ .putBoolean(context.getString(R.string.configs_load_natives), true)
+ .apply()
+ permissionGrantedCallback?.invoke()
+ }
+ }
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .padding(16.dp)
+ .fillMaxSize()
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize().weight(1f)
+ ) {
+ Image(
+ painter= painterResource(R.drawable.undraw_team_work_i1f3),
+ contentDescription = stringResource(R.string.welcome_image),
+ contentScale = ContentScale.Fit,
+ modifier = Modifier
+ .size(250.dp)
+ )
+ Spacer(Modifier.size(32.dp))
+
+ Button(onClick = {
+ getDefaultPermission.launch(makeDefault(context))
+ }) {
+ Text(stringResource(R.string.default_check_btn_text))
+ }
+
+ }
+ TextButton(onClick = {
+ val url = context.getString(R.string.privacy_policy_url)
+ val shareIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ context.startActivity(shareIntent)
+ }) {
+ Text(
+ stringResource(R.string.privacy_policy_url),
+ fontSize = 12.sp
+ )
+ }
+ }
+}
+
+fun makeDefault(context: Context): Intent {
+ // TODO: replace this with checking other permissions - since this gives null in level 35
+ return if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ val roleManager = context.getSystemService(ROLE_SERVICE) as RoleManager
+ roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS).apply {
+ putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName)
+ }
+ } else {
+ Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT).apply {
+ putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName)
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ThreadsConversationMain.kt b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ThreadsConversationMain.kt
index 643909a40..a6c307ad0 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ThreadsConversationMain.kt
+++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ui/ThreadsConversationMain.kt
@@ -1,54 +1,38 @@
package com.afkanerd.deku.DefaultSMS.ui
-import android.app.Activity.RESULT_OK
+import android.Manifest
import android.content.Context
import android.content.Intent
import android.provider.BlockedNumberContract
-import android.provider.ContactsContract
-import android.provider.Telephony
-import android.text.InputType
-import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.animation.expandIn
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material.DismissDirection
import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.FractionalThreshold
-import androidx.compose.material.ListItem
-import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.filled.VolumeOff
-import androidx.compose.material.icons.automirrored.outlined.InsertComment
-import androidx.compose.material.icons.automirrored.twotone.InsertComment
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Block
-import androidx.compose.material.icons.filled.ChatBubbleOutline
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Drafts
-import androidx.compose.material.icons.filled.EnhancedEncryption
import androidx.compose.material.icons.filled.Inbox
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.MoreVert
@@ -56,12 +40,6 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Security
import androidx.compose.material.icons.filled.Unarchive
import androidx.compose.material.icons.rounded.Delete
-import androidx.compose.material.icons.twotone.Edit
-import androidx.compose.material.rememberDismissState
-import androidx.compose.material3.Badge
-import androidx.compose.material3.BadgedBox
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu
@@ -76,7 +54,6 @@ import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SearchBar
import androidx.compose.material3.Surface
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxState
@@ -89,67 +66,56 @@ import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.core.app.RemoteInput
-import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
-import androidx.preference.PreferenceManager
-import androidx.room.util.TableInfo
-import com.afkanerd.deku.Datastore
import com.afkanerd.deku.DefaultSMS.AboutActivity
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
import com.afkanerd.deku.DefaultSMS.BuildConfig
import com.afkanerd.deku.DefaultSMS.Commons.Helpers
-import com.afkanerd.deku.DefaultSMS.ComposeNewMessageScreen
-import com.afkanerd.deku.DefaultSMS.ConversationsScreen
+import com.afkanerd.deku.ComposeNewMessageScreen
+import com.afkanerd.deku.ConversationsScreen
import com.afkanerd.deku.DefaultSMS.Extensions.isScrollingUp
-import com.afkanerd.deku.DefaultSMS.HomeScreen
-import com.afkanerd.deku.DefaultSMS.Models.Archive
import com.afkanerd.deku.DefaultSMS.Models.Contacts
import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
-import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations
import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversationsHandler
-import com.afkanerd.deku.DefaultSMS.Models.ExportImportHandlers
-import com.afkanerd.deku.DefaultSMS.Models.Notifications
import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
import com.afkanerd.deku.DefaultSMS.Models.ThreadsCount
import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.DefaultSMS.SearchThreadScreen
+import com.afkanerd.deku.SearchThreadScreen
import com.afkanerd.deku.DefaultSMS.SettingsActivity
-import com.afkanerd.deku.DefaultSMS.ui.Components.ConversationStatusTypes
import com.afkanerd.deku.DefaultSMS.ui.Components.DeleteConfirmationAlert
import com.afkanerd.deku.DefaultSMS.ui.Components.ImportDetails
import com.afkanerd.deku.DefaultSMS.ui.Components.ThreadConversationCard
+import com.afkanerd.deku.RemoteListenersScreen
import com.afkanerd.deku.Router.GatewayServers.GatewayServerRoutedActivity
import com.example.compose.AppTheme
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
-import kotlin.math.exp
+import java.io.BufferedReader
+import java.io.FileOutputStream
+import java.io.InputStreamReader
enum class InboxType(val value: Int) {
INBOX(0),
@@ -410,14 +376,66 @@ fun ModalDrawerSheetLayout(
}
}
-@Preview(showBackground = true)
+@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun MainDropDownMenu(
expanded: Boolean = false,
- importCallback: (() -> Unit)? = null,
+ conversationViewModel: ConversationsViewModel = ConversationsViewModel(),
+ navController: NavController,
dismissCallback: ((Boolean) -> Unit)? = null,
) {
val context = LocalContext.current
+ val defaultPermission = rememberPermissionState(Manifest.permission.READ_SMS)
+
+ val exportLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.CreateDocument("application/json")) { uri ->
+ println(uri)
+ uri?.let {
+ CoroutineScope(Dispatchers.IO).launch {
+ with(context.contentResolver.openFileDescriptor(uri, "w")) {
+ this?.fileDescriptor.let { fd ->
+ val fileOutputStream = FileOutputStream(fd);
+ fileOutputStream.write(conversationViewModel
+ .getAllExport(context).encodeToByteArray());
+ // Let the document provider know you're done by closing the stream.
+ fileOutputStream.close();
+ }
+ this?.close();
+
+ CoroutineScope(Dispatchers.Main).launch {
+ Toast.makeText(context,
+ context.getString(R.string.conversations_exported_complete),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+ }
+ }
+
+ val importLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.GetContent()) { uri ->
+ println(uri)
+ uri?.let {
+ CoroutineScope(Dispatchers.IO).launch {
+ val stringBuilder = StringBuilder()
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ BufferedReader(InputStreamReader(inputStream)).use { reader ->
+ var line: String? = reader.readLine()
+ while (line != null) {
+ stringBuilder.append(line)
+ line = reader.readLine()
+ }
+ }
+ }
+ conversationViewModel.importDetails = stringBuilder.toString()
+ CoroutineScope(Dispatchers.Main).launch {
+ Toast.makeText(context,
+ context.getString(R.string.conversations_import_complete),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+ }
Box(modifier = Modifier
.fillMaxWidth()
@@ -427,34 +445,66 @@ private fun MainDropDownMenu(
expanded = expanded,
onDismissRequest = { dismissCallback?.let{ it(false) } },
) {
+
+
DropdownMenuItem(
text = {
Text(
- text=stringResource(R.string.homepage_menu_routed),
+ text=stringResource(R.string.settings_title),
color = MaterialTheme.colorScheme.onBackground
)
},
onClick = {
dismissCallback?.let { it(false) }
context.startActivity(
- Intent(context, GatewayServerRoutedActivity::class.java).apply {
+ Intent(context, SettingsActivity::class.java).apply {
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME)
}
)
}
)
+ if(defaultPermission.status.isGranted || LocalInspectionMode.current) {
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = stringResource(R.string.conversation_menu_export),
+ color = MaterialTheme.colorScheme.onBackground
+ )
+ },
+ onClick = {
+ dismissCallback?.let { it(false) }
+ val filename = "Deku_SMS_All_Backup" + System.currentTimeMillis() + ".json";
+ exportLauncher.launch(filename)
+ }
+ )
+
+ if(BuildConfig.DEBUG)
+ DropdownMenuItem(
+ text = {
+ Text(
+ text= stringResource(R.string.conversation_menu_import),
+ color = MaterialTheme.colorScheme.onBackground
+ )
+ },
+ onClick = {
+ dismissCallback?.let { it(false) }
+ importLauncher.launch("application/json")
+ }
+ )
+ }
+
DropdownMenuItem(
text = {
Text(
- text=stringResource(R.string.settings_title),
+ text=stringResource(R.string.homepage_menu_routed),
color = MaterialTheme.colorScheme.onBackground
)
},
onClick = {
dismissCallback?.let { it(false) }
context.startActivity(
- Intent(context, SettingsActivity::class.java).apply {
+ Intent(context, GatewayServerRoutedActivity::class.java).apply {
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME)
}
)
@@ -464,31 +514,16 @@ private fun MainDropDownMenu(
DropdownMenuItem(
text = {
Text(
- text=stringResource(R.string.conversation_menu_export),
+ text= stringResource(R.string.remote_listeners),
color = MaterialTheme.colorScheme.onBackground
)
},
onClick = {
dismissCallback?.let { it(false) }
- ExportImportHandlers.exportInbox(context)
+ navController.navigate(RemoteListenersScreen)
}
)
- if(BuildConfig.DEBUG)
- DropdownMenuItem(
- text = {
- Text(
- text= stringResource(R.string.conversation_menu_import),
- color = MaterialTheme.colorScheme.onBackground
- )
- },
- onClick = {
- dismissCallback?.let { it(false) }
-// ExportImportHandlers.importInbox(context)
- importCallback?.invoke()
- }
- )
-
DropdownMenuItem(
text = {
Text(
@@ -509,8 +544,15 @@ private fun MainDropDownMenu(
}
}
+
+private fun loadNatives(context: Context, conversationViewModel: ConversationsViewModel) {
+ CoroutineScope(Dispatchers.Default).launch {
+ conversationViewModel.reset(context)
+ }
+}
+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
- ExperimentalFoundationApi::class
+ ExperimentalFoundationApi::class, ExperimentalPermissionsApi::class
)
@Composable
fun ThreadConversationLayout(
@@ -523,8 +565,9 @@ fun ThreadConversationLayout(
val inPreviewMode = LocalInspectionMode.current
val context = LocalContext.current
+ val defaultPermission = rememberPermissionState(Manifest.permission.READ_SMS)
+
intent?.let {
-// conversationsViewModel.text = ""
val defaultRegion = if(inPreviewMode) "cm" else Helpers.getUserCountry(context)
processIntents(context, intent, defaultRegion)?.let {
intent.apply {
@@ -573,7 +616,7 @@ fun ThreadConversationLayout(
val listState = rememberLazyListState()
val scrollBehaviour = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
- val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
+ var drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
var selectedItems = remember { mutableStateListOf() }
var slideDeleteItem = remember { mutableStateOf("") }
@@ -599,7 +642,7 @@ fun ThreadConversationLayout(
}
LaunchedEffect(inboxType) {
- if(inboxType == InboxType.BLOCKED) {
+ if(inboxType == InboxType.BLOCKED && defaultPermission.status.isGranted) {
coroutineScope.launch {
items.forEach {
if(BlockedNumberContract.isBlocked(context, it.address)) blockedItems.add(it)
@@ -630,9 +673,7 @@ fun ThreadConversationLayout(
MainDropDownMenu(
expanded=rememberMenuExpanded,
- importCallback = {
- ExportImportHandlers.importInbox(context)
- }
+ navController=navController
) {
rememberMenuExpanded = it
}
@@ -684,13 +725,15 @@ fun ThreadConversationLayout(
}
},
actions = {
- IconButton(onClick = {
- navController.navigate(SearchThreadScreen)
- }) {
- Icon(
- imageVector = Icons.Filled.Search,
- contentDescription = stringResource(R.string.search_messages)
- )
+ if(defaultPermission.status.isGranted || inPreviewMode) {
+ IconButton(onClick = {
+ navController.navigate(SearchThreadScreen)
+ }) {
+ Icon(
+ imageVector = Icons.Filled.Search,
+ contentDescription = stringResource(R.string.search_messages)
+ )
+ }
}
IconButton(onClick = {
rememberMenuExpanded = !rememberMenuExpanded
@@ -819,223 +862,232 @@ fun ThreadConversationLayout(
}
},
floatingActionButton = {
- ExtendedFloatingActionButton(
- onClick = {
- navController.navigate(ComposeNewMessageScreen)
- },
- icon = { Icon( Icons.AutoMirrored.Default.Message,
- stringResource(R.string.compose_new_message)) },
- text = { Text(text = stringResource(R.string.compose)) },
- expanded = listState.isScrollingUp()
- )
+ if(defaultPermission.status.isGranted || inPreviewMode) {
+ ExtendedFloatingActionButton(
+ onClick = {
+ navController.navigate(ComposeNewMessageScreen)
+ },
+ icon = { Icon( Icons.AutoMirrored.Default.Message,
+ stringResource(R.string.compose_new_message)) },
+ text = { Text(text = stringResource(R.string.compose)) },
+ expanded = listState.isScrollingUp()
+ )
+ }
}
) { innerPadding ->
Box(
modifier = Modifier.padding(innerPadding)
) {
-
- when(inboxType) {
- InboxType.INBOX -> {
- if(items.isEmpty())
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text(
- stringResource(R.string.homepage_no_message),
- fontSize = 24.sp
- )
- }
- }
- InboxType.ARCHIVED -> {
- if(archivedItems.isEmpty())
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text(
- stringResource(R.string.homepage_archive_no_message),
- fontSize = 24.sp
- )
- }
+ if(!defaultPermission.status.isGranted) {
+ DefaultCheckMain {
+ loadNatives(context, conversationsViewModel)
}
- InboxType.ENCRYPTED -> {}
- InboxType.BLOCKED -> {}
- InboxType.DRAFTS -> {}
- InboxType.MUTED -> {}
}
+ else {
+ when(inboxType) {
+ InboxType.INBOX -> {
+ if(items.isEmpty())
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ stringResource(R.string.homepage_no_message),
+ fontSize = 24.sp
+ )
+ }
+ }
+ InboxType.ARCHIVED -> {
+ if(archivedItems.isEmpty())
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ stringResource(R.string.homepage_archive_no_message),
+ fontSize = 24.sp
+ )
+ }
+ }
+ InboxType.ENCRYPTED -> {}
+ InboxType.BLOCKED -> {}
+ InboxType.DRAFTS -> {}
+ InboxType.MUTED -> {}
+ }
- LazyColumn(
- modifier = Modifier.fillMaxSize(),
- state = listState
- ) {
- itemsIndexed(
- items = if(inPreviewMode) _items else when(inboxType) {
- InboxType.INBOX -> items
- InboxType.ARCHIVED -> archivedItems
- InboxType.ENCRYPTED -> encryptedItems
- InboxType.BLOCKED -> blockedItems
- InboxType.DRAFTS -> draftsItems
- InboxType.MUTED -> mutedItems
- },
- key = { index, message -> message.thread_id!! }
- ) { index, message ->
- message.address?.let { address ->
- val isBlocked by remember { mutableStateOf(BlockedNumberContract
- .isBlocked(context, message.address)) }
- val contactName: String? by remember { mutableStateOf(Contacts
- .retrieveContactName(context, message.address))
- }
- var firstName = message.address
- var lastName = ""
- if (!contactName.isNullOrEmpty()) {
- contactName!!.split(" ").let {
- firstName = it[0]
- if (it.size > 1)
- lastName = it[1]
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ state = listState
+ ) {
+ itemsIndexed(
+ items = if(inPreviewMode) _items else when(inboxType) {
+ InboxType.INBOX -> items
+ InboxType.ARCHIVED -> archivedItems
+ InboxType.ENCRYPTED -> encryptedItems
+ InboxType.BLOCKED -> blockedItems
+ InboxType.DRAFTS -> draftsItems
+ InboxType.MUTED -> mutedItems
+ },
+ key = { index, message -> message.thread_id!! }
+ ) { index, message ->
+ message.address?.let { address ->
+ val isBlocked by remember { mutableStateOf(BlockedNumberContract
+ .isBlocked(context, message.address)) }
+ val contactName: String? by remember { mutableStateOf(Contacts
+ .retrieveContactName(context, message.address))
+ }
+ var firstName = message.address
+ var lastName = ""
+ val isSelected = selectedItems.contains(message)
+ if (!contactName.isNullOrEmpty()) {
+ contactName!!.split(" ").let {
+ firstName = it[0]
+ if (it.size > 1)
+ lastName = it[1]
+ }
}
- }
- var isMute by remember { mutableStateOf( false) }
- LaunchedEffect(message.thread_id) {
- coroutineScope.launch {
- isMute = conversationsViewModel.isMuted(context,
- message.thread_id)
+ var isMute by remember { mutableStateOf( false) }
+ LaunchedEffect(message.thread_id) {
+ coroutineScope.launch {
+ isMute = conversationsViewModel.isMuted(context,
+ message.thread_id)
+ }
}
- }
- val dismissState = rememberSwipeToDismissBoxState(
- confirmValueChange = {
- when(it) {
- SwipeToDismissBoxValue.StartToEnd -> {
- slideDeleteItem.value = message.thread_id!!
- rememberDeleteMenu = true
- return@rememberSwipeToDismissBoxState false
- }
- SwipeToDismissBoxValue.EndToStart -> {
- coroutineScope.launch {
- when(inboxType) {
- InboxType.ARCHIVED ->
- conversationsViewModel.unArchive(context,
+ val dismissState = rememberSwipeToDismissBoxState(
+ confirmValueChange = {
+ when(it) {
+ SwipeToDismissBoxValue.StartToEnd -> {
+ slideDeleteItem.value = message.thread_id!!
+ rememberDeleteMenu = true
+ return@rememberSwipeToDismissBoxState false
+ }
+ SwipeToDismissBoxValue.EndToStart -> {
+ coroutineScope.launch {
+ when(inboxType) {
+ InboxType.ARCHIVED ->
+ conversationsViewModel.unArchive(context,
+ message.thread_id)
+ else -> conversationsViewModel.archive(context,
message.thread_id)
- else -> conversationsViewModel.archive(context,
- message.thread_id)
+ }
}
+ return@rememberSwipeToDismissBoxState true
}
- return@rememberSwipeToDismissBoxState true
+ SwipeToDismissBoxValue.Settled ->
+ return@rememberSwipeToDismissBoxState false
+ else -> {}
}
- SwipeToDismissBoxValue.Settled ->
- return@rememberSwipeToDismissBoxState false
- else -> {}
- }
- return@rememberSwipeToDismissBoxState true
- },
- positionalThreshold = { it * .75f }
- )
+ return@rememberSwipeToDismissBoxState true
+ },
+ positionalThreshold = { it * .75f }
+ )
- SwipeToDismissBox(
- state = dismissState,
- backgroundContent = {
- SwipeToDeleteBackground(
- dismissState,
- inboxType == InboxType.ARCHIVED
+ SwipeToDismissBox(
+ state = dismissState,
+ backgroundContent = {
+ SwipeToDeleteBackground(
+ dismissState,
+ inboxType == InboxType.ARCHIVED
+ )
+ }
+ ) {
+ ThreadConversationCard(
+ id = message.thread_id!!,
+ firstName = firstName!!,
+ lastName = lastName,
+ phoneNumber = address,
+ content = if(message.text.isNullOrBlank())
+ stringResource(R.string.conversation_threads_secured_content)
+ else message.text!!,
+ date =
+ if(!message.date.isNullOrBlank())
+ Helpers.formatDate(context, message.date!!.toLong())
+ else "Tues",
+ isRead = message.isRead,
+ isContact = !contactName.isNullOrBlank(),
+ isBlocked = isBlocked,
+ modifier = Modifier.combinedClickable(
+ onClick = {
+ if(selectedItems.isEmpty()) {
+ navigateToConversation(
+ conversationsViewModel = conversationsViewModel,
+ address = message.address!!,
+ threadId = message.thread_id!!,
+ subscriptionId =
+ SIMHandler.getDefaultSimSubscription(context),
+ navController = navController,
+ )
+ } else {
+ if(selectedItems.contains(message))
+ selectedItems.remove(message)
+ else
+ selectedItems.add(message)
+ }
+ },
+ onLongClick = {
+ selectedItems.add(message)
+ }
+ ),
+ isSelected = isSelected,
+ isMuted = isMute,
+ type = message.type
)
}
- ) {
- ThreadConversationCard(
- id = message.thread_id!!,
- firstName = firstName!!,
- lastName = lastName,
- phoneNumber = address,
- content = if(message.text.isNullOrBlank())
- stringResource(R.string.conversation_threads_secured_content)
- else message.text!!,
- date =
- if(!message.date.isNullOrBlank())
- Helpers.formatDate(context, message.date!!.toLong())
- else "Tues",
- isRead = message.isRead,
- isContact = !contactName.isNullOrBlank(),
- isBlocked = isBlocked,
- modifier = Modifier.combinedClickable(
- onClick = {
- if(selectedItems.isEmpty()) {
- navigateToConversation(
- conversationsViewModel = conversationsViewModel,
- address = message.address!!,
- threadId = message.thread_id!!,
- subscriptionId =
- SIMHandler.getDefaultSimSubscription(context),
- navController = navController,
- )
- } else {
- if(selectedItems.contains(message))
- selectedItems.remove(message)
- else
- selectedItems.add(message)
- }
- },
- onLongClick = {
- selectedItems.add(message)
- }
- ),
- isSelected = selectedItems.contains(message),
- isMuted = isMute,
- type = message.type
- )
}
}
}
- }
- if(rememberDeleteMenu) {
- DeleteConfirmationAlert(
- confirmCallback = {
- coroutineScope.launch {
- val threads: List = selectedItems.map { it.thread_id!! }
- conversationsViewModel.deleteThreads(context,
- if(threads.isNotEmpty()) threads
- else listOf(slideDeleteItem.value)
- )
- selectedItems.clear()
- rememberDeleteMenu = false
+ if(rememberDeleteMenu) {
+ DeleteConfirmationAlert(
+ confirmCallback = {
+ coroutineScope.launch {
+ val threads: List = selectedItems.map { it.thread_id!! }
+ conversationsViewModel.deleteThreads(context,
+ if(threads.isNotEmpty()) threads
+ else listOf(slideDeleteItem.value)
+ )
+ selectedItems.clear()
+ rememberDeleteMenu = false
+ }
}
+ ) {
+ rememberDeleteMenu = false
+ selectedItems.clear()
}
- ) {
- rememberDeleteMenu = false
- selectedItems.clear()
}
- }
- if(rememberImportMenuExpanded) {
- val importConversations by remember { mutableStateOf(conversationsViewModel
- .importAll(context, detailsOnly = true)) }
- val numThreads by remember { mutableStateOf(
- importConversations.map { it.thread_id }.toSet()
- ) }
- ImportDetails(
- numOfConversations = importConversations.size,
- numOfThreads = numThreads.size,
- resetConfirmCallback = {
- coroutineScope.launch {
- conversationsViewModel.clear(context)
- conversationsViewModel.importAll(context)
- conversationsViewModel.importDetails = ""
- }
- },
- confirmCallback = {
- coroutineScope.launch {
- conversationsViewModel.importAll(context)
- conversationsViewModel.importDetails = ""
- }
- }) {
+ if(rememberImportMenuExpanded) {
+ val importConversations by remember { mutableStateOf(conversationsViewModel
+ .importAll(context, detailsOnly = true)) }
+ val numThreads by remember { mutableStateOf(
+ importConversations.map { it.thread_id }.toSet()
+ ) }
+ ImportDetails(
+ numOfConversations = importConversations.size,
+ numOfThreads = numThreads.size,
+ resetConfirmCallback = {
+ coroutineScope.launch {
+ conversationsViewModel.clear(context)
+ conversationsViewModel.importAll(context)
+ conversationsViewModel.importDetails = ""
+ }
+ },
+ confirmCallback = {
+ coroutineScope.launch {
+ conversationsViewModel.importAll(context)
+ conversationsViewModel.importDetails = ""
+ }
+ }) {
// rememberImportMenuExpanded = false
- conversationsViewModel.importDetails = ""
+ conversationsViewModel.importDetails = ""
+ }
}
- }
+ }
}
}
diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/MainActivity.kt b/app/src/main/java/com/afkanerd/deku/MainActivity.kt
similarity index 50%
rename from app/src/main/java/com/afkanerd/deku/DefaultSMS/MainActivity.kt
rename to app/src/main/java/com/afkanerd/deku/MainActivity.kt
index 02a413bdb..4c2d685ff 100644
--- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/MainActivity.kt
+++ b/app/src/main/java/com/afkanerd/deku/MainActivity.kt
@@ -1,40 +1,26 @@
-package com.afkanerd.deku.DefaultSMS
+package com.afkanerd.deku
import android.Manifest
-import android.annotation.SuppressLint
-import android.app.Activity
import android.app.ComponentCaller
import android.content.Intent
import android.content.pm.PackageManager
-import android.net.Uri
import android.os.Bundle
-import android.os.ParcelFileDescriptor
import android.provider.Telephony
-import android.text.Layout
-import android.util.Log
-import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.safeDrawingPadding
-import androidx.compose.foundation.layout.width
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
@@ -43,15 +29,10 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import androidx.preference.PreferenceManager
-import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ContactsViewModel
-import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
-import androidx.window.layout.WindowMetricsCalculator
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel
import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.SearchViewModel
-import com.afkanerd.deku.DefaultSMS.Models.ExportImportHandlers
import com.afkanerd.deku.DefaultSMS.ui.ComposeNewMessage
import com.afkanerd.deku.DefaultSMS.ui.ContactDetails
import com.afkanerd.deku.DefaultSMS.ui.Conversations
@@ -60,45 +41,37 @@ import com.afkanerd.deku.DefaultSMS.ui.ThreadConversationLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.serialization.Serializable
-import java.io.BufferedReader
-import java.io.FileOutputStream
-import java.io.InputStreamReader
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
-import androidx.compose.ui.input.key.type
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenerQueuesViewModel
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersViewModel
+import com.afkanerd.deku.RemoteListeners.ui.RMQAddComposable
+import com.afkanerd.deku.RemoteListeners.ui.RMQMainComposable
+import com.afkanerd.deku.RemoteListeners.ui.RMQQueuesComposable
-@Serializable
-object HomeScreen
-@Serializable
-object ConversationsScreen
-@Serializable
-object ComposeNewMessageScreen
-@Serializable
-object SearchThreadScreen
-@Serializable
-object ContactDetailsScreen
class MainActivity : AppCompatActivity(){
- lateinit var navController: NavHostController
+ private lateinit var navController: NavHostController
- val conversationViewModel: ConversationsViewModel by viewModels()
- val searchViewModel: SearchViewModel by viewModels()
+ private val conversationViewModel: ConversationsViewModel by viewModels()
+ private val searchViewModel: SearchViewModel by viewModels()
+// private val remoteListenersViewModel: RemoteListenersViewModel by viewModels()
+ private lateinit var remoteListenersViewModel: RemoteListenersViewModel
+ private val remoteListenersProjectsViewModel:
+ RemoteListenerQueuesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- checkLoadNatives()
+ remoteListenersViewModel = RemoteListenersViewModel(applicationContext)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -117,79 +90,7 @@ class MainActivity : AppCompatActivity(){
navController.navigate(HomeScreen)
}
- private fun checkLoadNatives() {
- val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
- if(sharedPreferences.getBoolean(getString(R.string.configs_load_natives), false)){
- CoroutineScope(Dispatchers.Default).launch {
- conversationViewModel.reset(applicationContext)
- }
- sharedPreferences.edit()
- .putBoolean(getString(R.string.configs_load_natives), false)
- .apply()
- }
-
- }
-
- override fun onResume() {
- super.onResume()
- checkIsDefault()
- }
-
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
- super.onActivityResult(requestCode, resultCode, resultData)
- if(resultCode == RESULT_OK) {
- resultData?.let {
- val uri: Uri? = resultData.data
- // Perform operations on the document using its URI.
-
- uri?.let {
- CoroutineScope(Dispatchers.Default).launch {
- if (requestCode == ExportImportHandlers.exportRequestCode) {
- with(contentResolver.openFileDescriptor(uri, "w")) {
- this?.fileDescriptor.let { fd ->
- val fileOutputStream = FileOutputStream(fd);
- fileOutputStream.write(conversationViewModel
- .getAllExport(applicationContext).encodeToByteArray());
- // Let the document provider know you're done by closing the stream.
- fileOutputStream.close();
- }
- this?.close();
-
- runOnUiThread {
- Toast.makeText(applicationContext,
- getString(R.string.conversations_exported_complete),
- Toast.LENGTH_LONG).show();
- }
- }
- }
- else if(requestCode == ExportImportHandlers.importRequestCode) {
- val stringBuilder = StringBuilder()
- contentResolver.openInputStream(uri)?.use { inputStream ->
- BufferedReader(InputStreamReader(inputStream)).use { reader ->
- var line: String? = reader.readLine()
- while (line != null) {
- stringBuilder.append(line)
- line = reader.readLine()
- }
- }
- }
- conversationViewModel.importDetails = stringBuilder.toString()
-// conversationViewModel.importAll(applicationContext,
-// stringBuilder.toString())
-// runOnUiThread {
-// Toast.makeText(applicationContext,
-// getString(R.string.conversations_exported_complete),
-// Toast.LENGTH_LONG).show();
-// }
- }
- }
- }
- }
- }
- }
-
- fun onLayoutInfoChanged(newLayoutInfo: WindowLayoutInfo) {
+ private fun onLayoutInfoChanged(newLayoutInfo: WindowLayoutInfo) {
conversationViewModel.newLayoutInfo = newLayoutInfo
setContent {
AppTheme {
@@ -221,28 +122,30 @@ class MainActivity : AppCompatActivity(){
composable{
ContactDetailsScreenComposable()
}
+ composable{
+ RMQMainComposable(
+ remoteListenerViewModel = remoteListenersViewModel,
+ remoteListenerProjectsViewModel =
+ remoteListenersProjectsViewModel,
+ navController = navController
+ )
+ }
+ composable{
+ RMQAddComposable(
+ navController = navController,
+ remoteListenerViewModel = remoteListenersViewModel
+ )
+ }
+ composable{
+ RMQQueuesComposable(
+ remoteListenersViewModel = remoteListenersViewModel,
+ navController = navController
+ )
+ }
}
else {
composable{
- Row {
- Column(modifier = Modifier.fillMaxWidth(0.5f)){
- HomeScreenComposable()
- }
-
-
- if(conversationViewModel.address.isNotEmpty() &&
- conversationViewModel.threadId.isNotEmpty()
- )
- Column { ConversationScreenComposable() }
- else
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- NoMessageSelected()
- }
- }
+ Folded()
}
}
}
@@ -251,12 +154,35 @@ class MainActivity : AppCompatActivity(){
}
}
+ @Composable
+ fun Folded() {
+ Row {
+ Column(modifier = Modifier.fillMaxWidth(0.5f)){
+ HomeScreenComposable()
+ }
+
+ if(conversationViewModel.address.isNotEmpty() &&
+ conversationViewModel.threadId.isNotEmpty()
+ )
+ Column { ConversationScreenComposable() }
+ else
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ NoMessageSelected()
+ }
+ }
+ }
+
@Preview(showBackground = true)
@Composable
fun NoMessageSelected() {
Text(
- stringResource(R.string
+ stringResource(
+ R.string
.select_a_conversation_from_the_list_on_the_left),
fontSize = 12.sp,
textAlign = TextAlign.Center
@@ -316,17 +242,13 @@ class MainActivity : AppCompatActivity(){
}
}
- fun checkIsDefault() {
+ fun checkIsDefault(): Boolean {
val defaultName = Telephony.Sms.getDefaultSmsPackage(applicationContext)
if(defaultName.isNullOrBlank() || packageName != defaultName) {
- when {
- ContextCompat.checkSelfPermission(applicationContext,
- Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED -> {
- startActivity(Intent(this, DefaultCheckActivity::class.java))
- finish()
- }
- }
+ return ContextCompat.checkSelfPermission(applicationContext,
+ Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED
}
+ return false
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java
deleted file mode 100644
index fccf7cafe..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java
+++ /dev/null
@@ -1,180 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients;
-
-
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.RadioGroup;
-
-import com.afkanerd.deku.Datastore;
-import com.afkanerd.deku.DefaultSMS.R;
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor;
-import com.google.android.material.button.MaterialButton;
-import com.google.android.material.textfield.TextInputEditText;
-
-public class GatewayClientAddActivity extends AppCompatActivity {
- Toolbar toolbar;
-
- Datastore datastore;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_gateway_client_add);
-
- toolbar = findViewById(R.id.gateway_client_add_toolbar);
- setSupportActionBar(toolbar);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- getSupportActionBar().setTitle(getString(R.string.add_new_gateway_server_toolbar_title));
-
- datastore = Datastore.getDatastore(getApplicationContext());
-
- if(getIntent().hasExtra(GatewayClientListingActivity.Companion.getGATEWAY_CLIENT_ID())) {
- try {
- editGatewayClient();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- MaterialButton materialButton = findViewById(R.id.gateway_client_customization_save_btn);
- materialButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- onSaveGatewayClient(v);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- }
-
- long id = -1;
- public void editGatewayClient() throws InterruptedException {
- id = getIntent().getLongExtra(GatewayClientListingActivity.Companion.getGATEWAY_CLIENT_ID(), -1);
-
- if(id != -1 ) {
- TextInputEditText url = findViewById(R.id.new_gateway_client_url_input);
- TextInputEditText username = findViewById(R.id.new_gateway_client_username);
- TextInputEditText password = findViewById(R.id.new_gateway_password);
- TextInputEditText friendlyName = findViewById(R.id.new_gateway_client_friendly_name);
- TextInputEditText virtualHost = findViewById(R.id.new_gateway_client_virtualhost);
- TextInputEditText port = findViewById(R.id.new_gateway_client_port);
-
- GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext());
- GatewayClient gatewayClient = gatewayClientHandler.fetch(id);
-
- url.setText(gatewayClient.getHostUrl());
- username.setText(gatewayClient.getUsername());
- password.setText(gatewayClient.getPassword());
- friendlyName.setText(gatewayClient.getFriendlyConnectionName());
- virtualHost.setText(gatewayClient.getVirtualHost());
- port.setText(String.valueOf(gatewayClient.getPort()));
- }
- }
-
- public void onSaveGatewayClient(View view) throws InterruptedException {
- TextInputEditText url = findViewById(R.id.new_gateway_client_url_input);
- TextInputEditText username = findViewById(R.id.new_gateway_client_username);
- TextInputEditText password = findViewById(R.id.new_gateway_password);
- TextInputEditText friendlyName = findViewById(R.id.new_gateway_client_friendly_name);
- TextInputEditText virtualHost = findViewById(R.id.new_gateway_client_virtualhost);
- TextInputEditText port = findViewById(R.id.new_gateway_client_port);
-
- if(url.getText().toString().isEmpty()) {
- url.setError(getResources().getString(R.string.settings_gateway_client_cannot_be_empty));
- return;
- }
- if(username.getText().toString().isEmpty()) {
- username.setError(getResources().getString(R.string.settings_gateway_client_cannot_be_empty));
- return;
- }
- if(password.getText().toString().isEmpty()) {
- password.setError(getString(R.string.settings_gateway_client_cannot_be_empty));
- return;
- }
- if(virtualHost.getText().toString().isEmpty()) {
- virtualHost.setText(getResources().getString(R.string.settings_gateway_client_default_virtualhost));
- }
- if(port.getText().toString().isEmpty()) {
- port.setText(getResources().getString(R.string.settings_gateway_client_default_port));
- }
-
- GatewayClient gatewayClient = new GatewayClient();
- gatewayClient.setHostUrl(url.getText().toString());
- gatewayClient.setUsername(username.getText().toString());
- gatewayClient.setPassword(password.getText().toString());
- gatewayClient.setVirtualHost(virtualHost.getText().toString());
- gatewayClient.setPort(Integer.parseInt(port.getText().toString()));
- gatewayClient.setDate(System.currentTimeMillis());
-
- if(!friendlyName.getText().toString().isEmpty()) {
- gatewayClient.setFriendlyConnectionName(friendlyName.getText().toString());
- }
-
- RadioGroup radioGroup = findViewById(R.id.add_gateway_client_protocol_group);
- int checkedRadioId = radioGroup.getCheckedRadioButtonId();
- if(checkedRadioId == R.id.add_gateway_client_protocol_amqp)
- gatewayClient.setProtocol(getString(R.string.settings_gateway_client_amqp_protocol).toLowerCase());
-
- GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext());
- if(getIntent().hasExtra(GatewayClientListingActivity.Companion.getGATEWAY_CLIENT_ID())) {
- long gatewayClientId = getIntent().getLongExtra(GatewayClientListingActivity.Companion
- .getGATEWAY_CLIENT_ID(), -1);
-
- GatewayClient gatewayClient1 = gatewayClientHandler.fetch(gatewayClientId);
-
- gatewayClient.setId(gatewayClient1.getId());
- gatewayClient.setProjectName(gatewayClient1.getProjectName());
- gatewayClient.setProjectBinding(gatewayClient1.getProjectBinding());
- gatewayClientHandler.update(gatewayClient);
- }
- else {
- gatewayClientHandler.add(gatewayClient);
- }
-
- Intent intent = new Intent(this, GatewayClientListingActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- }
-
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if(id != -1)
- getMenuInflater().inflate(R.menu.gateway_server_add_menu, menu);
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if(item.getItemId() == R.id.gateway_client_delete) {
- SharedPreferences sharedPreferences = getSharedPreferences(GatewayClientListingActivity
- .Companion.getGATEWAY_CLIENT_LISTENERS(), Context.MODE_PRIVATE);
- sharedPreferences.edit().remove(String.valueOf(id))
- .apply();
-
- ThreadingPoolExecutor.executorService.execute(new Runnable() {
- @Override
- public void run() {
- datastore.gatewayClientProjectDao().deleteGatewayClientId(id);
- }
- });
-
- startActivity(new Intent(this, GatewayClientListingActivity.class));
- finish();
- return true;
- }
- return false;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java
deleted file mode 100644
index a11b34671..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients;
-
-import androidx.lifecycle.LiveData;
-import androidx.room.Dao;
-import androidx.room.Delete;
-import androidx.room.Insert;
-import androidx.room.OnConflictStrategy;
-import androidx.room.Query;
-import androidx.room.Transaction;
-import androidx.room.Update;
-
-import java.util.List;
-
-@Dao
-public interface GatewayClientDAO {
-
- @Query("SELECT * FROM GatewayClient")
- List getAll();
-
- @Query("SELECT * FROM GatewayClient")
- LiveData> fetch();
-
- @Query("SELECT * FROM GatewayClient WHERE activated = 1")
- List fetchActivated();
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- long insert(GatewayClient gatewayClient);
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- void insert(List gatewayClients);
-
- @Delete
- int delete(GatewayClient gatewayClient);
-
- @Delete
- void delete(List gatewayClients);
-
- @Query("DELETE FROM GatewayClient")
- void deleteAll();
-
- @Query("SELECT * FROM GatewayClient WHERE id=:id")
- GatewayClient fetch(long id);
-
- @Query("SELECT * FROM GatewayClient WHERE id=:id")
- LiveData fetchLiveData(long id);
-
-// @Query("UPDATE GatewayClient SET projectName=:projectName, projectBinding=:projectBinding WHERE id=:id")
-// void updateProjectNameAndProjectBinding(String projectName, String projectBinding, int id);
-
- @Update
- void update(GatewayClient gatewayClient);
-
-// @Transaction
-// default void repentance(List sinFulGatewayClients, List afreshGatewayClient) {
-// delete(sinFulGatewayClients);
-// insert(afreshGatewayClient);
-// }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.kt
deleted file mode 100644
index af4deeb47..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Context
-import android.util.Log
-import androidx.work.BackoffPolicy
-import androidx.work.Constraints
-import androidx.work.Data
-import androidx.work.ExistingWorkPolicy
-import androidx.work.NetworkType
-import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.WorkManager
-import androidx.work.WorkRequest
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.BuildConfig
-import com.afkanerd.deku.DefaultSMS.Commons.Helpers
-import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-import com.afkanerd.deku.QueueListener.RMQ.RMQWorkManager
-import java.util.concurrent.TimeUnit
-
-class GatewayClientHandler(context: Context?) {
- var databaseConnector: Datastore = Datastore.getDatastore(context)
-
- @Throws(InterruptedException::class)
- fun add(gatewayClient: GatewayClient): Long {
- gatewayClient.date = System.currentTimeMillis()
- val id = longArrayOf(-1)
- val thread = Thread {
- val gatewayClientDAO = databaseConnector.gatewayClientDAO()
- id[0] = gatewayClientDAO.insert(gatewayClient)
- }
- thread.start()
- thread.join()
-
- return id[0]
- }
-
- @Throws(InterruptedException::class)
- fun delete(gatewayClient: GatewayClient) {
- gatewayClient.date = System.currentTimeMillis()
- val thread = Thread {
- val gatewayClientDAO = databaseConnector.gatewayClientDAO()
- gatewayClientDAO.delete(gatewayClient)
- }
- thread.start()
- thread.join()
- }
-
- @Throws(InterruptedException::class)
- fun update(gatewayClient: GatewayClient) {
- gatewayClient.date = System.currentTimeMillis()
- val thread = Thread {
- val gatewayClientDAO = databaseConnector.gatewayClientDAO()
- gatewayClientDAO.update(gatewayClient)
- }
- thread.start()
- thread.join()
- }
-
- @Throws(InterruptedException::class)
- fun fetch(id: Long): GatewayClient {
- val gatewayClient = arrayOf(GatewayClient())
- val thread = Thread {
- val gatewayClientDAO = databaseConnector.gatewayClientDAO()
- gatewayClient[0] = gatewayClientDAO.fetch(id)
- }
- thread.start()
- thread.join()
-
- return gatewayClient[0]
- }
-
- companion object {
- const val MIGRATIONS: String = "MIGRATIONS"
- const val MIGRATIONS_TO_11: String = "MIGRATIONS_TO_11"
-
- fun getPublisherDetails(context: Context?, projectName: String): List {
- val simcards = SIMHandler.getSimCardInformation(context)
-
- val operatorCountry = Helpers.getUserCountry(context)
-
- val operatorDetails: MutableList = ArrayList()
- for (i in simcards.indices) {
- val mcc = simcards[i].mcc.toString()
- val _mnc = simcards[i].mnc
- val mnc = if (_mnc < 10) "0$_mnc" else _mnc.toString()
- val carrierId = mcc + mnc
-
- val publisherName = "$projectName.$operatorCountry.$carrierId"
- operatorDetails.add(publisherName)
- }
-
- return operatorDetails
- }
-
- fun startListening(context: Context, gatewayClient: GatewayClient) {
- ThreadingPoolExecutor.executorService.execute(object : Runnable {
- override fun run() {
- Datastore.getDatastore(context).gatewayClientDAO().update(gatewayClient)
- Log.d(javaClass.name, "Gateway client: " + gatewayClient.activated)
- if (gatewayClient.activated)
- startWorkManager(context, gatewayClient)
- }
- })
- }
-
- val UNIQUE_WORK_MANAGER_NAME = BuildConfig.APPLICATION_ID
-
- fun startWorkManager(context: Context, gatewayClient: GatewayClient) : WorkManager {
- val constraints : Constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build();
-
- val workManager = WorkManager.getInstance(context)
- Log.d(javaClass.name, "WorkManager: ${gatewayClient.id}:${gatewayClient.hostUrl}")
- val gatewayClientListenerWorker = OneTimeWorkRequestBuilder()
- .setConstraints(constraints)
- .setBackoffCriteria(
- BackoffPolicy.LINEAR,
- WorkRequest.MIN_BACKOFF_MILLIS,
- TimeUnit.MILLISECONDS
- )
- .setInputData(Data.Builder()
- .putLong(GatewayClient.GATEWAY_CLIENT_ID, gatewayClient.id)
- .build())
- .addTag(GatewayClient::class.java.name)
- .build();
-
- workManager.enqueueUniqueWork(
- UNIQUE_WORK_MANAGER_NAME,
- ExistingWorkPolicy.KEEP,
- gatewayClientListenerWorker
- )
- return workManager
- }
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.kt
deleted file mode 100644
index bada66e77..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.widget.Toolbar
-import com.afkanerd.deku.DefaultSMS.R
-
-class GatewayClientListingActivity : AppCompatActivity() {
-
- var toolbar: Toolbar? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_gateway_client_listing)
-
- toolbar = findViewById(R.id.gateway_client_listing_toolbar)
- setSupportActionBar(toolbar)
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
-
- supportActionBar!!.title = getString(R.string.gateway_client_listing_toolbar_title)
-
- val gatewayClientListingFragment = GatewayClientListingFragment()
- supportFragmentManager.beginTransaction()
- .add( R.id.view_fragment, gatewayClientListingFragment)
- .setReorderingAllowed(true)
- .setReorderingAllowed(true)
- .commit()
- }
-
-
- companion object {
- var GATEWAY_CLIENT_ID: String = "GATEWAY_CLIENT_ID"
- var GATEWAY_CLIENT_ID_NEW: String = "GATEWAY_CLIENT_ID_NEW"
- var GATEWAY_CLIENT_USERNAME: String = "GATEWAY_CLIENT_USERNAME"
- var GATEWAY_CLIENT_PASSWORD: String = "GATEWAY_CLIENT_PASSWORD"
- var GATEWAY_CLIENT_VIRTUAL_HOST: String = "GATEWAY_CLIENT_VIRTUAL_HOST"
- var GATEWAY_CLIENT_HOST: String = "GATEWAY_CLIENT_HOST"
- var GATEWAY_CLIENT_PORT: String = "GATEWAY_CLIENT_PORT"
- var GATEWAY_CLIENT_FRIENDLY_NAME: String = "GATEWAY_CLIENT_FRIENDLY_NAME"
-
- var GATEWAY_CLIENT_LISTENERS: String = "GATEWAY_CLIENT_LISTENERS"
- var GATEWAY_CLIENT_STOP_LISTENERS: String = "GATEWAY_CLIENT_STOP_LISTENERS"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingFragment.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingFragment.kt
deleted file mode 100644
index 8222a31e9..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingFragment.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.viewModels
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.afkanerd.deku.DefaultSMS.R
-
-class GatewayClientListingFragment : Fragment(R.layout.fragment_gateway_client_listing) {
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- setHasOptionsMenu(true)
- return super.onCreateView(inflater, container, savedInstanceState)
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- val gatewayClientViewModel: GatewayClientViewModel by viewModels()
- val gatewayClientRecyclerAdapter = GatewayClientRecyclerAdapter()
-
- val linearLayoutManager = LinearLayoutManager(view.context)
- val recyclerView = view.findViewById(R.id.gateway_client_listing_recycler_view)
- recyclerView.layoutManager = linearLayoutManager
-
- val dividerItemDecoration = DividerItemDecoration(view.context,
- linearLayoutManager.orientation )
- recyclerView.addItemDecoration(dividerItemDecoration)
-
- recyclerView.adapter = gatewayClientRecyclerAdapter
-
- gatewayClientRecyclerAdapter.onSelectedListener.observe(viewLifecycleOwner, Observer {
- it?.let {
- gatewayClientRecyclerAdapter.onSelectedListener = MutableLiveData()
-
- val gatewayClientProjectListingFragment = GatewayClientProjectListingFragment(it.id)
- activity?.supportFragmentManager?.beginTransaction()
- ?.replace( R.id.view_fragment, gatewayClientProjectListingFragment)
- ?.setReorderingAllowed(true)
- ?.addToBackStack(gatewayClientProjectListingFragment.javaClass.name)
- ?.commit()
- }
- })
-
- gatewayClientViewModel.getGatewayClientList(view.context).observe(this,
- Observer {
- if (it.isNullOrEmpty())
- view.findViewById(R.id.gateway_client_no_gateway_client_label)
- .visibility = View.VISIBLE
- gatewayClientRecyclerAdapter.submitList(it)
- })
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- menu.clear()
- inflater.inflate(R.menu.gateway_client_listing_menu, menu)
- super.onCreateOptionsMenu(menu, inflater)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.gateway_client_add_manually) {
- val addGatewayIntent = Intent(requireContext(), GatewayClientAddActivity::class.java)
- startActivity(addGatewayIntent)
- return true
- }
- return false
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddModalFragment.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddModalFragment.kt
deleted file mode 100644
index 11fd1e557..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddModalFragment.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-import android.view.View
-import android.widget.LinearLayout
-import androidx.appcompat.widget.Toolbar
-import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
-import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import com.google.android.material.button.MaterialButton
-import com.google.android.material.textfield.TextInputEditText
-
-class GatewayClientProjectAddModalFragment(private val gatewayClientProjectListingViewModel:
- GatewayClientProjectListingViewModel,
- private val gatewayClientId: Long,
- private var gatewayClientProjects:
- GatewayClientProjects? = GatewayClientProjects()) :
- BottomSheetDialogFragment(R.layout.fragment_modalsheet_gateway_client_project_add_edit) {
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- getGatewayClient(view)
-
- val materialButton = view.findViewById(R.id.gateway_client_customization_save_btn)
- materialButton.setOnClickListener { v ->
- try {
- onSaveGatewayClientConfiguration(view)
- } catch (e: InterruptedException) {
- e.printStackTrace()
- }
- }
-
- val toolbar = view.findViewById(R.id.gateway_client_project_add_edit_toolbar)
- toolbar.title = view.context.getString(R.string.gateway_client_add_edit_add_gateway_client)
-
- val bottomSheet = view.findViewById(R.id.gateway_client_project_add_edit_layout)
- val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
- bottomSheetBehavior.isFitToContents = true
- bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
- }
-
- private fun getGatewayClient(view: View) {
- val projectName = view.findViewById(R.id.new_gateway_client_project_name)
- val projectBinding =
- view.findViewById(R.id.new_gateway_client_project_binding_sim_1)
- val projectBinding2 =
- view.findViewById(R.id.new_gateway_client_project_binding_sim_2)
-
- val isDualSim = SIMHandler.isDualSim(view.context)
- if (isDualSim) {
- view.findViewById(R.id.new_gateway_client_project_binding_sim_2_layout)
- .visibility = View.VISIBLE
- }
-
- gatewayClientProjects?.let {
- activity?.runOnUiThread {
- projectName.setText(gatewayClientProjects!!.name)
- projectBinding.setText(gatewayClientProjects!!.binding1Name)
- if (isDualSim) {
- projectBinding2.setText(gatewayClientProjects!!.binding2Name)
- }
- }
- }
-
- projectName.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- }
-
- override fun afterTextChanged(s: Editable) {
- val projectBindings = GatewayClientHandler
- .getPublisherDetails( view.context, s.toString())
-
- projectBinding.setText(projectBindings[0])
- if (projectBindings.size > 1) {
- projectBinding2.setText(projectBindings[1])
- }
- }
- })
- }
-
- private fun onSaveGatewayClientConfiguration(view: View) {
- val projectName = view.findViewById(R.id.new_gateway_client_project_name)
- val projectBinding =
- view.findViewById(R.id.new_gateway_client_project_binding_sim_1)
- val projectBinding2 =
- view.findViewById(R.id.new_gateway_client_project_binding_sim_2)
- val projectBindingConstraint =
- view.findViewById(R.id.new_gateway_client_project_binding_sim_2_layout)
-
- if (projectName.text == null || projectName.text.toString().isEmpty()) {
- projectName.error = getString(R.string.settings_gateway_client_cannot_be_empty)
- return
- }
-
- if (projectBinding.text == null || projectBinding.text.toString().isEmpty()) {
- projectBinding.error = getString(R.string.settings_gateway_client_cannot_be_empty)
- return
- }
-
- if (projectBindingConstraint.visibility == View.VISIBLE &&
- (projectBinding2.text == null || projectBinding2.text.toString().isEmpty())) {
- projectBinding2.error = getString(R.string.settings_gateway_client_cannot_be_empty)
- return
- }
-
- if(gatewayClientProjects == null)
- gatewayClientProjects = GatewayClientProjects()
-
- gatewayClientProjects?.name = projectName.text.toString()
- gatewayClientProjects?.binding1Name = projectBinding.text.toString()
- gatewayClientProjects?.binding2Name = projectBinding2.text.toString()
- gatewayClientProjects?.gatewayClientId = gatewayClientId
-
- ThreadingPoolExecutor.executorService.execute {
- try {
- gatewayClientProjectListingViewModel.insert(gatewayClientProjects!!)
- } catch(e: Exception) {
- e.printStackTrace()
- }
- dismiss()
- }
- }
-
- companion object {
- const val GATEWAY_CLIENT_PROJECT_ID: String = "GATEWAY_CLIENT_PROJECT_ID"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java
deleted file mode 100644
index f39671f22..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients;
-
-import androidx.lifecycle.LiveData;
-import androidx.room.Dao;
-import androidx.room.Delete;
-import androidx.room.Insert;
-import androidx.room.OnConflictStrategy;
-import androidx.room.Query;
-import androidx.room.Update;
-
-import java.util.List;
-
-@Dao
-public interface GatewayClientProjectDao {
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- void insert(List gatewayClientProjectsList);
-
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- long insert(GatewayClientProjects gatewayClientProjects);
-
- @Query("SELECT * FROM GatewayClientProjects WHERE id = :id")
- GatewayClientProjects fetch(long id);
-
- @Query("SELECT * FROM GatewayClientProjects WHERE id = :id")
- LiveData fetchLiveData(long id);
-
- @Query("SELECT * FROM GatewayClientProjects WHERE gatewayClientId = :gatewayClientId")
- LiveData> fetchGatewayClientId(long gatewayClientId);
-
- @Query("SELECT * FROM GatewayClientProjects WHERE gatewayClientId = :gatewayClientId")
- List fetchGatewayClientIdList(long gatewayClientId);
-
- @Update
- void update(GatewayClientProjects gatewayClientProjects);
-
- @Query("DELETE FROM GatewayClientProjects WHERE gatewayClientId = :id")
- void deleteGatewayClientId(long id);
-
- @Query("DELETE FROM GatewayClientProjects WHERE id = :id")
- void delete(long id);
-
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingFragment.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingFragment.kt
deleted file mode 100644
index 6710eeeb8..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingFragment.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-
-class GatewayClientProjectListingFragment(val gatewayClientId: Long) :
- Fragment(R.layout.fragment_modalsheet_gateway_client_project_listing_layout) {
- private val gatewayClientProjectListingViewModel :
- GatewayClientProjectListingViewModel by viewModels()
-
- override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle? ): View? {
- setHasOptionsMenu(true);
- return super.onCreateView(inflater, container, savedInstanceState)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val linearLayoutManager = LinearLayoutManager(view.context)
- val recyclerView = view
- .findViewById(R.id.gateway_client_project_listing_recycler_view)
-
- val gatewayClientProjectListingRecyclerAdapter = GatewayClientProjectListingRecyclerAdapter()
- recyclerView.layoutManager = linearLayoutManager
- recyclerView.adapter = gatewayClientProjectListingRecyclerAdapter
-
- gatewayClientProjectListingRecyclerAdapter.onSelectedLiveData.observe(viewLifecycleOwner,
- Observer {
- it?.let {
- gatewayClientProjectListingRecyclerAdapter.onSelectedLiveData = MutableLiveData()
- showAddGatewayClientModal(it)
- }
- })
-
- gatewayClientProjectListingViewModel.get(requireContext(), gatewayClientId)
- .observe(viewLifecycleOwner, Observer {
- gatewayClientProjectListingRecyclerAdapter.mDiffer.submitList(it)
- if (it.isNullOrEmpty())
- view.findViewById(R.id.gateway_client_project_listing_no_projects)
- .visibility = View.VISIBLE
- else view.findViewById(R.id.gateway_client_project_listing_no_projects)
- .visibility = View.GONE
- })
-
- ThreadingPoolExecutor.executorService.execute {
- val gatewayClientLiveData = Datastore.getDatastore(view.context).gatewayClientDAO()
- .fetchLiveData(gatewayClientId)
- activity?.runOnUiThread {
- gatewayClientLiveData.observe(viewLifecycleOwner, Observer {
- gatewayClient = it
- activity?.invalidateOptionsMenu()
- })
- }
- }
- }
-
- private var gatewayClient = GatewayClient()
-
- private fun showAddGatewayClientModal(gatewayClientProjects: GatewayClientProjects? = null) {
- val fragmentManager: FragmentManager = activity?.supportFragmentManager!!
- val fragmentTransaction = fragmentManager.beginTransaction()
- val gatewayClientProjectAddModalFragment =
- GatewayClientProjectAddModalFragment(gatewayClientProjectListingViewModel,
- gatewayClientId, gatewayClientProjects)
- fragmentTransaction.add(gatewayClientProjectAddModalFragment,
- "gateway_client_add_edit")
- fragmentTransaction.show(gatewayClientProjectAddModalFragment)
- fragmentTransaction.commit()
- }
-
-
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- menu.clear()
- inflater.inflate(R.menu.gateway_client_project_listing_menu, menu)
- super.onCreateOptionsMenu(menu, inflater)
- }
-
- override fun onPrepareOptionsMenu(menu: Menu) {
- if(gatewayClient.activated) {
- menu.findItem(R.id.gateway_client_project_disconnect)
- .setVisible(true)
- } else {
- menu.findItem(R.id.gateway_client_project_connect)
- .setVisible(true)
- }
- super.onPrepareOptionsMenu(menu)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.gateway_client_project_add) {
- showAddGatewayClientModal()
- return true
- }
- if (item.itemId == R.id.gateway_client_edit) {
- val intent = Intent(requireContext(), GatewayClientAddActivity::class.java)
- intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, gatewayClientId)
-
- startActivity(intent)
- return true
- }
- if (item.itemId == R.id.gateway_client_project_connect) {
-// val gatewayClient = Datastore.getDatastore(requireContext()).gatewayClientDAO()
-// .fetch(gatewayClientId)
- gatewayClient.activated = true
- GatewayClientHandler.startListening(requireContext(), gatewayClient)
- return true
- }
- if (item.itemId == R.id.gateway_client_project_disconnect) {
-// val gatewayClient = Datastore.getDatastore(requireContext()).gatewayClientDAO()
-// .fetch(gatewayClientId)
- gatewayClient.activated = false
-// Datastore.getDatastore(requireContext()).gatewayClientDAO().update(gatewayClient)
- GatewayClientHandler.startListening(requireContext(), gatewayClient)
- return true
- }
- return false
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.kt
deleted file mode 100644
index 4436cc1f9..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Intent
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.cardview.widget.CardView
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.recyclerview.widget.AsyncListDiffer
-import androidx.recyclerview.widget.RecyclerView
-import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjectAddModalFragment
-
-class GatewayClientProjectListingRecyclerAdapter :
- RecyclerView.Adapter() {
- val mDiffer: AsyncListDiffer = AsyncListDiffer(
- this, GatewayClientProjects.DIFF_CALLBACK )
-
- var onSelectedLiveData: MutableLiveData = MutableLiveData()
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val view = inflater.inflate(R.layout.layout_gateway_client_project_listing, parent, false)
- return ViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val gatewayClientProjects = mDiffer.currentList[position]
- holder.bind(gatewayClientProjects, onSelectedLiveData)
- }
-
- override fun getItemCount(): Int { return mDiffer.currentList.size }
-
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- private var cardView: CardView = itemView.findViewById(R.id.gateway_client_project_listing_card)
-
- private var projectNameTextView: TextView =
- itemView.findViewById(R.id.gateway_client_project_listing_project_name)
-
- private var projectBinding1TextView: TextView =
- itemView.findViewById(R.id.gateway_client_project_listing_project_binding1)
-
- private var projectBinding2TextView: TextView =
- itemView.findViewById(R.id.gateway_client_project_listing_project_binding2)
-
- fun bind(gatewayClientProjects: GatewayClientProjects,
- onSelectedLiveData: MutableLiveData) {
- projectNameTextView.text = gatewayClientProjects.name
- projectBinding1TextView.text = gatewayClientProjects.binding1Name
- projectBinding2TextView.text = gatewayClientProjects.binding2Name
-
- cardView.setOnClickListener { onSelectedLiveData.value = gatewayClientProjects }
- }
-
- }
-
- override fun getItemViewType(position: Int): Int {
- return super.getItemViewType(position)
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.kt
deleted file mode 100644
index 4bb76d17b..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Context
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-
-class GatewayClientProjectListingViewModel : ViewModel() {
- private lateinit var datastore: Datastore
-
- private lateinit var liveData : LiveData>
- fun get(context: Context, gatewayClientId: Long): LiveData>{
- datastore = Datastore.getDatastore(context)
- if(!::liveData.isInitialized) {
- liveData = MutableLiveData()
- liveData = datastore.gatewayClientProjectDao().fetchGatewayClientId(gatewayClientId)
- }
- return liveData
- }
-
- fun insert(gatewayClientProjects: GatewayClientProjects) {
- datastore.gatewayClientProjectDao().insert(gatewayClientProjects)
- }
-
- fun update(gatewayClientProjects: GatewayClientProjects) {
- datastore.gatewayClientProjectDao().update(gatewayClientProjects)
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java
deleted file mode 100644
index 0236d83e5..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.DiffUtil;
-import androidx.room.Entity;
-import androidx.room.PrimaryKey;
-
-import java.util.Objects;
-
-@Entity
-public class GatewayClientProjects {
-
- @PrimaryKey(autoGenerate = true)
- public long id;
- public long gatewayClientId;
-
- public String name;
- public String binding1Name;
- public String binding2Name;
-
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if(obj instanceof GatewayClientProjects) {
- GatewayClientProjects gatewayClientProjects = (GatewayClientProjects) obj;
-
- return gatewayClientProjects.id == this.id &&
- Objects.equals(gatewayClientProjects.name, this.name) &&
- Objects.equals(gatewayClientProjects.binding1Name, this.binding1Name) &&
- Objects.equals(gatewayClientProjects.binding2Name, this.binding2Name) &&
- gatewayClientProjects.gatewayClientId == this.gatewayClientId;
- }
- return false;
- }
-
- public static final DiffUtil.ItemCallback DIFF_CALLBACK =
- new DiffUtil.ItemCallback() {
- @Override
- public boolean areItemsTheSame(@NonNull GatewayClientProjects oldItem,
- @NonNull GatewayClientProjects newItem) {
- return oldItem.id == newItem.id;
- }
-
- @Override
- public boolean areContentsTheSame(@NonNull GatewayClientProjects oldItem,
- @NonNull GatewayClientProjects newItem) {
- return oldItem.equals(newItem);
- }
- };
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.kt
deleted file mode 100644
index f84992686..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.app.ActivityManager
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.cardview.widget.CardView
-import androidx.lifecycle.MutableLiveData
-import androidx.recyclerview.widget.AsyncListDiffer
-import androidx.recyclerview.widget.RecyclerView
-import com.afkanerd.deku.DefaultSMS.Commons.Helpers
-import com.afkanerd.deku.DefaultSMS.Models.ServiceHandler
-import com.afkanerd.deku.DefaultSMS.R
-
-class GatewayClientRecyclerAdapter :
- RecyclerView.Adapter() {
- private val mDiffer: AsyncListDiffer = AsyncListDiffer( this,
- GatewayClient.DIFF_CALLBACK )
-
- var onSelectedListener: MutableLiveData = MutableLiveData()
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val view = inflater.inflate(R.layout.gateway_client_listing_layout, parent, false)
- return ViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val gatewayClient = mDiffer.currentList[position]
- holder.bind(gatewayClient, onSelectedListener)
- }
-
-
- fun submitList(gatewayClientList: List?) {
- mDiffer.submitList(gatewayClientList)
- }
-
- override fun getItemCount(): Int {
- return mDiffer.currentList.size
- }
-
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- private var url: TextView = itemView.findViewById(R.id.gateway_client_url)
- private var virtualHost: TextView = itemView.findViewById(R.id.gateway_client_virtual_host)
- private var friendlyName: TextView = itemView.findViewById(R.id.gateway_client_friendly_name_text)
- private var date: TextView = itemView.findViewById(R.id.gateway_client_date)
- private var username: TextView = itemView.findViewById(R.id.gateway_client_username)
- private var connectionStatus = itemView.findViewById(R.id.gateway_client_connection_status)
-
- private var cardView: CardView = itemView.findViewById(R.id.gateway_client_card)
-
- fun bind(gatewayClient: GatewayClient, onSelectedListener: MutableLiveData) {
- val urlBuilder = gatewayClient.protocol + "://" +
- gatewayClient.hostUrl + ":" +
- gatewayClient.port
-
- url.text = urlBuilder
- virtualHost.text = gatewayClient.virtualHost
- friendlyName.text = gatewayClient.friendlyConnectionName
- username.text = gatewayClient.username
- connectionStatus.text = gatewayClient.connectionStatus
-
- val date = Helpers.formatDate(itemView.context, gatewayClient.date)
- this.date.text = date
-
- if (gatewayClient.friendlyConnectionName.isNullOrEmpty())
- friendlyName.visibility = View.GONE
- else friendlyName.text = gatewayClient.friendlyConnectionName
-
- cardView.setOnClickListener { onSelectedListener.value = gatewayClient }
- }
- }
-
- companion object {
- const val ADAPTER_POSITION: String = "ADAPTER_POSITION"
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.kt
deleted file mode 100644
index cbc606180..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
-
-import android.content.Context
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-
-class GatewayClientViewModel : ViewModel() {
- private var gatewayClientList: LiveData> = MutableLiveData()
-
- private lateinit var datastore: Datastore
-
- fun getGatewayClientList(context: Context): LiveData> {
- datastore = Datastore.getDatastore(context)
- if(gatewayClientList.value.isNullOrEmpty()) {
- gatewayClientList = loadGatewayClients()
- }
- return gatewayClientList
- }
-
- private fun loadGatewayClients() : LiveData> {
- return datastore.gatewayClientDAO().fetch()
- }
-
- fun update(gatewayClient: GatewayClient) {
- ThreadingPoolExecutor.executorService.execute {
- datastore.gatewayClientDAO().update(gatewayClient)
- }
- }
-
-// private fun normalizeGatewayClients(gatewayClients: List): List {
-// val filteredGatewayClients: MutableList = ArrayList()
-// for (gatewayClient in gatewayClients) {
-// var contained = false
-// for (gatewayClient1 in filteredGatewayClients) {
-// if (gatewayClient1.same(gatewayClient)) {
-// contained = true
-// break
-// }
-// }
-// if (!contained) {
-// filteredGatewayClients.add(gatewayClient)
-// }
-// }
-//
-// return filteredGatewayClients
-// }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.kt
deleted file mode 100644
index b862f28a6..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.afkanerd.deku.QueueListener.RMQ
-
-import com.rabbitmq.client.Channel
-import com.rabbitmq.client.Connection
-import com.rabbitmq.client.DeliverCallback
-import okhttp3.internal.toImmutableMap
-import java.io.IOException
-
-class RMQConnection(var id: Long, var connection: Connection) {
- private val autoDelete: Boolean = false
- private val exclusive: Boolean = false
- private val durable: Boolean = true
-
- private val channelList: MutableList = ArrayList()
- private val channelTagMap = mutableMapOf()
-
-
- fun removeChannel(channel: Channel) {
- channelList.remove(channel)
- }
-
- fun createChannel(): Channel {
- return connection.createChannel().apply {
- val prefetchCount = 1
- basicQos(prefetchCount)
- }
- }
-
- fun bindChannelToTag(channel: Channel, channelTag: String) {
- channelTagMap[channelTag] = channel
- }
-
- fun findChannelByTag(channelTag: String) : Channel? {
- return channelTagMap[channelTag]
- }
-
- fun close() {
- if (connection.isOpen)
- connection.close()
- }
-
- fun createQueue(exchangeName: String, bindingKey: String, channel: Channel,
- queueName: String = bindingKey.replace("\\.".toRegex(), "_")) :
- String {
- channel.queueDeclare(queueName, durable, exclusive, autoDelete, null)
- channel.queueBind(queueName, exchangeName, bindingKey)
-
- return queueName
- }
-
- companion object {
- const val MESSAGE_SID: String = "sid"
-
- const val RMQ_ID: String = "RMQ_ID"
- const val RMQ_DELIVERY_TAG: String = "RMQ_DELIVERY_TAG"
- const val RMQ_CONSUMER_TAG: String = "RMQ_CONSUMER_TAG"
- }
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.kt
deleted file mode 100644
index 6735d2c59..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.afkanerd.deku.QueueListener.RMQ
-
-import android.app.PendingIntent
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ServiceInfo
-import android.os.Build
-import android.os.IBinder
-import androidx.core.app.NotificationCompat
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
-import androidx.work.WorkInfo
-import androidx.work.WorkManager
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.R
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity
-
-class RMQConnectionService : Service() {
- private var nConnected = 0
- private var nEnqueued = 0
- private var nReconnecting = 0
-
- private lateinit var gatewayClientListLiveData: LiveData>
-
- private lateinit var workManagerLiveData: LiveData>
-
- private val gatewayClientObserver = Observer> {
- it.forEach {gatewayClient ->
- if(gatewayClient.activated) {
- println("Starting work manager")
- GatewayClientHandler.startWorkManager(applicationContext, gatewayClient)
- }
- }
- }
-
- private val workManagerObserver = Observer> {
- nConnected = 0
- nEnqueued = 0
- nReconnecting = 0
- it.forEach { workInfo ->
- when(workInfo.state) {
- WorkInfo.State.ENQUEUED -> ++nEnqueued
- WorkInfo.State.RUNNING -> ++nReconnecting
- WorkInfo.State.SUCCEEDED -> ++nConnected
- WorkInfo.State.FAILED -> {}
- WorkInfo.State.BLOCKED -> {}
- WorkInfo.State.CANCELLED -> {}
- }
- }
- createForegroundNotification()
- }
-
- override fun onBind(intent: Intent?): IBinder? {
- return null
- }
-
- override fun onDestroy() {
- super.onDestroy()
-
- workManagerLiveData.removeObserver(workManagerObserver)
- gatewayClientListLiveData.removeObserver(gatewayClientObserver)
- }
-
- override fun onCreate() {
- super.onCreate()
- workManagerLiveData = WorkManager.getInstance(applicationContext)
- .getWorkInfosByTagLiveData(GatewayClient::class.java.name)
-
- workManagerLiveData.observeForever(workManagerObserver)
-
- gatewayClientListLiveData = Datastore.getDatastore(applicationContext)
- .gatewayClientDAO().fetch()
- gatewayClientListLiveData.observeForever(gatewayClientObserver)
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- createForegroundNotification()
- return START_STICKY
- }
-
- private fun createForegroundNotification() {
- val notificationIntent = Intent(applicationContext, GatewayClientListingActivity::class.java)
- val pendingIntent = PendingIntent
- .getActivity(applicationContext,
- 0,
- notificationIntent,
- PendingIntent.FLAG_IMMUTABLE)
-
- val description = "$nEnqueued ${getString(R.string.gateway_client_enqueue_description)}\n" +
- "$nReconnecting ${getString(R.string.gateway_client_reconnecting_description)}"
-
- val title = "$nConnected ${getString(R.string.gateway_client_running_description)}"
-
- val notification =
- NotificationCompat.Builder(applicationContext,
- getString(R.string.running_gateway_clients_channel_id))
- .setContentTitle(title)
- .setSmallIcon(R.drawable.ic_stat_name)
- .setPriority(NotificationCompat.DEFAULT_ALL)
- .setSilent(true)
- .setOngoing(true)
- .setContentText(description)
- .setContentIntent(pendingIntent)
- .build()
-
- val notificationId = getString(R.string.gateway_client_service_notification_id).toInt()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- startForeground(notificationId, notification,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- } else startForeground(notificationId, notification)
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionServiceInitializer.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionServiceInitializer.kt
deleted file mode 100644
index ec7d892d6..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionServiceInitializer.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.afkanerd.deku.QueueListener.RMQ
-
-import android.content.Context
-import android.content.Intent
-import androidx.startup.Initializer
-import androidx.work.WorkManagerInitializer
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-import com.afkanerd.deku.NotificationsInitializer
-
-class RMQConnectionServiceInitializer : Initializer {
- override fun create(context: Context): Intent {
- val intent = Intent(context, RMQConnectionService::class.java)
-
- ThreadingPoolExecutor.executorService.execute {
- if(!Datastore.getDatastore(context).gatewayClientDAO()
- .fetchActivated().isNullOrEmpty()) context.startForegroundService(intent)
- }
-
- return intent
- }
-
- override fun dependencies(): List>> {
- return listOf(WorkManagerInitializer::class.java,
- NotificationsInitializer::class.java)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionWorker.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionWorker.kt
deleted file mode 100644
index 164a0d1b4..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionWorker.kt
+++ /dev/null
@@ -1,277 +0,0 @@
-package com.afkanerd.deku.QueueListener.RMQ
-
-import android.app.Activity
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.Bundle
-import android.provider.Telephony
-import android.telephony.SubscriptionInfo
-import android.util.Log
-import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver
-import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
-import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB
-import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
-import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper
-import com.afkanerd.deku.Modules.SemaphoreManager
-import com.afkanerd.deku.Modules.ThreadingPoolExecutor
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjects
-import com.rabbitmq.client.Channel
-import com.rabbitmq.client.ConnectionFactory
-import com.rabbitmq.client.ConsumerShutdownSignalCallback
-import com.rabbitmq.client.DeliverCallback
-import com.rabbitmq.client.Delivery
-import com.rabbitmq.client.ShutdownSignalException
-import com.rabbitmq.client.impl.DefaultExceptionHandler
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.SerializationException
-import kotlinx.serialization.json.Json
-import org.hamcrest.CoreMatchers.anyOf
-import org.junit.Assert
-import java.io.IOException
-import java.nio.charset.StandardCharsets
-import java.util.concurrent.TimeoutException
-
-class RMQConnectionWorker(val context: Context, val gatewayClientId: Long) {
- private lateinit var rmqConnection: RMQConnection
- private val factory = ConnectionFactory()
-
- private val databaseConnector: Datastore = Datastore.getDatastore(context)
- private val subscriptionInfoList: List =
- SIMHandler.getSimCardInformation(context)
-
- private lateinit var messageStateChangedBroadcast: BroadcastReceiver
-
- init {
- handleBroadcast()
- }
- fun start() {
- connectGatewayClient(gatewayClientId)
- }
-
- private fun handleBroadcast() {
- val intentFilter = IntentFilter()
- intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_SENT_BROADCAST_INTENT)
- messageStateChangedBroadcast = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action != null && intentFilter.hasAction(intent.action)) {
- Log.d(javaClass.name, "Received incoming broadcast")
- if (intent.hasExtra(RMQConnection.MESSAGE_SID) &&
- intent.hasExtra(RMQConnection.RMQ_DELIVERY_TAG)) {
-
- val sid = intent.getStringExtra(RMQConnection.MESSAGE_SID)
- val messageId = intent.getStringExtra(NativeSMSDB.ID)
-
- val consumerTag = intent.getStringExtra(RMQConnection.RMQ_CONSUMER_TAG)
- val deliveryTag =
- intent.getLongExtra(RMQConnection.RMQ_DELIVERY_TAG, -1)
-
- Assert.assertTrue(!consumerTag.isNullOrEmpty())
- Assert.assertTrue(deliveryTag != -1L)
-
- rmqConnection.findChannelByTag(consumerTag!!)?.let {
- Log.d(javaClass.name, "Received an ACK of the message...")
- if (resultCode == Activity.RESULT_OK) {
- ThreadingPoolExecutor.executorService.execute {
- if (it.isOpen) it.basicAck(deliveryTag, false)
- }
- } else {
- Log.w(javaClass.name, "Rejecting message sent")
- ThreadingPoolExecutor.executorService.execute {
- if (it.isOpen) it.basicReject(deliveryTag, true)
- }
- }
- }
-
- }
- }
- }
- }
-
- context.registerReceiver(messageStateChangedBroadcast, intentFilter,
- Context.RECEIVER_EXPORTED)
- }
-
- @Serializable
- private data class SMSRequest(val text: String, val to: String, val sid: String, val id: Int)
- private suspend fun sendSMS(smsRequest: SMSRequest,
- subscriptionId: Int,
- consumerTag: String,
- deliveryTag: Long,
- rmqConnectionId: Long) {
- SemaphoreManager.resourceSemaphore.acquire()
- val messageId = System.currentTimeMillis()
- SemaphoreManager.resourceSemaphore.release()
-
- val threadId = Telephony.Threads.getOrCreateThreadId(context, smsRequest.to)
-
- val bundle = Bundle()
- bundle.putString(RMQConnection.MESSAGE_SID, smsRequest.sid)
- bundle.putString(RMQConnection.RMQ_CONSUMER_TAG, consumerTag)
- bundle.putLong(RMQConnection.RMQ_DELIVERY_TAG, deliveryTag)
- bundle.putLong(RMQConnection.RMQ_ID, rmqConnectionId)
-
- val conversation = Conversation()
- conversation.message_id = messageId.toString()
- conversation.text = smsRequest.text
- conversation.address = smsRequest.to
- conversation.subscription_id = subscriptionId
- conversation.type = Telephony.Sms.MESSAGE_TYPE_OUTBOX
- conversation.date = System.currentTimeMillis().toString()
- conversation.thread_id = threadId.toString()
- conversation.status = Telephony.Sms.STATUS_PENDING
-
- databaseConnector.conversationDao()._insert(conversation)
- SMSDatabaseWrapper.send_text(context, conversation, bundle)
- Log.d(javaClass.name, "SMS sent...")
- }
- private fun getDeliverCallback(channel: Channel, subscriptionId: Int,
- rmqConnectionId: Long): DeliverCallback {
- return DeliverCallback { consumerTag: String, delivery: Delivery ->
- val message = String(delivery.body, StandardCharsets.UTF_8)
-
- CoroutineScope(Dispatchers.IO).launch {
- try {
- val smsRequest = Json.decodeFromString(message)
- sendSMS(smsRequest,
- subscriptionId,
- consumerTag,
- delivery.envelope.deliveryTag,
- rmqConnectionId)
- } catch (e: Exception) {
- Log.e(javaClass.name, "", e)
- when(e) {
- is SerializationException -> {
- channel.let {
- if (it.isOpen)
- it.basicReject(delivery.envelope.deliveryTag, false)
- }
- }
- is IllegalArgumentException -> {
- channel.let {
- if (it.isOpen)
- it.basicReject(delivery.envelope.deliveryTag, true)
- }
- }
- else -> {
- e.printStackTrace()
- }
- }
- }
- }
- }
- }
-
- private fun startConnection(factory: ConnectionFactory, gatewayClient: GatewayClient) {
- Log.d(javaClass.name, "Starting new connection...")
-
- try {
- val connection = factory.newConnection(ThreadingPoolExecutor.executorService,
- gatewayClient.friendlyConnectionName)
-
- rmqConnection = RMQConnection(gatewayClient.id, connection)
-
- connection.addShutdownListener {
- /**
- * The logic here, if the user has not deactivated this - which can be known
- * from the database connection state then reconnect this client.
- */
- Log.e(javaClass.name, "Connection shutdown cause: $it")
- if(gatewayClient.activated)
- GatewayClientHandler.startWorkManager(context, gatewayClient)
- }
-
- val gatewayClientProjectsList = databaseConnector.gatewayClientProjectDao()
- .fetchGatewayClientIdList(gatewayClient.id)
-
- // TODO: try to match the operator code (carrier code) by the binding name
- // TODO: if enabled in settings
- for (i in gatewayClientProjectsList.indices) {
- for (j in subscriptionInfoList.indices) {
- val channel = rmqConnection.createChannel()
- val gatewayClientProjects = gatewayClientProjectsList[i]
- val subscriptionId = subscriptionInfoList[j].subscriptionId
- val bindingName = if (j > 0)
- gatewayClientProjects.binding2Name else gatewayClientProjects.binding1Name
-
- Log.d(javaClass.name, "Starting channel for sim slot $j in project #$i")
- startChannelConsumption(rmqConnection, channel, subscriptionId,
- gatewayClientProjects, bindingName)
- }
- }
-
- } catch (e: Exception) {
- e.printStackTrace()
- when(e) {
- is TimeoutException, is IOException -> {
- e.printStackTrace()
- Thread.sleep(3000)
- Log.d(javaClass.name, "Attempting a reconnect to the server...")
- startConnection(factory, gatewayClient)
- }
- else -> {
- Log.e(javaClass.name, "Exception connecting rmq", e)
- }
- }
- }
- }
-
- private fun startChannelConsumption(rmqConnection: RMQConnection,
- channel: Channel,
- subscriptionId: Int,
- gatewayClientProjects: GatewayClientProjects,
- bindingName: String) {
- Log.d(javaClass.name, "Starting channel connection")
- channel.basicRecover(true)
- val deliverCallback = getDeliverCallback(channel, subscriptionId, rmqConnection.id)
- val queueName = rmqConnection.createQueue(gatewayClientProjects.name, bindingName, channel)
- val messagesCount = channel.messageCount(queueName)
-
- val consumerTag = channel.basicConsume(queueName, false, deliverCallback,
- object : ConsumerShutdownSignalCallback {
- override fun handleShutdownSignal(consumerTag: String, sig: ShutdownSignalException) {
- if (rmqConnection.connection.isOpen) {
- Log.e(javaClass.name, "Consumer error", sig)
- startChannelConsumption(rmqConnection,
- rmqConnection.createChannel(),
- subscriptionId,
- gatewayClientProjects,
- bindingName)
- }
- }
- })
- Log.d(javaClass.name, "Created Queue: $queueName ($messagesCount) - tag: $consumerTag")
- rmqConnection.bindChannelToTag(channel, consumerTag)
- }
-
-
- private fun connectGatewayClient(gatewayClientId: Long) {
- Log.d(javaClass.name, "Starting new service connection...")
-
- ThreadingPoolExecutor.executorService.execute {
- val gatewayClient = Datastore.getDatastore(context).gatewayClientDAO()
- .fetch(gatewayClientId)
-
- factory.username = gatewayClient.username
- factory.password = gatewayClient.password
- factory.virtualHost = gatewayClient.virtualHost
- factory.host = gatewayClient.hostUrl
- factory.port = gatewayClient.port
- factory.exceptionHandler = DefaultExceptionHandler()
-
- /**
- * Increase connectivity sensitivity
- */
- factory.isAutomaticRecoveryEnabled = false
- startConnection(factory, gatewayClient)
- }
- }
-
-}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.kt
deleted file mode 100644
index 0cb8f729b..000000000
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.afkanerd.deku.QueueListener.RMQ
-
-import android.content.Context
-import androidx.work.Worker
-import androidx.work.WorkerParameters
-import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient
-
-
-class RMQWorkManager(context: Context, workerParams: WorkerParameters)
- : Worker(context, workerParams) {
- override fun doWork(): Result {
- val gatewayClientId = inputData.getLong(GatewayClient.GATEWAY_CLIENT_ID, -1)
-
- RMQConnectionWorker(applicationContext, gatewayClientId)
- .start()
-
- return Result.success()
- }
-
-}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenerQueuesViewModel.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenerQueuesViewModel.kt
new file mode 100644
index 000000000..a1f766ce6
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenerQueuesViewModel.kt
@@ -0,0 +1,50 @@
+package com.afkanerd.deku.RemoteListeners.Models.RemoteListener
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.afkanerd.deku.Datastore
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionHandler
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionService
+import com.rabbitmq.client.Channel
+
+class RemoteListenerQueuesViewModel( context: Context? = null ) : ViewModel() {
+ private lateinit var datastore: Datastore
+
+ var remoteListenerQueues by mutableStateOf(null)
+ private lateinit var liveData : LiveData>
+ private lateinit var channelsLiveData : LiveData>>
+ private lateinit var rmqConnectionHandlers: LiveData>
+
+ fun get(context: Context, gatewayClientId: Long): LiveData>{
+ datastore = Datastore.getDatastore(context)
+ if(!::liveData.isInitialized) {
+ liveData = MutableLiveData()
+ liveData = datastore.remoteListenersQueuesDao().fetchRemoteListenerQueue(gatewayClientId)
+ }
+ return liveData
+ }
+
+ fun insert(remoteListenersQueues: RemoteListenersQueues) {
+ datastore.remoteListenersQueuesDao().insert(remoteListenersQueues)
+ }
+
+ fun update(remoteListenersQueues: RemoteListenersQueues) {
+ datastore.remoteListenersQueuesDao().update(remoteListenersQueues)
+ }
+
+ fun delete(context: Context, remoteListenerId: Long) {
+ Datastore.getDatastore(context).remoteListenersQueuesDao().delete(remoteListenerId)
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersQueuesDao.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersQueuesDao.kt
new file mode 100644
index 000000000..99dda241b
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersQueuesDao.kt
@@ -0,0 +1,43 @@
+package com.afkanerd.deku.RemoteListeners.Models.RemoteListener
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+
+@Dao
+interface RemoteListenersQueuesDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(remoteListenersQueuesList: List)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(remoteListenersQueues: RemoteListenersQueues): Long
+
+ @Query("SELECT * FROM RemoteListenersQueues WHERE id = :id")
+ fun fetch(id: Long): RemoteListenersQueues?
+
+ @Query("SELECT * FROM RemoteListenersQueues WHERE id = :id")
+ fun fetchLiveData(id: Long): LiveData
+
+ @Query("SELECT * FROM RemoteListenersQueues WHERE gatewayClientId = :gatewayClientId")
+ fun fetchRemoteListenerQueue(gatewayClientId: Long): LiveData>
+
+ @Query("SELECT * FROM RemoteListenersQueues WHERE gatewayClientId = :gatewayClientId")
+ fun fetchRemoteListenersQueues(gatewayClientId: Long): List
+
+ @Update
+ fun update(remoteListenersQueues: RemoteListenersQueues)
+
+ @Query("DELETE FROM RemoteListenersQueues WHERE gatewayClientId = :id")
+ fun deleteRemoteListenerQueue(id: Long)
+
+ @Query("DELETE FROM RemoteListenersQueues WHERE id = :id")
+ fun delete(id: Long)
+
+ @Delete
+ fun delete(remoteListenerQueue: RemoteListenersQueues)
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersViewModel.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersViewModel.kt
new file mode 100644
index 000000000..663fcc0fd
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListener/RemoteListenersViewModel.kt
@@ -0,0 +1,77 @@
+package com.afkanerd.deku.RemoteListeners.Models.RemoteListener
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import androidx.compose.runtime.mutableStateOf
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.ViewModel
+import com.afkanerd.deku.Datastore
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.getValue
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionHandler
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionService
+
+class RemoteListenersViewModel(context: Context? = null) : ViewModel() {
+ private lateinit var remoteListenersList: LiveData>
+ private lateinit var rmqConnectionHandlers: LiveData>
+
+ var remoteListener by mutableStateOf(null)
+
+ private lateinit var datastore: Datastore
+
+ lateinit var binder: RMQConnectionService.LocalBinder
+
+ /** Defines callbacks for service binding, passed to bindService(). **/
+ val connection = object : ServiceConnection {
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance.
+ binder = service as RMQConnectionService.LocalBinder
+ rmqConnectionHandlers = binder.getService().getRmqConnections()
+ }
+
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ }
+ }
+
+ init {
+ context?.let {
+ Intent(context, RMQConnectionService::class.java).also { intent ->
+ context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
+ }
+ }
+ }
+
+ fun getRmqConnections(): LiveData> {
+ return rmqConnectionHandlers
+ }
+
+ fun get(context: Context): LiveData> {
+ datastore = Datastore.getDatastore(context)
+ if(!::remoteListenersList.isInitialized) {
+ remoteListenersList = loadGatewayClients()
+ }
+ return remoteListenersList
+ }
+
+ private fun loadGatewayClients() : LiveData> {
+ return datastore.remoteListenerDAO().fetch()
+ }
+
+ fun update(remoteListeners: RemoteListeners) {
+ datastore.remoteListenerDAO().update(remoteListeners)
+ }
+
+ fun insert(remoteListeners: RemoteListeners) {
+ datastore.remoteListenerDAO().insert(remoteListeners)
+ }
+
+ fun delete(remoteListeners: RemoteListeners) {
+ datastore.remoteListenerDAO().delete(remoteListeners)
+ }
+
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenerDAO.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenerDAO.kt
new file mode 100644
index 000000000..dd4cd3c0d
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenerDAO.kt
@@ -0,0 +1,42 @@
+package com.afkanerd.deku.RemoteListeners.Models
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+
+@Dao
+interface RemoteListenerDAO {
+ @get:Query("SELECT * FROM RemoteListeners")
+ val all: List
+
+ @Query("SELECT * FROM RemoteListeners")
+ fun fetch(): LiveData>
+
+ @Query("SELECT * FROM RemoteListeners WHERE activated = 1")
+ fun fetchActivated(): List
+
+ @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
+ fun insert(remoteListeners: RemoteListeners): Long
+
+ @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
+ fun insert(remoteListeners: List)
+
+ @Delete
+ fun delete(remoteListeners: RemoteListeners): Int
+
+ @Delete
+ fun delete(remoteListeners: List)
+
+ @Query("SELECT * FROM RemoteListeners WHERE id=:id")
+ fun fetch(id: Long): RemoteListeners
+
+ @Update
+ fun update(remoteListeners: RemoteListeners)
+
+ @Update
+ fun update(remoteListeners: List)
+}
diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListeners.kt
similarity index 73%
rename from app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.kt
rename to app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListeners.kt
index eae0c36ea..fe2fbd516 100644
--- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.kt
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListeners.kt
@@ -1,15 +1,13 @@
-package com.afkanerd.deku.QueueListener.GatewayClients
+package com.afkanerd.deku.RemoteListeners.Models
import androidx.recyclerview.widget.DiffUtil
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
-import org.apache.commons.codec.digest.MurmurHash3
-import java.nio.charset.StandardCharsets
@Entity
-class GatewayClient {
+class RemoteListeners {
@Ignore
var connectionStatus: String? = null
@@ -51,13 +49,13 @@ class GatewayClient {
var state = 0
companion object {
- val DIFF_CALLBACK: DiffUtil.ItemCallback =
- object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: GatewayClient, newItem: GatewayClient):
+ val DIFF_CALLBACK: DiffUtil.ItemCallback =
+ object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: RemoteListeners, newItem: RemoteListeners):
Boolean {
return oldItem.id == newItem.id
}
- override fun areContentsTheSame(oldItem: GatewayClient, newItem: GatewayClient):
+ override fun areContentsTheSame(oldItem: RemoteListeners, newItem: RemoteListeners):
Boolean {
return oldItem == newItem
}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersHandler.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersHandler.kt
new file mode 100644
index 000000000..babbf1819
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersHandler.kt
@@ -0,0 +1,143 @@
+package com.afkanerd.deku.RemoteListeners.Models
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.telephony.SubscriptionInfo
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.Data
+import androidx.work.ExistingWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.WorkRequest
+import com.afkanerd.deku.Datastore
+import com.afkanerd.deku.DefaultSMS.BuildConfig
+import com.afkanerd.deku.DefaultSMS.Commons.Helpers
+import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionService
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQWorkManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.util.UUID
+import java.util.concurrent.TimeUnit
+
+object RemoteListenersHandler {
+ const val UNIQUE_WORK_MANAGER_NAME = BuildConfig.APPLICATION_ID
+ const val UNIQUE_WORK_MANAGER_TAG = BuildConfig.APPLICATION_ID + ".REMOTE_LISTENERS"
+
+ fun getPublisherDetails(context: Context?, projectName: String): List {
+ val simCards = SIMHandler.getSimCardInformation(context)
+
+ val operatorCountry = Helpers.getUserCountry(context)
+
+ val operatorDetails: MutableList = ArrayList()
+ for (i in simCards.indices) {
+ val mcc = simCards[i].mcc.toString()
+ val _mnc = simCards[i].mnc
+ val mnc = if (_mnc < 10) "0$_mnc" else _mnc.toString()
+ val carrierId = mcc + mnc
+
+ val publisherName = "$projectName.$operatorCountry.$carrierId"
+ operatorDetails.add(publisherName)
+ }
+
+ return operatorDetails
+ }
+
+ fun getCarrierId(subscriptionInformation: SubscriptionInfo) : Int {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+// subscriptionInformation.carrierId
+// (subscriptionInformation.mccString + subscriptionInformation.mncString).toInt()
+ subscriptionInformation.mncString?.toInt() ?: -1
+ } else {
+ "${subscriptionInformation.mnc}".toInt()
+ }
+ }
+
+ fun generateUuidFromLong(input: Long): UUID {
+ // Generate a UUID from the long by using the input directly
+ // for the most significant bits and setting the least significant bits to 0.
+ val mostSigBits = input
+ val leastSigBits = 0L // You can modify this if you want to use more of the long
+
+ return UUID(mostSigBits, leastSigBits)
+ }
+
+ fun stopListening(context: Context, remoteListener: RemoteListeners) {
+ CoroutineScope(Dispatchers.Default).launch {
+ Datastore.getDatastore(context).remoteListenerDAO().update(remoteListener)
+ val workManager = WorkManager.getInstance(context)
+ workManager.getWorkInfoById(generateUuidFromLong(remoteListener.id)).apply {
+ cancel(true)
+ }
+ }
+ }
+
+ fun onOffAgain(context: Context, remoteListener: RemoteListeners) {
+ if(remoteListener.activated) {
+ remoteListener.activated = false
+ Datastore.getDatastore(context).remoteListenerDAO().update(remoteListener)
+ Thread.sleep(1000)
+
+ remoteListener.activated = true
+ Datastore.getDatastore(context).remoteListenerDAO().update(remoteListener)
+ }
+ }
+
+ fun toggleRemoteListeners(context: Context, remoteListener: RemoteListeners? = null) {
+ val gatewayClients = Datastore.getDatastore(context).remoteListenerDAO().all
+ gatewayClients.forEach { it.activated = remoteListener?.id == it.id }
+ Datastore.getDatastore(context).remoteListenerDAO().update(gatewayClients)
+ }
+
+ fun startListening(context: Context, remoteListener: RemoteListeners) {
+ CoroutineScope(Dispatchers.Default).launch {
+ toggleRemoteListeners(context, remoteListener)
+
+ val intent = Intent(context, RMQConnectionService::class.java)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent)
+ } else {
+ context.startService(intent)
+ }
+ }
+ }
+
+ /**
+ * This would get queued up until the the constraints are met - once it can execute it is done
+ * Don't use this for any long running metrics - just a constraints metrics
+ */
+ fun startWorkManager(context: Context, remoteListeners: RemoteListeners) {
+ val constraints : Constraints = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build();
+
+ val workManager = WorkManager.getInstance(context)
+
+ val remoteListenersListenerWorker = OneTimeWorkRequestBuilder()
+ .setConstraints(constraints)
+ .setId(generateUuidFromLong(remoteListeners.id))
+ .setBackoffCriteria(
+ BackoffPolicy.LINEAR,
+ WorkRequest.MIN_BACKOFF_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ .setInputData(Data.Builder()
+ .putLong(RemoteListeners.GATEWAY_CLIENT_ID, remoteListeners.id)
+ .build()
+ )
+ .addTag(UNIQUE_WORK_MANAGER_TAG)
+ .build();
+
+ val operation = workManager.enqueueUniqueWork(
+ "$UNIQUE_WORK_MANAGER_NAME.${remoteListeners.id}",
+ ExistingWorkPolicy.REPLACE,
+ remoteListenersListenerWorker
+ )
+
+ println(operation.state.value)
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersQueues.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersQueues.kt
new file mode 100644
index 000000000..b7a6265af
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/Models/RemoteListenersQueues.kt
@@ -0,0 +1,39 @@
+package com.afkanerd.deku.RemoteListeners.Models
+
+import androidx.recyclerview.widget.DiffUtil
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+class RemoteListenersQueues {
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0
+ var gatewayClientId: Long = 0
+
+ var name: String? = null
+ var binding1Name: String? = null
+ var binding2Name: String? = null
+
+
+ override fun equals(obj: Any?): Boolean {
+ if (obj is RemoteListenersQueues) {
+ val remoteListenersQueues = obj
+
+ return remoteListenersQueues.id == this.id &&
+ remoteListenersQueues.name == this.name &&
+ remoteListenersQueues.binding1Name == this.binding1Name &&
+ remoteListenersQueues.binding2Name == this.binding2Name &&
+ remoteListenersQueues.gatewayClientId == this.gatewayClientId
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ var result = id.hashCode()
+ result = 31 * result + gatewayClientId.hashCode()
+ result = 31 * result + (name?.hashCode() ?: 0)
+ result = 31 * result + (binding1Name?.hashCode() ?: 0)
+ result = 31 * result + (binding2Name?.hashCode() ?: 0)
+ return result
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionHandler.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionHandler.kt
new file mode 100644
index 000000000..6e00245ce
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionHandler.kt
@@ -0,0 +1,133 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.rabbitmq.client.AMQP
+import com.rabbitmq.client.BuiltinExchangeType
+import com.rabbitmq.client.Channel
+import com.rabbitmq.client.Connection
+
+class RMQConnectionHandler(var id: Long, var connection: Connection) {
+ private val autoDelete: Boolean = false
+ private val exclusive: Boolean = false
+ private val durable: Boolean = true
+
+ private val channelTagMap = mutableMapOf()
+
+ private val remoteListenersChannelLiveData:
+ MutableLiveData>> = MutableLiveData()
+
+ fun hasChannel(remoteListenersQueues: RemoteListenersQueues, channelNumber: Int): Boolean {
+ return remoteListenersChannelLiveData.value?.get(remoteListenersQueues)
+ ?.find { it.channelNumber == channelNumber } != null
+ }
+
+ fun getChannel(remoteListenersQueues: RemoteListenersQueues, channelNumber: Int) : Channel? {
+ return remoteListenersChannelLiveData.value?.get(remoteListenersQueues)
+ ?.find { it.channelNumber == channelNumber }
+ }
+
+ fun updateChannel(remoteListenersQueues: RemoteListenersQueues, channel: Channel) {
+ remoteListenersChannelLiveData.value?.get(remoteListenersQueues).let { channels ->
+ if(channels?.find { channel == it } != null) {
+ val channels: MutableMap>? =
+ remoteListenersChannelLiveData.value
+
+ channels?.get(remoteListenersQueues)?.toMutableList().let {
+ it?.let {
+ val index = it.indexOf(channel)
+ val listChannels: MutableList = it
+ listChannels[index] = channel
+
+ remoteListenersChannelLiveData.value?.let { rlChannels ->
+ rlChannels[remoteListenersQueues] = it
+ remoteListenersChannelLiveData.postValue(rlChannels)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Channel numbers cannot go beyond the max - connection.channelMax - current 2047
+ * https://github.com/rabbitmq/amqp-0.9.1-spec/blob/main/docs/amqp-0-9-1-reference.md#--------tune------------------------------------shortchannel-max----------------------------------------------------longframe-max----------------------------------------------------shortheartbeat------------------------tune-ok------------
+ */
+ fun createChannel(
+ remoteListenersQueues: RemoteListenersQueues,
+ channelNumber: Int? = null
+ ): Channel? {
+ val prefetchCount = 1
+ val channel = (
+ if(channelNumber != null)
+ connection.createChannel(channelNumber)
+ else connection.createChannel()
+ )
+ channel?.let {
+ it.basicQos(prefetchCount)
+ val channels = remoteListenersChannelLiveData.value ?: mutableMapOf()
+ if(channels.isEmpty() || !channels.containsKey(remoteListenersQueues))
+ channels.put(remoteListenersQueues, listOf(channel))
+ else {
+ channels[remoteListenersQueues] = channels[remoteListenersQueues]!!
+ .plusElement(channel)
+ }
+ remoteListenersChannelLiveData.postValue(channels)
+ }
+
+ return channel
+ }
+
+ fun getChannelsLiveData(): LiveData>> {
+ return remoteListenersChannelLiveData
+ }
+
+ fun bindChannelToTag(channel: Channel, channelTag: String) {
+ channelTagMap[channelTag] = channel
+ }
+
+ fun findChannelByTag(channelTag: String) : Channel? {
+ return channelTagMap[channelTag]
+ }
+
+ fun close() {
+ if (connection.isOpen)
+ connection.close()
+ }
+
+ fun createExchange(
+ exchangeName: String,
+ channel: Channel,
+ ): AMQP.Exchange.DeclareOk? {
+ return channel.exchangeDeclare(
+ exchangeName,
+ BuiltinExchangeType.TOPIC,
+ true
+ )
+ }
+
+ fun createQueue(
+ exchangeName: String,
+ bindingKey: String,
+ channel: Channel,
+ queueName: String = getQueueName(bindingKey),
+ ) : String {
+ channel.queueDeclare(queueName, durable, exclusive, autoDelete, null)
+ channel.queueBind(queueName, exchangeName, bindingKey)
+
+ return queueName
+ }
+
+ companion object {
+ const val MESSAGE_SID: String = "sid"
+
+ const val RMQ_ID: String = "RMQ_ID"
+ const val RMQ_DELIVERY_TAG: String = "RMQ_DELIVERY_TAG"
+ const val RMQ_CONSUMER_TAG: String = "RMQ_CONSUMER_TAG"
+
+ fun getQueueName(binding: String): String {
+ return binding.replace("\\.".toRegex(), "_")
+ }
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionService.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionService.kt
new file mode 100644
index 000000000..dfb848566
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionService.kt
@@ -0,0 +1,246 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.ServiceInfo
+import android.os.Binder
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.MainActivity
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersViewModel
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class RMQConnectionService : Service() {
+ private lateinit var remoteListenersLiveData: LiveData>
+ private lateinit var workManagerLiveData: LiveData>
+ private var rmqConnectionHandlers : MutableLiveData> = MutableLiveData()
+
+ private lateinit var remoteListenersViewModel: RemoteListenersViewModel
+
+ private var numberOfActiveRemoteListeners = 0
+ private var numberFailedToStart = 0
+ private var numberWaitingToStart = 0
+ private var numberStarting = 0
+ private var numberStarted = 0
+
+ // TODO: when the state changes in here, you should know - else would have false readings
+ private val rmqConnectionHandlerObserver = Observer> { rch ->
+ numberStarted = rch.filter { it.connection.isOpen }.size
+
+ val remoteListeners = remoteListenersViewModel.get(applicationContext).value
+ rch.filter{ !it.connection.isOpen }.forEach { rch ->
+ remoteListeners?.find { rch.id == it.id }?.let {
+ RemoteListenersHandler.startWorkManager(applicationContext, it)
+ }
+ }
+ createForegroundNotification()
+ }
+
+ private val remoteListenerObserver = Observer> {
+ var numberOfActiveRemoteListeners = 0
+ it.forEach { remoteListener ->
+ val rl = rmqConnectionHandlers.value?.find{ it.id == remoteListener.id}
+ /**
+ * RemoteListener has been deleted
+ */
+ rmqConnectionHandlers.value?.forEach { rc ->
+ if(it.find{ rc.id == it.id} == null) {
+ CoroutineScope(Dispatchers.Default).launch {
+ rc.connection.close()
+ }
+ }
+ }
+
+ if(remoteListener.activated) {
+ numberOfActiveRemoteListeners += 1
+ if(rl == null || !rl.connection.isOpen)
+ RemoteListenersHandler.startWorkManager(applicationContext, remoteListener)
+ }
+ else {
+ rl?.let {
+ CoroutineScope(Dispatchers.Default).launch {
+ if(it.connection.isOpen) it.close()
+ }
+ }
+ }
+ }
+
+
+ this.numberOfActiveRemoteListeners = numberOfActiveRemoteListeners
+ createForegroundNotification()
+ }
+
+ private val workManagerObserver = Observer> {
+ var numberFailedToStart = 0
+ var numberWaitingToStart = 0
+ var numberStarting = 0
+ var numberStarted = 0
+
+ it.forEach { workInfo ->
+ when(workInfo.state) {
+ WorkInfo.State.ENQUEUED -> {
+ numberWaitingToStart += 1
+ }
+ WorkInfo.State.RUNNING -> {
+ numberStarting += 1
+ }
+ WorkInfo.State.SUCCEEDED -> {
+// numberStarted +=1
+ }
+ WorkInfo.State.FAILED -> {
+ numberFailedToStart += 1
+ }
+ WorkInfo.State.BLOCKED -> {}
+ WorkInfo.State.CANCELLED -> {}
+ }
+ }
+ this.numberFailedToStart = numberFailedToStart
+ this.numberWaitingToStart = numberWaitingToStart
+// this.numberStarted = numberStarted
+ this.numberStarting = numberStarting
+ createForegroundNotification()
+ }
+
+ fun changes(rmqConnection: RMQConnectionHandler) {
+ rmqConnectionHandlers.value?.toMutableList()?.let { mutableList ->
+ val index = mutableList.indexOfFirst { it.id == rmqConnection.id }
+ if (index != -1) {
+ mutableList[index] = rmqConnection
+ } else {
+ mutableList.add(rmqConnection)
+ }
+ rmqConnectionHandlers.postValue(mutableList)
+ }
+ }
+
+ fun putRmqConnection(rmqConnection: RMQConnectionHandler) {
+ if(rmqConnectionHandlers.value != null) {
+ changes(rmqConnection)
+ } else {
+ rmqConnectionHandlers.postValue(listOf(rmqConnection))
+ }
+ }
+
+ fun getRmqConnections(): LiveData> {
+ return rmqConnectionHandlers
+ }
+
+ // Binder given to clients.
+ private val binder = LocalBinder()
+ /**
+ * Class used for the client Binder. Because we know this service always
+ * runs in the same process as its clients, we don't need to deal with IPC.
+ */
+ inner class LocalBinder : Binder() {
+ // Return this instance of LocalService so clients can call public methods.
+ fun getService(): RMQConnectionService = this@RMQConnectionService
+ }
+
+ override fun onBind(intent: Intent): IBinder {
+ return binder
+ }
+
+ override fun unbindService(conn: ServiceConnection) {
+ super.unbindService(conn)
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ remoteListenersViewModel = RemoteListenersViewModel(applicationContext)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ workManagerLiveData.removeObserver(workManagerObserver)
+ rmqConnectionHandlers.removeObserver(rmqConnectionHandlerObserver)
+ remoteListenersLiveData.removeObserver(remoteListenerObserver)
+ rmqConnectionHandlers.value?.forEach { it.close() }
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ // Put content in intent which can be used to kill this in future
+ createForegroundNotification()
+ workManagerLiveData = WorkManager.getInstance(applicationContext)
+ .getWorkInfosByTagLiveData(RemoteListenersHandler.UNIQUE_WORK_MANAGER_TAG).apply {
+ observeForever(workManagerObserver)
+ }
+
+ remoteListenersLiveData = remoteListenersViewModel.get(applicationContext).apply {
+ observeForever(remoteListenerObserver)
+ }
+
+ rmqConnectionHandlers.observeForever(rmqConnectionHandlerObserver)
+
+ return START_STICKY
+ }
+
+ private fun stopForegroundNotification() {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ }
+
+ private fun createForegroundNotification() {
+ if(numberOfActiveRemoteListeners < 1) {
+ stopSelf()
+ stopForegroundNotification()
+ return
+ }
+
+ val notificationIntent = Intent(applicationContext, MainActivity::class.java)
+ val pendingIntent = PendingIntent
+ .getActivity(applicationContext,
+ 0,
+ notificationIntent,
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val title = "$numberOfActiveRemoteListeners Active..."
+ val description = ""
+ .plus("# Failed to start: ")
+ .plus("$numberFailedToStart\n")
+ .plus("# Waiting to start: ")
+ .plus("$numberWaitingToStart\n")
+ .plus("# Starting: ")
+ .plus("$numberStarting\n")
+ .plus("# Connected: ")
+ .plus(numberStarted)
+
+ val notification =
+ NotificationCompat.Builder(
+ applicationContext,
+ getString(R.string.running_gateway_clients_channel_id))
+ .setContentTitle(title)
+ .setContentText("Status")
+ .setSmallIcon(R.drawable.ic_stat_name)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setSilent(true)
+ .setOngoing(true)
+ .setContentIntent(pendingIntent)
+ .setStyle(NotificationCompat.BigTextStyle()
+ .bigText(description))
+ .build()
+ .apply {
+ flags = Notification.FLAG_ONGOING_EVENT
+ }
+
+ val notificationId = getString(R.string.gateway_client_service_notification_id).toInt()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(notificationId, notification,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ } else startForeground(notificationId, notification)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionServiceInitializer.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionServiceInitializer.kt
new file mode 100644
index 000000000..377ac2d8d
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionServiceInitializer.kt
@@ -0,0 +1,44 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.startup.Initializer
+import androidx.work.WorkManagerInitializer
+import com.afkanerd.deku.Datastore
+import com.afkanerd.deku.NotificationsInitializer
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class RMQConnectionServiceInitializer : Initializer {
+ override fun create(context: Context): Intent {
+ val intent = Intent(context, RMQConnectionService::class.java)
+
+ CoroutineScope(Dispatchers.Default).launch {
+ Datastore.getDatastore(context).remoteListenerDAO().all.apply {
+ this.forEach {
+ if(it.activated)
+ RemoteListenersHandler.toggleRemoteListeners(context, it)
+ }
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent)
+ } else {
+ context.startService(intent)
+ }
+ } catch(e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ return intent
+ }
+
+ override fun dependencies(): List>> {
+ return listOf(WorkManagerInitializer::class.java,
+ NotificationsInitializer::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionWorker.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionWorker.kt
new file mode 100644
index 000000000..19abe0671
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQConnectionWorker.kt
@@ -0,0 +1,353 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.os.Bundle
+import android.os.IBinder
+import android.provider.Telephony
+import android.telephony.SubscriptionInfo
+import android.util.Log
+import com.afkanerd.deku.Datastore
+import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver
+import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation
+import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB
+import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
+import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper
+import com.afkanerd.deku.Modules.SemaphoreManager
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.rabbitmq.client.Channel
+import com.rabbitmq.client.ConnectionFactory
+import com.rabbitmq.client.ConsumerShutdownSignalCallback
+import com.rabbitmq.client.DeliverCallback
+import com.rabbitmq.client.Delivery
+import com.rabbitmq.client.ShutdownSignalException
+import com.rabbitmq.client.impl.DefaultExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.Json
+import org.junit.Assert
+import java.nio.charset.StandardCharsets
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+class RMQConnectionWorker(
+ val context: Context,
+ val gatewayClientId: Long
+) {
+
+ /**
+ * - Start connection
+ * - Create channels (per simcard per queue)
+ * - Connect to exchange (create if not exist)
+ * - Connect to queues (create if not exist)
+ */
+
+ @Serializable
+ private data class SMSRequest(val text: String, val to: String, val sid: String, val id: Int)
+
+ private lateinit var rmqConnectionHandler: RMQConnectionHandler
+ private val factory = ConnectionFactory()
+
+ private val databaseConnector: Datastore = Datastore.getDatastore(context)
+
+ private lateinit var messageStateChangedBroadcast: BroadcastReceiver
+
+ private val executorService: ExecutorService = Executors.newFixedThreadPool(4)
+
+ init {
+ handleBroadcast()
+ }
+
+ private lateinit var mService: RMQConnectionService
+ /** Defines callbacks for service binding, passed to bindService(). */
+ private val serviceConnection = object : ServiceConnection {
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance.
+ val binder = service as RMQConnectionService.LocalBinder
+ mService = binder.getService()
+ }
+
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ }
+ }
+
+ fun start(): RMQConnectionHandler {
+ Log.d(javaClass.name, "Starting new service connection...")
+
+ Intent(context, RMQConnectionService::class.java).also { intent ->
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+
+ val gatewayClient = Datastore.getDatastore(context).remoteListenerDAO()
+ .fetch(gatewayClientId)
+
+ factory.username = gatewayClient.username
+ factory.password = gatewayClient.password
+ factory.virtualHost = gatewayClient.virtualHost
+ factory.host = gatewayClient.hostUrl
+ factory.port = gatewayClient.port
+ factory.exceptionHandler = DefaultExceptionHandler()
+
+ /**
+ * Increase connectivity sensitivity
+ */
+ factory.isAutomaticRecoveryEnabled = false
+
+ startConnection(factory, gatewayClient)
+ try {
+ mService.putRmqConnection(rmqConnectionHandler)
+ } catch(e: Exception) {
+ e.printStackTrace()
+ }
+
+ return rmqConnectionHandler
+ }
+
+ private fun startConnection(factory: ConnectionFactory, remoteListener: RemoteListeners) {
+ Log.d(javaClass.name, "Starting new connection...")
+
+ try {
+ val connection = factory.newConnection(
+ executorService,
+ remoteListener.friendlyConnectionName
+ )
+
+ connection.addShutdownListener {
+ /**
+ * The logic here, if the user has not deactivated this - which can be known
+ * from the database connection state then reconnect this client.
+ */
+ Log.e(javaClass.name, "Connection shutdown cause: $it")
+ if (it.isInitiatedByApplication) {
+ mService.changes(rmqConnectionHandler)
+ mService.unbindService(serviceConnection)
+ } else if (remoteListener.activated) {
+ mService.changes(rmqConnectionHandler)
+ mService.unbindService(serviceConnection)
+ }
+ }
+
+ rmqConnectionHandler = RMQConnectionHandler(remoteListener.id, connection)
+ } catch(e: Exception) {
+ e.printStackTrace()
+ if(::rmqConnectionHandler.isInitialized)
+ rmqConnectionHandler.close()
+ throw e
+ }
+
+ try {
+ val remoteListenerQueues = databaseConnector.remoteListenersQueuesDao()
+ .fetchRemoteListenersQueues(remoteListener.id)
+
+ val subscriptionInfoList: List =
+ SIMHandler.getSimCardInformation(context)
+
+ /**
+ * Due to prefetch count, we need just one channel per simcard
+ * - High number of throughput would overwhelm sending and lead to massive failures
+ */
+ remoteListenerQueues.forEachIndexed { i, rlq ->
+ subscriptionInfoList.forEachIndexed { simSlot, subscriptionInfo ->
+ val channelNumber = RemoteListenersHandler.getCarrierId(subscriptionInfo)
+ if(rmqConnectionHandler.hasChannel(rlq, channelNumber)) {
+ rmqConnectionHandler.getChannel(rlq, channelNumber)
+ } else {
+ rmqConnectionHandler.createChannel(
+ rlq,
+ channelNumber,
+ ) .apply { this?.basicRecover(true) }
+ }?.let { channel ->
+ channel.addShutdownListener {
+ Log.e(javaClass.name, "Channel shutdown cause: $it")
+ }
+
+ val bindingName: String? = when(simSlot) {
+ 0 -> {
+ rmqConnectionHandler.createExchange(rlq.name!!, channel)
+ rlq.binding1Name
+ }
+ 1 -> rlq.binding2Name
+ else -> null
+ }
+
+ bindingName?.let {
+ startChannelConsumption(
+ rmqConnectionHandler,
+ channel,
+ subscriptionInfo.subscriptionId,
+ rlq,
+ bindingName
+ )
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ throw e
+ }
+ }
+
+ private fun startChannelConsumption(
+ rmqConnectionHandler: RMQConnectionHandler,
+ channel: Channel,
+ subscriptionId: Int,
+ remoteListenersQueues: RemoteListenersQueues,
+ bindingName: String
+ ) {
+ val deliverCallback = getDeliverCallback(channel, subscriptionId, rmqConnectionHandler.id)
+ val queueName = rmqConnectionHandler.createQueue(
+ exchangeName = remoteListenersQueues.name!!,
+ bindingKey = bindingName,
+ channel = channel,
+ )
+ rmqConnectionHandler.createExchange(remoteListenersQueues.name!!, channel)
+ val messagesCount = channel.messageCount(queueName)
+
+ val consumerTag = channel.basicConsume(
+ queueName,
+ false,
+ deliverCallback,
+ object : ConsumerShutdownSignalCallback {
+ override fun handleShutdownSignal(consumerTag: String, sig: ShutdownSignalException) {
+ Log.e(javaClass.name, "Consumer error", sig)
+ rmqConnectionHandler.updateChannel(
+ remoteListenersQueues,
+ channel
+ )
+ }
+ })
+ Log.d(javaClass.name, "Created Queue: $queueName ($messagesCount) - tag: $consumerTag")
+// rmqConnectionHandler.bindChannelToTag(channel, consumerTag)
+ }
+
+ private fun handleBroadcast() {
+ val intentFilter = IntentFilter()
+ intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_SENT_BROADCAST_INTENT)
+ messageStateChangedBroadcast = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != null && intentFilter.hasAction(intent.action)) {
+ Log.d(javaClass.name, "Received incoming broadcast")
+ if (intent.hasExtra(RMQConnectionHandler.MESSAGE_SID) &&
+ intent.hasExtra(RMQConnectionHandler.RMQ_DELIVERY_TAG)) {
+
+ val sid = intent.getStringExtra(RMQConnectionHandler.MESSAGE_SID)
+ val messageId = intent.getStringExtra(NativeSMSDB.ID)
+
+ val consumerTag = intent.getStringExtra(RMQConnectionHandler.RMQ_CONSUMER_TAG)
+ val deliveryTag =
+ intent.getLongExtra(RMQConnectionHandler.RMQ_DELIVERY_TAG, -1)
+
+ Assert.assertTrue(!consumerTag.isNullOrEmpty())
+ Assert.assertTrue(deliveryTag != -1L)
+
+ rmqConnectionHandler.findChannelByTag(consumerTag!!)?.let {
+ Log.d(javaClass.name, "Received an ACK of the message...")
+ if (resultCode == Activity.RESULT_OK) {
+ CoroutineScope(Dispatchers.Default).launch {
+ if (it.isOpen) it.basicAck(deliveryTag, false)
+ }
+ } else {
+ Log.w(javaClass.name, "Rejecting message sent")
+ CoroutineScope(Dispatchers.Default).launch {
+ if (it.isOpen) it.basicReject(deliveryTag, true)
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ context.registerReceiver(messageStateChangedBroadcast, intentFilter,
+ Context.RECEIVER_EXPORTED)
+ }
+
+ private suspend fun sendSMS(
+ smsRequest: SMSRequest,
+ subscriptionId: Int,
+ consumerTag: String,
+ deliveryTag: Long,
+ rmqConnectionId: Long
+ ) {
+ SemaphoreManager.resourceSemaphore.acquire()
+ val messageId = System.currentTimeMillis()
+ SemaphoreManager.resourceSemaphore.release()
+
+ val threadId = Telephony.Threads.getOrCreateThreadId(context, smsRequest.to)
+
+ val bundle = Bundle()
+ bundle.putString(RMQConnectionHandler.MESSAGE_SID, smsRequest.sid)
+ bundle.putString(RMQConnectionHandler.RMQ_CONSUMER_TAG, consumerTag)
+ bundle.putLong(RMQConnectionHandler.RMQ_DELIVERY_TAG, deliveryTag)
+ bundle.putLong(RMQConnectionHandler.RMQ_ID, rmqConnectionId)
+
+ val conversation = Conversation()
+ conversation.message_id = messageId.toString()
+ conversation.text = smsRequest.text
+ conversation.address = smsRequest.to
+ conversation.subscription_id = subscriptionId
+ conversation.type = Telephony.Sms.MESSAGE_TYPE_OUTBOX
+ conversation.date = System.currentTimeMillis().toString()
+ conversation.thread_id = threadId.toString()
+ conversation.status = Telephony.Sms.STATUS_PENDING
+
+ databaseConnector.conversationDao()._insert(conversation)
+ SMSDatabaseWrapper.send_text(context, conversation, bundle)
+ Log.d(javaClass.name, "SMS sent...")
+ }
+
+ private fun getDeliverCallback(
+ channel: Channel,
+ subscriptionId: Int,
+ rmqConnectionId: Long
+ ): DeliverCallback {
+ return DeliverCallback { consumerTag: String, delivery: Delivery ->
+ val message = String(delivery.body, StandardCharsets.UTF_8)
+ val smsRequest = Json.decodeFromString(message)
+
+ CoroutineScope(Dispatchers.Default).launch {
+ try {
+ sendSMS(smsRequest,
+ subscriptionId,
+ consumerTag,
+ delivery.envelope.deliveryTag,
+ rmqConnectionId)
+ } catch (e: Exception) {
+ Log.e(javaClass.name, "", e)
+ when(e) {
+ is SerializationException -> {
+ channel.let {
+ if (it.isOpen)
+ it.basicReject(delivery.envelope.deliveryTag, false)
+ }
+ }
+ is IllegalArgumentException -> {
+ channel.let {
+ if (it.isOpen)
+ it.basicReject(delivery.envelope.deliveryTag, true)
+ }
+ }
+ else -> {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQLongRunningConnectionWorker.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQLongRunningConnectionWorker.kt
new file mode 100644
index 000000000..8a86dba67
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQLongRunningConnectionWorker.kt
@@ -0,0 +1,60 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ServiceInfo
+import android.os.Build
+import android.provider.Settings.Global.getString
+import androidx.core.app.NotificationCompat
+import androidx.work.CoroutineWorker
+import androidx.work.ForegroundInfo
+import androidx.work.WorkerParameters
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.MainActivity
+import com.google.common.util.concurrent.Service
+
+
+class RMQLongRunningConnectionWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
+ override suspend fun doWork(): Result {
+ setForeground(createForegroundNotification())
+ while(true) {
+ Thread.sleep(1000*10)
+ break
+ }
+ return Result.success()
+ }
+
+
+ private fun createForegroundNotification() : ForegroundInfo{
+ val notificationIntent = Intent(applicationContext, MainActivity::class.java)
+ val pendingIntent = PendingIntent
+ .getActivity(applicationContext,
+ 0,
+ notificationIntent,
+ PendingIntent.FLAG_IMMUTABLE)
+
+ val title = "Long running..."
+ val description = ""
+
+ val notification =
+ NotificationCompat.Builder( applicationContext,
+ applicationContext.getString(R.string.running_gateway_clients_channel_id))
+ .setContentTitle(title)
+ .setContentText("Status")
+ .setSmallIcon(R.drawable.ic_stat_name)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setSilent(true)
+ .setOngoing(true)
+ .setContentIntent(pendingIntent)
+ .setStyle(NotificationCompat.BigTextStyle()
+ .bigText(description))
+ .build()
+ .apply {
+ flags = Notification.FLAG_ONGOING_EVENT
+ }
+
+ return ForegroundInfo(0, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQWorkManager.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQWorkManager.kt
new file mode 100644
index 000000000..899cca036
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/RMQ/RMQWorkManager.kt
@@ -0,0 +1,57 @@
+package com.afkanerd.deku.RemoteListeners.RMQ
+
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.IOException
+import java.net.UnknownHostException
+import java.util.concurrent.TimeoutException
+
+
+class RMQWorkManager(
+ context: Context,
+ workerParams: WorkerParameters,
+) : Worker(context, workerParams) {
+ override fun doWork(): Result {
+ val remoteListenersId = inputData.getLong(RemoteListeners.GATEWAY_CLIENT_ID, -1)
+
+ try {
+ RMQConnectionWorker(applicationContext, remoteListenersId).start().let {
+ if(!it.connection.isOpen) return Result.failure()
+ }
+ } catch(e: Exception) {
+ e.printStackTrace()
+ when(e) {
+ is TimeoutException, is UnknownHostException -> {
+ e.printStackTrace()
+ return Result.retry()
+ }
+ is IOException -> {
+ CoroutineScope(Dispatchers.Main).launch {
+ Toast.makeText(
+ applicationContext,
+ e.cause?.message ?: "",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ return Result.failure()
+ }
+ else -> {
+ Log.e(javaClass.name, "Exception connecting rmq", e)
+ return Result.failure()
+ }
+ }
+ }
+ return Result.success()
+ }
+
+ override fun onStopped() {
+ super.onStopped()
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerCards.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerCards.kt
new file mode 100644
index 000000000..ce25bb7c0
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerCards.kt
@@ -0,0 +1,149 @@
+package com.afkanerd.deku.RemoteListeners.components
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.example.compose.AppTheme
+
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun RemoteListenerCards(
+ remoteListeners: RemoteListeners,
+ status: Boolean,
+ modifier: Modifier
+) {
+ Card(modifier) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(remoteListeners.username!!,
+ color = if(remoteListeners.activated) MaterialTheme.colorScheme.primary
+ else MaterialTheme.colorScheme.secondary,
+ fontWeight = if(remoteListeners.activated) FontWeight.SemiBold else null
+ )
+
+ Spacer(Modifier.weight(1f))
+
+ Row {
+ Text(
+ if(remoteListeners.activated) stringResource(R.string.activated)
+ else stringResource(R.string.deactivated),
+ style = MaterialTheme.typography.bodySmall,
+ color =
+ if(remoteListeners.activated) MaterialTheme.colorScheme.primary
+ else MaterialTheme.colorScheme.secondary
+ )
+ }
+ }
+ Spacer(modifier = Modifier.padding(2.dp))
+ Text(
+ remoteListeners.hostUrl!!,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+ remoteListeners.friendlyConnectionName?.let {
+ Row {
+ Text(
+ stringResource(R.string.friendly_name),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(it,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+ Spacer(modifier = Modifier.padding(2.dp))
+ }
+ Row {
+ Text(
+ stringResource(R.string.port),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(
+ remoteListeners.port.toString(),
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+ Spacer(modifier = Modifier.padding(2.dp))
+ Row {
+ Text(
+ stringResource(R.string.virtual_host),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(
+ remoteListeners.virtualHost!!,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+
+
+ Spacer(modifier = Modifier.padding(8.dp))
+ Row {
+ Text(
+ stringResource(R.string.status),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(
+ if(status) stringResource(R.string.connected)
+ else stringResource(R.string.disconnected),
+ style = MaterialTheme.typography.bodySmall,
+ fontWeight = if(status) FontWeight.SemiBold else null,
+ color =
+ if(remoteListeners.activated) {
+ if(status) MaterialTheme.colorScheme.primary
+ else MaterialTheme.colorScheme.error
+ }
+ else MaterialTheme.colorScheme.secondary
+ )
+ }
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun ConnectionCards_Preview() {
+ val remoteListeners = RemoteListeners()
+ remoteListeners.id = 0
+ remoteListeners.hostUrl = "amqp://example.com"
+ remoteListeners.virtualHost = "/"
+ remoteListeners.port = 5671
+ remoteListeners.username = "example_user"
+ remoteListeners.activated = true
+ remoteListeners.friendlyConnectionName = "frieren"
+ AppTheme {
+ RemoteListenerCards(remoteListeners, false, Modifier)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerQueuesCards.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerQueuesCards.kt
new file mode 100644
index 000000000..724fb84e1
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/components/RemoteListenerQueuesCards.kt
@@ -0,0 +1,189 @@
+package com.afkanerd.deku.RemoteListeners.components
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.room.util.TableInfo
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionHandler
+import com.example.compose.AppTheme
+import com.rabbitmq.client.Channel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+
+@Composable
+fun RemoteListenersQueuesCard(
+ remoteListenersQueues: RemoteListenersQueues,
+ channel1: Channel? = null,
+ channel2: Channel? = null,
+ onClickListener: () -> Unit
+) {
+ Card(onClick = onClickListener ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Text(remoteListenersQueues.name!!)
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ Column(
+ Modifier.fillMaxWidth()
+ ) {
+ val queue1Name = RMQConnectionHandler
+ .getQueueName(remoteListenersQueues.binding1Name!!,)
+
+ var channel1MessageCount = -1L
+ LaunchedEffect(Unit) {
+ launch(Dispatchers.Default) {
+ try {
+ channel1MessageCount = channel1?.messageCount(queue1Name) ?: -1
+ } catch(e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ QueueComponent(
+ bindingName = remoteListenersQueues.binding1Name!!,
+ queueName = queue1Name,
+ channelNumber =
+ if(channel1?.isOpen == true)
+ channel1.channelNumber.toString()
+ else stringResource(R.string.disconnected),
+ messageCount = channel1MessageCount.toString(),
+ status = channel1?.isOpen == true
+ )
+
+ if(!remoteListenersQueues.binding2Name.isNullOrBlank()) {
+ val queue2Name = RMQConnectionHandler
+ .getQueueName(remoteListenersQueues.binding2Name!!)
+
+ var channel2MessageCount = -1L
+ LaunchedEffect(Unit) {
+ launch(Dispatchers.Default) {
+ try {
+ channel2MessageCount = channel1?.messageCount(queue2Name) ?: -1
+ } catch(e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ Spacer(Modifier.padding(8.dp))
+ HorizontalDivider()
+ Spacer(Modifier.padding(8.dp))
+
+ QueueComponent(
+ bindingName = remoteListenersQueues.binding2Name!!,
+ queueName = RMQConnectionHandler
+ .getQueueName(remoteListenersQueues.binding2Name!!,),
+ channelNumber =
+ if(channel2?.isOpen == true)
+ channel2.channelNumber.toString()
+ else stringResource(R.string.disconnected),
+ messageCount = channel2MessageCount.toString(),
+ status = channel2?.isOpen == true
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun QueueComponent(
+ bindingName: String,
+ queueName: String,
+ channelNumber: String,
+ messageCount: String,
+ status: Boolean,
+) {
+ Column {
+ Row {
+ Text(
+ stringResource(R.string.sim_1),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(
+ bindingName,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+ Row {
+ Text(
+ stringResource(R.string.sim_1_queue),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text(
+ queueName,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+
+ Spacer(Modifier.padding(8.dp))
+ Row {
+ Text(
+ stringResource(R.string.channel_number),
+ style = MaterialTheme.typography.labelSmall,
+ color = if(status) MaterialTheme.colorScheme.tertiary
+ else MaterialTheme.colorScheme.secondary,
+ )
+ Text(
+ channelNumber,
+ style = MaterialTheme.typography.bodySmall,
+ color = if(status) MaterialTheme.colorScheme.tertiary
+ else MaterialTheme.colorScheme.secondary,
+ )
+ }
+
+ Row {
+ Text(
+ stringResource(R.string.messages),
+ style = MaterialTheme.typography.labelSmall,
+ color = if(status) MaterialTheme.colorScheme.tertiary
+ else MaterialTheme.colorScheme.secondary,
+ )
+ Text(
+ messageCount,
+ style = MaterialTheme.typography.bodySmall,
+ color = if(status) MaterialTheme.colorScheme.tertiary
+ else MaterialTheme.colorScheme.secondary,
+ )
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun QueuesCards_Preview() {
+ val remoteListenersQueues = RemoteListenersQueues()
+ remoteListenersQueues.name = "Exchange"
+ remoteListenersQueues.binding1Name = "sim_1_binding"
+ remoteListenersQueues.binding2Name = "sim_2_binding"
+ AppTheme {
+ RemoteListenersQueuesCard(remoteListenersQueues, null, null) {}
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerAddQueues.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerAddQueues.kt
new file mode 100644
index 000000000..c13fcf6d7
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerAddQueues.kt
@@ -0,0 +1,230 @@
+package com.afkanerd.deku.RemoteListeners.modals
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.SheetValue
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberStandardBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.afkanerd.deku.DefaultSMS.BuildConfig
+import com.example.compose.AppTheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.ImeAction
+import com.afkanerd.deku.DefaultSMS.Models.SIMHandler
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionHandler
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RemoteListenerAddQueuesModal(
+ showModal: Boolean,
+ remoteListenersQueue: RemoteListenersQueues?,
+ remoteListener: RemoteListeners,
+ onClickCallback: (RemoteListenersQueues) -> Unit,
+ dismissCallback: () -> Unit,
+) {
+ val state = rememberStandardBottomSheetState(
+ initialValue = if(BuildConfig.DEBUG) SheetValue.Expanded else SheetValue.Hidden,
+ skipHiddenState = false
+ )
+
+ var showModal by remember { mutableStateOf(showModal) }
+
+ var exchange by remember { mutableStateOf(remoteListenersQueue?.name ?: "") }
+ var sim1Binding by remember { mutableStateOf(remoteListenersQueue?.binding1Name ?: "") }
+ var sim2Binding by remember { mutableStateOf(remoteListenersQueue?.binding2Name ?: "") }
+
+ var sim1QueueName by remember { mutableStateOf("") }
+ var sim2QueueName by remember { mutableStateOf("") }
+
+ val context = LocalContext.current
+ val inspectMode = LocalInspectionMode.current
+
+ val isDualSim by remember{
+ mutableStateOf(if(inspectMode) true else SIMHandler.isDualSim(context))
+ }
+
+ LaunchedEffect(sim1Binding, sim2Binding) {
+ sim1QueueName = RMQConnectionHandler.getQueueName(sim1Binding)
+ sim2QueueName = RMQConnectionHandler.getQueueName(sim1Binding)
+ }
+
+ if(showModal) {
+ ModalBottomSheet(
+ onDismissRequest = {
+ showModal = false
+ dismissCallback()
+ },
+ sheetState = state,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(stringResource(R.string.new_queues), style = MaterialTheme.typography.titleMedium)
+
+ Spacer(Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = exchange,
+ supportingText = {
+ Text(stringResource(R.string.defaulting_to_topic_exchange))
+ },
+ onValueChange = {
+ exchange = RemoteListenersHandler.getPublisherDetails(context, it)
+ .let { details ->
+ if(details.isNotEmpty()) {
+ sim1Binding = details[0]
+ if(details.getOrNull(1) != null)
+ sim2Binding = details[1]
+ }
+ it
+ }
+ },
+ placeholder = {
+ Text(stringResource(R.string.exchange))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ )
+ )
+
+ Spacer(Modifier.padding(16.dp))
+
+ Column(Modifier.fillMaxWidth()) {
+ Row {
+ Text(
+ stringResource(R.string.auto_filled_to),
+ style=MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text("{exchange}.{operator_country_code}.{operator_id}",
+ style=MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+ }
+
+ OutlinedTextField(
+ value = sim1Binding,
+ onValueChange = {
+ sim1Binding = it
+ },
+ label = {
+ Text(stringResource(R.string.sim_1_binding))
+ },
+ supportingText = {
+ Row {
+ Text(stringResource(R.string.queue_name))
+ Text(sim1QueueName)
+ }
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ )
+ )
+ Spacer(Modifier.padding(8.dp))
+
+ if(isDualSim) {
+
+ Column(Modifier.fillMaxWidth()) {
+ Row {
+ Text(
+ stringResource(R.string.auto_filled_to),
+ style=MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Text("{exchange}.{operator_country_code}.{operator_id}",
+ style=MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+ }
+ OutlinedTextField(
+ value = sim2Binding,
+ onValueChange = {
+ sim2Binding = it
+ },
+ label = {
+ Row {
+ Text(stringResource(R.string.queue_name))
+ Text(sim2QueueName)
+ }
+ },
+ supportingText = {
+ Text(stringResource(R.string.queue_name, sim2QueueName))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ )
+ )
+ }
+
+ Spacer(Modifier.padding(16.dp))
+
+ if(remoteListenersQueue != null) {
+ Text(
+ stringResource(R.string.editing_a_queue_would_restart_the_connection),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.tertiary
+ )
+ }
+ Button(onClick = {
+ val rlq = RemoteListenersQueues()
+ rlq.name = exchange
+ rlq.binding1Name = sim1Binding
+ rlq.binding2Name = sim2Binding
+ rlq.gatewayClientId = remoteListener.id
+
+ onClickCallback(rlq)
+ }, enabled = exchange.isNotBlank() && sim1Binding.isNotEmpty()) {
+ Text(if(remoteListenersQueue == null) stringResource(R.string.add)
+ else stringResource( R.string.edit ))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun RemoteListenersAddQueuesModal_Preview() {
+ AppTheme {
+ RemoteListenerAddQueuesModal(
+ true,
+ RemoteListenersQueues(),
+ RemoteListeners(),
+ {}
+ ){}
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerModal.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerModal.kt
new file mode 100644
index 000000000..1e8527a11
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/modals/RemoteListenerModal.kt
@@ -0,0 +1,96 @@
+package com.afkanerd.deku.RemoteListeners.modals
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.SheetValue
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.rememberStandardBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.afkanerd.deku.DefaultSMS.BuildConfig
+import com.example.compose.AppTheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.style.TextAlign
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RemoteListenerModal(
+ showModal: Boolean,
+ activated: Boolean,
+ editCallback: () -> Unit,
+ connectionCallback: () -> Unit,
+ deleteCallback: () -> Unit,
+ dismissCallback: () -> Unit,
+) {
+ val state = rememberStandardBottomSheetState(
+ initialValue = if(BuildConfig.DEBUG) SheetValue.Expanded else SheetValue.Hidden,
+ skipHiddenState = false
+ )
+ var showModal by remember { mutableStateOf(showModal) }
+
+ if(showModal) {
+ ModalBottomSheet(
+ onDismissRequest = {
+ showModal = false
+ dismissCallback()
+ },
+ sheetState = state,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text("Configure Remote listener",
+ style = MaterialTheme.typography.titleMedium)
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ Button(onClick = connectionCallback, modifier = Modifier.fillMaxWidth()) {
+ Text(if(activated) "Deactivate" else "Activate")
+ }
+ Text(
+ if(activated)
+ "Deactivating stops the remote listener and tries to kill all remote connections."
+ else "Activating begins the service that tries to connect this remote listener",
+ style = MaterialTheme.typography.labelSmall,
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Spacer(modifier = Modifier.padding(16.dp))
+
+ Button(onClick = editCallback, modifier = Modifier.fillMaxWidth()) {
+ Text("Edit" )
+ }
+
+ TextButton(onClick = deleteCallback) {
+ Text("Delete", color = MaterialTheme.colorScheme.error)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun RemoteListenersModal_Preview() {
+ AppTheme {
+ RemoteListenerModal(true, true, {}, {}, {}) {}
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQAdd.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQAdd.kt
new file mode 100644
index 000000000..10e5299c9
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQAdd.kt
@@ -0,0 +1,313 @@
+package com.afkanerd.deku.RemoteListeners.ui
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import com.example.compose.AppTheme
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.unit.dp
+import com.afkanerd.deku.DefaultSMS.BuildConfig
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersViewModel
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListenersScreen
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RMQAddComposable(
+ navController: NavController,
+ remoteListenerViewModel: RemoteListenersViewModel
+) {
+
+ val remoteListener = remoteListenerViewModel.remoteListener
+
+ var hostUrl by remember { mutableStateOf(remoteListener?.hostUrl ?: "" ) }
+ var username by remember { mutableStateOf(remoteListener?.username ?: "" ) }
+ var password by remember { mutableStateOf(remoteListener?.password ?: "" ) }
+ var friendlyName by remember { mutableStateOf(remoteListener?.friendlyConnectionName ?: "" ) }
+ var virtualHost by remember { mutableStateOf(remoteListener?.virtualHost ?: "/" ) }
+ var port by remember { mutableIntStateOf(remoteListener?.port ?: 5672 ) }
+
+ var passwordVisible by remember { mutableStateOf(false) }
+
+ val protocolOptions = listOf("amqp", "amqps")
+ val (selectedOption, onOptionSelected) = remember { mutableStateOf(protocolOptions[0]) }
+
+ if(BuildConfig.DEBUG) {
+ LaunchedEffect(Unit) {
+ hostUrl = "staging.smswithoutborders.com"
+ username = "sherlock"
+ password = "asshole"
+ friendlyName = "android-emulator"
+ }
+ }
+
+ BackHandler {
+ remoteListenerViewModel.remoteListener = null
+ navController.popBackStack()
+ }
+
+ val scrollBehaviour = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
+ val context = LocalContext.current
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.new_remote_listener))},
+ navigationIcon = {
+ IconButton(onClick = {
+ remoteListenerViewModel.remoteListener = null
+ navController.popBackStack()
+ }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.ArrowBack,
+ contentDescription = stringResource(R.string.return_back)
+ )
+ }
+ },
+ actions = { },
+ scrollBehavior = scrollBehaviour
+ )
+ },
+ modifier = Modifier
+ .nestedScroll(scrollBehaviour.nestedScrollConnection),
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .imePadding()
+ .padding(innerPadding)
+ .padding(8.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ OutlinedTextField(
+ value = hostUrl,
+ onValueChange = { hostUrl = it },
+ label = {
+ Text(stringResource(R.string.host_url))
+ },
+ placeholder = {
+ Text(stringResource(R.string.example_com))
+ },
+ prefix = {
+ Text("amqp(s)://")
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ )
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = username,
+ onValueChange = { username = it },
+ label = {
+ Text(stringResource(R.string.username))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ )
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = password,
+ onValueChange = { password = it },
+ label = {
+ Text(stringResource(R.string.password))
+ },
+ visualTransformation =
+ if (passwordVisible) VisualTransformation.None
+ else PasswordVisualTransformation(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
+ trailingIcon = {
+ val image = if (passwordVisible)
+ Icons.Filled.Visibility
+ else Icons.Filled.VisibilityOff
+
+ val description =
+ if(passwordVisible) stringResource(R.string.hide_password)
+ else stringResource( R.string.show_password )
+ IconButton(onClick = { passwordVisible = !passwordVisible }) {
+ Icon(imageVector = image, description)
+ }
+ },
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = friendlyName,
+ onValueChange = { friendlyName = it },
+ label = {
+ Text(stringResource(R.string.friendly_name))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ )
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = virtualHost,
+ onValueChange = { virtualHost = it },
+ label = {
+ Text(stringResource(R.string.virtual_host))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ )
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ OutlinedTextField(
+ value = port.toString(),
+ onValueChange = { port = it.toInt() },
+ label = {
+ Text(stringResource(R.string.port))
+ },
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next,
+ keyboardType = KeyboardType.Number
+ )
+ )
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ Text(stringResource(R.string.choose_protocol))
+
+ Column(Modifier.selectableGroup()) {
+ protocolOptions.forEach { text ->
+ Row (
+ Modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .selectable(
+ selected = (text == selectedOption),
+ onClick = { onOptionSelected(text) },
+ role = Role.RadioButton
+ )
+ .padding(horizontal = 16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
+ selected = (text == selectedOption),
+ enabled = text != "amqps",
+ onClick = null // null recommended for accessibility with screen readers
+ )
+ Text(
+ text = text,
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(start = 16.dp)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.padding(8.dp))
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(onClick = {
+ val newRemoteListener = remoteListener ?: RemoteListeners()
+ newRemoteListener.hostUrl = hostUrl
+ newRemoteListener.username = username
+ newRemoteListener.password = password
+ newRemoteListener.friendlyConnectionName = friendlyName
+ newRemoteListener.virtualHost = virtualHost
+ newRemoteListener.port = port.toInt()
+ newRemoteListener.protocol = selectedOption
+
+ CoroutineScope(Dispatchers.Default).launch {
+ if(remoteListener != null)
+ remoteListenerViewModel.update(newRemoteListener)
+ else
+ remoteListenerViewModel.insert(newRemoteListener)
+
+ RemoteListenersHandler.onOffAgain(context, remoteListener!!)
+
+ launch(Dispatchers.Main) {
+ navController.popBackStack(RemoteListenersScreen, false)
+ }
+ }
+ }, enabled = hostUrl.isNotEmpty() && username.isNotEmpty() && password.isNotEmpty()) {
+ Text(
+ if(remoteListener == null) stringResource(R.string.add)
+ else stringResource(R.string.edit)
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun RMQAddComposable_Preview() {
+ AppTheme {
+ RMQAddComposable( navController = rememberNavController(), RemoteListenersViewModel(
+ LocalContext.current
+ ))
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQMain.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQMain.kt
new file mode 100644
index 000000000..ccf427ca6
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQMain.kt
@@ -0,0 +1,283 @@
+package com.afkanerd.deku.RemoteListeners.ui
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material.icons.rounded.AddCircleOutline
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListeners
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersViewModel
+import com.afkanerd.deku.RemoteListeners.components.RemoteListenerCards
+import com.example.compose.AppTheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.stringResource
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenerQueuesViewModel
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionHandler
+import com.afkanerd.deku.RemoteListeners.modals.RemoteListenerModal
+import com.afkanerd.deku.RemoteListenersAddScreen
+import com.afkanerd.deku.RemoteListenersQueuesScreen
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@Composable
+fun RMQMainComposable(
+ _remoteListeners: List = emptyList(),
+ remoteListenerViewModel: RemoteListenersViewModel,
+ remoteListenerProjectsViewModel: RemoteListenerQueuesViewModel,
+ navController: NavController,
+) {
+ val context = LocalContext.current
+ val listState = rememberLazyListState()
+ val scrollBehaviour = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
+
+ val remoteListeners: List = if(LocalInspectionMode.current) _remoteListeners
+ else remoteListenerViewModel.get(context).observeAsState(emptyList()).value
+
+ val rmqConnectionHandlers: List =
+ if(LocalInspectionMode.current) emptyList()
+ else remoteListenerViewModel.getRmqConnections().observeAsState(emptyList()).value
+
+ var showRemoteListenerModal by remember { mutableStateOf(false) }
+
+ BackHandler {
+ remoteListenerViewModel.remoteListener = null
+ navController.popBackStack()
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.remote_listeners)) },
+ navigationIcon = {
+ IconButton(onClick = {
+ remoteListenerViewModel.remoteListener = null
+ navController.popBackStack()
+ }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.ArrowBack,
+ contentDescription = stringResource(R.string.return_back)
+ )
+ }
+ },
+ actions = {
+ IconButton(onClick = {
+ remoteListenerViewModel.remoteListener = null
+ navController.navigate(RemoteListenersAddScreen)
+ }) {
+ Icon(
+ imageVector = Icons.Rounded.AddCircleOutline,
+ contentDescription = stringResource(R.string.new_remote_listener)
+ )
+ }
+ },
+ scrollBehavior = scrollBehaviour
+ )
+ },
+ modifier = Modifier
+ .nestedScroll(scrollBehaviour.nestedScrollConnection),
+ ) { innerPadding ->
+ Box(
+ modifier = Modifier
+ .padding(8.dp)
+ .padding(innerPadding)
+ .fillMaxSize(),
+ ) {
+ Column {
+ if( remoteListeners.isEmpty()) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Text(
+ stringResource(R.string.no_remote_listeners),
+ style = MaterialTheme.typography.titleMedium)
+ }
+ }
+ else {
+ Column(modifier = Modifier.padding(8.dp)) {
+ Text(
+ stringResource(R.string.click_to_add_queues),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ Spacer(Modifier.padding(4.dp))
+ Text(
+ stringResource(R.string.press_and_hold_to_manage),
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.secondary
+ )
+ }
+
+ Column(modifier = Modifier.padding(8.dp)) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = RoundedCornerShape(4.dp)
+ )
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier.padding(8.dp)
+ ) {
+ Icon(imageVector = Icons.Outlined.Info, "")
+ Text(
+ stringResource(R.string.only_1_connection_at_a_time_due_to_the_bottleneck_between_channels_and_phone_radios_ability_to_sms_messages_in_parallel),
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onTertiaryContainer,
+ modifier = Modifier
+ .padding(16.dp)
+ )
+ }
+ }
+ }
+
+ Spacer(Modifier.padding(8.dp))
+
+ LazyColumn(
+ state = listState,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ itemsIndexed(
+ items = remoteListeners,
+ key = { _, remoteListener -> remoteListener.id}
+ ) { _, remoteListener ->
+ RemoteListenerCards(
+ remoteListener,
+ if(LocalInspectionMode.current) true else
+ rmqConnectionHandlers.find{ remoteListener.id == it.id}
+ ?.connection?.isOpen == true,
+ modifier = Modifier.combinedClickable(
+ onClick = {
+ remoteListenerViewModel.remoteListener = remoteListener
+ navController.navigate(RemoteListenersQueuesScreen)
+ },
+ onLongClick = {
+ remoteListenerViewModel.remoteListener = remoteListener
+ showRemoteListenerModal = true
+ }
+ ),
+ )
+ }
+ }
+ }
+ }
+
+ if(showRemoteListenerModal) {
+ val activated = remoteListenerViewModel.remoteListener?.activated == true
+ RemoteListenerModal(
+ showModal = showRemoteListenerModal,
+ activated = activated,
+ editCallback = {
+ showRemoteListenerModal = false
+ navController.navigate(RemoteListenersAddScreen)
+ },
+ connectionCallback = {
+ if(activated) {
+ //Deactivating
+ remoteListenerViewModel.remoteListener?.activated = false
+ RemoteListenersHandler.stopListening(
+ context,
+ remoteListenerViewModel.remoteListener!!
+ )
+ } else {
+ //Activating
+ remoteListenerViewModel.remoteListener?.activated = true
+ RemoteListenersHandler.startListening(
+ context,
+ remoteListenerViewModel.remoteListener!!
+ )
+ }
+ showRemoteListenerModal = false
+ },
+ deleteCallback = {
+ CoroutineScope(Dispatchers.Default).launch {
+ remoteListenerProjectsViewModel.delete(
+ context,
+ remoteListenerViewModel.remoteListener!!.id
+ )
+ remoteListenerViewModel.delete(
+ remoteListenerViewModel.remoteListener!!
+ )
+ showRemoteListenerModal = false
+ }
+ }
+ ) {
+ showRemoteListenerModal = false
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun ConnectionCards_Preview() {
+ AppTheme {
+ val remoteListeners = RemoteListeners()
+ remoteListeners.id = 0
+ remoteListeners.hostUrl = "amqp://example.com"
+ remoteListeners.virtualHost = "/"
+ remoteListeners.port = 5671
+ remoteListeners.username = "example_user"
+
+ val remoteListeners1 = RemoteListeners()
+ remoteListeners1.id = 1
+ remoteListeners1.hostUrl = "amqp://example.com"
+ remoteListeners1.virtualHost = "/"
+ remoteListeners1.port = 5671
+ remoteListeners1.username = "example_user"
+
+ RMQMainComposable(
+ listOf(remoteListeners, remoteListeners1),
+ RemoteListenersViewModel(),
+ RemoteListenerQueuesViewModel(),
+ rememberNavController(),
+ )
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQQueues.kt b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQQueues.kt
new file mode 100644
index 000000000..0ebff2e4e
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/RemoteListeners/ui/RMQQueues.kt
@@ -0,0 +1,233 @@
+package com.afkanerd.deku.RemoteListeners.ui
+
+import android.content.Context
+import android.content.ContextWrapper
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.rounded.AddCircleOutline
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.navigation.NavController
+import androidx.navigation.compose.rememberNavController
+import androidx.work.multiprocess.RemoteWorkerService
+import com.afkanerd.deku.DefaultSMS.R
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenerQueuesViewModel
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersQueues
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListener.RemoteListenersViewModel
+import com.afkanerd.deku.RemoteListeners.Models.RemoteListenersHandler
+import com.afkanerd.deku.RemoteListeners.RMQ.RMQConnectionService
+import com.afkanerd.deku.RemoteListeners.components.RemoteListenersQueuesCard
+import com.afkanerd.deku.RemoteListeners.modals.RemoteListenerAddQueuesModal
+import com.afkanerd.deku.RemoteListenersScreen
+import com.example.compose.AppTheme
+import com.rabbitmq.client.Channel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import okhttp3.internal.notify
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@Composable
+fun RMQQueuesComposable(
+ _remoteListenersQueues: List = emptyList(),
+ remoteListenersViewModel: RemoteListenersViewModel,
+ navController: NavController
+) {
+ val scrollBehaviour = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
+ val listState = rememberLazyListState()
+ val context = LocalContext.current
+
+ var showRemoteListenerAddQueuesModal by remember { mutableStateOf(false) }
+ val remoteListenersQueuesViewModel = RemoteListenerQueuesViewModel()
+
+ val remoteListenersQueues: List =
+ if(LocalInspectionMode.current) _remoteListenersQueues
+ else remoteListenersQueuesViewModel.get(context,
+ remoteListenersViewModel.remoteListener!!.id
+ ).observeAsState(emptyList()).value
+
+ val lifeCycleOwner = LocalLifecycleOwner.current
+ var channelsObserver: MutableLiveData>> =
+ MutableLiveData()
+
+ val channels by channelsObserver
+ .observeAsState(emptyMap>())
+
+ if(!LocalInspectionMode.current)
+ remoteListenersViewModel.binder.getService().getRmqConnections()
+ .observe(lifeCycleOwner) {
+ it.find { it.id == remoteListenersViewModel.remoteListener!!.id }.let {
+ it!!.getChannelsLiveData().observe(lifeCycleOwner) { queueChannels ->
+ channelsObserver.value = queueChannels
+ }
+ }
+ }
+
+ BackHandler {
+ remoteListenersQueuesViewModel.remoteListenerQueues = null
+ navController.popBackStack(RemoteListenersScreen, false)
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(
+ stringResource(
+ R.string.queues,
+ remoteListenersViewModel.remoteListener?.username!!
+ )
+ ) },
+ navigationIcon = {
+ IconButton(onClick = {
+ remoteListenersQueuesViewModel.remoteListenerQueues = null
+ navController.popBackStack(RemoteListenersScreen, false)
+ }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.ArrowBack,
+ contentDescription = stringResource(R.string.return_back)
+ )
+ }
+ },
+ actions = {
+ IconButton(onClick = {
+ showRemoteListenerAddQueuesModal = true
+ }) {
+ Icon(
+ imageVector = Icons.Rounded.AddCircleOutline,
+ contentDescription = stringResource(R.string.new_remote_listener)
+ )
+ }
+ },
+ scrollBehavior = scrollBehaviour
+ )
+ },
+ modifier = Modifier
+ .nestedScroll(scrollBehaviour.nestedScrollConnection),
+ ) { innerPadding ->
+ Box(
+ modifier = Modifier
+ .padding(8.dp)
+ .padding(innerPadding)
+ .fillMaxSize(),
+ ) {
+ Column {
+ if( remoteListenersQueues.isEmpty()) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Text(
+ stringResource(R.string.no_queues_added),
+ style = MaterialTheme.typography.titleMedium)
+ }
+ }
+ else {
+ LazyColumn(
+ state = listState,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ itemsIndexed(
+ items = remoteListenersQueues,
+ key = { _, remoteListenerQueue -> remoteListenerQueue.id}
+ ) { _, remoteListenerQueue ->
+ RemoteListenersQueuesCard(
+ remoteListenersQueues = remoteListenerQueue,
+ channel1 = channels[remoteListenerQueue]?.getOrNull(0),
+ channel2 = channels[remoteListenerQueue]?.getOrNull(1),
+ ) {
+ remoteListenersQueuesViewModel.remoteListenerQueues =
+ remoteListenerQueue
+ showRemoteListenerAddQueuesModal = true
+ }
+ }
+ }
+ }
+ }
+
+ if(showRemoteListenerAddQueuesModal) {
+ RemoteListenerAddQueuesModal(
+ showModal = showRemoteListenerAddQueuesModal,
+ remoteListenersQueue = remoteListenersQueuesViewModel.remoteListenerQueues,
+ remoteListener = remoteListenersViewModel.remoteListener!!,
+ onClickCallback = {
+ val newRemoteListenerQueues = it
+ remoteListenersQueuesViewModel.remoteListenerQueues?.let {
+ newRemoteListenerQueues.id = it.id
+ }
+
+ CoroutineScope(Dispatchers.Default).launch {
+ if(remoteListenersQueuesViewModel.remoteListenerQueues != null)
+ remoteListenersQueuesViewModel.update(newRemoteListenerQueues)
+ else
+ remoteListenersQueuesViewModel.insert(newRemoteListenerQueues)
+
+ showRemoteListenerAddQueuesModal = false
+
+ RemoteListenersHandler.onOffAgain(context,
+ remoteListenersViewModel.remoteListener!!)
+ }
+ },
+ ) {
+ showRemoteListenerAddQueuesModal = false
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun RMQQueuesComposable_Preview() {
+ val rlq = RemoteListenersQueues()
+ rlq.id = 0
+ rlq.name = "rlq"
+ rlq.binding1Name = "binding1"
+ rlq.binding2Name = "binding2"
+
+ AppTheme {
+ RMQQueuesComposable(
+ listOf(rlq),
+ RemoteListenersViewModel(LocalContext.current),
+ rememberNavController()
+ )
+ }
+}
diff --git a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerRoutedActivity.kt b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerRoutedActivity.kt
index a77d93b10..2eac6e936 100644
--- a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerRoutedActivity.kt
+++ b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerRoutedActivity.kt
@@ -16,7 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkInfo
import com.afkanerd.deku.Datastore
-import com.afkanerd.deku.DefaultSMS.MainActivity
+import com.afkanerd.deku.MainActivity
import com.afkanerd.deku.Modules.ThreadingPoolExecutor
import com.afkanerd.deku.DefaultSMS.R
import com.afkanerd.deku.Router.Models.RouterHandler
diff --git a/app/src/main/java/com/afkanerd/deku/screens.kt b/app/src/main/java/com/afkanerd/deku/screens.kt
new file mode 100644
index 000000000..0ca968914
--- /dev/null
+++ b/app/src/main/java/com/afkanerd/deku/screens.kt
@@ -0,0 +1,22 @@
+package com.afkanerd.deku
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+object HomeScreen
+@Serializable
+object ConversationsScreen
+@Serializable
+object ComposeNewMessageScreen
+@Serializable
+object SearchThreadScreen
+@Serializable
+object ContactDetailsScreen
+
+// Remote Listeners
+@Serializable
+object RemoteListenersScreen
+@Serializable
+object RemoteListenersQueuesScreen
+@Serializable
+object RemoteListenersAddScreen
diff --git a/app/src/main/res/drawable/baseline_arrow_forward_ios_24.xml b/app/src/main/res/drawable/baseline_arrow_forward_ios_24.xml
deleted file mode 100644
index 56d8468d3..000000000
--- a/app/src/main/res/drawable/baseline_arrow_forward_ios_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/baseline_enhanced_encryption_24.xml b/app/src/main/res/drawable/baseline_enhanced_encryption_24.xml
deleted file mode 100644
index b91313962..000000000
--- a/app/src/main/res/drawable/baseline_enhanced_encryption_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/baseline_unarchive_24.xml b/app/src/main/res/drawable/baseline_unarchive_24.xml
deleted file mode 100644
index 9e19692d0..000000000
--- a/app/src/main/res/drawable/baseline_unarchive_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/bubble_round_speech.xml b/app/src/main/res/drawable/bubble_round_speech.xml
deleted file mode 100644
index a529cc467..000000000
--- a/app/src/main/res/drawable/bubble_round_speech.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/exclamation_circle_svgrepo_com.xml b/app/src/main/res/drawable/exclamation_circle_svgrepo_com.xml
deleted file mode 100644
index 5a32c6447..000000000
--- a/app/src/main/res/drawable/exclamation_circle_svgrepo_com.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/hello_rafiki.xml b/app/src/main/res/drawable/hello_rafiki.xml
deleted file mode 100644
index b576d415c..000000000
--- a/app/src/main/res/drawable/hello_rafiki.xml
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index ca3826a46..3babbfea7 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,74 +1,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..1520de8be
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_search_24.xml b/app/src/main/res/drawable/ic_outline_search_24.xml
deleted file mode 100644
index 7ef86ca8f..000000000
--- a/app/src/main/res/drawable/ic_outline_search_24.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/imagegallery_120168.xml b/app/src/main/res/drawable/imagegallery_120168.xml
deleted file mode 100644
index fd5d89ac8..000000000
--- a/app/src/main/res/drawable/imagegallery_120168.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/received_mesages_start_view_drawable.xml b/app/src/main/res/drawable/received_mesages_start_view_drawable.xml
deleted file mode 100644
index 0fecf160c..000000000
--- a/app/src/main/res/drawable/received_mesages_start_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/received_messages_drawable.xml b/app/src/main/res/drawable/received_messages_drawable.xml
deleted file mode 100644
index 2d11f85aa..000000000
--- a/app/src/main/res/drawable/received_messages_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/received_messages_end_view_drawable.xml b/app/src/main/res/drawable/received_messages_end_view_drawable.xml
deleted file mode 100644
index 36b964ee7..000000000
--- a/app/src/main/res/drawable/received_messages_end_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/received_messages_middle_view_drawable.xml b/app/src/main/res/drawable/received_messages_middle_view_drawable.xml
deleted file mode 100644
index 568365fce..000000000
--- a/app/src/main/res/drawable/received_messages_middle_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/round_archive_24.xml b/app/src/main/res/drawable/round_archive_24.xml
deleted file mode 100644
index 02da7a3d6..000000000
--- a/app/src/main/res/drawable/round_archive_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_arrow_back_ios_24.xml b/app/src/main/res/drawable/round_arrow_back_ios_24.xml
deleted file mode 100644
index 11ed85221..000000000
--- a/app/src/main/res/drawable/round_arrow_back_ios_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_call_24.xml b/app/src/main/res/drawable/round_call_24.xml
deleted file mode 100644
index 750fae802..000000000
--- a/app/src/main/res/drawable/round_call_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_inbox_24.xml b/app/src/main/res/drawable/round_inbox_24.xml
deleted file mode 100644
index 765fee41b..000000000
--- a/app/src/main/res/drawable/round_inbox_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_lock_24.xml b/app/src/main/res/drawable/round_lock_24.xml
deleted file mode 100644
index a4f9f518a..000000000
--- a/app/src/main/res/drawable/round_lock_24.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/round_menu_24.xml b/app/src/main/res/drawable/round_menu_24.xml
deleted file mode 100644
index 60ea5b26e..000000000
--- a/app/src/main/res/drawable/round_menu_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_notifications_off_24.xml b/app/src/main/res/drawable/round_notifications_off_24.xml
deleted file mode 100644
index d3d69abba..000000000
--- a/app/src/main/res/drawable/round_notifications_off_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/round_refresh_24.xml b/app/src/main/res/drawable/round_refresh_24.xml
deleted file mode 100644
index fe35755ed..000000000
--- a/app/src/main/res/drawable/round_refresh_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/sent_messages_drawable.xml b/app/src/main/res/drawable/sent_messages_drawable.xml
deleted file mode 100644
index 65a797100..000000000
--- a/app/src/main/res/drawable/sent_messages_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/sent_messages_end_view_drawable.xml b/app/src/main/res/drawable/sent_messages_end_view_drawable.xml
deleted file mode 100644
index f0212d2d5..000000000
--- a/app/src/main/res/drawable/sent_messages_end_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/sent_messages_middle_view_drawable.xml b/app/src/main/res/drawable/sent_messages_middle_view_drawable.xml
deleted file mode 100644
index 80af6b8b1..000000000
--- a/app/src/main/res/drawable/sent_messages_middle_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/sent_messages_start_view_drawable.xml b/app/src/main/res/drawable/sent_messages_start_view_drawable.xml
deleted file mode 100644
index 583a19b47..000000000
--- a/app/src/main/res/drawable/sent_messages_start_view_drawable.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/twotone_folder_24.xml b/app/src/main/res/drawable/twotone_folder_24.xml
deleted file mode 100644
index 1db9dec0b..000000000
--- a/app/src/main/res/drawable/twotone_folder_24.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/twotone_send_24.xml b/app/src/main/res/drawable/twotone_send_24.xml
deleted file mode 100644
index db57a7f75..000000000
--- a/app/src/main/res/drawable/twotone_send_24.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/undraw_team_work_i1f3.xml b/app/src/main/res/drawable/undraw_team_work_i1f3.xml
new file mode 100644
index 000000000..d5cbd71f0
--- /dev/null
+++ b/app/src/main/res/drawable/undraw_team_work_i1f3.xml
@@ -0,0 +1,295 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_compose_new_message.xml b/app/src/main/res/layout/activity_compose_new_message.xml
deleted file mode 100644
index 8ae69ea12..000000000
--- a/app/src/main/res/layout/activity_compose_new_message.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_conversations.xml b/app/src/main/res/layout/activity_conversations.xml
deleted file mode 100644
index 820dcf586..000000000
--- a/app/src/main/res/layout/activity_conversations.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_conversations2.xml b/app/src/main/res/layout/activity_conversations2.xml
deleted file mode 100644
index 8bc656d29..000000000
--- a/app/src/main/res/layout/activity_conversations2.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_conversations_threads.xml b/app/src/main/res/layout/activity_conversations_threads.xml
deleted file mode 100644
index c8ab26d66..000000000
--- a/app/src/main/res/layout/activity_conversations_threads.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_default_check.xml b/app/src/main/res/layout/activity_default_check.xml
deleted file mode 100644
index 5d78bd149..000000000
--- a/app/src/main/res/layout/activity_default_check.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_gateway_client_add.xml b/app/src/main/res/layout/activity_gateway_client_add.xml
deleted file mode 100644
index a978a0059..000000000
--- a/app/src/main/res/layout/activity_gateway_client_add.xml
+++ /dev/null
@@ -1,346 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_gateway_client_listing.xml b/app/src/main/res/layout/activity_gateway_client_listing.xml
deleted file mode 100644
index 67f0c7bbd..000000000
--- a/app/src/main/res/layout/activity_gateway_client_listing.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search_messages_threads.xml b/app/src/main/res/layout/activity_search_messages_threads.xml
deleted file mode 100644
index a0539e7fa..000000000
--- a/app/src/main/res/layout/activity_search_messages_threads.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_threads_conversation.xml b/app/src/main/res/layout/activity_threads_conversation.xml
deleted file mode 100644
index 9affce0fd..000000000
--- a/app/src/main/res/layout/activity_threads_conversation.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_web.xml b/app/src/main/res/layout/activity_web.xml
deleted file mode 100644
index c2991bda5..000000000
--- a/app/src/main/res/layout/activity_web.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/conversation_secure_popup.xml b/app/src/main/res/layout/conversation_secure_popup.xml
deleted file mode 100644
index 7b524487a..000000000
--- a/app/src/main/res/layout/conversation_secure_popup.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/conversation_secure_popup_menu.xml b/app/src/main/res/layout/conversation_secure_popup_menu.xml
deleted file mode 100644
index c003273f2..000000000
--- a/app/src/main/res/layout/conversation_secure_popup_menu.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/conversation_threads_toolbar.xml b/app/src/main/res/layout/conversation_threads_toolbar.xml
deleted file mode 100644
index 7f15043cc..000000000
--- a/app/src/main/res/layout/conversation_threads_toolbar.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_conversations_threads.xml b/app/src/main/res/layout/fragment_conversations_threads.xml
deleted file mode 100644
index 5b15676ef..000000000
--- a/app/src/main/res/layout/fragment_conversations_threads.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_gateway_client_listing.xml b/app/src/main/res/layout/fragment_gateway_client_listing.xml
deleted file mode 100644
index 2be9b1794..000000000
--- a/app/src/main/res/layout/fragment_gateway_client_listing.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_add_edit.xml b/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_add_edit.xml
deleted file mode 100644
index ac8bc2bd9..000000000
--- a/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_add_edit.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_listing_layout.xml b/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_listing_layout.xml
deleted file mode 100644
index 79ec72f75..000000000
--- a/app/src/main/res/layout/fragment_modalsheet_gateway_client_project_listing_layout.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/gateway_client_listing_layout.xml b/app/src/main/res/layout/gateway_client_listing_layout.xml
deleted file mode 100644
index a3bca8549..000000000
--- a/app/src/main/res/layout/gateway_client_listing_layout.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_conversation_compose.xml b/app/src/main/res/layout/layout_conversation_compose.xml
deleted file mode 100644
index 3c7a301a1..000000000
--- a/app/src/main/res/layout/layout_conversation_compose.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_conversation_contact_card.xml b/app/src/main/res/layout/layout_conversation_contact_card.xml
deleted file mode 100644
index 64f11f37f..000000000
--- a/app/src/main/res/layout/layout_conversation_contact_card.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_conversation_search_bar.xml b/app/src/main/res/layout/layout_conversation_search_bar.xml
deleted file mode 100644
index 4e8c49feb..000000000
--- a/app/src/main/res/layout/layout_conversation_search_bar.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_conversation_threads_navigation_drawer_version.xml b/app/src/main/res/layout/layout_conversation_threads_navigation_drawer_version.xml
deleted file mode 100644
index 2c33fc1f2..000000000
--- a/app/src/main/res/layout/layout_conversation_threads_navigation_drawer_version.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_conversations_received.xml b/app/src/main/res/layout/layout_conversations_received.xml
deleted file mode 100644
index 44d17f5c9..000000000
--- a/app/src/main/res/layout/layout_conversations_received.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_conversations_search_navigation.xml b/app/src/main/res/layout/layout_conversations_search_navigation.xml
deleted file mode 100644
index 6fbacc7cc..000000000
--- a/app/src/main/res/layout/layout_conversations_search_navigation.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_conversations_sent.xml b/app/src/main/res/layout/layout_conversations_sent.xml
deleted file mode 100644
index 73dc5ede2..000000000
--- a/app/src/main/res/layout/layout_conversations_sent.xml
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_conversations_threads.xml b/app/src/main/res/layout/layout_conversations_threads.xml
deleted file mode 100644
index e996581ee..000000000
--- a/app/src/main/res/layout/layout_conversations_threads.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_gateway_client_project_listing.xml b/app/src/main/res/layout/layout_gateway_client_project_listing.xml
deleted file mode 100644
index 7302c9797..000000000
--- a/app/src/main/res/layout/layout_gateway_client_project_listing.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_secure_received_banner.xml b/app/src/main/res/layout/layout_secure_received_banner.xml
deleted file mode 100644
index 7957be52c..000000000
--- a/app/src/main/res/layout/layout_secure_received_banner.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_secure_requested_banner.xml b/app/src/main/res/layout/layout_secure_requested_banner.xml
deleted file mode 100644
index 87a8f3f9d..000000000
--- a/app/src/main/res/layout/layout_secure_requested_banner.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_sim_chooser.xml b/app/src/main/res/layout/layout_sim_chooser.xml
deleted file mode 100644
index 86e5c394d..000000000
--- a/app/src/main/res/layout/layout_sim_chooser.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/archive_menu.xml b/app/src/main/res/menu/archive_menu.xml
deleted file mode 100644
index ac6831016..000000000
--- a/app/src/main/res/menu/archive_menu.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/archive_menu_items_selected.xml b/app/src/main/res/menu/archive_menu_items_selected.xml
deleted file mode 100644
index c97c3df22..000000000
--- a/app/src/main/res/menu/archive_menu_items_selected.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/blocked_conversations.xml b/app/src/main/res/menu/blocked_conversations.xml
deleted file mode 100644
index 83fadddcd..000000000
--- a/app/src/main/res/menu/blocked_conversations.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/blocked_conversations_items_selected.xml b/app/src/main/res/menu/blocked_conversations_items_selected.xml
deleted file mode 100644
index 0a49b5457..000000000
--- a/app/src/main/res/menu/blocked_conversations_items_selected.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_menu.xml b/app/src/main/res/menu/conversations_menu.xml
deleted file mode 100644
index 2dc462030..000000000
--- a/app/src/main/res/menu/conversations_menu.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_menu_item_selected.xml b/app/src/main/res/menu/conversations_menu_item_selected.xml
deleted file mode 100644
index c7a74650d..000000000
--- a/app/src/main/res/menu/conversations_menu_item_selected.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_menu_items_selected.xml b/app/src/main/res/menu/conversations_menu_items_selected.xml
deleted file mode 100644
index 96c17255d..000000000
--- a/app/src/main/res/menu/conversations_menu_items_selected.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_threads_menu.xml b/app/src/main/res/menu/conversations_threads_menu.xml
deleted file mode 100644
index dba0633cc..000000000
--- a/app/src/main/res/menu/conversations_threads_menu.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_threads_menu_items_selected.xml b/app/src/main/res/menu/conversations_threads_menu_items_selected.xml
deleted file mode 100644
index 1fdd03c53..000000000
--- a/app/src/main/res/menu/conversations_threads_menu_items_selected.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml b/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml
deleted file mode 100644
index 943e284d6..000000000
--- a/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/drafts_menu.xml b/app/src/main/res/menu/drafts_menu.xml
deleted file mode 100644
index 79459fc2c..000000000
--- a/app/src/main/res/menu/drafts_menu.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/gateway_client_customization_menu.xml b/app/src/main/res/menu/gateway_client_customization_menu.xml
deleted file mode 100644
index 34aa63ae9..000000000
--- a/app/src/main/res/menu/gateway_client_customization_menu.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/menu/muted_menu.xml b/app/src/main/res/menu/muted_menu.xml
deleted file mode 100644
index 5bc057b49..000000000
--- a/app/src/main/res/menu/muted_menu.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/muted_menu_items_selected.xml b/app/src/main/res/menu/muted_menu_items_selected.xml
deleted file mode 100644
index a4f6955b4..000000000
--- a/app/src/main/res/menu/muted_menu_items_selected.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/read_menu.xml b/app/src/main/res/menu/read_menu.xml
deleted file mode 100644
index 2adfa8df1..000000000
--- a/app/src/main/res/menu/read_menu.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index c4a603d4c..50ec88623 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,6 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index c4a603d4c..50ec88623 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,6 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index fdef7dedf..39d920110 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index d6d09cf8e..b4d3919a8 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index 7254e8ba5..9663f0d7e 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index 87c96f299..27df1b2e0 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index e1bae5b1a..080d97bde 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index b109657d1..d93a102f9 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index e688480c7..4470539b9 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index bd337725d..f880a8543 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index 576a98149..c8fe43581 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 8a176bf31..0104dff2e 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 66b7a16ec..d141ae835 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -205,6 +205,7 @@
Posteingang
LOAD NATIVES
Export abgeschlossen!
+ Import abgeschlossen!
Projektname
Keine Projekte hinzugefügt
John hat eine sichere Unterhaltung angefordert.
@@ -286,6 +287,49 @@
Zurücksetzen und importieren
Blockieren
Entsperren
+ Polieren
+ Willkommensbild
+ Remote-Hörer
+ "Port: "
+ Virtueller Host:
+ Keine Warteschlangen hinzugefügt
+ Freundlicher Name:
+ Zustand:
+ Aktiviert
+ Deaktiviert
+ Status:
+ Verbunden
+ Getrennt
+ Hinzufügen
+ Bearbeiten
+ SIM-2-Bindung
+ Warteschlangenname:
+ Das Bearbeiten einer Warteschlange würde die Verbindung neu starten
+ SIM-1-Bindung
+ Automatisch ausgefüllt bis:
+ Austausch
+ Standardmäßig Topic-Austausch
+ Neue Warteschlangen
+ Keine Remote-Hörer
+ Nur 1 Verbindung gleichzeitig – aufgrund des Engpasses zwischen den Kanälen und dem Funk des Telefons (Fähigkeit, SMS-Nachrichten parallel zu senden).
+ SIM-2-Warteschlange:
+ SIM 2:
+ Kanalnummer:
+ SIM-1-Warteschlange:
+ SIM 1:
+ # Nachrichten:
+ Neuer Remote-Listener
+ Zurückkehren
+ Host-URL
+ example.com
+ Benutzername
+ Passwort
+ Passwort ausblenden
+ Passwort anzeigen
+ Protokoll auswählen
+ • Zum Hinzufügen von Warteschlangen klicken
+ • Zum Verwalten gedrückt halten
+ %1$s Warteschlangen
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 01008c25b..2726b9b49 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -168,6 +168,7 @@
Réactiver tout le son
Exporter
Exportation terminée
+ Importation terminée !
Nom du projet
Aucun projet ajouté
John a demandé à sécuriser cette conversation.
@@ -261,4 +262,47 @@
Réinitialiser et importer
Recevoir des notifications en cas d\'échec d\'envoi de messages
Ajouter un serveur passerelle FTP
+ polonais
+ Image de bienvenue
+ Écouteurs distants
+ "Port: "
+ Hôte virtuel :
+ Aucune file d\'attente ajoutée
+ Nom convivial :
+ état :
+ Activé
+ Désactivé
+ état :
+ Connecté
+ Déconnecté
+ Ajouter
+ Modifier
+ Liaison SIM 2
+ Nom de la file d\'attente :
+ Modification d\'une file d\'attente, redémarrera la connexion
+ Liaison SIM 1
+ Rempli automatiquement à :
+ Échange
+ Par défaut, échange de type Topic
+ Nouvelles files d\'attente
+ Aucun écouteur distant
+ Une seule connexion à la fois - en raison du goulot d\'étranglement entre les canaux et la radio du téléphone (capacité à envoyer des SMS en parallèle).
+ File d\'attente SIM 2 :
+ SIM 2:
+ Numéro de canal :
+ File d\'attente SIM 1 :
+ SIM 1:
+ # Messages :
+ Nouveau récepteur distant
+ Revenir en arrière
+ URL de l\'hôte
+ example.com
+ Nom d\'utilisateur
+ Mot de passe
+ Masquer le mot de passe
+ Afficher le mot de passe
+ Choisir le protocole
+ • Cliquez pour ajouter des files d\'attente
+ • Appuyez longuement pour gérer
+ %1$s files d\'attente
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 000000000..0a0442584
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,397 @@
+
+ Deku SMS
+ https://smswithoutborders.com/privacy-policy
+ Przychodzące Wiadomości
+ Running Gateway Listeners
+ Gateway clients auto-reconnect
+ Message Failed
+
+ Jest wywoływane, gdy otrzymana zostanie nowa wiadomość.
+ Powiadamia, gdy bramki są aktywne.
+ Kliknij, aby ponownie połączyć bramki
+ Otrzymuj powiadomienia, gdy wiadomości nie są dostarczane
+
+
+ 100000000
+ 100000001
+
+
+ @string/incoming_messages_channel_name
+ @string/running_gateway_clients_channel_name
+ @string/foreground_service_failed_channel_name
+ @string/message_failed_channel_name
+
+ Szukaj wiadomości
+
+ Brak przekazanych wiadomości do wyświetlenia!
+
+ Zapisz
+ Wprowadź URL - np. https://example.com:8080/api/
+ (Opcjonalnie) Tag Identyfikatora
+ Wprowadź URL - np. example.com (nie włączając protokołu)
+ Wprowadź nazwę użytkownika
+ Wprowadź hasło
+ Wprowadź przyjazną nazwę - np. Przykładowy Węzeł
+ Wprowadź wirtualnego hosta - domyślny /
+ Port: domyślny 5672
+ Zarchiwizowane
+ Przekazywanie Wiadomości
+ Wyczyść wszystkie szkice
+ Oznacz wszystko jako przeczytane
+ Wycisz wszystko
+ Oznacz jako nieprzeczytane
+ Oznacz jako przeczytane
+ Wyślij swoją pierwszą wiadomość
+ Brak szkiców
+ Archiwum jest puste
+ Brak zablokowanych kontaktów
+ Brak wyciszonych kontaktów
+ Brak nieprzeczytanych wiadomości
+ Brak szyfrowanych kontaktów komunikacyjnych
+ Nie dodano serwera bramy
+ Bramki routingu SMS
+ Bramki nasłuchowe SMS
+ Dodaj bramę
+
+ Zaktualizuj
+ Dodaj serwer HTTPS bramy
+ Dodaj serwer FTP bramy
+
+ Dodaj serwer SMTP bramy
+ Host
+ Nazwa użytkownika
+ Hasło
+ Port
+ Odbiorca
+ Użyj przecinków do oddzielenia wielu odbiorców, np. example@email.com, example1@email.com
+ Od
+ (Opcjonalnie) Temat
+ Dodaj
+
+
+ Odpowiedz
+ Oznacz jako przeczytane
+
+ Prośba o bezpieczną komunikację
+
+ Ty
+
+ Ty:
+
+ Zabezpieczona treść
+ Zabezpieczone
+
+ Nic nie znaleziono
+
+
+ Aby korzystać z Deku SMS, ustaw go jako domyślną aplikację SMS
+ Ustaw domyślną aplikację SMS
+ Przeczytaj naszą politykę prywatności
+
+ Dostarczono
+ Wysłano
+ wysyłanie…
+ niepowodzenie, kliknij, aby spróbować ponownie.
+ niepowodzenie
+
+ settings_advanced_developers_key
+ settings_locale
+ Wybierz język
+ Włącz automatyczne routowanie protokołów
+ Włącz to, aby routować tylko do jednego protokołu na raz. Rozpoczyna się od HTTP i wykonuje przeszukiwanie okrężne dla innych protokołów. Użyj tego, aby uniknąć otrzymywania tej samej wiadomości na wiele protokołów.
+
+ O aplikacji
+ Ustawienia
+ Ustawienia serwera bramki
+ Zaawansowane
+ Dla deweloperów
+ Pozwól, aby prośba o szyfrowanie wygasła
+ Wyłącz prośbę o zabezpieczenie wiadomości
+ Nie wpłynie to na kontakty, z którymi już komunikujesz się w sposób bezpieczny, ale nie będziesz proszony o zabezpieczenie komunikacji z innymi kontaktami.
+ Po włączeniu, prośba o szyfrowanie wysyłana do kontaktów wygasa po 1 godzinie braku zgody.
+
+
+ Historia przekazywania
+ Nasłuchiwanie SMS
+ Skonfiguruj bramki nasłuchujące SMS
+ Konfiguracje do nasłuchiwania wiadomości SMS przez AMQP/s
+
+ Dodaj ręcznie
+
+ Base64
+ Wszystkie (domyślnie)
+
+ Wybierz protokół nasłuchu
+ Wszystkie POST są routowane jako formaty JSON.\nWszystkie formaty używają następującego formatu klucz/wartość:\n\nMSISDN:- Adres przychodzącej wiadomości w formacie E.164\n\ntext:- Treść przychodzącej wiadomości
+
+ Protokół
+ Nazwa użytkownika
+ Format danych
+ Wirtualny host
+ Nie może być puste!
+ /
+ 5672
+
+
+ AMQP
+ AMQP/SSL
+
+ Wprowadź nazwę projektu - (Wymiana)
+ Wprowadź powiązanie projektu - (domyślnie project_name.operator_country.operator_name)
+ Brak nasłuchujących bramek
+
+ Połączono
+
+ Aktywuj
+
+ Dezaktywuj
+ Dezaktywowano
+
+ Ponowne łączenie
+
+ Usuń
+ Edytuj
+
+ Klient bramki: Węzeł działa
+ Połączone bramki
+ Bramki ponownie łączą się…
+ Bramki czekają na połączenie…
+
+ Zaszyfrowane
+
+ Potwierdzenie usunięcia
+ Czy chcesz kontynuować usuwanie wątków?
+ Tak
+ Anuluj
+
+ Wczoraj
+
+Wyślij wiadomość przez
+
+ Kod QR urządzeń powiązanych
+ usuń
+ anuluj
+ archiwizuj
+
+ O aplikacji
+ Otwarty kod źródłowy SMS zrobiony dobrze
+
+ GitHub
+ Deku SMS pozostaje darmowy i otwartoźródłowy. Wierzymy w oddawanie społeczności, która dała nam tak wiele. Wiele rzeczy w tej aplikacji jest eksperymentalnych, ale z wielkimi eksperymentami przychodzą wielkie odkrycia. Prosimy o wsparcie, dając gwiazdkę projektowi na GitHubie i dzieląc się nim z przyjaciółmi.
+ Zobacz na GitHubie
+ https://github.com/deku-messaging/Deku-SMS-Android
+
+ Wiadomość tekstowa
+ Wiadomość tekstowa (zabezpieczona)
+ Zadzwoń
+ Szyfruj
+ Zabezpiecz
+ Zarządzanie zablokowanymi
+ Odblokuj
+ Kontakt zablokowany pomyślnie
+ Szukaj
+ Odśwież
+
+ znalezione wyniki
+
+
+ Skopiowano!
+ Informacje o kontakcie
+ Zobacz serwery bramek
+ skopiuj
+ Usuń
+ Przywróć z archiwum
+ Udostępnij
+ Zablokuj
+ Eksportuj
+ Wyłącz dźwięk
+ Wyłączony dźwięk
+ Włącz dźwięk
+ Kontakt jest teraz wyciszony!
+ Kontakt jest teraz odciszony!
+
+ Zobacz szczegóły
+ Odtwórz
+ Szczegóły wiadomości
+ Typ:\u00A0
+ Wiadomość tekstowa
+ Wiadomość danych
+ Od:\u00A0
+ Do:\u00A0
+ Wysłano:\u00A0
+ Odebrane:\u00A0
+
+ Kliknij, aby zażądać bezpiecznej rozmowy
+
+ Zabezpieczona treść
+ Bezpieczne żądanie
+
+ Wyślij
+ Anuluj
+ Prośba o bezpieczną rozmowę
+ Prośba o bezpieczną komunikację wyśle SMS do tego kontaktu. \n\nAby odebrać to żądanie, kontakt musi mieć zainstalowaną aplikację Deku SMS.
+ * Koszt SMS nadal obowiązuje
+
+ Nie można odpowiedzieć na ten krótki kod
+ Dowiedz się więcej
+ Krótkie kody to lokalne lub spersonalizowane numery do wysyłania międzynarodowych wiadomości SMS.\n\nMożesz odpowiadać tylko na krótkie kody składające się wyłącznie z cyfr, ale nie na kody z literami i cyframi, takie jak „MSG007”.
+ OK
+
+ Szkic
+ Wczoraj
+
+
+
+ Angielski
+ Francuski
+ Rosyjski
+ Niemiecki
+
+ Polski
+ en
+ fr
+ ru
+ de
+
+
+ 9:26 AM . MTN
+ Szkice
+ Archiwum
+ Nieprzeczytane
+ Zaszyfrowane
+ Zablokowane
+ Foldery
+ Skrzynka odbiorcza
+
+ LOAD NATIVES
+ Eksport zakończony!
+ Import zakończony!
+ Nazwa projektu
+ Brak dodanych projektów
+
+
+ John poprosił o zabezpieczenie tej rozmowy.
+ Zgódź się na zabezpieczenie!
+ Możesz wysłać prośbę o szyfrowanie wiadomości do wszystkich korzystających z Deku SMS. Gdy się zgodzą, oboje możecie rozpocząć bezpieczną komunikację.
+ https://github.com/deku-messaging/Deku-SMS-Android?tab=readme-ov-file#-end-to-end-encryption
+ Dowiedz się więcej
+ Routuj przychodzące wiadomości w formacie
+
+ Sukces
+ Niepowodzenie
+ W kolejce
+ Anulowane
+ Działa
+ Dodaj klienta bramki
+ "Wiadomość nie została wysłana do "
+
+ Do
+ Nowa rozmowa
+ Ta wiadomość nie została wysłana.
+ Spróbuj ponownie
+ Wyślij prośbę
+ -- Prośba o bezpieczną komunikację wysłana --
+ -- Prośba o bezpieczną komunikację odebrana --
+ Zabezpieczone
+ Szukaj wiadomości
+ Otwórz menu
+ Napisz nową wiadomość
+ Otwórz menu boczne
+ Zadzwoń
+ Wiadomość tekstowa
+ Wyślij wiadomość
+ skopiuj wiadomość
+ usuń wiadomość
+ udostępnij wiadomość
+ przekaż wiadomość
+ Poproś o bezpieczną komunikację
+ Wróć
+ Wpisz imiona lub numery telefonów
+ wybrane
+ Anuluj wybór
+ SMS
+ Foldery
+ Folder skrzynki odbiorczej
+ Folder archiwum
+ Folder zaszyfrow
+ Zablokowany folder
+ Przesuń wyszukiwanie wstecz
+ Przesuń wyszukiwanie do przodu
+ znaleziono wyniki
+ Przejdź do najnowszej treści
+ Anuluj wybrane wiadomości
+ Anuluj wyszukiwanie
+ Odarchiwizuj wiadomości
+ Wątek wyciszony
+ Bezpieczne żądanie
+ Wyślij ponownie wiadomość
+ Usuń wiadomość
+ Wyślij do
+ Wybierz kartę SIM
+ Rozmowa jest zabezpieczona
+ Utwórz nowy
+ Komponować
+ Powiadomienia
+
+ Szyfrowanie typu end-to-end
+ Szyfrowanie typu end-to-end:
+ W tej rozmowie nie jest dostępne szyfrowanie typu end-to-end.
+ Szyfrowanie typu End-to-End jest dostępne w tej rozmowie
+ NA
+ Wyłączony
+ Skopiowano \'%1$s\' do schowka
+ Kontakt jest zablokowany
+ Import
+ Rozmowy:
+ Wątki:
+ Archiwum
+ Wybierz konwersację z listy po lewej stronie
+ Importuj konwersacje
+ Resetuj i importuj
+ Blok
+ Odblokować
+ Obraz powitalny
+ Zdalni słuchacze
+ "Port: "
+ Wirtualny host:
+ Nie dodano kolejek
+ Nazwa przyjazna:
+ stan:
+ Aktywowany
+ Dezaktywowany
+ status:
+ Połączono
+ Rozłączono
+ Dodaj
+ Edytuj
+ Wiązanie SIM 2
+ Nazwa kolejki:
+ Edytowanie kolejki spowoduje ponowne uruchomienie połączenia
+ Wiązanie SIM 1
+ Wypełniono automatycznie do:
+ Wymiana
+ Domyślnie wymiana typu Topic
+ Nowe kolejki
+ Brak zdalnych słuchaczy
+ Tylko 1 połączenie naraz – ze względu na wąskie gardło między kanałami a radiem telefonu (możliwość równoległego wysyłania wiadomości SMS).
+ Kolejka SIM 2:
+ SIM 2:
+ Numer kanału:
+ Kolejka SIM 1:
+ SIM 1:
+ # Wiadomości:
+ Nowy zdalny odbiornik
+ Wróć
+ Adres URL hosta
+ example.com
+ Nazwa użytkownika
+ Hasło
+ Ukryj hasło
+ Pokaż hasło
+ Wybierz protokół
+ • Kliknij, aby dodać kolejki
+ • Naciśnij i przytrzymaj, aby zarządzać
+ %1$s kolejek
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 5bbbdfc51..98e7f9423 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -198,6 +198,7 @@
Входящие
Экспорт завершён!
+ Импорт завершен!
Имя проекта
Нет добавленных проектов
@@ -305,4 +306,47 @@
Сбросить и импортировать
Получать уведомления о сбоях отправки сообщений
Защищено
+ польский
+ Приветственное изображение
+ Удаленные слушатели
+ Порт:
+ Виртуальный хост:
+ Очереди не добавлены
+ Дружественное имя:
+ состояние:
+ Активировано
+ Деактивировано
+ статус:
+ Подключено
+ Отключено
+ Добавить
+ Редактировать
+ Привязка SIM 2
+ Имя очереди:
+ Редактирование очереди приведет к перезапуску соединения
+ Привязка SIM 1
+ Автоматически заполнено до:
+ Обмен
+ По умолчанию используется обмен типа Topic
+ Новые очереди
+ Нет удаленных слушателей
+ Только 1 соединение одновременно — из-за узкого места между каналами и радиомодулем телефона (возможность параллельной отправки SMS-сообщений).
+ Очередь SIM 2:
+ SIM 2:
+ Номер канала:
+ Очередь SIM 1:
+ SIM 1:
+ # Сообщения:
+ Новый удаленный прослушиватель
+ Вернуться назад
+ URL хоста
+ example.com
+ Имя пользователя
+ Пароль
+ Скрыть пароль
+ Показать пароль
+ Выберите протокол
+ • Нажмите, чтобы добавить очереди
+ • Нажмите и удерживайте для управления
+ %1$s очередей
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index b962d6b5f..b045d4b74 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -5,12 +5,14 @@
- @string/language_french
- @string/language_russian
- @string/language_german
+ - @string/language_polish
- @string/language_english_value
- @string/language_french_value
- @string/language_russian_value
- @string/language_german_value
+ - @string/language_polish_value
@@ -346,5 +349,47 @@
Block
Unblock
+ Welcome image
+ Remote listeners
+ "Port: "
+ "Virtual host: "
+ No queues added
+ "Friendly name: "
+ "state: "
+ Activated
+ Deactivated
+ "status: "
+ Connected
+ Disconnected
+ Add
+ Edit
+ Sim 2 Binding
+ Queue name:
+ Editing a queue, would restart the connection
+ Sim 1 Binding
+ "Auto-filled to: "
+ Exchange
+ Defaulting to Topic exchange
+ New Queues
+ No remote listeners
+ Only 1 connection at a time - due to the bottleneck between channels and the phone\'s radio (ability to SMS messages in parallel).
+ SIM 2 queue:
+ SIM 2:
+ "Channel number: "
+ "SIM 1 queue: "
+ SIM 1:
+ "# Messages: "
+ New remote listener
+ Return back
+ Host Url
+ example.com
+ Username
+ Password
+ Hide password
+ Show password
+ Choose protocol
+ • Click to add queues
+ • Press and hold to manage
+ %1$s Queues
\ No newline at end of file
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
index ac013c324..b716adcf8 100644
--- a/app/src/main/res/xml/locales_config.xml
+++ b/app/src/main/res/xml/locales_config.xml
@@ -4,4 +4,5 @@
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/settings_preferences.xml b/app/src/main/res/xml/settings_preferences.xml
index f92d8db58..e7589ff96 100644
--- a/app/src/main/res/xml/settings_preferences.xml
+++ b/app/src/main/res/xml/settings_preferences.xml
@@ -24,12 +24,4 @@
-
-
-
-
-
diff --git a/build.gradle b/build.gradle
index 97afea10c..ffd1a0efb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
// kotlin_version = '1.9.23'
kotlin_version = '2.0.0'
agp_version = '8.5.0'
- agp_version1 = '8.7.3'
+ agp_version1 = '8.8.0'
}
repositories {
google()
diff --git a/deku_chat.png b/deku_chat.png
new file mode 100644
index 000000000..6831293b1
Binary files /dev/null and b/deku_chat.png differ
diff --git a/deku_chat_details.png b/deku_chat_details.png
new file mode 100644
index 000000000..1962ff37f
Binary files /dev/null and b/deku_chat_details.png differ
diff --git a/deku_default.png b/deku_default.png
new file mode 100644
index 000000000..5ed1c5247
Binary files /dev/null and b/deku_default.png differ
diff --git a/deku_home.png b/deku_home.png
new file mode 100644
index 000000000..0190c41de
Binary files /dev/null and b/deku_home.png differ
diff --git a/deku_secure_request.png b/deku_secure_request.png
new file mode 100644
index 000000000..9688fdb4c
Binary files /dev/null and b/deku_secure_request.png differ
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index af5480539..8ce742fac 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,58 +1,66 @@
[versions]
-activityCompose = "1.9.3"
+accompanistPermissions = ""
+accompanistPermissionsVersion = "0.37.0"
+activityCompose = "1.10.0"
amqpClient = "5.20.0"
androidMail = "1.6.2"
appcompat = "1.7.0"
+atomfingerTouuid = "1.0.1"
+atomfingerTouuidVersion = "1.0.1"
autolinktext = "2.0.0"
avatarviewCoil = "1.0.7"
coilCompose = "3.0.4"
coilNetworkOkhttp = "3.0.4"
commonsCodec = "1.16.0"
commonsNet = "3.6"
-composeBom = "2024.11.00"
+composeBom = "2025.01.00"
+conscryptAndroid = "2.5.2"
constraintlayout = "2.2.0"
-datastorePreferences = "1.1.1"
-datastorePreferencesRxjava2 = "1.1.1"
+coreKtx = "1.15.0"
+datastorePreferences = "1.1.2"
+datastorePreferencesRxjava2 = "1.1.2"
espressoCore = "3.6.1"
fuelVersion = "2.3.1"
graphicsShapes = "1.0.1"
gson = "2.11.0"
+guava = "33.0.0-jre"
+hkdf = "2.0.0"
javaWebsocket = "1.5.5"
junit = "4.13.2"
junitVersion = "1.2.1"
kotlin = "2.0.0"
-kotlinxCoroutinesTest = "1.8.1"
+kotlinxCoroutinesTest = "1.9.0"
kotlinxSerializationJson = "1.6.3"
ktx = "1.15.0"
-androidGradlePlugin = "7.4.2"
legacySupportV4 = "1.0.0"
-libphonenumber = "8.13.40"
+libphonenumber = "8.13.50"
lifecycleRuntimeKtx = "2.8.7"
lifecycleViewmodelCompose = "2.8.7"
lz4 = "1.3.0"
material = "1.12.0"
material3 = "1.3.1"
-materialIconsExtended = "1.7.5"
-navigationCompose = "2.8.4"
-navigationDynamicFeaturesFragment = "2.8.4"
-navigationFragment = "2.8.4"
-navigationTesting = "2.8.4"
-navigationUi = "2.8.4"
-pagingGuava = "3.3.4"
+materialIconsExtended = "1.7.6"
+navigationCompose = "2.8.5"
+navigationFragment = "2.8.5"
+navigationTesting = "2.8.5"
+navigationUi = "2.8.5"
+pagingGuava = "3.3.5"
preference = "1.2.1"
prov = "1.58.0.0"
roomTesting = "2.6.1"
rxbinding = "0.4.0"
securityCrypto = "1.1.0-alpha06"
startupRuntime = "1.2.0"
-uiTextGoogleFonts = "1.7.5"
+uiTextGoogleFonts = "1.7.6"
volley = "1.2.1"
window = "1.3.0"
windowTesting = "1.3.0"
workRuntime = "2.10.0"
-activity = "1.9.3"
+activity = "1.10.0"
+x25519 = "2.0"
[libraries]
+accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissionsVersion" }
amqp-client = { module = "com.rabbitmq:amqp-client", version.ref = "amqpClient" }
android-activation = { module = "com.sun.mail:android-activation", version.ref = "androidMail" }
android-mail = { module = "com.sun.mail:android-mail", version.ref = "androidMail" }
@@ -61,6 +69,7 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx-core = { module = "androidx.core:core", version.ref = "ktx" }
+androidx-core-ktx-v1131 = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-datastore-preferences-rxjava2 = { module = "androidx.datastore:datastore-preferences-rxjava2", version.ref = "datastorePreferencesRxjava2" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-datastore-preferences-rxjava3 = { module = "androidx.datastore:datastore-preferences-rxjava3", version.ref = "datastorePreferences" }
@@ -72,12 +81,11 @@ androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx"
androidx-legacy-support-v4 = { module = "androidx.legacy:legacy-support-v4", version.ref = "legacySupportV4" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
-androidx-material = { module = "androidx.compose.material:material", version = "1.7.5" }
+androidx-material = { module = "androidx.compose.material:material", version = "1.7.6" }
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
-androidx-navigation-dynamic-features-fragment = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "navigationDynamicFeaturesFragment" }
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigationFragment" }
androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navigationTesting" }
androidx-navigation-ui = { module = "androidx.navigation:navigation-ui", version.ref = "navigationUi" }
@@ -93,8 +101,7 @@ androidx-runtime-rxjava2 = { module = "androidx.compose.runtime:runtime-rxjava2"
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" }
androidx-ui = { module = "androidx.compose.ui:ui" }
-androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
-androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version = "1.7.5" }
+androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version = "1.7.6" }
androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
@@ -104,16 +111,20 @@ androidx-work-multiprocess = { module = "androidx.work:work-multiprocess", versi
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" }
androidx-work-rxjava2 = { module = "androidx.work:work-rxjava2", version.ref = "workRuntime" }
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "workRuntime" }
+atomfinger-touuid = { module = "io.github.atomfinger:atomfinger-touuid", version.ref = "atomfingerTouuidVersion" }
autolinktext = { module = "sh.calvin.autolinktext:autolinktext", version.ref = "autolinktext" }
avatarview-coil = { module = "io.getstream:avatarview-coil", version.ref = "avatarviewCoil" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilNetworkOkhttp" }
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commonsCodec" }
commons-net = { module = "commons-net:commons-net", version.ref = "commonsNet" }
+conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroid" }
fuel = { module = "com.github.kittinunf.fuel:fuel", version.ref = "fuelVersion" }
fuel-android = { module = "com.github.kittinunf.fuel:fuel-android", version.ref = "fuelVersion" }
fuel-coroutines = { module = "com.github.kittinunf.fuel:fuel-coroutines", version.ref = "fuelVersion" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
+guava = { module = "com.google.guava:guava", version.ref = "guava" }
+hkdf = { module = "at.favre.lib:hkdf", version.ref = "hkdf" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "javaWebsocket" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
@@ -127,6 +138,7 @@ prov = { module = "com.madgag.spongycastle:prov", version.ref = "prov" }
rxbinding = { module = "com.jakewharton.rxbinding:rxbinding", version.ref = "rxbinding" }
volley = { module = "com.android.volley:volley", version.ref = "volley" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+x25519 = { module = "com.github.netricecake:x25519", version.ref = "x25519" }
[plugins]
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 364295c23..7c993c8ca 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Aug 29 13:47:19 WAT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/smswithoutborders_libsignal-doubleratchet b/smswithoutborders_libsignal-doubleratchet
index 2a24d15f4..efee51a19 160000
--- a/smswithoutborders_libsignal-doubleratchet
+++ b/smswithoutborders_libsignal-doubleratchet
@@ -1 +1 @@
-Subproject commit 2a24d15f4b52d0c887f79c10273fe98dde1c494c
+Subproject commit efee51a19cf6721a2dff8e85727b09523622f73b
diff --git a/version.properties b/version.properties
index 002430aec..c36d07367 100644
--- a/version.properties
+++ b/version.properties
@@ -1,5 +1,5 @@
releaseVersion=0
-stagingVersion=57
+stagingVersion=58
nightlyVersion=0
-versionName=0.57.0
-tagVersion=63
\ No newline at end of file
+versionName=0.58.0
+tagVersion=64
\ No newline at end of file