From 89f32053bd3cbbbc18b5e1f64dc54949c61db377 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Mon, 20 Nov 2023 11:00:50 +0530 Subject: [PATCH 001/139] feat: show which SIM a message was received on/sent with --- .../com.bnyro.contacts.db.AppDatabase/4.json | 165 ++++++++++++++++++ .../java/com/bnyro/contacts/db/AppDatabase.kt | 4 +- .../java/com/bnyro/contacts/db/obj/SmsData.kt | 3 +- .../com/bnyro/contacts/repo/DeviceSmsRepo.kt | 16 +- .../contacts/ui/screens/SmsThreadScreen.kt | 28 ++- 5 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 app/schemas/com.bnyro.contacts.db.AppDatabase/4.json diff --git a/app/schemas/com.bnyro.contacts.db.AppDatabase/4.json b/app/schemas/com.bnyro.contacts.db.AppDatabase/4.json new file mode 100644 index 00000000..32a2ff4b --- /dev/null +++ b/app/schemas/com.bnyro.contacts.db.AppDatabase/4.json @@ -0,0 +1,165 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "9c6b62e79ca517c51c6b234b62f23eb4", + "entities": [ + { + "tableName": "localContacts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `displayName` TEXT, `firstName` TEXT, `surName` TEXT, `nickName` TEXT, `organization` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surName", + "columnName": "surName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "nickName", + "columnName": "nickName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "organization", + "columnName": "organization", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "valuableTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `contactId` INTEGER NOT NULL, `category` INTEGER NOT NULL, `value` TEXT NOT NULL, `type` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contactId", + "columnName": "contactId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "localSms", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `address` TEXT NOT NULL, `body` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `threadId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `simNumber` INTEGER DEFAULT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "body", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "threadId", + "columnName": "threadId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "simNumber", + "columnName": "simNumber", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "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, '9c6b62e79ca517c51c6b234b62f23eb4')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt b/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt index 1f4ce2af..52c4280f 100644 --- a/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt +++ b/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt @@ -11,8 +11,8 @@ import com.bnyro.contacts.db.obj.SmsData @Database( entities = [LocalContact::class, DbDataItem::class, SmsData::class], - autoMigrations = [AutoMigration(2, 3)], - version = 3 + autoMigrations = [AutoMigration(2, 3), AutoMigration(3, 4)], + version = 4 ) abstract class AppDatabase : RoomDatabase() { abstract fun localContactsDao(): LocalContactsDao diff --git a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt index 640c7baf..8e5b8ce0 100644 --- a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt +++ b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt @@ -18,7 +18,8 @@ data class SmsData( @ColumnInfo val body: String = "", @ColumnInfo val timestamp: Long = 0, @ColumnInfo val threadId: Long = 0, - @ColumnInfo val type: Int = 0 + @ColumnInfo val type: Int = 0, + @ColumnInfo(defaultValue = "NULL") var simNumber: Int? = null ) { val formatted: AnnotatedString get() { diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt index ee2590d3..da9746fb 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt @@ -2,6 +2,7 @@ package com.bnyro.contacts.repo import android.content.ContentValues import android.content.Context +import android.os.Build import android.provider.Telephony import android.util.Log import com.bnyro.contacts.db.obj.SmsData @@ -15,6 +16,12 @@ class DeviceSmsRepo : SmsRepository { private val contentUri = Telephony.Sms.CONTENT_URI override suspend fun getSmsList(context: Context): List { + val simSlotMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + SmsUtil.getSubscriptions(context) + .associateBy({ it.subscriptionId }, { it.simSlotIndex }) + } else { + null + } context.contentResolver .query(contentUri, null, null, null, null) ?.use { cursor -> @@ -28,8 +35,15 @@ class DeviceSmsRepo : SmsRepository { val timestamp = cursor.longValue(Telephony.Sms.DATE) ?: 0 val body = cursor.stringValue(Telephony.Sms.BODY).orEmpty() val type = cursor.intValue(Telephony.Sms.TYPE) ?: 0 + val simIndex = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + cursor.intValue(Telephony.Sms.SUBSCRIPTION_ID)?.takeIf { it > 0 }?.let { + simSlotMap?.get(it) + }?.plus(1) + } else { + null + } - smsList.add(SmsData(id, address, body, timestamp, threadId, type)) + smsList.add(SmsData(id, address, body, timestamp, threadId, type, simIndex)) } while (cursor.moveToNext()) return smsList diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index 9149e243..e67dc57d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -205,15 +205,27 @@ fun SmsThreadScreen( ) } Spacer(modifier = Modifier.height(4.dp)) - Text( - modifier = Modifier.align(messageAlignment), - fontSize = 14.sp, - color = MaterialTheme.colorScheme.primary, - text = DateUtils.getRelativeTimeSpanString( - smsData.timestamp + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Text( + fontSize = 14.sp, + color = MaterialTheme.colorScheme.primary, + text = DateUtils.getRelativeTimeSpanString( + smsData.timestamp + ) + .toString() ) - .toString() - ) + if (smsData.simNumber != null) { + Spacer(modifier = Modifier.width(8.dp)) + Text( + fontSize = 14.sp, + color = MaterialTheme.colorScheme.primary, + text = "SIM ${smsData.simNumber}" + ) + } + } } } }, From 453f152bc20f08e20d0db53e5a3bc7ccc0bf6d79 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Mon, 20 Nov 2023 03:35:44 +0100 Subject: [PATCH 002/139] Added translation using Weblate (Interlingua) --- app/src/main/res/values-ia/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-ia/strings.xml diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-ia/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From d252b993dbcdcb46fa164d2081d41c9e2118b574 Mon Sep 17 00:00:00 2001 From: David Brofalk Date: Wed, 22 Nov 2023 19:00:30 +0100 Subject: [PATCH 003/139] Added translation using Weblate (Swedish) --- app/src/main/res/values-sv/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-sv/strings.xml diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-sv/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 45fd7fee6c2e636d61e7dd4bbb0427e744d86858 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 20 Nov 2023 18:31:11 +0000 Subject: [PATCH 004/139] Translated using Weblate (French) Currently translated at 80.5% (87 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fr/ --- app/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 54e30dc3..1458c616 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -7,7 +7,7 @@ Évènement Prénom Nom de famille - Auteur + Auteur·e Version Traduction Connect You @@ -44,7 +44,7 @@ Rien ici. Copier Déplacer - Paramètres + Réglages Onglet de démarrage Supprimer Choisir un répertoire From 7cfaaf0a565841e87ed549c5164bb720601f8924 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Wed, 22 Nov 2023 05:11:37 +0000 Subject: [PATCH 005/139] Translated using Weblate (Arabic) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ar/ --- app/src/main/res/values-ar/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4e7ec9cf..f03ff623 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -55,7 +55,7 @@ داكن نسخ احتياطيٌّ تلقائيٌّ المسار - لا شيء + لاشيء كلاهما أدر المجموعات حذف المجموعة للجميع From d4555db45bfbee97fbf7cd6cb8a979d516f0929a Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Tue, 21 Nov 2023 01:02:46 +0000 Subject: [PATCH 006/139] Translated using Weblate (Interlingua) Currently translated at 32.4% (35 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index a6b3daec..437b1a66 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -1,2 +1,38 @@ - \ No newline at end of file + + Comportamento + Salvar + Filtrar + Reinitialisar + Message + Adder al contacto + Sito web + Nihil hic. + Nove contacto + E-mail + Modificar + Copiar + Necun + Deler le contacto + Telephono + Crear contacto + Contactos + Evento + Retentar + Contacto + Monstrar plus de campos + Le nomine non pote esser vacue! + Licentia + A proposito de + Plus + Traduction + Adresse + Cancellar + OK + Seliger un contacto + Monstrar minus + Parametros + Crear un accesso directe + Systema + Compartir + \ No newline at end of file From 4850115a172ca49ea39076fc23134a5bfb609828 Mon Sep 17 00:00:00 2001 From: David Brofalk Date: Wed, 22 Nov 2023 18:47:16 +0000 Subject: [PATCH 007/139] Translated using Weblate (English) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/en/ --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3f7be6e5..9f2502d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ Search Copied to clipboard Delete contact - Are you sure\? This can\'t be undone. + Are you sure? This can\'t be undone! Cancel Okay Reset From 6777e855743719972232b4e1371a51fe72959cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D0=B0=D1=80=20=D0=A0=D0=B0=D0=B7=D0=B8?= =?UTF-8?q?=D0=BD?= Date: Thu, 23 Nov 2023 17:24:20 +0000 Subject: [PATCH 008/139] Translated using Weblate (Ukrainian) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/uk/ --- app/src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 92305399..bb44ebde 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -8,7 +8,7 @@ Скопійовано в буфер обміну Ім’я не може бути порожнім! Факс дому - Ви впевнені\? Це не можна відмінити. + Ви впевнені? Це не можна відмінити! Створити контакт Поділитися Спробувати знову From 4bb53ec2c65e1fa77dd278b44dabcc9798e6fbec Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Thu, 23 Nov 2023 10:13:32 +0000 Subject: [PATCH 009/139] Translated using Weblate (Arabic) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ar/ --- app/src/main/res/values-ar/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index f03ff623..33dfb024 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1,6 +1,6 @@ - هل أنت متأكد؟ هذا لا يمكن التراجع عنه. + هل أنت متأكد؟ هذا لا يمكن التراجع عنه! البريد الإلكترونيُّ أخرى سيَّارة From 1fcdebeece7cc244cb5c88f0f7c3af63b1a8db2c Mon Sep 17 00:00:00 2001 From: Subham Jena Date: Sat, 25 Nov 2023 07:50:55 +0000 Subject: [PATCH 010/139] Translated using Weblate (Odia) Currently translated at 54.6% (59 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/or/ --- app/src/main/res/values-or/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 230a2184..08b1835f 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -14,7 +14,7 @@ ଜନ୍ମଦିନ ବାର୍ଷିକୀ VCard ରପ୍ତାନି କରନ୍ତୁ - ଵିଷୟରେ + ସମ୍ବନ୍ଧରେ ଲାଇସେନ୍ସ ଲେଖକ ସଂସ୍କରଣ From eb25731ee4459938e58399b40dbeb8da0bc03bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D0=B0=D1=80=20=D0=A0=D0=B0=D0=B7=D0=B8?= =?UTF-8?q?=D0=BD?= Date: Thu, 23 Nov 2023 17:30:43 +0000 Subject: [PATCH 011/139] Translated using Weblate (Russian) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ru/ --- app/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8ce64850..1fa92925 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -7,7 +7,7 @@ Рабочий факс Юбилей Версия - Вы уверены\? Это действие невозможно отменить. + Вы уверены? Это действие невозможно отменить! Отмена Ок Сброс @@ -53,7 +53,7 @@ Тёмная Автоматическое резервное копирование В оба расположения - Отключить + Откл. Удалить Папка Управление группами From 71f757bd5257f709ae47ab8a7515c96e3021ae7f Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 23 Nov 2023 07:49:58 +0000 Subject: [PATCH 012/139] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index facc35bd..2dddf685 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -11,7 +11,7 @@ 搜索 已复制到剪贴板 删除联系人 - 你确定吗?此操作无法撤销。 + 你确定吗?此操作无法撤销! 取消 确定 重设 From a25c12e59f554b3020c98de947130f5f732cc66e Mon Sep 17 00:00:00 2001 From: Fjuro Date: Thu, 23 Nov 2023 06:54:59 +0000 Subject: [PATCH 013/139] Translated using Weblate (Czech) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/cs/ --- app/src/main/res/values-cs/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 35f3800e..49c89c37 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -16,7 +16,7 @@ Překlad Adresář Obnovit - Opravdu\? Tato akce je nevratná. + Opravdu? Tato akce je nevratná! Okay Adresa Událost From 99a1e514832d4dd4e718013784cc39062acda389 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 22 Nov 2023 20:27:27 +0000 Subject: [PATCH 014/139] Translated using Weblate (Spanish) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/es/ --- app/src/main/res/values-es/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 72fb85db..d4e909f4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -38,7 +38,7 @@ Sistema Intervalo de copia de seguridad Iconos de contacto de colores - ¿Estás seguro\? Esto no se puede deshacer. + ¿Estás seguro? ¡Esto no se puede deshacer! Compartir Acerca de Licencia From f2e4f1189bbe2f9f4d72cf43559901a5f7cafca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Thu, 23 Nov 2023 06:03:04 +0000 Subject: [PATCH 015/139] Translated using Weblate (Galician) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/gl/ --- app/src/main/res/values-gl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 42eab5ef..2b8babd1 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -9,7 +9,7 @@ Si Copiado ao portapapeis Eliminar contacto - Continuar\? A decisión non ten volta. + Continuar? A decisión non ten volta. Compartir Crear atallo Marcar From fb56264d1be539bd252f6059486417e83f7de631 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Wed, 22 Nov 2023 18:55:05 +0000 Subject: [PATCH 016/139] Translated using Weblate (Hebrew) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/he/ --- app/src/main/res/values-iw/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index be6c81d8..9f9a4b2a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -1,6 +1,6 @@ - להמשיך\? זה בלתי הפיך. + להמשיך? זה בלתי הפיך! ניסיון חוזר הוספה לאיש קשר איש קשר חדש From 47f2b83631dbb316e2ff87d67d94918d427b4cdd Mon Sep 17 00:00:00 2001 From: NEXI Date: Thu, 23 Nov 2023 11:15:52 +0000 Subject: [PATCH 017/139] Translated using Weblate (Serbian) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/sr/ --- app/src/main/res/values-sr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 834fd923..6cfbea3d 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -5,7 +5,7 @@ Претрага Копирано у привремену меморију Избриши контакт - Да ли сте сигурни\? Ово се не може поништити. + Да ли сте сигурни? Ово се не може поништити! Откажи У реду Ресетуј From 1520e4b64ec2576f00ca45f276d05c877bc0377c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D0=B0=D1=80=20=D0=A0=D0=B0=D0=B7=D0=B8?= =?UTF-8?q?=D0=BD?= Date: Thu, 23 Nov 2023 17:24:52 +0000 Subject: [PATCH 018/139] Translated using Weblate (Belarusian) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/be/ --- app/src/main/res/values-be/strings.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index cca38121..321a1b50 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -1,7 +1,7 @@ Пошук - Вы ўпэўнены\? Гэтае дзеянне немагчыма адмяніць. + Вы ўпэўнены? Гэтае дзеянне немагчыма адмяніць! Адмена ОК Скід @@ -98,7 +98,13 @@ Паведамлення Адправіць паведамленне Адказаць - Памылка злучэння! Не забудзьцеся адключыць рэжым палёту! + Не атрымалася злучыцца. Пераканайцеся, што рэжым палёту выключаны. Кантакты Паведамленне занадта доўгае! + Захаваць + Выдаліць галінку + База дадзеных прыватных SMS + Выдаліць паведамленне + SMS будзе захоўвацца толькі ў праграме і не будзе даступны ў іншых праграмах. + Нумар тэлефона \ No newline at end of file From 91c17defa14b60a930c3851fc07e16feed824c0c Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Fri, 24 Nov 2023 23:37:50 +0000 Subject: [PATCH 019/139] Translated using Weblate (Interlingua) Currently translated at 63.8% (69 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 35 +++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 437b1a66..b63f6d50 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -1,5 +1,5 @@ - + Comportamento Salvar Filtrar @@ -35,4 +35,37 @@ Crear un accesso directe Systema Compartir + Responder + Apparato + Ambe + Importar ab un carta SIM + FTP + Deler message + Thema + Typo + Apparentia + Importar vCard + Intervallo de copias de securitate + Clar + Deler + Le message es troppo longe! + Version + %1$s seligite + Scheda de initio + Copiate al area de transferentia + Inviar un message + Altere + Directorio + Copia de securitate + Local + Obscur + Cercar + Nove + Messages + Copia de securitate automatic + Autor + Typo de conto + Contrasigno + Exportar vCard + Personalisate \ No newline at end of file From 26a77f9a17c0e25893c8e61c676575ac13dc359c Mon Sep 17 00:00:00 2001 From: David Brofalk Date: Wed, 22 Nov 2023 19:16:18 +0000 Subject: [PATCH 020/139] Translated using Weblate (Swedish) Currently translated at 84.2% (91 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/sv/ --- app/src/main/res/values-sv/strings.xml | 94 +++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a6b3daec..92479679 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,2 +1,94 @@ - \ No newline at end of file + + Beteende + Svara + Spara + Enhet + Titel + Filtrera + Återställ + Importera från SIM-kort + FTP + Assistent + Meddela + Flytta + Fax arbete + Lägg till i kontakt + Webbplats + Fax hem + Ta bort meddelande + Ingenting här. + Ny kontakt + Födelsedag + E-post + Redigera + Kopiera + Ingen + Tema + Typ + Utseende + Namn + Importera vCard + Ta bort kontakt + Organisation + Ljust + Hantera grupper + Telefon + Ta bort + Skapa kontakt + Meddelandet är för långt! + Version + %1$s vald + Kontakter + Kopiera till urklippet + Skicka meddelande + Försök igen + Övrigt + Kontakt + Foto + Visa fler fält + Arbete + Säkerhetskopiera + Licens + Bil + Om + Lokalt + Mer + Översättning + Adress + Efternamn + Mörkt + Hem + Avbryt + Är du säker? Detta kan inte ångras! + Blogg + Smeknamn + Färgglada kontaktikoner + Sök + Ta bort grupp för alla + Ny + Grupper + Välj kontakt + Meddelanden + Förnamn + Automatisk säkerhetskopiering + Visa mindre + Inställningar + Telefonnummer + Gruppen finns redan + Skapare + Skapa genväg + Importerad. + Jubileum + Kontotyp + Lösenord + System + Kunde inte ansluta. Kontrollera att flygplansläget är avstängt. + Dela + Exportera vCard + Mobil + Ring + Anpassad + Anteckning + Exporterad. + \ No newline at end of file From 9c4c5eb0bf4875910d15891ef880a9ff64db945a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 27 Nov 2023 21:15:39 +0100 Subject: [PATCH 021/139] fix: can't use new lines to write sms (closes #328) --- .../components/base/ElevatedTextInputField.kt | 106 ++++++++++++++++++ .../contacts/ui/screens/SmsThreadScreen.kt | 16 +-- 2 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt new file mode 100644 index 00000000..675990a7 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt @@ -0,0 +1,106 @@ +package com.bnyro.contacts.ui.components.base + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.contentColorFor +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex + +private val SearchBarIconOffsetX: Dp = 4.dp +private val TonalElevation = 10.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ElevatedTextInputField( + query: String, + onQueryChange: (String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + colors: TextFieldColors = SearchBarDefaults.inputFieldColors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusRequester = remember { FocusRequester() } + + val elevationColor = MaterialTheme.colorScheme.surfaceColorAtElevation(TonalElevation) + Surface( + shape = RoundedCornerShape(36.dp), + color = elevationColor, + contentColor = contentColorFor(elevationColor), + tonalElevation = TonalElevation, + modifier = modifier + .zIndex(1f) + ) { + BasicTextField( + value = query, + onValueChange = onQueryChange, + modifier = modifier + .height(SearchBarDefaults.InputFieldHeight) + .fillMaxWidth() + .focusRequester(focusRequester) + .semantics { + onClick { + focusRequester.requestFocus() + true + } + }, + enabled = enabled, + textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onBackground), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Default), + cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), + interactionSource = interactionSource, + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.DecorationBox( + value = query, + innerTextField = innerTextField, + enabled = enabled, + singleLine = true, + visualTransformation = VisualTransformation.None, + interactionSource = interactionSource, + placeholder = placeholder, + leadingIcon = leadingIcon?.let { leading -> + { + Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() } + } + }, + trailingIcon = trailingIcon?.let { trailing -> + { + Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() } + } + }, + shape = SearchBarDefaults.inputFieldShape, + colors = colors, + contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(), + container = {}, + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index e67dc57d..04c885f7 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -50,8 +50,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource @@ -61,6 +59,7 @@ import androidx.compose.ui.unit.sp import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.base.ClickableIcon +import com.bnyro.contacts.ui.components.base.ElevatedTextInputField import com.bnyro.contacts.ui.components.base.FullScreenDialog import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.models.SmsModel @@ -271,23 +270,15 @@ fun SmsThreadScreen( var text by remember { mutableStateOf(initialText) } - val focusRequester = remember { - FocusRequester() - } - SearchBar( + ElevatedTextInputField( modifier = Modifier - .weight(1f) - .focusRequester(focusRequester), + .weight(1f), query = text, onQueryChange = { text = it }, placeholder = { Text(stringResource(R.string.send)) }, - onSearch = {}, - active = false, - onActiveChange = {}, - content = {} ) Spacer(modifier = Modifier.width(8.dp)) @@ -309,7 +300,6 @@ fun SmsThreadScreen( smsModel.sendSms(context, address, text) text = "" - focusRequester.freeFocus() } ) { Icon( From cbf34886c70fc180f7691e4024c8dded317a34d6 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 27 Nov 2023 21:34:02 +0100 Subject: [PATCH 022/139] feat: automatically split sms into multiple if too long (closes #337) --- .../contacts/ui/screens/SmsThreadScreen.kt | 26 ++++++++++++++----- .../java/com/bnyro/contacts/util/SmsUtil.kt | 22 +++++++++++++++- app/src/main/res/values/strings.xml | 1 + .../com/bnyro/contacts/ExampleUnitTest.kt | 24 +++++++++++++++-- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index 04c885f7..be39cbc9 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.PlainTooltipBox import androidx.compose.material3.Scaffold -import androidx.compose.material3.SearchBar import androidx.compose.material3.SwipeToDismiss import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -267,6 +266,10 @@ fun SmsThreadScreen( .padding(start = 10.dp, end = 5.dp, bottom = 20.dp), verticalAlignment = Alignment.CenterVertically ) { + var showConfirmSendMultipleSmsDialog by remember { + mutableStateOf(false) + } + var text by remember { mutableStateOf(initialText) } @@ -288,12 +291,7 @@ fun SmsThreadScreen( onClick = { if (text.isBlank()) return@FilledIconButton if (!SmsUtil.isShortEnoughForSms(text)) { - Toast.makeText( - context, - R.string.message_too_long, - Toast.LENGTH_SHORT - ) - .show() + showConfirmSendMultipleSmsDialog = true return@FilledIconButton } @@ -307,6 +305,20 @@ fun SmsThreadScreen( contentDescription = stringResource(R.string.send) ) } + + if (showConfirmSendMultipleSmsDialog) { + ConfirmationDialog( + onDismissRequest = { showConfirmSendMultipleSmsDialog = false }, + title = stringResource(R.string.message_too_long), + text = stringResource(R.string.send_message_as_multiple) + ) { + SmsUtil.splitSmsText(text).forEach { + smsModel.sendSms(context, address, it) + } + + text = "" + } + } } } } diff --git a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt index a0c62b93..aa4af563 100644 --- a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt +++ b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt @@ -18,7 +18,7 @@ import java.lang.Character.UnicodeBlock import java.util.Calendar object SmsUtil { - private const val MAX_CHAR_LIMIT = 160 + const val MAX_CHAR_LIMIT = 160 private const val MAX_CHAR_LIMIT_WITH_UNICODE = 70 lateinit var smsRepo: SmsRepository @@ -109,4 +109,24 @@ object SmsUtil { return true } + + fun splitSmsText(text: String): List { + var currentIndex = 0 + val splits = mutableListOf() + + while (currentIndex < text.length) { + var fullPart = text.substring(currentIndex, minOf(currentIndex + MAX_CHAR_LIMIT, text.length)) + + if (isShortEnoughForSms(fullPart)) { + currentIndex += MAX_CHAR_LIMIT + } else { + fullPart = text.substring(currentIndex, minOf(currentIndex + MAX_CHAR_LIMIT_WITH_UNICODE, text.length)) + currentIndex += MAX_CHAR_LIMIT_WITH_UNICODE + } + + splits.add(fullPart) + } + + return splits + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3f7be6e5..a5783795 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,6 +84,7 @@ Reply Could not connect. Please ensure airplane mode is off. Message is too long! + Do you want to send the message as multiple smaller ones? Private SMS Database The SMS will only be stored within the app and not be accessible in other apps. Delete thread diff --git a/app/src/test/java/com/bnyro/contacts/ExampleUnitTest.kt b/app/src/test/java/com/bnyro/contacts/ExampleUnitTest.kt index 6471e642..af668cca 100644 --- a/app/src/test/java/com/bnyro/contacts/ExampleUnitTest.kt +++ b/app/src/test/java/com/bnyro/contacts/ExampleUnitTest.kt @@ -1,6 +1,7 @@ package com.bnyro.contacts import com.bnyro.contacts.util.CalendarUtils +import com.bnyro.contacts.util.SmsUtil import org.junit.Assert.assertEquals import org.junit.Test @@ -13,7 +14,26 @@ class ExampleUnitTest { @Test fun dateConversion() { val date = "2023-05-09" - val str = CalendarUtils.dateToMillis(date) - assertEquals(date, CalendarUtils.millisToDate(str.toString(), CalendarUtils.isoDateFormat)) + val millis = CalendarUtils.dateToMillis(date)!! + assertEquals(date, CalendarUtils.millisToDate(millis, CalendarUtils.isoDateFormat)) + } + + @Test + fun splitLongSms() { + val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ornare lectus sit amet est placerat in egestas. Lectus sit amet est placerat. Molestie a iaculis at erat pellentesque adipiscing commodo elit. Sociis natoque penatibus et magnis dis parturient montes. Non enim praesent elementum facilisis leo vel fringilla est. Ut morbi tincidunt augue interdum velit euismod. Pharetra massa massa ultricies mi quis hendrerit. At erat pellentesque adipiscing commodo elit at imperdiet. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat est. In hac habitasse platea dictumst quisque sagittis purus. Netus et malesuada fames ac turpis egestas sed tempus. Neque volutpat ac tincidunt vitae semper quis lectus. Dui faucibus in ornare quam viverra.\n" + + "\n" + + "Tempor id eu nisl nunc mi ipsum. Nulla porttitor massa id neque aliquam vestibulum morbi. Sit amet nulla facilisi morbi tempus iaculis urna id. Aliquet bibendum enim facilisis gravida. Velit scelerisque in dictum non consectetur a erat. Sit amet facilisis magna etiam tempor orci eu lobortis. Vulputate enim nulla aliquet porttitor lacus luctus. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis. Luctus accumsan tortor posuere ac ut consequat semper viverra. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. Tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula. Montes nascetur ridiculus mus mauris vitae ultricies leo integer malesuada. Nulla facilisi nullam vehicula ipsum. Turpis egestas sed tempus urna et. Leo integer malesuada nunc vel. Aliquet nec ullamcorper sit amet risus nullam eget felis. Consequat nisl vel pretium lectus quam id leo. Magnis dis parturient montes nascetur ridiculus mus mauris vitae ultricies.\n" + + "\n" + + "In nisl nisi scelerisque eu ultrices vitae auctor eu. Eu nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Dictum varius duis at consectetur lorem donec massa. Purus in mollis nunc sed id semper. Euismod nisi porta lorem mollis aliquam ut. Aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis. Volutpat maecenas volutpat blandit aliquam etiam erat. Velit laoreet id donec ultrices tincidunt arcu non sodales neque. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Viverra mauris in aliquam sem fringilla ut morbi tincidunt augue.\n" + + "\n" + + "Viverra orci sagittis eu volutpat odio facilisis mauris. Mi eget mauris pharetra et ultrices neque ornare aenean euismod. Facilisis leo vel fringilla est. Vulputate odio ut enim blandit volutpat maecenas. Volutpat commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend. Risus at ultrices mi tempus imperdiet nulla. Praesent elementum facilisis leo vel. Eros in cursus turpis massa tincidunt dui ut. Elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt lobortis. Quis blandit turpis cursus in hac habitasse platea dictumst. Id aliquet risus feugiat in. Condimentum mattis pellentesque id nibh tortor id. Ac tortor dignissim convallis aenean et tortor at risus viverra. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus.\n" + + "\n" + + "Porttitor rhoncus dolor purus non. Vel elit scelerisque mauris pellentesque. Semper auctor neque vitae tempus quam. Elementum nibh tellus molestie nunc non blandit massa enim nec. Dictum non consectetur a erat nam at lectus. Vestibulum sed arcu non odio euismod lacinia. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Massa placerat duis ultricies lacus sed turpis. Arcu non odio euismod lacinia at quis risus sed vulputate. Mauris in aliquam sem fringilla. Odio tempor orci dapibus ultrices in iaculis. Nec ultrices dui sapien eget mi proin. Nisi quis eleifend quam adipiscing. Morbi tristique senectus et netus et. Neque laoreet suspendisse interdum consectetur.\n" + + "\n" + + "Nunc lobortis mattis aliquam faucibus purus in. Augue eget arcu dictum varius duis. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Sodales ut etiam sit amet nisl purus. Viverra orci sagittis eu volutpat odio facilisis mauris sit. Turpis cursus in hac habitasse platea. Aenean euismod elementum nisi quis eleifend. Sagittis id consectetur purus ut faucibus pulvinar elementum integer. Turpis egestas pretium aenean pharetra magna ac placerat vestibulum. Et malesuada fames ac turpis egestas sed. Velit egestas dui id ornare arcu odio ut sem. Ut enim blandit volutpat maecenas. Et netus et malesuada fames ac turpis egestas. Cursus eget nunc scelerisque viverra. Eros donec ac odio tempor. Suscipit tellus mauris a diam." + + SmsUtil.splitSmsText(text).forEach { + assert(it.length <= SmsUtil.MAX_CHAR_LIMIT) + } } } From a730cb82498ad5cf5a41c7b017b7cecced09c34c Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Fri, 1 Dec 2023 11:18:24 +0530 Subject: [PATCH 023/139] style: improve message thread design --- .../ui/components/conversation/Message.kt | 202 ++++++++++++++++++ .../contacts/ui/screens/SmsThreadScreen.kt | 136 +----------- 2 files changed, 205 insertions(+), 133 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt new file mode 100644 index 00000000..50c0df78 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt @@ -0,0 +1,202 @@ +package com.bnyro.contacts.ui.components.conversation + +import android.provider.Telephony +import android.text.format.DateUtils +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.Divider +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import com.bnyro.contacts.db.obj.SmsData + +@Composable +fun ColumnScope.Messages( + messages: List, + scrollState: LazyListState, + modifier: Modifier = Modifier +) { + val timestamped = messages.groupBy { + DateUtils.getRelativeDateTimeString( + LocalContext.current, + it.timestamp, + DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + DateUtils.FORMAT_ABBREV_ALL + ).split(", ").first() + } + LazyColumn( + state = scrollState, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + timestamped.forEach { timestamp -> + item { + DayHeader( + timestamp.key + ) + } + items(items = timestamp.value) { + val isUserMe = it.type in listOf( + Telephony.Sms.MESSAGE_TYPE_DRAFT, + Telephony.Sms.MESSAGE_TYPE_SENT, + Telephony.Sms.MESSAGE_TYPE_OUTBOX + ) + Message( + msg = it, + isUserMe = isUserMe + ) + } + } + } +} + +@Composable +fun Message( + msg: SmsData, + isUserMe: Boolean +) { + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + ChatItemBubble(msg, isUserMe) + Spacer(modifier = Modifier.height(4.dp)) + } +} + +private val leftChatBubbleShape = RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp) +private val rightChatBubbleShape = RoundedCornerShape(20.dp, 4.dp, 20.dp, 20.dp) + +@Composable +fun DayHeader(dayString: String) { + Row( + modifier = Modifier + .padding(vertical = 8.dp, horizontal = 16.dp) + .height(16.dp) + ) { + DayHeaderLine() + Text( + text = dayString, + modifier = Modifier.padding(horizontal = 16.dp), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + DayHeaderLine() + } +} + +@Composable +private fun RowScope.DayHeaderLine() { + Divider( + modifier = Modifier + .weight(1f) + .align(Alignment.CenterVertically), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) +} + +@Composable +fun ChatItemBubble( + message: SmsData, + isUserMe: Boolean +) { + val backgroundBubbleColor = if (isUserMe) { + MaterialTheme.colorScheme.surfaceColorAtElevation(100.dp) + } else { + MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp) + } + + val textColor = + MaterialTheme.colorScheme.primary + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = if (isUserMe) Arrangement.End else Arrangement.Start, + verticalAlignment = Alignment.Bottom + ) { + Surface( + modifier = if (isUserMe) { + Modifier.padding(start = 40.dp) + } else { + Modifier.padding( + end = 40.dp + ) + }, + color = backgroundBubbleColor, + shape = if (isUserMe) rightChatBubbleShape else leftChatBubbleShape + ) { + Column(modifier = Modifier.padding(16.dp)) { + ClickableMessage( + smsData = message + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(Modifier.align(if (isUserMe) Alignment.Start else Alignment.End)) { + Spacer(modifier = Modifier.width(8.dp)) + Text( + style = MaterialTheme.typography.bodyMedium, + color = textColor, + text = DateUtils.getRelativeDateTimeString( + LocalContext.current, + message.timestamp, + DateUtils.MINUTE_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + DateUtils.FORMAT_ABBREV_ALL + ).split(", ")[1] + ) + message.simNumber?.let { + Spacer(modifier = Modifier.width(8.dp)) + Text( + style = MaterialTheme.typography.bodyMedium, + color = textColor, + text = "SIM $it" + ) + } + } + } + } + } +} + +@Composable +fun ClickableMessage( + smsData: SmsData +) { + SelectionContainer { + val uriHandler = LocalUriHandler.current + ClickableText( + text = smsData.formatted, + style = MaterialTheme.typography.bodyLarge.copy(color = LocalContentColor.current), + onClick = { offset -> + val annotation = + smsData.formatted.getStringAnnotations( + offset, + offset + ).firstOrNull() + annotation?.let { + uriHandler.openUri(it.item) + } + } + ) + } +} diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index e67dc57d..ebb060db 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -2,8 +2,6 @@ package com.bnyro.contacts.ui.screens import android.annotation.SuppressLint import android.os.Build -import android.provider.Telephony -import android.text.format.DateUtils import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -16,31 +14,19 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Send -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.DismissDirection -import androidx.compose.material3.DismissValue -import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.PlainTooltipBox import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBar -import androidx.compose.material3.SwipeToDismiss import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.rememberDismissState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -53,16 +39,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.base.FullScreenDialog -import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.components.conversation.Messages import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.util.SmsUtil @@ -81,7 +64,6 @@ fun SmsThreadScreen( val threadId = smsModel.smsList.firstOrNull { it.address == address }?.threadId val smsList = threadId?.let { smsModel.smsGroups[threadId]?.sortedBy { it.timestamp } } ?: listOf() - val lazyListState = rememberLazyListState() var showContactScreen by remember { mutableStateOf(false) } @@ -129,120 +111,8 @@ fun SmsThreadScreen( modifier = Modifier .padding(pV) ) { - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - state = lazyListState - ) { - items(smsList) { smsData -> - var showDeleteSmsDialog by remember { - mutableStateOf(false) - } - - val state = rememberDismissState( - confirmValueChange = { - if (it == DismissValue.DismissedToEnd) { - showDeleteSmsDialog = true - } - return@rememberDismissState false - } - ) - val isSender = smsData.type in listOf( - Telephony.Sms.MESSAGE_TYPE_DRAFT, - Telephony.Sms.MESSAGE_TYPE_SENT, - Telephony.Sms.MESSAGE_TYPE_OUTBOX - ) - val defaultCornerRadius = 12.dp - val edgedCornerRadius = 3.dp - val messageSidePadding = 70.dp - - val messageAlignment = if (isSender) Alignment.End else Alignment.Start - SwipeToDismiss( - modifier = Modifier.align(messageAlignment), - state = state, - background = {}, - dismissContent = { - ElevatedCard( - modifier = Modifier - .padding(horizontal = 10.dp, vertical = 5.dp) - .padding( - start = if (isSender) messageSidePadding else 0.dp, - end = if (!isSender) messageSidePadding else 0.dp - ) - .fillMaxWidth(), - shape = RoundedCornerShape( - bottomEnd = if (isSender) edgedCornerRadius else defaultCornerRadius, - bottomStart = if (isSender) defaultCornerRadius else edgedCornerRadius, - topEnd = defaultCornerRadius, - topStart = defaultCornerRadius - ), - elevation = CardDefaults.elevatedCardElevation( - defaultElevation = if (isSender) 20.dp else 5.dp - ) - ) { - Column( - modifier = Modifier.padding(12.dp) - ) { - SelectionContainer { - val uriHandler = LocalUriHandler.current - ClickableText( - text = smsData.formatted, - style = TextStyle.Default.copy( - color = MaterialTheme.colorScheme.onBackground, - fontSize = 15.sp - ), - onClick = { offset -> - val annotation = - smsData.formatted.getStringAnnotations( - offset, - offset - ).firstOrNull() - annotation?.let { - uriHandler.openUri(it.item) - } - } - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - Text( - fontSize = 14.sp, - color = MaterialTheme.colorScheme.primary, - text = DateUtils.getRelativeTimeSpanString( - smsData.timestamp - ) - .toString() - ) - if (smsData.simNumber != null) { - Spacer(modifier = Modifier.width(8.dp)) - Text( - fontSize = 14.sp, - color = MaterialTheme.colorScheme.primary, - text = "SIM ${smsData.simNumber}" - ) - } - } - } - } - }, - directions = setOf(DismissDirection.StartToEnd) - ) - - if (showDeleteSmsDialog) { - ConfirmationDialog( - onDismissRequest = { showDeleteSmsDialog = false }, - title = stringResource(R.string.delete_message), - text = stringResource(R.string.irreversible) - ) { - smsModel.deleteSms(context, smsData.id, smsData.threadId) - } - } - } - } + val state = rememberLazyListState() + Messages(messages = smsList, scrollState = state) Spacer(modifier = Modifier.height(10.dp)) if (subscriptions != null && subscriptions.size >= 2) { From f08843b9c82dc2a6f8da0393e74baf0d99f94630 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 3 Dec 2023 21:18:20 +0530 Subject: [PATCH 024/139] feat: swipe to delete --- .../ui/components/conversation/Message.kt | 53 ++++++++++++++++--- .../contacts/ui/screens/SmsThreadScreen.kt | 2 +- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt index 50c0df78..aed85116 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt @@ -18,25 +18,38 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.DismissValue import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.SwipeToDismiss import androidx.compose.material3.Text +import androidx.compose.material3.rememberDismissState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable +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.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData +import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.models.SmsModel +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColumnScope.Messages( messages: List, scrollState: LazyListState, - modifier: Modifier = Modifier + smsModel: SmsModel ) { val timestamped = messages.groupBy { DateUtils.getRelativeDateTimeString( @@ -59,16 +72,44 @@ fun ColumnScope.Messages( timestamp.key ) } - items(items = timestamp.value) { - val isUserMe = it.type in listOf( + items(items = timestamp.value) { smsData -> + val isUserMe = smsData.type in listOf( Telephony.Sms.MESSAGE_TYPE_DRAFT, Telephony.Sms.MESSAGE_TYPE_SENT, Telephony.Sms.MESSAGE_TYPE_OUTBOX ) - Message( - msg = it, - isUserMe = isUserMe + var showDeleteSmsDialog by remember { + mutableStateOf(false) + } + + val state = rememberDismissState( + confirmValueChange = { + if (it == DismissValue.DismissedToEnd) { + showDeleteSmsDialog = true + } + return@rememberDismissState false + } + ) + SwipeToDismiss( + state = state, + background = {}, + dismissContent = { + Message( + msg = smsData, + isUserMe = isUserMe + ) + } ) + if (showDeleteSmsDialog) { + val context = LocalContext.current + ConfirmationDialog( + onDismissRequest = { showDeleteSmsDialog = false }, + title = stringResource(R.string.delete_message), + text = stringResource(R.string.irreversible) + ) { + smsModel.deleteSms(context, smsData.id, smsData.threadId) + } + } } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index ebb060db..4570229e 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -112,7 +112,7 @@ fun SmsThreadScreen( .padding(pV) ) { val state = rememberLazyListState() - Messages(messages = smsList, scrollState = state) + Messages(messages = smsList, scrollState = state, smsModel = smsModel) Spacer(modifier = Modifier.height(10.dp)) if (subscriptions != null && subscriptions.size >= 2) { From 30c60359e991487501cead97959a803c63e2e37f Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 3 Dec 2023 22:00:12 +0530 Subject: [PATCH 025/139] feat: improve annotation performance --- .../java/com/bnyro/contacts/db/obj/SmsData.kt | 94 ++----------------- .../bnyro/contacts/util/TextFormatUtils.kt | 73 ++++++++++++++ 2 files changed, 83 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt index 8e5b8ce0..7ac21903 100644 --- a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt +++ b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt @@ -1,94 +1,20 @@ package com.bnyro.contacts.db.obj -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextDecoration import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Ignore import androidx.room.PrimaryKey -import com.bnyro.contacts.util.Format -import com.bnyro.contacts.util.addKeywords +import com.bnyro.contacts.util.generateAnnotations @Entity(tableName = "localSms") data class SmsData( @PrimaryKey(autoGenerate = true) var id: Long = 0, - @ColumnInfo val address: String = "", - @ColumnInfo val body: String = "", - @ColumnInfo val timestamp: Long = 0, - @ColumnInfo val threadId: Long = 0, - @ColumnInfo val type: Int = 0, - @ColumnInfo(defaultValue = "NULL") var simNumber: Int? = null -) { - val formatted: AnnotatedString - get() { - val text = body - val keywords = mutableListOf>().apply { - addKeywords( - text, - linkRegex, - Format.LINK - ) - addKeywords( - text, - emailRegex, - Format.EMAIL - ) - addKeywords( - text, - phoneRegex, - Format.PHONE - ) - } - - return buildAnnotatedString { - append(text) - keywords.forEach { kw -> - val (format, keyword) = kw - val indexOf = text.indexOf(keyword) - addStyle( - style = SpanStyle( - color = Color.Blue, - textDecoration = when (format) { - Format.LINK -> TextDecoration.Underline - else -> TextDecoration.None - } - ), - start = indexOf, - end = indexOf + keyword.length - ) - val link = when (format) { - Format.LINK -> if (keyword.startsWith("http")) { - keyword - } else { - "http://$keyword" - } - - Format.PHONE -> - "tel:$keyword" - - Format.EMAIL -> "mailto:$keyword" - } - addStringAnnotation( - tag = format.name, - annotation = link, - start = indexOf, - end = indexOf + keyword.length - ) - } - } - } - - companion object { - private val linkRegex = Regex( - "(?>.addKeywords( results = results.next() } } + +fun generateAnnotations(text: String): AnnotatedString { + val keywords = mutableListOf>().apply { + addKeywords( + text, + linkRegex, + Format.LINK + ) + addKeywords( + text, + emailRegex, + Format.EMAIL + ) + addKeywords( + text, + phoneRegex, + Format.PHONE + ) + } + + return buildAnnotatedString { + append(text) + keywords.forEach { kw -> + val (format, keyword) = kw + val indexOf = text.indexOf(keyword) + addStyle( + style = SpanStyle( + color = Color.Blue, + textDecoration = when (format) { + Format.LINK -> TextDecoration.Underline + else -> TextDecoration.None + } + ), + start = indexOf, + end = indexOf + keyword.length + ) + val link = when (format) { + Format.LINK -> if (keyword.startsWith("http")) { + keyword + } else { + "http://$keyword" + } + + Format.PHONE -> + "tel:$keyword" + + Format.EMAIL -> "mailto:$keyword" + } + addStringAnnotation( + tag = format.name, + annotation = link, + start = indexOf, + end = indexOf + keyword.length + ) + } + } +} + +private val linkRegex = Regex( + "(? Date: Mon, 11 Dec 2023 21:02:27 +0100 Subject: [PATCH 026/139] feat: option to add unknown sms sender to contact (closes #339) --- .idea/gradle.xml | 4 ++-- .../components/dialogs/AddToContactDialog.kt | 4 +--- .../contacts/ui/screens/SmsThreadScreen.kt | 23 ++++++++++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f01eec37..0897082f 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,15 +4,15 @@ diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/AddToContactDialog.kt b/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/AddToContactDialog.kt index dcf8eb1b..ad89b6bb 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/AddToContactDialog.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/AddToContactDialog.kt @@ -28,9 +28,7 @@ import com.bnyro.contacts.ui.screens.EditorScreen import kotlinx.coroutines.launch @Composable -fun AddToContactDialog( - newNumber: String -) { +fun AddToContactDialog(newNumber: String) { val context = LocalContext.current val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index ab7f2beb..e1c9ae63 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.PersonAddAlt1 import androidx.compose.material.icons.filled.Send import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledIconButton @@ -43,6 +44,7 @@ import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.base.ElevatedTextInputField import com.bnyro.contacts.ui.components.base.FullScreenDialog import com.bnyro.contacts.ui.components.conversation.Messages +import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.util.SmsUtil @@ -62,9 +64,6 @@ fun SmsThreadScreen( val threadId = smsModel.smsList.firstOrNull { it.address == address }?.threadId val smsList = threadId?.let { smsModel.smsGroups[threadId]?.sortedBy { it.timestamp } } ?: listOf() - var showContactScreen by remember { - mutableStateOf(false) - } val subscriptions = remember { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { SmsUtil.getSubscriptions(context) @@ -73,6 +72,13 @@ fun SmsThreadScreen( } } + var showContactScreen by remember { + mutableStateOf(false) + } + var showAddToContactDialog by remember { + mutableStateOf(false) + } + FullScreenDialog(onClose = onClose) { Scaffold( topBar = { @@ -101,6 +107,13 @@ fun SmsThreadScreen( ) { onClose.invoke() } + }, + actions = { + if (contactData == null) { + ClickableIcon(icon = Icons.Default.PersonAddAlt1) { + showAddToContactDialog = true + } + } } ) } @@ -199,4 +212,8 @@ fun SmsThreadScreen( showContactScreen = false } } + + if (showAddToContactDialog) { + AddToContactDialog(newNumber = address) + } } From a8399b1f4d82db82cb0e7a6d4d7674f3daa86f9d Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 11 Dec 2023 21:05:51 +0100 Subject: [PATCH 027/139] fix: delete sms action doesn't work --- app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt index 8787c389..ee3d25d9 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt @@ -95,7 +95,7 @@ class SmsReceiver : BroadcastReceiver() { val smsThreadPendingIntent = PendingIntent.getActivity( context, - 1, + 2, smsThreadIntent, PendingIntent.FLAG_IMMUTABLE ) From 391a21298db04b1a48c148e24f79c0c96781e8f1 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 12 Dec 2023 17:56:34 +0100 Subject: [PATCH 028/139] fix: enter in contacts search field creates new line (closes #350) --- .../com/bnyro/contacts/ui/components/ContactSearchScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt index fba08a5c..02b2807b 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search @@ -23,6 +25,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData @@ -94,7 +97,8 @@ fun ContactSearchScreen( colors = TextFieldDefaults.colors( focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent - ) + ), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) ) ContactsList( contacts = visibleContacts, From 8425a183d2d4f95b6656f6400a332d6034ad7279 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 12 Dec 2023 18:09:42 +0100 Subject: [PATCH 029/139] feat: expand sms text field (closes #327) --- .../ui/components/ContactSearchScreen.kt | 31 ++--- .../components/base/ElevatedTextInputField.kt | 117 +++++------------- .../contacts/ui/screens/SmsThreadScreen.kt | 7 +- 3 files changed, 45 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt index 02b2807b..712b5cbb 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt @@ -31,6 +31,7 @@ import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions import com.bnyro.contacts.ui.components.base.ClickableIcon +import com.bnyro.contacts.ui.components.base.ElevatedTextInputField import com.bnyro.contacts.ui.components.base.FullScreenDialog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -74,31 +75,17 @@ fun ContactSearchScreen( } Column(Modifier.fillMaxSize()) { - TextField( + ElevatedTextInputField( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp) - .padding(top = 8.dp) - .focusRequester(focusRequester), - value = searchQuery, - onValueChange = { searchQuery = it }, - placeholder = { Text(stringResource(id = R.string.search)) }, - leadingIcon = { - Icon(imageVector = Icons.Default.Search, contentDescription = null) - }, - trailingIcon = { - if (searchQuery.isNotEmpty()) { - ClickableIcon(icon = Icons.Default.Close) { - searchQuery = "" - } - } - }, - shape = RoundedCornerShape(50), - colors = TextFieldDefaults.colors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) + .padding(top = 8.dp), + query = searchQuery, + onQueryChange = { searchQuery = it }, + leadingIcon = Icons.Default.Search, + placeholder = stringResource(id = R.string.search), + imeAction = ImeAction.Done, + focusRequester = focusRequester ) ContactsList( contacts = visibleContacts, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt index 74dbd235..0bdba8b3 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt @@ -1,106 +1,57 @@ package com.bnyro.contacts.ui.components.base -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.Surface -import androidx.compose.material3.TextFieldColors +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.contentColorFor -import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.semantics.onClick -import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex -private val SearchBarIconOffsetX: Dp = 4.dp -private val TonalElevation = 10.dp - -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ElevatedTextInputField( + modifier: Modifier = Modifier, query: String, onQueryChange: (String) -> Unit, - modifier: Modifier = Modifier, enabled: Boolean = true, - placeholder: @Composable (() -> Unit)? = null, - leadingIcon: @Composable (() -> Unit)? = null, - trailingIcon: @Composable (() -> Unit)? = null, - colors: TextFieldColors = SearchBarDefaults.inputFieldColors(), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + placeholder: String? = null, + leadingIcon: ImageVector? = null, + imeAction: ImeAction = ImeAction.Default, + focusRequester: FocusRequester = remember { FocusRequester() } ) { - val focusRequester = remember { FocusRequester() } - - val elevationColor = MaterialTheme.colorScheme.surfaceColorAtElevation(TonalElevation) - Surface( - shape = RoundedCornerShape(36.dp), - color = elevationColor, - contentColor = contentColorFor(elevationColor), - tonalElevation = TonalElevation, + TextField( modifier = modifier - .zIndex(1f) - ) { - BasicTextField( - value = query, - onValueChange = onQueryChange, - modifier = modifier - .height(SearchBarDefaults.InputFieldHeight) - .fillMaxWidth() - .focusRequester(focusRequester) - .semantics { - onClick { - focusRequester.requestFocus() - true - } - }, - enabled = enabled, - textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onBackground), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Default), - cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), - interactionSource = interactionSource, - decorationBox = @Composable { innerTextField -> - TextFieldDefaults.DecorationBox( - value = query, - innerTextField = innerTextField, - enabled = enabled, - singleLine = true, - visualTransformation = VisualTransformation.None, - interactionSource = interactionSource, - placeholder = placeholder, - leadingIcon = leadingIcon?.let { leading -> - { - Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() } - } - }, - trailingIcon = trailingIcon?.let { trailing -> - { - Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() } - } - }, - shape = SearchBarDefaults.inputFieldShape, - colors = colors, - contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(), - container = {} - ) + .focusRequester(focusRequester), + value = query, + onValueChange = onQueryChange, + placeholder = { placeholder?.let { Text(it) } }, + leadingIcon = { + leadingIcon?.let { Icon(imageVector = it, contentDescription = null) } + }, + trailingIcon = { + if (query.isNotEmpty()) { + ClickableIcon(icon = Icons.Default.Close) { + onQueryChange("") + } } - ) - } + }, + shape = RoundedCornerShape(50), + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ), + keyboardOptions = KeyboardOptions(imeAction = imeAction) + ) } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index e1c9ae63..f8af039c 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -158,13 +158,10 @@ fun SmsThreadScreen( } ElevatedTextInputField( - modifier = Modifier - .weight(1f), + modifier = Modifier.weight(1f), query = text, onQueryChange = { text = it }, - placeholder = { - Text(stringResource(R.string.send)) - } + placeholder = stringResource(R.string.send) ) Spacer(modifier = Modifier.width(8.dp)) From 5b8c786394ba2304794c1d31d2fc38b133f38ebf Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Thu, 21 Dec 2023 22:21:56 +0530 Subject: [PATCH 030/139] refactor: use coroutine flow for sms --- app/src/main/java/com/bnyro/contacts/App.kt | 22 +++- .../com/bnyro/contacts/db/dao/LocalSmsDao.kt | 3 +- .../contacts/receivers/DeleteSmsReceiver.kt | 8 +- .../bnyro/contacts/receivers/SmsReceiver.kt | 18 +-- .../com/bnyro/contacts/repo/DeviceSmsRepo.kt | 70 +++++++---- .../com/bnyro/contacts/repo/LocalSmsRepo.kt | 11 +- .../com/bnyro/contacts/repo/SmsRepository.kt | 5 +- .../contacts/ui/activities/BaseActivity.kt | 56 ++++++++- .../contacts/ui/activities/MainActivity.kt | 18 +-- .../com/bnyro/contacts/ui/models/SmsModel.kt | 119 +++++------------- .../contacts/ui/screens/SettingsScreen.kt | 2 +- .../contacts/ui/screens/SmsListScreen.kt | 13 +- .../contacts/ui/screens/SmsThreadScreen.kt | 8 +- .../java/com/bnyro/contacts/util/SmsUtil.kt | 50 ++------ 14 files changed, 196 insertions(+), 207 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/App.kt b/app/src/main/java/com/bnyro/contacts/App.kt index 39065819..2e928277 100644 --- a/app/src/main/java/com/bnyro/contacts/App.kt +++ b/app/src/main/java/com/bnyro/contacts/App.kt @@ -3,11 +3,13 @@ package com.bnyro.contacts import android.app.Application import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.repo.DeviceContactsRepository +import com.bnyro.contacts.repo.DeviceSmsRepo import com.bnyro.contacts.repo.LocalContactsRepository +import com.bnyro.contacts.repo.LocalSmsRepo +import com.bnyro.contacts.repo.SmsRepository import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.Preferences import com.bnyro.contacts.util.ShortcutHelper -import com.bnyro.contacts.util.SmsUtil import com.bnyro.contacts.workers.BackupWorker class App : Application() { @@ -17,6 +19,22 @@ class App : Application() { val localContactsRepository by lazy { LocalContactsRepository(this) } + val localSmsRepo by lazy { + LocalSmsRepo() + } + val deviceSmsRepo by lazy { + DeviceSmsRepo() + } + + lateinit var smsRepo: SmsRepository + + fun initSmsRepo() { + smsRepo = if (Preferences.getBoolean(Preferences.storeSmsLocallyKey, false)) { + LocalSmsRepo() + } else { + DeviceSmsRepo() + } + } override fun onCreate() { super.onCreate() @@ -31,6 +49,6 @@ class App : Application() { NotificationHelper.createChannels(this) - SmsUtil.initSmsRepo() + initSmsRepo() } } diff --git a/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt b/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt index 9475e7bb..fbb5c3c1 100644 --- a/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt +++ b/app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt @@ -4,11 +4,12 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.Query import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow @Dao interface LocalSmsDao { @Query("SELECT * from localSms") - suspend fun getAll(): List + fun getStream(): Flow> @Query("DELETE FROM localSms WHERE id = :id") suspend fun deleteSms(id: Long) diff --git a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt index 881a6e33..bb39aeb4 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt @@ -4,8 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.NotificationManagerCompat -import com.bnyro.contacts.ui.activities.MainActivity -import com.bnyro.contacts.util.SmsUtil +import com.bnyro.contacts.App import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -19,10 +18,7 @@ class DeleteSmsReceiver : BroadcastReceiver() { NotificationManagerCompat.from(context).cancel(notificationId) CoroutineScope(Dispatchers.IO).launch { - MainActivity.smsModel?.deleteSms(context, smsId, threadId) ?: run { - // if the UI is not currently opened, only delete the SMS and don't touch the UI - SmsUtil.deleteMessage(context, smsId) - } + (context.applicationContext as App).smsRepo.deleteSms(context, smsId) } } } diff --git a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt index ee3d25d9..a33d281d 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt @@ -10,15 +10,14 @@ import android.provider.Telephony import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput +import com.bnyro.contacts.App import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.enums.IntentActionType -import com.bnyro.contacts.ui.activities.MainActivity import com.bnyro.contacts.util.IntentHelper import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.NotificationHelper.MESSAGES_CHANNEL_ID import com.bnyro.contacts.util.PermissionHelper -import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -33,16 +32,19 @@ class SmsReceiver : BroadcastReceiver() { val body = message.displayMessageBody val timestamp = message.timestampMillis val threadId = - runBlocking(Dispatchers.IO) { SmsUtil.getOrCreateThreadId(context, address) } + runBlocking(Dispatchers.IO) { + (context.applicationContext as App).smsRepo.getOrCreateThreadId( + context, + address + ) + } val bareSmsData = - SmsData(-1, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX) + SmsData(0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX) createNotification(context, notificationId, bareSmsData) - val smsData = runBlocking(Dispatchers.IO) { - SmsUtil.persistMessage(context, bareSmsData) + runBlocking(Dispatchers.IO) { + (context.applicationContext as App).smsRepo.persistSms(context, bareSmsData) } - - MainActivity.smsModel?.addSmsToList(smsData) } } diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt index da9746fb..9ea63876 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt @@ -1,21 +1,46 @@ package com.bnyro.contacts.repo +import android.Manifest +import android.annotation.SuppressLint +import android.content.ContentResolver import android.content.ContentValues import android.content.Context +import android.database.ContentObserver +import android.net.Uri import android.os.Build import android.provider.Telephony -import android.util.Log +import androidx.annotation.RequiresPermission import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.ext.intValue import com.bnyro.contacts.ext.longValue import com.bnyro.contacts.ext.stringValue +import com.bnyro.contacts.util.PermissionHelper import com.bnyro.contacts.util.SmsUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext import kotlin.random.Random class DeviceSmsRepo : SmsRepository { private val contentUri = Telephony.Sms.CONTENT_URI - override suspend fun getSmsList(context: Context): List { + @SuppressLint("MissingPermission") + override fun getSmsStream(context: Context): Flow> { + return if (PermissionHelper.hasPermission(context, Manifest.permission.READ_SMS)) { + context.contentResolver.observe(contentUri).map { + getSmsList(context) + } + } else { + emptyFlow() + } + } + + @RequiresPermission(Manifest.permission.READ_SMS) + private suspend fun getSmsList(context: Context): List = withContext(Dispatchers.IO) { val simSlotMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { SmsUtil.getSubscriptions(context) .associateBy({ it.subscriptionId }, { it.simSlotIndex }) @@ -23,9 +48,9 @@ class DeviceSmsRepo : SmsRepository { null } context.contentResolver - .query(contentUri, null, null, null, null) + .query(contentUri, null, null, null, "date ASC") ?.use { cursor -> - if (!cursor.moveToFirst()) return emptyList() + if (!cursor.moveToFirst()) return@withContext emptyList() val smsList = mutableListOf() do { @@ -46,13 +71,13 @@ class DeviceSmsRepo : SmsRepository { smsList.add(SmsData(id, address, body, timestamp, threadId, type, simIndex)) } while (cursor.moveToNext()) - return smsList + return@withContext smsList } - return emptyList() + return@withContext emptyList() } - override suspend fun persistSms(context: Context, smsData: SmsData): SmsData { + override suspend fun persistSms(context: Context, smsData: SmsData) { val values = ContentValues() values.put(Telephony.Sms.ADDRESS, smsData.address) values.put(Telephony.Sms.BODY, smsData.body) @@ -61,20 +86,7 @@ class DeviceSmsRepo : SmsRepository { values.put(Telephony.Sms.TYPE, smsData.type) values.put(Telephony.Sms.THREAD_ID, smsData.threadId) - val messageUri = context.contentResolver.insert(contentUri, values) ?: return smsData - - Log.v("send_transaction", "inserted to uri: $messageUri") - - context.contentResolver.query( - messageUri, - arrayOf(Telephony.Sms._ID), - null, - null, - null - )?.use { - if (it.moveToFirst()) smsData.id = it.longValue(Telephony.Sms._ID)!! - } - return smsData + context.contentResolver.insert(contentUri, values) } override suspend fun deleteSms(context: Context, id: Long) { @@ -92,7 +104,7 @@ class DeviceSmsRepo : SmsRepository { )?.use { cursor -> while (cursor.moveToNext()) { val id = cursor.longValue(Telephony.Sms._ID) ?: continue - SmsUtil.deleteMessage(context, id) + deleteSms(context, id) } } } @@ -115,3 +127,17 @@ class DeviceSmsRepo : SmsRepository { return Random.nextLong() } } + +fun ContentResolver.observe(uri: Uri) = callbackFlow { + val observer = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(selfChange) + } + } + registerContentObserver(uri, true, observer) + // trigger first. + trySend(false) + awaitClose { + unregisterContentObserver(observer) + } +} diff --git a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt index 0234890b..eca809d0 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt @@ -3,16 +3,15 @@ package com.bnyro.contacts.repo import android.content.Context import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow import kotlin.random.Random class LocalSmsRepo : SmsRepository { - override suspend fun getSmsList(context: Context): List { - return DatabaseHolder.Db.localSmsDao().getAll() - } + override fun getSmsStream(context: Context): Flow> = + DatabaseHolder.Db.localSmsDao().getStream() - override suspend fun persistSms(context: Context, smsData: SmsData): SmsData { - val id = DatabaseHolder.Db.localSmsDao().createSms(smsData) - return smsData.copy(id = id) + override suspend fun persistSms(context: Context, smsData: SmsData) { + DatabaseHolder.Db.localSmsDao().createSms(smsData) } override suspend fun getOrCreateThreadId(context: Context, address: String): Long { diff --git a/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt index 9e4a61cb..88f250e8 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt @@ -2,10 +2,11 @@ package com.bnyro.contacts.repo import android.content.Context import com.bnyro.contacts.db.obj.SmsData +import kotlinx.coroutines.flow.Flow interface SmsRepository { - suspend fun getSmsList(context: Context): List - suspend fun persistSms(context: Context, smsData: SmsData): SmsData + fun getSmsStream(context: Context): Flow> + suspend fun persistSms(context: Context, smsData: SmsData) suspend fun getOrCreateThreadId(context: Context, address: String): Long suspend fun deleteSms(context: Context, id: Long) suspend fun deleteThread(context: Context, threadId: Long) diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt index 3eb30d07..8d5026b8 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt @@ -1,13 +1,20 @@ package com.bnyro.contacts.ui.activities import android.Manifest +import android.app.role.RoleManager +import android.content.Context +import android.content.Intent +import android.os.Build import android.os.Bundle +import android.provider.Telephony import androidx.activity.ComponentActivity import androidx.activity.viewModels import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.get import com.bnyro.contacts.ui.models.ContactsModel +import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.ui.models.ThemeModel +import com.bnyro.contacts.util.NotificationHelper import com.bnyro.contacts.util.PermissionHelper abstract class BaseActivity : ComponentActivity() { @@ -15,17 +22,58 @@ abstract class BaseActivity : ComponentActivity() { val contactsModel by viewModels { ContactsModel.Factory } + val smsModel by viewModels { SmsModel.Factory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val requiredPermissions = + smsPermissions + contactPermissions + NotificationHelper.notificationPermissions + + requestDefaultSMSApp(this) PermissionHelper.checkPermissions( this, - arrayOf( - Manifest.permission.WRITE_CONTACTS, - Manifest.permission.READ_CONTACTS - ) + requiredPermissions ) val viewModelProvider = ViewModelProvider(this) themeModel = viewModelProvider.get() } + + private fun requestDefaultSMSApp(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val roleManager = context.getSystemService(RoleManager::class.java) + if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) { + if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) { + getSmsPermissions(context) + } else { + val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS) + context.startActivity(intent) + } + } + } else { + if (Telephony.Sms.getDefaultSmsPackage(context) == context.packageName) { + getSmsPermissions(context) + } else { + val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) + intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName) + context.startActivity(intent) + } + } + } + + private fun getSmsPermissions(context: Context) { + PermissionHelper.checkPermissions(context, smsPermissions) + } + + companion object { + private val smsPermissions = arrayOf( + Manifest.permission.SEND_SMS, + Manifest.permission.READ_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.READ_PHONE_STATE + ) + private val contactPermissions = arrayOf( + Manifest.permission.WRITE_CONTACTS, + Manifest.permission.READ_CONTACTS + ) + } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index 393f2f27..8e5ffc65 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -8,13 +8,10 @@ import android.provider.ContactsContract.Intents import android.provider.ContactsContract.QuickContact import android.util.Log import androidx.activity.compose.setContent -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.get import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog import com.bnyro.contacts.ui.models.ContactsModel -import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper @@ -34,12 +31,11 @@ class MainActivity : BaseActivity() { contactsModel.initialContactData = getInsertContactData() handleVcfShareAction(contactsModel) - smsModel = ViewModelProvider(this).get() - smsModel?.initialAddressAndBody = getInitialSmsAddressAndBody() + smsModel.initialAddressAndBody = getInitialSmsAddressAndBody() setContent { ConnectYouTheme(themeModel.themeMode) { - MainAppContent(smsModel!!) + MainAppContent(smsModel) getInsertOrEditNumber()?.let { AddToContactDialog(it) } @@ -79,6 +75,7 @@ class MainActivity : BaseActivity() { ) ) } + intent?.getStringExtra("action") == "create" -> ContactData() else -> null } @@ -125,13 +122,4 @@ class MainActivity : BaseActivity() { contactsModel.importVcf(this, it) } } - - override fun onDestroy() { - super.onDestroy() - smsModel = null - } - - companion object { - var smsModel: SmsModel? = null - } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt index df963e44..3f471754 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt @@ -1,129 +1,72 @@ package com.bnyro.contacts.ui.models -import android.Manifest import android.annotation.SuppressLint -import android.app.role.RoleManager import android.content.Context -import android.content.Intent -import android.os.Build -import android.provider.Telephony import android.telephony.SubscriptionInfo import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY import androidx.lifecycle.viewModelScope -import com.bnyro.contacts.db.obj.SmsData -import com.bnyro.contacts.util.NotificationHelper -import com.bnyro.contacts.util.PermissionHelper +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.bnyro.contacts.App import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -class SmsModel : ViewModel() { - val smsList = mutableStateListOf() - val smsGroups = mutableStateMapOf>() +class SmsModel(val app: App) : ViewModel() { + var smsList = app.smsRepo.getSmsStream(context = app.applicationContext).stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), + initialValue = listOf() + ) var initialAddressAndBody by mutableStateOf?>(null) var currentSubscription: SubscriptionInfo? = null - fun fetchSmsList(context: Context) { - val requiredPermissions = smsPermissions + NotificationHelper.notificationPermissions - if (!PermissionHelper.checkPermissions(context, requiredPermissions)) return - - requestDefaultSMSApp(context) - - viewModelScope.launch { - val tempSmsList = withContext(Dispatchers.IO) { - SmsUtil.getSmsList(context) - } - - smsList.clear() - smsGroups.clear() - - smsList.addAll(tempSmsList) - val groups = smsList.groupBy { it.threadId } - .map { (threadId, smsList) -> threadId to smsList.toMutableList() } - smsGroups.putAll(groups) - } - } - - fun addSmsToList(sms: SmsData) { - smsList.add(sms) - if (smsGroups.containsKey(sms.threadId)) { - smsGroups[sms.threadId]?.add(sms) - } else { - smsGroups[sms.threadId] = mutableListOf(sms) - } + private fun updateSmsList() { + smsList = app.smsRepo.getSmsStream(context = app.applicationContext).stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(5000L), + initialValue = listOf() + ) } fun deleteSms(context: Context, id: Long, threadId: Long) { - smsList.removeAll { it.id == id } - smsGroups[threadId]?.removeAll { it.id == id } viewModelScope.launch(Dispatchers.IO) { - SmsUtil.deleteMessage(context, id) + app.smsRepo.deleteSms(context, id) } } fun deleteThread(context: Context, threadId: Long) { - smsList.removeAll { it.threadId == threadId } - smsGroups.remove(threadId) viewModelScope.launch(Dispatchers.IO) { - SmsUtil.deleteThread(context, threadId) + app.smsRepo.deleteThread(context, threadId) } } @SuppressLint("NewApi") fun sendSms(context: Context, address: String, body: String) { - viewModelScope.launch { - val sms = withContext(Dispatchers.IO) { - SmsUtil.sendSms(context, address, body, currentSubscription?.subscriptionId) - } ?: return@launch - addSmsToList(sms) - } - } - - private fun requestDefaultSMSApp(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val roleManager = context.getSystemService(RoleManager::class.java) - if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) { - if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) { - getSmsPermissions(context) - } else { - val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS) - context.startActivity(intent) - } - } - } else { - if (Telephony.Sms.getDefaultSmsPackage(context) == context.packageName) { - getSmsPermissions(context) - } else { - val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) - intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName) - context.startActivity(intent) - } + viewModelScope.launch(Dispatchers.IO) { + SmsUtil.sendSms(context, address, body, currentSubscription?.subscriptionId) } } - private fun getSmsPermissions(context: Context) { - PermissionHelper.checkPermissions(context, smsPermissions) - } - - fun refreshLocalSmsPreference(context: Context) { - SmsUtil.initSmsRepo() - fetchSmsList(context) + fun refreshLocalSmsPreference() { + app.initSmsRepo() + updateSmsList() } companion object { - private val smsPermissions = arrayOf( - Manifest.permission.SEND_SMS, - Manifest.permission.READ_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.READ_PHONE_STATE - ) + val Factory = viewModelFactory { + initializer { + val application = this[APPLICATION_KEY] as App + SmsModel(application) + } + } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt index c5cffc3d..eebeb715 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt @@ -100,7 +100,7 @@ fun SettingsScreen(onDismissRequest: () -> Unit) { title = stringResource(R.string.private_sms_database), summary = stringResource(R.string.private_sms_database_desc) ) { - smsModel.refreshLocalSmsPreference(context) + smsModel.refreshLocalSmsPreference() } Divider( modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt index 27fec6b7..7e8a3b9b 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt @@ -33,7 +33,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberDismissState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -67,10 +67,6 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { mutableStateOf(null) } - LaunchedEffect(Unit) { - smsModel.fetchSmsList(context) - } - Scaffold(floatingActionButton = { FloatingActionButton( onClick = { @@ -80,13 +76,14 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { Icon(Icons.Default.Edit, null) } }) { pv -> - if (smsModel.smsList.isNotEmpty()) { + val smsList by smsModel.smsList.collectAsState() + if (smsList.isNotEmpty()) { LazyColumn( modifier = Modifier .fillMaxSize() .padding(pv) ) { - val smsGroups = smsModel.smsGroups.entries.toList() + val smsGroups = smsList.groupBy { it.threadId }.toList() .sortedBy { (_, smsList) -> smsList.maxOf { it.timestamp } } .reversed() @@ -180,7 +177,7 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { Spacer(modifier = Modifier.height(3.dp)) Text( // safe call to avoid crashes when re-rendering - text = smsList.firstOrNull()?.body.orEmpty(), + text = smsList.lastOrNull()?.body.orEmpty(), maxLines = 2, fontSize = 14.sp, lineHeight = 18.sp diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index f8af039c..04e66684 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -61,9 +62,8 @@ fun SmsThreadScreen( ) { val context = LocalContext.current - val threadId = smsModel.smsList.firstOrNull { it.address == address }?.threadId - val smsList = - threadId?.let { smsModel.smsGroups[threadId]?.sortedBy { it.timestamp } } ?: listOf() + val allSmsList by smsModel.smsList.collectAsState() + val smsList = allSmsList.filter { it.address == address } val subscriptions = remember { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { SmsUtil.getSubscriptions(context) @@ -209,7 +209,7 @@ fun SmsThreadScreen( showContactScreen = false } } - + if (showAddToContactDialog) { AddToContactDialog(newNumber = address) } diff --git a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt index 19d5625d..9c70dcf6 100644 --- a/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt +++ b/app/src/main/java/com/bnyro/contacts/util/SmsUtil.kt @@ -7,13 +7,12 @@ import android.provider.Telephony import android.telephony.SmsManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import android.util.Log import android.widget.Toast import androidx.annotation.RequiresApi +import com.bnyro.contacts.App import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData -import com.bnyro.contacts.repo.DeviceSmsRepo -import com.bnyro.contacts.repo.LocalSmsRepo -import com.bnyro.contacts.repo.SmsRepository import java.lang.Character.UnicodeBlock import java.util.Calendar @@ -21,18 +20,6 @@ object SmsUtil { const val MAX_CHAR_LIMIT = 160 private const val MAX_CHAR_LIMIT_WITH_UNICODE = 70 - lateinit var smsRepo: SmsRepository - - fun initSmsRepo() { - smsRepo = if (Preferences.getBoolean(Preferences.storeSmsLocallyKey, false)) { - LocalSmsRepo() - } else { - DeviceSmsRepo() - } - } - - suspend fun getSmsList(context: Context) = smsRepo.getSmsList(context) - private fun getSmsManager(subscriptionId: Int?): SmsManager { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && subscriptionId != null) { @Suppress("DEPRECATION") @@ -56,49 +43,32 @@ object SmsUtil { address: String, body: String, subscriptionId: Int? = null - ): SmsData? { + ) { + Log.d("Send SMS", body) if (!ConnectionHelper.hasSignalForSms(context)) { Toast.makeText(context, R.string.connection_error, Toast.LENGTH_LONG).show() - return null + return } getSmsManager(subscriptionId) .sendTextMessage(address, null, body, null, null) + val smsRepo = (context.applicationContext as App).smsRepo val timestamp = Calendar.getInstance().timeInMillis - val threadId = getOrCreateThreadId(context, address) + val threadId = + smsRepo.getOrCreateThreadId(context, address) val smsData = SmsData( - -1, + 0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_SENT ) - persistMessage(context, smsData) - - return smsData + smsRepo.persistSms(context, smsData) } - suspend fun deleteMessage(context: Context, id: Long) = smsRepo.deleteSms(context, id) - - suspend fun deleteThread(context: Context, threadId: Long) = smsRepo.deleteThread( - context, - threadId - ) - - suspend fun persistMessage(context: Context, smsData: SmsData) = smsRepo.persistSms( - context, - smsData - ) - - suspend fun getOrCreateThreadId(context: Context, address: String) = - smsRepo.getOrCreateThreadId( - context, - address - ) - fun isShortEnoughForSms(text: String): Boolean { if (text.length > MAX_CHAR_LIMIT) return false From 5613969783eb6fffd24a0eaf8066f3465ec88599 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Mon, 1 Jan 2024 20:03:30 +0530 Subject: [PATCH 031/139] fix: clear contact selection instead of closing app when back button is pressed --- .../com/bnyro/contacts/ui/components/ContactsPage.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt index e1367496..00c0b36d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt @@ -1,5 +1,6 @@ package com.bnyro.contacts.ui.components +import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.Crossfade @@ -132,8 +133,14 @@ fun ContactsPage( Column( modifier = Modifier.padding(pv).fillMaxSize() ) { - Crossfade(targetState = selectedContacts.isEmpty(), label = "main layout") { state -> - when (state) { + Crossfade( + targetState = selectedContacts.isEmpty(), + label = "main layout" + ) { selectionEmpty -> + BackHandler(enabled = !selectionEmpty) { + selectedContacts.clear() + } + when (selectionEmpty) { true -> { TopAppBar( title = { @@ -240,6 +247,7 @@ fun ContactsPage( } ) } + false -> { TopAppBar( title = { From 4b3b28d487ef08cab1bc581f7d5b7f5444aa81ee Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Wed, 3 Jan 2024 16:45:38 +0530 Subject: [PATCH 032/139] style: improve contact editor design --- .../com/bnyro/contacts/obj/ValueWithType.kt | 15 + .../contacts/ui/components/ContactEditor.kt | 391 +++++++++--------- .../ui/components/base/LabeledTextField.kt | 50 ++- .../ui/components/editor/DatePickerEditor.kt | 153 +++++-- .../ui/components/editor/TextFieldEditor.kt | 111 ++++- .../ui/components/editor/TextFieldGroup.kt | 127 ++++++ app/src/main/res/values/strings.xml | 7 + 7 files changed, 580 insertions(+), 274 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt diff --git a/app/src/main/java/com/bnyro/contacts/obj/ValueWithType.kt b/app/src/main/java/com/bnyro/contacts/obj/ValueWithType.kt index 2e2e92f4..01ca0005 100644 --- a/app/src/main/java/com/bnyro/contacts/obj/ValueWithType.kt +++ b/app/src/main/java/com/bnyro/contacts/obj/ValueWithType.kt @@ -1,5 +1,20 @@ package com.bnyro.contacts.obj +/** + * A data class that represents a value with its type. + * + * @property value The value of the data. + * @property type The type of the data. This is the column index of the **TYPE** + * + * for example: + * - [Email][android.provider.ContactsContract.CommonDataKinds.Email] + * - [Phone][android.provider.ContactsContract.CommonDataKinds.Phone] + * - [StructuredPostal][android.provider.ContactsContract.CommonDataKinds.StructuredPostal] + * - [Provider][android.provider.ContactsContract.CommonDataKinds.Website] + * - [Event][android.provider.ContactsContract.CommonDataKinds.Event] + * - [Note][android.provider.ContactsContract.CommonDataKinds.Note] + * - [Organization][android.provider.ContactsContract.CommonDataKinds.Organization] + */ data class ValueWithType( var value: String, var type: Int? = null diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index af3a65cc..d01b8b61 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -6,29 +6,46 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable 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.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Save -import androidx.compose.material3.AlertDialog +import androidx.compose.material.icons.outlined.Cases +import androidx.compose.material.icons.outlined.Email +import androidx.compose.material.icons.outlined.LocationOn +import androidx.compose.material.icons.outlined.Note +import androidx.compose.material.icons.outlined.Person2 +import androidx.compose.material.icons.outlined.Phone +import androidx.compose.material.icons.outlined.Web +import androidx.compose.material.icons.rounded.AddPhotoAlternate +import androidx.compose.material.icons.rounded.ExpandLess +import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -43,7 +60,6 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -51,16 +67,15 @@ import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.base.LabeledTextField -import com.bnyro.contacts.ui.components.dialogs.DialogButton import com.bnyro.contacts.ui.components.dialogs.GroupsDialog -import com.bnyro.contacts.ui.components.editor.DatePickerEditor -import com.bnyro.contacts.ui.components.editor.TextFieldEditor +import com.bnyro.contacts.ui.components.editor.EventFieldGroup +import com.bnyro.contacts.ui.components.editor.TextFieldGroup import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.ImageHelper import com.bnyro.contacts.util.Preferences -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun ContactEditor( modifier: Modifier = Modifier, @@ -137,10 +152,6 @@ fun ContactEditor( mutableStateOf(false) } - var showAccountTypeDialog by remember { - mutableStateOf(false) - } - var groups by remember { mutableStateOf(contact?.groups.orEmpty()) } @@ -149,7 +160,7 @@ fun ContactEditor( val lastChosenAccount = Preferences.getLastChosenAccount() mutableStateOf( (contact?.accountType ?: lastChosenAccount.first) to - (contact?.accountName ?: lastChosenAccount.second) + (contact?.accountName ?: lastChosenAccount.second) ) } @@ -166,22 +177,98 @@ fun ContactEditor( } } - Box( - modifier = modifier.fillMaxSize() - ) { + Scaffold( + modifier = modifier.fillMaxSize(), + floatingActionButton = { + FloatingActionButton( + onClick = { + val editedContact = (contact ?: ContactData()).also { + it.firstName = firstName.value.trim() + it.surName = surName.value.trim() + it.nickName = nickName.value.takeIf { n -> n.isNotBlank() }?.trim() + it.organization = organization.value.takeIf { o -> o.isNotBlank() }?.trim() + it.displayName = "${firstName.value.trim()} ${surName.value.trim()}" + it.photo = profilePicture + it.accountType = selectedAccount.first + it.accountName = selectedAccount.second + it.websites = websites.clean() + it.numbers = phoneNumber.clean() + it.emails = emails.clean() + it.addresses = addresses.clean() + it.events = events.clean() + it.notes = notes.clean() + it.groups = groups + } + onSave.invoke(editedContact) + } + ) { + Icon( + imageVector = Icons.Default.Save, + contentDescription = null + ) + } + }, topBar = { + if (isCreatingNewDeviceContact && availableAccounts.size > 1) { + TopAppBar(title = { + var expanded by remember { mutableStateOf(false) } + Row( + Modifier + .padding(8.dp) + .clickable { + expanded = true + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedAccount.second.ifBlank { selectedAccount.first } + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = null + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + availableAccounts.forEach { + DropdownMenuItem( + text = { Text(it.second) }, + onClick = { + selectedAccount = it + Preferences.edit { + putString( + Preferences.lastChosenAccount, + "${it.first}|${it.second}" + ) + } + expanded = false + } + ) + } + } + }) + } + } + ) { pV -> val scrollState = rememberScrollState() Column( modifier = Modifier .fillMaxWidth() + .padding(pV) + .padding(horizontal = 8.dp) .verticalScroll(scrollState), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) ) { Box( modifier = Modifier .padding(top = 50.dp, bottom = 15.dp) .size(180.dp) - .clip(RoundedCornerShape(20.dp)) + .clip(if (profilePicture == null) CircleShape else RoundedCornerShape(20.dp)) + .background(MaterialTheme.colorScheme.primaryContainer) .combinedClickable( onClick = { val request = PickVisualMediaRequest( @@ -192,7 +279,8 @@ fun ContactEditor( onLongClick = { profilePicture = null } - ) + ), + contentAlignment = Alignment.Center ) { profilePicture?.let { Image( @@ -203,56 +291,70 @@ fun ContactEditor( ) } ?: run { Icon( - modifier = Modifier.fillMaxSize(), - imageVector = Icons.Default.Person, - contentDescription = null + modifier = Modifier.size(48.dp), + imageVector = Icons.Rounded.AddPhotoAlternate, + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer ) } } LabeledTextField( label = R.string.first_name, - state = firstName + state = firstName, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Person2, + contentDescription = null + ) + } ) LabeledTextField( label = R.string.last_name, - state = surName + state = surName, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Person2, + contentDescription = null + ) + } ) AnimatedVisibility(showAdvanced) { - Column { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { LabeledTextField( label = R.string.nick_name, - state = nickName + state = nickName, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Person2, + contentDescription = null + ) + } ) LabeledTextField( label = R.string.organization, - state = organization - ) - websites.forEachIndexed { index, it -> - TextFieldEditor( - label = R.string.website, - state = it, - types = ContactsHelper.websiteTypes, - keyboardType = KeyboardType.Uri, - onDelete = { - websites.removeAt(index) - }, - showDeleteAction = websites.size > 1, - moveToTop = { - websites.add(0, it) - websites.removeAt(index + 1) - } - ) { - websites.add(emptyMutable()) + state = organization, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Cases, + contentDescription = null + ) } - } + ) + TextFieldGroup( + items = websites, + label = R.string.website, + addActionText = R.string.add_website, + keyboardType = KeyboardType.Uri, + leadingIcon = Icons.Outlined.Web + ) } } - Button( - onClick = { + Row( + Modifier.clickable { showAdvanced = !showAdvanced } ) { @@ -261,85 +363,49 @@ fun ContactEditor( if (showAdvanced) R.string.show_less else R.string.show_more_fields ) ) + Icon( + imageVector = if (showAdvanced) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore, + contentDescription = null + ) } - phoneNumber.forEachIndexed { index, it -> - TextFieldEditor( - label = R.string.phone, - state = it, - types = ContactsHelper.phoneNumberTypes, - keyboardType = KeyboardType.Phone, - onDelete = { - phoneNumber.removeAt(index) - }, - showDeleteAction = phoneNumber.size > 1, - moveToTop = { - phoneNumber.add(0, it) - phoneNumber.removeAt(index + 1) - } - ) { - phoneNumber.add(emptyMutable()) - } - } - - emails.forEachIndexed { index, it -> - TextFieldEditor( - label = R.string.email, - state = it, - types = ContactsHelper.emailTypes, - keyboardType = KeyboardType.Email, - onDelete = { - emails.removeAt(index) - }, - showDeleteAction = emails.size > 1 - ) { - emails.add(emptyMutable()) - } - } - - addresses.forEachIndexed { index, it -> - TextFieldEditor( - label = R.string.address, - state = it, - types = ContactsHelper.addressTypes, - imeAction = if (it == addresses.last()) ImeAction.Done else ImeAction.Next, - onDelete = { - addresses.removeAt(index) - }, - showDeleteAction = addresses.size > 1 - ) { - addresses.add(emptyMutable()) - } - } + TextFieldGroup( + items = phoneNumber, + label = R.string.phone, + addActionText = R.string.add_phone_number, + keyboardType = KeyboardType.Phone, + leadingIcon = Icons.Outlined.Phone, + types = ContactsHelper.phoneNumberTypes + ) + TextFieldGroup( + items = emails, + label = R.string.email, + addActionText = R.string.add_e_mail, + keyboardType = KeyboardType.Email, + leadingIcon = Icons.Outlined.Email, + types = ContactsHelper.emailTypes + ) + TextFieldGroup( + items = addresses, + label = R.string.address, + addActionText = R.string.add_address, + leadingIcon = Icons.Outlined.LocationOn, + types = ContactsHelper.addressTypes + ) - events.forEachIndexed { index, it -> - DatePickerEditor( - label = R.string.event, - state = it, - types = ContactsHelper.eventTypes, - onDelete = { - events.removeAt(index) - }, - showDeleteAction = events.size > 1 - ) { - events.add(emptyMutable()) - } - } + EventFieldGroup( + items = events, + label = R.string.event, + types = ContactsHelper.eventTypes, + addActionText = R.string.add_event + ) - notes.forEachIndexed { index, it -> - TextFieldEditor( - label = R.string.note, - state = it, - types = listOf(), - imeAction = if (it == notes.last()) ImeAction.Done else ImeAction.Next, - onDelete = { - notes.removeAt(index) - }, - showDeleteAction = notes.size > 1 - ) { - notes.add(emptyMutable()) - } - } + TextFieldGroup( + items = notes, + label = R.string.note, + addActionText = R.string.add_note, + leadingIcon = Icons.Outlined.Note + ) Column( modifier = Modifier @@ -353,56 +419,10 @@ fun ContactEditor( ) { Text(stringResource(R.string.manage_groups)) } - - if (isCreatingNewDeviceContact && availableAccounts.isNotEmpty()) { - Spacer(modifier = Modifier.width(10.dp)) - Button( - onClick = { - showAccountTypeDialog = true - } - ) { - Text( - text = "${stringResource(R.string.account_type)}: ${ - selectedAccount.second.ifBlank { selectedAccount.first } - }" - ) - } - } } Spacer(Modifier.height(30.dp)) } - - FloatingActionButton( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(16.dp), - onClick = { - val editedContact = (contact ?: ContactData()).also { - it.firstName = firstName.value.trim() - it.surName = surName.value.trim() - it.nickName = nickName.value.takeIf { n -> n.isNotBlank() }?.trim() - it.organization = organization.value.takeIf { o -> o.isNotBlank() }?.trim() - it.displayName = "${firstName.value.trim()} ${surName.value.trim()}" - it.photo = profilePicture - it.accountType = selectedAccount.first - it.accountName = selectedAccount.second - it.websites = websites.clean() - it.numbers = phoneNumber.clean() - it.emails = emails.clean() - it.addresses = addresses.clean() - it.events = events.clean() - it.notes = notes.clean() - it.groups = groups - } - onSave.invoke(editedContact) - } - ) { - Icon( - imageVector = Icons.Default.Save, - contentDescription = null - ) - } } if (showGroupsDialog) { @@ -413,33 +433,4 @@ fun ContactEditor( groups = it } } - - if (showAccountTypeDialog) { - AlertDialog( - onDismissRequest = { showAccountTypeDialog = false }, - confirmButton = { - DialogButton(text = stringResource(R.string.cancel)) { - showAccountTypeDialog = false - } - }, - title = { - Text(text = stringResource(R.string.account_type)) - }, - text = { - LazyColumn( - modifier = Modifier.fillMaxWidth() - ) { - items(availableAccounts) { - ClickableText(text = it.second) { - selectedAccount = it - Preferences.edit { - putString(Preferences.lastChosenAccount, "${it.first}|${it.second}") - } - showAccountTypeDialog = false - } - } - } - } - ) - } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/base/LabeledTextField.kt b/app/src/main/java/com/bnyro/contacts/ui/components/base/LabeledTextField.kt index bc997c0b..0410bebb 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/base/LabeledTextField.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/base/LabeledTextField.kt @@ -1,26 +1,44 @@ package com.bnyro.contacts.ui.components.base import androidx.annotation.StringRes +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.unit.dp -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +/** + * A composable function that creates a labeled text field. + * + * @param modifier The modifier to apply to the text field. + * @param label The resource id of the label to display above the text field. + * @param state The mutable state of the text field's value. + * @param keyboardType The keyboard type to use for the text field. + * @param imeAction The ime action to use for the text field. + * @param leadingIcon A composable function that returns the leading icon to display in the text field. + * @param trailingIcon A composable function that returns the trailing icon to display in the text field. + * @param interactionSource The interaction source for the text field. + * @param suffix A composable function that returns the suffix to display in the text field. + * @param shape The shape of the text field. + * @param onValueChange The function to call when the value of the text field changes. + */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun LabeledTextField( modifier: Modifier = Modifier, @@ -28,14 +46,17 @@ fun LabeledTextField( state: MutableState, keyboardType: KeyboardType = KeyboardType.Text, imeAction: ImeAction = ImeAction.Next, - onValueChange: (String) -> Unit = {} + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + suffix: @Composable (() -> Unit)? = null, + shape: Shape = RoundedCornerShape(50), + onValueChange: (String) -> Unit = {}, ) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current - OutlinedTextField( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 10.dp, vertical = 5.dp), + TextField( + modifier = modifier.fillMaxWidth(), value = state.value, onValueChange = { state.value = it @@ -51,6 +72,15 @@ fun LabeledTextField( focusManager.clearFocus() }, onNext = { focusManager.moveFocus(FocusDirection.Next) } + ), + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + suffix = suffix, + interactionSource = interactionSource, + shape = shape, + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent ) ) } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt index bde836a4..5382d4db 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt @@ -1,17 +1,29 @@ package com.bnyro.contacts.ui.components.editor import androidx.annotation.StringRes +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.outlined.Event +import androidx.compose.material.icons.rounded.Remove +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -20,8 +32,10 @@ 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.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -39,7 +53,7 @@ fun DatePickerEditor( types: List, showDeleteAction: Boolean, onDelete: () -> Unit, - onCreateNew: () -> Unit + shape: Shape, ) { val datePickerOffset = 1000 * 3600 * 23 @@ -58,61 +72,114 @@ fun DatePickerEditor( }.orEmpty() } - EditorEntry( - state = state, - types = types, - onCreateNew = onCreateNew, - onDelete = onDelete, - showDeleteAction = showDeleteAction + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .defaultMinSize( + minWidth = TextFieldDefaults.MinWidth, + minHeight = TextFieldDefaults.MinHeight + ) + .fillMaxWidth() + .clip(shape) + .background(MaterialTheme.colorScheme.surfaceVariant) ) { + Icon( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + imageVector = Icons.Outlined.Event, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) Column( modifier = Modifier .weight(1f) - .height(75.dp) - .padding(10.dp) - .clip(RoundedCornerShape(10.dp)) - .clickable { - showPicker = true - } - .padding(horizontal = 10.dp), - verticalArrangement = Arrangement.Center + .padding(vertical = 4.dp) ) { Text( text = stringResource(label), fontSize = 12.sp, color = MaterialTheme.colorScheme.primary ) - Text( - text = datePickerState.selectedDateMillis + OutlinedButton( + onClick = { showPicker = true }, + colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.onSurfaceVariant) + ) { + Text(text = datePickerState.selectedDateMillis ?.let { CalendarUtils.millisToDate(it) } ?.takeIf { state.value.value.isNotEmpty() } - .orEmpty() - ) + ?: stringResource(R.string.date)) + } } - if (showPicker) { - DatePickerDialog( - onDismissRequest = { - showPicker = false - }, - dismissButton = { - DialogButton(text = stringResource(R.string.reset)) { - state.value.value = "" - showPicker = false - } - }, - confirmButton = { - DialogButton(text = stringResource(R.string.okay)) { - showPicker = false - } - }, - content = { - DatePicker( - state = datePickerState, - title = {} + if (types.isNotEmpty()) { + var expanded by remember { mutableStateOf(false) } + Row( + Modifier + .padding(horizontal = 8.dp) + .clickable { + expanded = true + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = types.firstOrNull { + it.id == state.value.type + }?.title?.let { stringResource(it) }.orEmpty(), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = null + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + types.forEach { + DropdownMenuItem( + text = { Text(stringResource(id = it.title)) }, + onClick = { + state.value = state.value.also { v -> v.type = it.id } + expanded = false + } ) } - ) + } } + if (showDeleteAction) { + IconButton(onClick = { onDelete.invoke() }) { + Icon( + imageVector = Icons.Rounded.Remove, contentDescription = stringResource( + id = R.string.delete + ), tint = MaterialTheme.colorScheme.error + ) + } + } + } + + if (showPicker) { + DatePickerDialog( + onDismissRequest = { + showPicker = false + }, + dismissButton = { + DialogButton(text = stringResource(R.string.reset)) { + state.value.value = "" + showPicker = false + } + }, + confirmButton = { + DialogButton(text = stringResource(R.string.okay)) { + showPicker = false + } + }, + content = { + DatePicker( + state = datePickerState, + title = {} + ) + } + ) } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldEditor.kt index 5272416f..a34a68f3 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldEditor.kt @@ -1,18 +1,47 @@ package com.bnyro.contacts.ui.components.editor -import android.annotation.SuppressLint import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.rounded.Remove +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +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.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import com.bnyro.contacts.R import com.bnyro.contacts.obj.TranslatedType import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.base.LabeledTextField -@SuppressLint("UnrememberedMutableState") +/** + * A composable function that renders a text field with a dropdown menu to select the type and a button to delete the text field. + * + * @param label The label for the text field. + * @param state The state of the text field. + * @param types The list of types that can be selected. + * @param keyboardType The keyboard type for the text field. + * @param imeAction The ime action for the text field. + * @param showDeleteAction Whether or not to show the delete action. + * @param leadingIcon The leading icon for the text field. + * @param onDelete The function to call when the delete action is clicked. + * @param shape The shape of the text field. + */ @Composable fun TextFieldEditor( @StringRes label: Int, @@ -21,28 +50,68 @@ fun TextFieldEditor( keyboardType: KeyboardType = KeyboardType.Text, imeAction: ImeAction = ImeAction.Next, showDeleteAction: Boolean, + leadingIcon: @Composable (() -> Unit)? = null, onDelete: () -> Unit, - moveToTop: () -> Unit = {}, - onCreateNew: () -> Unit + shape: Shape, ) { - val textState = mutableStateOf(state.value.value) + val textState = remember(state.value) { mutableStateOf(state.value.value) } + LabeledTextField( + label = label, + state = textState, + keyboardType = keyboardType, + imeAction = imeAction, + leadingIcon = leadingIcon, + trailingIcon = if (showDeleteAction) ({ + IconButton(onClick = { onDelete.invoke() }) { + Icon( + imageVector = Icons.Rounded.Remove, contentDescription = stringResource( + id = R.string.delete + ), tint = MaterialTheme.colorScheme.error + ) + } + }) else { + null + }, + suffix = if (types.isNotEmpty()) ({ + var expanded by remember { mutableStateOf(false) } + Row( + Modifier + .clickable { + expanded = true + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = types.firstOrNull { + it.id == state.value.type + }?.title?.let { stringResource(it) }.orEmpty() + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = null + ) + } - EditorEntry( - state = state, - types = types, - onDelete = onDelete, - onCreateNew = onCreateNew, - showDeleteAction = showDeleteAction, - moveToTop = moveToTop + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + types.forEach { + DropdownMenuItem( + text = { Text(stringResource(id = it.title)) }, + onClick = { + state.value = state.value.also { v -> v.type = it.id } + expanded = false + } + ) + } + } + }) else { + null + }, shape = shape ) { - LabeledTextField( - modifier = Modifier.weight(1f), - label = label, - state = textState, - keyboardType = keyboardType, - imeAction = imeAction - ) { - state.value.value = it - } + state.value.value = it } + + } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt new file mode 100644 index 00000000..bad26190 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt @@ -0,0 +1,127 @@ +package com.bnyro.contacts.ui.components.editor + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Add +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.bnyro.contacts.obj.TranslatedType +import com.bnyro.contacts.obj.ValueWithType + +/** + * A composable function that creates a group of text fields. + * + * @param items The list of already filled fields as [ValueWithType] (if any). + * @param label The label for the text fields. + * @param addActionText The text for the add action button. + * @param types The list of supported dropdown items as [TranslatedType]. + * @param leadingIcon The leading icon for the text fields. + * @param keyboardType The keyboard type for the text fields. + */ +@Composable +fun TextFieldGroup( + items: SnapshotStateList>, + @StringRes label: Int, + @StringRes addActionText: Int, + types: List = listOf(), + leadingIcon: ImageVector, + keyboardType: KeyboardType = KeyboardType.Text, +) { + Column() { + with(items) { + forEachIndexed { index, it -> + TextFieldEditor( + label = label, + state = it, + types = types, + keyboardType = keyboardType, + imeAction = if (it == last()) ImeAction.Done else ImeAction.Next, + onDelete = { + removeAt(index) + }, + showDeleteAction = size > 1, + leadingIcon = { + if (index == 0) { + Icon( + imageVector = leadingIcon, + contentDescription = null + ) + } + }, + shape = if (index == 0) RoundedCornerShape(50, 50, 0, 0) else RectangleShape + ) + } + FilledTonalButton( + modifier = Modifier + .fillMaxWidth(), + onClick = { add(mutableStateOf(ValueWithType("", 0))) }, + shape = RoundedCornerShape(0, 0, 50, 50) + ) { + Icon(imageVector = Icons.Rounded.Add, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = stringResource(id = addActionText)) + } + } + } +} + + +/** + * A composable function that creates a group of text fields. + * + * @param items The list of already filled fields as [ValueWithType] (if any). + * @param label The label for the text fields. + * @param addActionText The text for the add action button. + * @param types The list of supported dropdown items as [TranslatedType]. + */ +@Composable +fun EventFieldGroup( + items: SnapshotStateList>, + @StringRes label: Int, + @StringRes addActionText: Int, + types: List = listOf(), +) { + Column() { + with(items) { + forEachIndexed { index, it -> + DatePickerEditor( + label = label, + state = it, + types = types, + onDelete = { + removeAt(index) + }, + showDeleteAction = size > 1, + shape = if (index == 0) RoundedCornerShape(50, 50, 0, 0) else RectangleShape + ) + } + FilledTonalButton( + modifier = Modifier + .fillMaxWidth(), + onClick = { add(mutableStateOf(ValueWithType("", 0))) }, + shape = RoundedCornerShape(0, 0, 50, 50) + ) { + Icon(imageVector = Icons.Rounded.Add, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = stringResource(id = addActionText)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dbb20545..bef30069 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -120,4 +120,11 @@ Author Version Translation + Add Website + Add Phone number + Add E-mail + Add Address + Add Note + Add Event + Date \ No newline at end of file From 3f6712327de9b9092bc7111dd021864bb5eef920 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Mon, 8 Jan 2024 19:03:41 +0530 Subject: [PATCH 033/139] style: fix date picker bad layout --- .../ui/components/editor/DatePickerEditor.kt | 27 ++-- .../ui/components/editor/EditorEntry.kt | 147 ------------------ .../ui/components/editor/EventFieldGroup.kt | 66 ++++++++ .../ui/components/editor/TextFieldGroup.kt | 44 ------ 4 files changed, 79 insertions(+), 205 deletions(-) delete mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/editor/EditorEntry.kt create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/editor/EventFieldGroup.kt diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt index 5382d4db..f09fc4af 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt @@ -130,20 +130,19 @@ fun DatePickerEditor( imageVector = Icons.Default.ArrowDropDown, contentDescription = null ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - types.forEach { - DropdownMenuItem( - text = { Text(stringResource(id = it.title)) }, - onClick = { - state.value = state.value.also { v -> v.type = it.id } - expanded = false - } - ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + types.forEach { + DropdownMenuItem( + text = { Text(stringResource(id = it.title)) }, + onClick = { + state.value = state.value.also { v -> v.type = it.id } + expanded = false + } + ) + } } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/EditorEntry.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/EditorEntry.kt deleted file mode 100644 index a07d5fd5..00000000 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/EditorEntry.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.bnyro.contacts.ui.components.editor - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Remove -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -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.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.bnyro.contacts.R -import com.bnyro.contacts.obj.TranslatedType -import com.bnyro.contacts.obj.ValueWithType -import com.bnyro.contacts.ui.components.base.ClickableIcon -import com.bnyro.contacts.ui.components.dialogs.DialogButton - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun EditorEntry( - state: MutableState, - types: List, - showDeleteAction: Boolean, - onCreateNew: () -> Unit, - onDelete: () -> Unit, - moveToTop: () -> Unit = {}, - content: @Composable RowScope.() -> Unit -) { - var showTypesDialog by remember { - mutableStateOf(false) - } - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Row( - modifier = Modifier.weight(1f) - ) { - content.invoke(this) - } - Column( - modifier = Modifier.width(100.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (types.isNotEmpty()) { - Text( - modifier = Modifier - .offset(y = 10.dp) - .clip(RoundedCornerShape(20.dp)) - .combinedClickable( - onClick = { - showTypesDialog = true - }, - onLongClick = { - moveToTop.invoke() - } - ) - .padding(10.dp), - text = types.firstOrNull { - it.id == state.value.type - }?.title?.let { stringResource(it) }.orEmpty(), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - Row { - ClickableIcon( - modifier = Modifier.offset(x = if (showDeleteAction) 5.dp else 0.dp), - icon = Icons.Default.Add - ) { - onCreateNew.invoke() - } - if (showDeleteAction) { - ClickableIcon( - modifier = Modifier.offset(x = (-5).dp), - icon = Icons.Default.Remove - ) { - onDelete.invoke() - } - } - } - } - Spacer(modifier = Modifier.width(5.dp)) - } - - if (showTypesDialog) { - AlertDialog( - onDismissRequest = { - showTypesDialog = false - }, - confirmButton = { - DialogButton(text = stringResource(R.string.cancel)) { - showTypesDialog = false - } - }, - title = { - Text(stringResource(R.string.type)) - }, - text = { - LazyColumn { - items(types) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(20.dp)) - .clickable { - state.value = state.value.also { v -> v.type = it.id } - showTypesDialog = false - } - ) { - RadioButton(selected = it.id == state.value.type, onClick = { - state.value = state.value.also { v -> v.type = it.id } - showTypesDialog = false - }) - Spacer(modifier = Modifier.width(10.dp)) - Text(stringResource(it.title)) - } - } - } - } - ) - } -} diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/EventFieldGroup.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/EventFieldGroup.kt new file mode 100644 index 00000000..63a0be22 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/EventFieldGroup.kt @@ -0,0 +1,66 @@ +package com.bnyro.contacts.ui.components.editor + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Add +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.bnyro.contacts.obj.TranslatedType +import com.bnyro.contacts.obj.ValueWithType + +/** + * A composable function that creates a group of text fields. + * + * @param items The list of already filled fields as [ValueWithType] (if any). + * @param label The label for the text fields. + * @param addActionText The text for the add action button. + * @param types The list of supported dropdown items as [TranslatedType]. + */ +@Composable +fun EventFieldGroup( + items: SnapshotStateList>, + @StringRes label: Int, + @StringRes addActionText: Int, + types: List = listOf(), +) { + Column() { + with(items) { + forEachIndexed { index, it -> + DatePickerEditor( + label = label, + state = it, + types = types, + onDelete = { + removeAt(index) + }, + showDeleteAction = size > 1, + shape = if (index == 0) RoundedCornerShape(50, 50, 0, 0) else RectangleShape + ) + } + FilledTonalButton( + modifier = Modifier + .fillMaxWidth(), + onClick = { add(mutableStateOf(ValueWithType("", 0))) }, + shape = RoundedCornerShape(0, 0, 50, 50) + ) { + Icon(imageVector = Icons.Rounded.Add, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = stringResource(id = addActionText)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt index bad26190..140d5a62 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/TextFieldGroup.kt @@ -81,47 +81,3 @@ fun TextFieldGroup( } } } - - -/** - * A composable function that creates a group of text fields. - * - * @param items The list of already filled fields as [ValueWithType] (if any). - * @param label The label for the text fields. - * @param addActionText The text for the add action button. - * @param types The list of supported dropdown items as [TranslatedType]. - */ -@Composable -fun EventFieldGroup( - items: SnapshotStateList>, - @StringRes label: Int, - @StringRes addActionText: Int, - types: List = listOf(), -) { - Column() { - with(items) { - forEachIndexed { index, it -> - DatePickerEditor( - label = label, - state = it, - types = types, - onDelete = { - removeAt(index) - }, - showDeleteAction = size > 1, - shape = if (index == 0) RoundedCornerShape(50, 50, 0, 0) else RectangleShape - ) - } - FilledTonalButton( - modifier = Modifier - .fillMaxWidth(), - onClick = { add(mutableStateOf(ValueWithType("", 0))) }, - shape = RoundedCornerShape(0, 0, 50, 50) - ) { - Icon(imageVector = Icons.Rounded.Add, contentDescription = null) - Spacer(modifier = Modifier.width(8.dp)) - Text(text = stringResource(id = addActionText)) - } - } - } -} \ No newline at end of file From e0a2076828c9e1e92f464e5f06f48450e5ae37ce Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 11 Jan 2024 18:48:58 +0100 Subject: [PATCH 034/139] feat: use fast scrollbar for contacts list --- app/build.gradle.kts | 1 + .../contacts/ui/components/ContactsList.kt | 77 ++++--- .../ui/components/modifier/Scrollable.kt | 218 ------------------ .../ContactsScreen.kt} | 9 +- .../contacts/ui/screens/MainAppContent.kt | 1 - settings.gradle.kts | 1 + 6 files changed, 48 insertions(+), 259 deletions(-) delete mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/modifier/Scrollable.kt rename app/src/main/java/com/bnyro/contacts/ui/{components/ContactsPage.kt => screens/ContactsScreen.kt} (98%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7e7bc151..343e17b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") implementation("androidx.compose.material3:material3:1.2.0-alpha02") implementation("androidx.compose.material:material-icons-extended:1.4.3") + implementation("com.github.nanihadesuka:LazyColumnScrollbar:1.9.0") // VCard implementation("com.googlecode.ez-vcard:ez-vcard:0.11.3") diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsList.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactsList.kt index c7edff8a..86fd3d88 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsList.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactsList.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -15,7 +16,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions -import com.bnyro.contacts.ui.components.modifier.scrollbar +import my.nanihadesuka.compose.LazyColumnScrollbar @OptIn(ExperimentalFoundationApi::class) @Composable @@ -43,46 +44,52 @@ fun ContactsList( it.getNameBySortOrder(filterOptions.sortOder)?.firstOrNull()?.uppercase() } } - LazyColumn( - state = state, - modifier = Modifier - .padding(end = 5.dp) - .scrollbar(state, false) - .let { modifier -> - scrollConnection?.let { modifier.nestedScroll(it) } ?: modifier - } + LazyColumnScrollbar( + listState = state, + thumbColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), + thumbSelectedColor = MaterialTheme.colorScheme.primary, + thickness = 8.dp ) { - contactGroups.forEach { (firstLetter, groupedContacts) -> - stickyHeader { - CharacterHeader(firstLetter.orEmpty()) - } - items(groupedContacts) { - ContactItem( - modifier = Modifier.padding(horizontal = 10.dp), - contact = it, - sortOrder = filterOptions.sortOder, - selected = selectedContacts.contains(it), - onSinglePress = { - if (selectedContacts.isEmpty()) { - false - } else { - if (selectedContacts.contains(it)) { - selectedContacts.remove(it) + LazyColumn( + state = state, + modifier = Modifier + .padding(end = 5.dp) + .let { modifier -> + scrollConnection?.let { modifier.nestedScroll(it) } ?: modifier + } + ) { + contactGroups.forEach { (firstLetter, groupedContacts) -> + stickyHeader { + CharacterHeader(firstLetter.orEmpty()) + } + items(groupedContacts) { + ContactItem( + modifier = Modifier.padding(horizontal = 10.dp), + contact = it, + sortOrder = filterOptions.sortOder, + selected = selectedContacts.contains(it), + onSinglePress = { + if (selectedContacts.isEmpty()) { + false } else { - selectedContacts.add(it) + if (selectedContacts.contains(it)) { + selectedContacts.remove(it) + } else { + selectedContacts.add(it) + } + true } - true + }, + onLongPress = { + if (!selectedContacts.contains(it)) selectedContacts.add(it) } - }, - onLongPress = { - if (!selectedContacts.contains(it)) selectedContacts.add(it) - } - ) + ) + } } - } - item { - Spacer(modifier = Modifier.height(10.dp)) + item { + Spacer(modifier = Modifier.height(10.dp)) + } } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/modifier/Scrollable.kt b/app/src/main/java/com/bnyro/contacts/ui/components/modifier/Scrollable.kt deleted file mode 100644 index d5597c76..00000000 --- a/app/src/main/java/com/bnyro/contacts/ui/components/modifier/Scrollable.kt +++ /dev/null @@ -1,218 +0,0 @@ -package com.bnyro.contacts.ui.components.modifier - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** - * Renders a scrollbar. - * - *
  • A scrollbar is composed of two components: a track and a knob. The knob moves across - * the track
  • The scrollbar appears automatically when the user starts scrolling and disappears - * after the scrolling is finished
- * - * @param state The [LazyListState] that has been passed into the lazy list or lazy row - * @param horizontal If `true`, this will be a horizontally-scrolling (left and right) scroll bar, - * if `false`, it will be vertically-scrolling (up and down) - * @param alignEnd If `true`, the scrollbar will appear at the "end" of the scrollable composable it - * is decorating (at the right-hand side in left-to-right locales or left-hand side in right-to-left - * locales, for the vertical scrollbars -or- the bottom for horizontal scrollbars). If `false`, the - * scrollbar will appear at the "start" of the scrollable composable it is decorating (at the - * left-hand side in left-to-right locales or right-hand side in right-to-left locales, for the - * vertical scrollbars -or- the top for horizontal scrollbars) - * @param thickness How thick/wide the track and knob should be - * @param fixedKnobRatio If not `null`, the knob will always have this size, proportional to the - * size of the track. You should consider doing this if the size of the items in the scrollable - * composable is not uniform, to avoid the knob from oscillating in size as you scroll through the - * list - * @param knobCornerRadius The corner radius for the knob - * @param trackCornerRadius The corner radius for the track - * @param knobColor The color of the knob - * @param trackColor The color of the track. Make it [Color.Transparent] to hide it - * @param padding Edge padding to "squeeze" the scrollbar start/end in so it's not flush with the - * contents of the scrollable composable it is decorating - * @param visibleAlpha The alpha when the scrollbar is fully faded-in - * @param hiddenAlpha The alpha when the scrollbar is fully faded-out. Use a non-`0` number to keep - * the scrollbar from ever fading out completely - * @param fadeInAnimationDurationMs The duration of the fade-in animation when the scrollbar appears - * once the user starts scrolling - * @param fadeOutAnimationDurationMs The duration of the fade-out animation when the scrollbar - * disappears after the user is finished scrolling - * @param fadeOutAnimationDelayMs Amount of time to wait after the user is finished scrolling before - * the scrollbar begins its fade-out animation - */ -@Composable -fun Modifier.scrollbar( - state: LazyListState, - horizontal: Boolean, - alignEnd: Boolean = true, - thickness: Dp = 4.dp, - fixedKnobRatio: Float? = null, - knobCornerRadius: Dp = 4.dp, - trackCornerRadius: Dp = 2.dp, - knobColor: Color = MaterialTheme.colorScheme.primary, - trackColor: Color = Color.Transparent, - padding: Dp = 0.dp, - visibleAlpha: Float = 1f, - hiddenAlpha: Float = 0f, - fadeInAnimationDurationMs: Int = 150, - fadeOutAnimationDurationMs: Int = 500, - fadeOutAnimationDelayMs: Int = 1000 -): Modifier { - check(thickness > 0.dp) { "Thickness must be a positive integer." } - check(fixedKnobRatio == null || fixedKnobRatio < 1f) { - "A fixed knob ratio must be smaller than 1." - } - check(knobCornerRadius >= 0.dp) { "Knob corner radius must be greater than or equal to 0." } - check(trackCornerRadius >= 0.dp) { "Track corner radius must be greater than or equal to 0." } - check(hiddenAlpha <= visibleAlpha) { "Hidden alpha cannot be greater than visible alpha." } - check(fadeInAnimationDurationMs >= 0) { - "Fade in animation duration must be greater than or equal to 0." - } - check(fadeOutAnimationDurationMs >= 0) { - "Fade out animation duration must be greater than or equal to 0." - } - check(fadeOutAnimationDelayMs >= 0) { - "Fade out animation delay must be greater than or equal to 0." - } - - val targetAlpha = - if (state.isScrollInProgress) { - visibleAlpha - } else { - hiddenAlpha - } - val animationDurationMs = - if (state.isScrollInProgress) { - fadeInAnimationDurationMs - } else { - fadeOutAnimationDurationMs - } - val animationDelayMs = - if (state.isScrollInProgress) { - 0 - } else { - fadeOutAnimationDelayMs - } - - val alpha by - animateFloatAsState( - targetValue = targetAlpha, - animationSpec = - tween(delayMillis = animationDelayMs, durationMillis = animationDurationMs) - ) - - return drawWithContent { - drawContent() - - state.layoutInfo.visibleItemsInfo.firstOrNull()?.let { firstVisibleItem -> - if (state.isScrollInProgress || alpha > 0f) { - // Size of the viewport, the entire size of the scrollable composable we are decorating with - // this scrollbar. - val viewportSize = - if (horizontal) { - size.width - } else { - size.height - } - padding.toPx() * 2 - - // The size of the first visible item. We use this to estimate how many items can fit in the - // viewport. Of course, this works perfectly when all items have the same size. When they - // don't, the scrollbar knob size will grow and shrink as we scroll. - val firstItemSize = firstVisibleItem.size - - // The *estimated* size of the entire scrollable composable, as if it's all on screen at - // once. It is estimated because it's possible that the size of the first visible item does - // not represent the size of other items. This will cause the scrollbar knob size to grow - // and shrink as we scroll, if the item sizes are not uniform. - val estimatedFullListSize = firstItemSize * state.layoutInfo.totalItemsCount - - // The difference in position between the first pixels visible in our viewport as we scroll - // and the top of the fully-populated scrollable composable, if it were to show all the - // items at once. At first, the value is 0 since we start all the way to the top (or start - // edge). As we scroll down (or towards the end), this number will grow. - val viewportOffsetInFullListSpace = - state.firstVisibleItemIndex * firstItemSize + state.firstVisibleItemScrollOffset - - // Where we should render the knob in our composable. - val knobPosition = - (viewportSize / estimatedFullListSize) * viewportOffsetInFullListSpace + padding.toPx() - // How large should the knob be. - val knobSize = - fixedKnobRatio?.let { it * viewportSize } - ?: ((viewportSize * viewportSize) / estimatedFullListSize) - - // Draw the track - drawRoundRect( - color = trackColor, - topLeft = - when { - // When the scrollbar is horizontal and aligned to the bottom: - horizontal && alignEnd -> Offset( - padding.toPx(), - size.height - thickness.toPx() - ) - // When the scrollbar is horizontal and aligned to the top: - horizontal && !alignEnd -> Offset(padding.toPx(), 0f) - // When the scrollbar is vertical and aligned to the end: - alignEnd -> Offset(size.width - thickness.toPx(), padding.toPx()) - // When the scrollbar is vertical and aligned to the start: - else -> Offset(0f, padding.toPx()) - }, - size = - if (horizontal) { - Size(size.width - padding.toPx() * 2, thickness.toPx()) - } else { - Size(thickness.toPx(), size.height - padding.toPx() * 2) - }, - alpha = alpha, - cornerRadius = CornerRadius( - x = trackCornerRadius.toPx(), - y = trackCornerRadius.toPx() - ) - ) - - // Draw the knob - drawRoundRect( - color = knobColor, - topLeft = - when { - // When the scrollbar is horizontal and aligned to the bottom: - horizontal && alignEnd -> Offset( - knobPosition, - size.height - thickness.toPx() - ) - // When the scrollbar is horizontal and aligned to the top: - horizontal && !alignEnd -> Offset(knobPosition, 0f) - // When the scrollbar is vertical and aligned to the end: - alignEnd -> Offset(size.width - thickness.toPx(), knobPosition) - // When the scrollbar is vertical and aligned to the start: - else -> Offset(0f, knobPosition) - }, - size = - if (horizontal) { - Size(knobSize, thickness.toPx()) - } else { - Size(thickness.toPx(), knobSize) - }, - alpha = alpha, - cornerRadius = CornerRadius( - x = knobCornerRadius.toPx(), - y = knobCornerRadius.toPx() - ) - ) - } - } - } -} diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt similarity index 98% rename from app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt rename to app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 00c0b36d..7b09ad96 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactsPage.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -1,4 +1,4 @@ -package com.bnyro.contacts.ui.components +package com.bnyro.contacts.ui.screens import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult @@ -50,6 +50,9 @@ import com.bnyro.contacts.R import com.bnyro.contacts.enums.ContactsSource import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions +import com.bnyro.contacts.ui.components.ContactSearchScreen +import com.bnyro.contacts.ui.components.ContactsList +import com.bnyro.contacts.ui.components.NothingHere import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.base.OptionMenu import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog @@ -57,10 +60,6 @@ import com.bnyro.contacts.ui.components.dialogs.FilterDialog import com.bnyro.contacts.ui.components.dialogs.SimImportDialog import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.models.state.ContactListState -import com.bnyro.contacts.ui.screens.AboutScreen -import com.bnyro.contacts.ui.screens.EditorScreen -import com.bnyro.contacts.ui.screens.SettingsScreen -import com.bnyro.contacts.ui.screens.SingleContactScreen import com.bnyro.contacts.util.BackupHelper import com.bnyro.contacts.util.Preferences diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt index 94883bde..f003f923 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R import com.bnyro.contacts.obj.NavBarItem -import com.bnyro.contacts.ui.components.ContactsPage import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.ui.models.ThemeModel diff --git a/settings.gradle.kts b/settings.gradle.kts index 1db13926..e824c721 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://jitpack.io") } } rootProject.name = "Connect You" From 37125fd955aae429b2b6b5da0eb22bde28195b8c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 17 Jan 2024 14:56:33 +0100 Subject: [PATCH 035/139] feat: format phone numbers using international standards --- app/build.gradle.kts | 3 +++ .../com/bnyro/contacts/ui/activities/MainActivity.kt | 3 ++- .../com/bnyro/contacts/ui/models/ContactsModel.kt | 7 +++---- .../bnyro/contacts/ui/screens/SingleContactScreen.kt | 12 ++++++++++-- .../java/com/bnyro/contacts/util/ContactsHelper.kt | 8 ++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 343e17b3..5d67ed1a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -85,6 +85,9 @@ dependencies { // Image parsing implementation("androidx.exifinterface:exifinterface:1.3.6") + // Phone number formatting + implementation("com.googlecode.libphonenumber:libphonenumber:8.2.0") + // Markdown support for notes implementation("com.halilibo.compose-richtext:richtext-ui-material3:0.17.0") implementation("com.halilibo.compose-richtext:richtext-commonmark:0.17.0") diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index 8e5ffc65..19aee1b4 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -15,6 +15,7 @@ import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper +import com.bnyro.contacts.util.ContactsHelper import java.net.URLDecoder class MainActivity : BaseActivity() { @@ -106,7 +107,7 @@ class MainActivity : BaseActivity() { ?: return null val body = intent?.getStringExtra(Intent.EXTRA_TEXT) - return address.replace(ContactsModel.normalizeNumberRegex, "") to body + return ContactsHelper.normalizePhoneNumber(address) to body } private fun handleVcfShareAction(contactsModel: ContactsModel) { diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt index c41251af..db301222 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt @@ -21,6 +21,7 @@ import com.bnyro.contacts.repo.ContactsRepository import com.bnyro.contacts.repo.DeviceContactsRepository import com.bnyro.contacts.repo.LocalContactsRepository import com.bnyro.contacts.ui.models.state.ContactListState +import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.ExportHelper import com.bnyro.contacts.util.IntentHelper import com.bnyro.contacts.util.PermissionHelper @@ -217,17 +218,15 @@ class ContactsModel( fun getAvailableGroups() = contacts.map { it.groups }.flatten().distinct() fun getContactByNumber(number: String): ContactData? { - val normalizedNumber = number.replace(normalizeNumberRegex, "") + val normalizedNumber = ContactsHelper.normalizePhoneNumber(number) return contacts.firstOrNull { it.numbers.any { (value, _) -> - value.replace(normalizeNumberRegex, "") == normalizedNumber + ContactsHelper.normalizePhoneNumber(value) == normalizedNumber } } } companion object { - val normalizeNumberRegex = Regex("[-_ ]") - val Factory = viewModelFactory { initializer { val application = this[APPLICATION_KEY] as App diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt index f195b55f..eec4abea 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt @@ -17,7 +17,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Call +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Message +import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.filled.Shortcut import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -227,7 +233,9 @@ fun SingleContactScreen(contact: ContactData, onClose: () -> Unit) { ContactEntryGroup( label = stringResource(R.string.phone), - entries = contact.numbers, + entries = contact.numbers.map { + ValueWithType(ContactsHelper.normalizePhoneNumber(it.value), it.type) + }, types = ContactsHelper.phoneNumberTypes ) { IntentHelper.launchAction(context, IntentActionType.DIAL, it.value) diff --git a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt index 72c2338d..e4a2e51c 100644 --- a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt @@ -3,6 +3,7 @@ package com.bnyro.contacts.util import android.provider.ContactsContract import com.bnyro.contacts.R import com.bnyro.contacts.obj.TranslatedType +import com.google.i18n.phonenumbers.PhoneNumberUtil import ezvcard.parameter.AddressType import ezvcard.parameter.EmailType import ezvcard.parameter.TelephoneType @@ -93,4 +94,11 @@ object ContactsHelper { } } } + + fun normalizePhoneNumber(number: String): String { + val phoneUtil = PhoneNumberUtil.getInstance() + val phoneNumber = runCatching { phoneUtil.parse(number, null) } + .getOrElse { return number } + return phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) + } } From c818383358a460cf5bbb7cdb935d8479a649e784 Mon Sep 17 00:00:00 2001 From: FineFindus Date: Sat, 27 Jan 2024 12:42:32 +0100 Subject: [PATCH 036/139] fix: set search text field to single line --- .../com/bnyro/contacts/ui/components/ContactSearchScreen.kt | 3 ++- .../contacts/ui/components/base/ElevatedTextInputField.kt | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt index 712b5cbb..34e80943 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt @@ -85,7 +85,8 @@ fun ContactSearchScreen( leadingIcon = Icons.Default.Search, placeholder = stringResource(id = R.string.search), imeAction = ImeAction.Done, - focusRequester = focusRequester + focusRequester = focusRequester, + singleLine = true ) ContactsList( contacts = visibleContacts, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt index 0bdba8b3..4dc14bd5 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/base/ElevatedTextInputField.kt @@ -29,6 +29,7 @@ fun ElevatedTextInputField( placeholder: String? = null, leadingIcon: ImageVector? = null, imeAction: ImeAction = ImeAction.Default, + singleLine: Boolean = false, focusRequester: FocusRequester = remember { FocusRequester() } ) { TextField( @@ -52,6 +53,7 @@ fun ElevatedTextInputField( focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ), - keyboardOptions = KeyboardOptions(imeAction = imeAction) + keyboardOptions = KeyboardOptions(imeAction = imeAction), + singleLine = singleLine, ) } From 7159376b849ecb1b67451874aaaaaed01c7df2be Mon Sep 17 00:00:00 2001 From: Sergio Marques Date: Mon, 27 Nov 2023 00:49:04 +0000 Subject: [PATCH 037/139] Translated using Weblate (Portuguese) Currently translated at 100.0% (108 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/pt/ --- app/src/main/res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1602a4ad..4148c55b 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -5,7 +5,7 @@ Pesquisar Copiado para a área de transferência Eliminar contacto - Tem a certeza\? Isto não pode ser desfeito. + Tem a certeza? Isto não pode ser desfeito! Cancelar Criar contacto Repor From ccfa33d80ff1e731023880cfb1221b8867a2ea86 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Sun, 26 Nov 2023 18:02:17 +0000 Subject: [PATCH 038/139] Translated using Weblate (Interlingua) Currently translated at 74.0% (80 of 108 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index b63f6d50..a2e2f15d 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -68,4 +68,15 @@ Contrasigno Exportar vCard Personalisate + Titulo + Fax de travalio + Die natal + Nomine + Organisation + Travalio + Automobile + Es tu secur? Isto non potera esser disfacite! + Numero de telephono + Anniversario + Mobile
\ No newline at end of file From dc52ead2a97f8feecde6dfd457ded99cbea2603c Mon Sep 17 00:00:00 2001 From: Fqwe1 Date: Wed, 29 Nov 2023 07:00:07 +0000 Subject: [PATCH 039/139] Translated using Weblate (Ukrainian) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/uk/ --- app/src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index bb44ebde..348b6138 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -108,4 +108,5 @@ Видалити повідомлення SMS зберігатимуться лише у застосунку і не будуть доступні в інших застосунках. Номер телефону + Ви хочете надіслати повідомлення кількома меншими?
\ No newline at end of file From 7349a22c71a1e32f42bff8e2a29937c2cefd5437 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Tue, 28 Nov 2023 14:38:23 +0000 Subject: [PATCH 040/139] Translated using Weblate (Arabic) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ar/ --- app/src/main/res/values-ar/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 33dfb024..67de40a2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -107,4 +107,5 @@ حذف الرسالة سيتم تخزين الرسائل النصية داخل التطبيق فقط ولن يكون من الممكن الوصول إليها في التطبيقات الأخرى. رقم الهاتف + هل تريد إرسال الرسالة كرسائل أصغر متعددة؟ \ No newline at end of file From 217517cab25eb702fd68ff03206ed86b4b38287d Mon Sep 17 00:00:00 2001 From: Fjuro Date: Tue, 28 Nov 2023 14:41:11 +0000 Subject: [PATCH 041/139] Translated using Weblate (Czech) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/cs/ --- app/src/main/res/values-cs/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 49c89c37..cdff1725 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -107,4 +107,5 @@ Odstranit zprávu Zprávy SMS budou ukládány pouze do této aplikace a nebudou k nim mít přístup ostatní aplikace. Číslo mobilu + Chcete odeslat zprávu jako několik menších? \ No newline at end of file From c14b456fb710108e5425fa1053403127a02903c0 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 28 Nov 2023 10:26:32 +0000 Subject: [PATCH 042/139] Translated using Weblate (Spanish) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/es/ --- app/src/main/res/values-es/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d4e909f4..871f30a0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -98,7 +98,7 @@ Mensajes Enviar un mensaje Responder - No se ha podido conectar. Asegúrate de que el modo avión está desactivado. + No se ha podido conectar. Por favor, asegúrate de que el modo avión está desactivado. Contactos ¡El mensaje es demasiado largo! Guardar @@ -107,4 +107,5 @@ Borrar el mensaje Los SMS sólo se almacenarán dentro de la aplicación y no serán accesibles en otras aplicaciones. Número de teléfono + ¿Quieres enviar el mensaje en varios más pequeños? \ No newline at end of file From 5d80c7ce24f7734c416eedf170ee4e68b5025693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Wed, 29 Nov 2023 06:00:37 +0000 Subject: [PATCH 043/139] Translated using Weblate (Galician) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/gl/ --- app/src/main/res/values-gl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 2b8babd1..93d807f7 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -107,4 +107,5 @@ Eliminar mensaxe Os SMS vanse gardar dentro da app e as outras apps non poden acceder a eles. Número de teléfono + Queres enviar a mensaxe en forma de varios máis pequenos? \ No newline at end of file From dfb0afe213ada6725ab21c35a043bb5da98321eb Mon Sep 17 00:00:00 2001 From: NEXI Date: Tue, 28 Nov 2023 23:42:36 +0000 Subject: [PATCH 044/139] Translated using Weblate (Serbian) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/sr/ --- app/src/main/res/values-sr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 6cfbea3d..bcf5515f 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -118,4 +118,5 @@ Избриши поруку SMS ће бити сачуван само у апликацији и неће бити доступан у другим апликацијама. Број телефона + Желите ли да пошаљете поруку као више мањих порука? \ No newline at end of file From f7ae1d2708ec60d32129bead47bfd3f157ddb4ad Mon Sep 17 00:00:00 2001 From: 0que <0que@users.noreply.hosted.weblate.org> Date: Thu, 30 Nov 2023 14:49:51 +0000 Subject: [PATCH 045/139] Translated using Weblate (Russian) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ru/ --- app/src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1fa92925..9de36d64 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -107,4 +107,5 @@ Удалить сообщение SMS будут сохранены внутри приложения и не будут доступны другим приложениям. Номер телефона + Разбить сообщение на несколько маленьких перед отправкой? \ No newline at end of file From c9045bb35b12061170652ffd49673f84df2dab74 Mon Sep 17 00:00:00 2001 From: Raymond Nee Date: Fri, 1 Dec 2023 07:35:12 +0000 Subject: [PATCH 046/139] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2dddf685..368d0ee4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -107,4 +107,5 @@ 删除消息 短信将仅保存在此应用内并阻止其他应用访问。 电话号码 + 你想将消息分为多个更小的消息发送吗? \ No newline at end of file From d8649464e0ef203e28ffddd7c47a11cb6e342180 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Thu, 30 Nov 2023 19:54:56 +0000 Subject: [PATCH 047/139] Translated using Weblate (Interlingua) Currently translated at 74.3% (81 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index a2e2f15d..c5bfafe2 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -79,4 +79,5 @@ Numero de telephono Anniversario Mobile + Ordinar \ No newline at end of file From 3d2a7498067ad402f8bcff9b7f47fab21b0c9da2 Mon Sep 17 00:00:00 2001 From: MattSolo451 Date: Sat, 2 Dec 2023 11:56:59 +0000 Subject: [PATCH 048/139] Translated using Weblate (Polish) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/pl/ --- app/src/main/res/values-pl/strings.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2fd8d046..c93aeafa 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2,7 +2,7 @@ Nazwisko Typ - Główny ekran + Dom Szukaj Skopiowano do schowka Usuń kontakt @@ -21,7 +21,7 @@ Nazwa nie może być pusta! Zaimportowano. Wyeksportowano. - Nic tutaj nie ma. + Nie ma tu nic. Telefon E-mail Adres @@ -41,7 +41,7 @@ Rocznica Notatka Zarządzaj grupami - Usuń grupę globalnie + Usuń grupę dla wszystkich Tytuł Nowe Grupa już istnieje @@ -106,4 +106,6 @@ Hasło Nie można się połączyć. Upewnij się, czy tryb samolotowy jest wyłączony. Strona główna + Strona internetowa + Czy chcesz wysłać wiadomość jako kilka mniejszych wiadomości? \ No newline at end of file From 71fc24f87f77d024c113cda4a9d1e5fdb1b6435e Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:23:47 +0000 Subject: [PATCH 049/139] Translated using Weblate (Italian) Currently translated at 94.4% (103 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/it/ --- app/src/main/res/values-it/strings.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b2d03381..aba5873d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -52,7 +52,7 @@ Chiaro Scuro Backup automatico - Scegli la cartella + Cartella Entrambi Nessuno Ordina per @@ -93,4 +93,14 @@ Comportamento Aspetto Backup + Rispondi + Salva + Il messaggio è troppo lungo! + Contatti + Invia messaggio + Messaggi + Numero di telefono + Password + Impossibile connettersi. Assicurati che la modalità aereo sia disattivata. + Vuoi inviare il messaggio come messaggi più piccoli? \ No newline at end of file From eff789db16f657d4a8a4ff3df15a04faa6069805 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Tue, 5 Dec 2023 22:04:09 +0000 Subject: [PATCH 050/139] Translated using Weblate (Interlingua) Currently translated at 91.7% (100 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index c5bfafe2..d8ad60dd 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -80,4 +80,24 @@ Anniversario Mobile Ordinar + Numero maxime de copias de securitate + Assistente + Displaciar + Deler le topico + Base de datos private de SMS + Gerer le gruppos + Photo + Supernomine + Le SMS solmente essera immagazinate intra del application e non essera accessibile in altere applicationes. + Blog + Pseudonymo + Deler le gruppo pro totos + Gruppos + Prenomine + Le gruppo jam existe + Importate. + Non pote connecter se. Assecura te de haber le modo aeroplano disactivate. + Nota + Pagina personal + Exportate. \ No newline at end of file From ea04a966957601e4b07c5cf58ff097c95093b21a Mon Sep 17 00:00:00 2001 From: BruBru Date: Wed, 13 Dec 2023 02:29:57 +0000 Subject: [PATCH 051/139] Translated using Weblate (French) Currently translated at 99.0% (108 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fr/ --- app/src/main/res/values-fr/strings.xml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1458c616..c0412aee 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,7 +6,7 @@ Adresse Évènement Prénom - Nom de famille + Nom de Famille Auteur·e Version Traduction @@ -47,7 +47,7 @@ Réglages Onglet de démarrage Supprimer - Choisir un répertoire + Choisir un Répertoire Ordre de tri Type de compte Note @@ -55,7 +55,7 @@ Système Clair Sombre - Sauvegarde automatique + Sauvegarde Automatique Les deux Aucun Modifier @@ -71,7 +71,7 @@ Blog Nouveau %1$s sélectionné - Intervalle de sauvegarde + Intervalle de Sauvegarde Nombre maximal de sauvegardes Message Nouveau contact @@ -91,4 +91,22 @@ Nom Importer depuis une SIM Comportement + Répondre + Sauvegarder + Supprimer la discussion + Base de données SMS privée + Effacer le message + Aspect + Chiffrer les sauvegardes sous format zip + Le message est trop long ! + Contacts + Send message + Sauvegarde + Les SMS ne seront stockés que dans l\'application et ne seront pas accessibles dans d\'autres applications. + Choisir le contact + Messages + N° de téléphone + Mot de passe + Impossible de se connecter. Veuillez vous assurer que le mode avion est désactivé. + Souhaitez-vous envoyer le message sous la forme de plusieurs petits messages ? \ No newline at end of file From 19e3d3c07aaeaf4b422fd67f80c3edfc55077ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=B5=E5=8F=AF=E6=8F=9A?= Date: Sat, 16 Dec 2023 16:23:02 +0100 Subject: [PATCH 052/139] Added translation using Weblate (Chinese (Traditional)) --- app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-zh-rTW/strings.xml diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 79372b013341e819534587eb459b3a049e3a6f04 Mon Sep 17 00:00:00 2001 From: rollsicecream <153316540+rollsicecream@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:18:22 +0000 Subject: [PATCH 053/139] Translated using Weblate (French) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fr/ --- app/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c0412aee..736edbd2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -96,7 +96,7 @@ Supprimer la discussion Base de données SMS privée Effacer le message - Aspect + Apparence Chiffrer les sauvegardes sous format zip Le message est trop long ! Contacts From 80339a8b750dba42dc52e27f4b3bd190528a8920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=B5=E5=8F=AF=E6=8F=9A?= Date: Sun, 17 Dec 2023 06:33:54 +0000 Subject: [PATCH 054/139] Translated using Weblate (Chinese (Traditional)) Currently translated at 21.1% (23 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 25 +++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a6b3daec..6bca08da 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,2 +1,25 @@ - \ No newline at end of file + + 儲存 + 篩選 + 重設 + 訊息 + 新增至聯絡人 + 新的聯絡人 + 編輯 + 移除聯絡人 + 建立聯絡人 + 聯絡人 + 已複製到剪貼簿 + 重試 + 聯絡人 + 更多 + 取消 + 您確定嗎?這個動作無法復原! + 確認 + 搜尋 + 顯示較少 + 建立捷徑 + 分享 + 撥號 + \ No newline at end of file From d522f3536b9cd5cdc82ff680df973240134f7a29 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Sun, 24 Dec 2023 23:24:37 +0000 Subject: [PATCH 055/139] Translated using Weblate (Interlingua) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index d8ad60dd..69882279 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -100,4 +100,12 @@ Nota Pagina personal Exportate. + Miscellanea + Fax de casa + Collaber le barra inferior al rolar + Cryptar copias de securitate como formato zip + Casa + Icones de contacto colorate + Desira tu inviar le message como plure messages plus minor? + Telephonar \ No newline at end of file From 924c475d7ea4fa063332059250965a3efe9d5b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=B5=E5=8F=AF=E6=8F=9A?= Date: Sun, 24 Dec 2023 10:43:43 +0000 Subject: [PATCH 056/139] Translated using Weblate (Chinese (Traditional)) Currently translated at 33.0% (36 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6bca08da..28f7022e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -22,4 +22,17 @@ 建立捷徑 分享 撥號 + 網站 + 這裡什麼都沒有。 + 電子郵件 + 電話 + 事件 + 圖片 + 顯示更多欄位 + 名稱不能是空的 + 地址 + 選擇聯絡人 + 電話號碼 + 已匯入。 + 已匯出。 \ No newline at end of file From 95b53f2c522e530c3cd627f43d0f47a511e55de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Gr=C3=B6nroos?= Date: Mon, 15 Jan 2024 19:04:17 +0000 Subject: [PATCH 057/139] Translated using Weblate (Finnish) Currently translated at 100.0% (109 of 109 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fi/ --- app/src/main/res/values-fi/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index da25726c..a567fb67 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1,7 +1,7 @@ Kopioitu leikepöydälle - Oletko varma\? Tätä ei voi perua. + Oletko varma? Tätä ei voi perua! Peru OK Nollaa @@ -107,4 +107,5 @@ Yksityinen SMS-tietokanta Salaa varmuuskopiot Zip-tiedostossa SMS tallennetaan vain tässä sovelluksessa, eikä muut sovellukset pääse siihen käsiksi. + Haluatko lähettää viestin useampana pienempänä viestinä? \ No newline at end of file From 1c30d7826cc5d6badd04bcff18c5c22edb480f8c Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Tue, 16 Jan 2024 23:55:22 +0000 Subject: [PATCH 058/139] Translated using Weblate (Arabic) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ar/ --- app/src/main/res/values-ar/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 67de40a2..85615783 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -108,4 +108,11 @@ سيتم تخزين الرسائل النصية داخل التطبيق فقط ولن يكون من الممكن الوصول إليها في التطبيقات الأخرى. رقم الهاتف هل تريد إرسال الرسالة كرسائل أصغر متعددة؟ + أضف موقعًا إلكترونيًا + إضافة رقم الهاتف + إضافة البريد الإلكتروني + اضف عنوان + اضف ملاحظة + أضف حدثًا + ‪التاريخ \ No newline at end of file From 7f118285c1d508e6d5467be68d7c8f4442af639e Mon Sep 17 00:00:00 2001 From: v1s7 Date: Wed, 17 Jan 2024 11:49:40 +0000 Subject: [PATCH 059/139] Translated using Weblate (Russian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ru/ --- app/src/main/res/values-ru/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9de36d64..e63fb47f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -108,4 +108,11 @@ SMS будут сохранены внутри приложения и не будут доступны другим приложениям. Номер телефона Разбить сообщение на несколько маленьких перед отправкой? + Добавить событие + Добавить веб-сайт + Добавить номер телефона + Добавить эл. почту + Добавить примечание + Добавить адрес + Дата \ No newline at end of file From 9eb9774bc0091f8cd32be1bd603366bcea708084 Mon Sep 17 00:00:00 2001 From: Fjuro Date: Wed, 17 Jan 2024 08:28:57 +0000 Subject: [PATCH 060/139] Translated using Weblate (Czech) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/cs/ --- app/src/main/res/values-cs/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index cdff1725..db6a59b4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -108,4 +108,11 @@ Zprávy SMS budou ukládány pouze do této aplikace a nebudou k nim mít přístup ostatní aplikace. Číslo mobilu Chcete odeslat zprávu jako několik menších? + Přidat poznámku + Přidat událost + Přidat e-mail + Přidat adresu + Přidat webové stránky + Přidat telefonní číslo + Datum \ No newline at end of file From a722ab2c00e58bf659fe9d9f7734f7f8b1302dce Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 16 Jan 2024 19:49:51 +0000 Subject: [PATCH 061/139] Translated using Weblate (Spanish) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/es/ --- app/src/main/res/values-es/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 871f30a0..58f0b33e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,4 +108,11 @@ Los SMS sólo se almacenarán dentro de la aplicación y no serán accesibles en otras aplicaciones. Número de teléfono ¿Quieres enviar el mensaje en varios más pequeños? + Añadir un número de teléfono + Añadir página web + Añadir correo electrónico + Añadir dirección + Añadir evento + Añadir nota + Fecha \ No newline at end of file From 05adf16dc49118ef93acb6265e09eeb1521cd1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Wed, 17 Jan 2024 05:05:49 +0000 Subject: [PATCH 062/139] Translated using Weblate (Galician) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/gl/ --- app/src/main/res/values-gl/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 93d807f7..67dd6bc1 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -9,7 +9,7 @@ Si Copiado ao portapapeis Eliminar contacto - Continuar? A decisión non ten volta. + Continuar? A decisión non ten volta! Compartir Crear atallo Marcar @@ -108,4 +108,11 @@ Os SMS vanse gardar dentro da app e as outras apps non poden acceder a eles. Número de teléfono Queres enviar a mensaxe en forma de varios máis pequenos? + Engadir Sitio web + Engadir número telf + Engadir nota + Engadir correo + Engadir enderezo + Engadir evento + Data \ No newline at end of file From ae29c0ca3be4710f4bf33bdf2fa24d882f52635c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wr=C3=B3bel?= Date: Fri, 19 Jan 2024 04:19:23 +0000 Subject: [PATCH 063/139] Translated using Weblate (Polish) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/pl/ --- app/src/main/res/values-pl/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c93aeafa..67736195 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -94,7 +94,7 @@ Wyślij wiadomość Zdjęcie Pokaż więcej pól - Kopia + Kopia zapasowa Więcej SMS będą przechowywane wyłącznie w aplikacji i nie będą dostępne w innych aplikacjach. Blog @@ -108,4 +108,11 @@ Strona główna Strona internetowa Czy chcesz wysłać wiadomość jako kilka mniejszych wiadomości? + Dodaj stronę + Dodaj numer telefonu + Dodaj adres e-mail + Dodaj adres + Dodaj notatkę + Dodaj wydarzenie + Data \ No newline at end of file From ad0daaf7182874a178d3fe547990771b7619c43a Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Thu, 18 Jan 2024 11:49:54 +0000 Subject: [PATCH 064/139] Translated using Weblate (Interlingua) Currently translated at 99.1% (115 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 69882279..595eea12 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -108,4 +108,10 @@ Icones de contacto colorate Desira tu inviar le message como plure messages plus minor? Telephonar + Adder sito web + Adder numero de telephono + Adder E-mail + Adder adresse + Adder nota + Data \ No newline at end of file From c4bd9d86a412a7c708f080556a657dcbf5c8daea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Gr=C3=B6nroos?= Date: Sat, 20 Jan 2024 12:23:10 +0000 Subject: [PATCH 065/139] Translated using Weblate (Finnish) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fi/ --- app/src/main/res/values-fi/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index a567fb67..958ec717 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -108,4 +108,11 @@ Salaa varmuuskopiot Zip-tiedostossa SMS tallennetaan vain tässä sovelluksessa, eikä muut sovellukset pääse siihen käsiksi. Haluatko lähettää viestin useampana pienempänä viestinä? + Lisää puhelinnumero + Lisää verkkosivusto + Lisää sähköposti + Lisää osoite + Lisää tapahtuma + Päiväys + Lisää muistiinpano \ No newline at end of file From 94a9e829d841275e4e3c9ed495b4f4706a6dbc50 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Fri, 19 Jan 2024 12:13:33 +0000 Subject: [PATCH 066/139] Translated using Weblate (Interlingua) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 595eea12..9e22dedb 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -114,4 +114,5 @@ Adder adresse Adder nota Data + Adder evento \ No newline at end of file From 69ca6f9780685a83e5733deb48598fbc58def11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 21 Jan 2024 13:05:04 +0100 Subject: [PATCH 067/139] Added translation using Weblate (Estonian) --- app/src/main/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-et/strings.xml diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-et/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 02ae71826e0cadbf8a727795c0f78b6f3576f416 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 20 Jan 2024 13:40:22 +0000 Subject: [PATCH 068/139] Translated using Weblate (Ukrainian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/uk/ --- app/src/main/res/values-uk/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 348b6138..e82a7d48 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -109,4 +109,11 @@ SMS зберігатимуться лише у застосунку і не будуть доступні в інших застосунках. Номер телефону Ви хочете надіслати повідомлення кількома меншими? + Додати вебсайт + Додати номер телефону + Додати пошту + Додати адресу + Дата + Додати примітку + Додати подію \ No newline at end of file From 4c667120051b46d72b65ec8f8a2ab43a5a020669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 21 Jan 2024 12:05:21 +0000 Subject: [PATCH 069/139] Translated using Weblate (Estonian) Currently translated at 2.5% (3 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/et/ --- app/src/main/res/values-et/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index a6b3daec..220fa249 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -1,2 +1,5 @@ - \ No newline at end of file + + Otsi + Kopeeritud lõikelauale + \ No newline at end of file From 8ecd7b9baf6f0ab2fb810c49c568080d6d2ed700 Mon Sep 17 00:00:00 2001 From: Raymond Nee Date: Mon, 22 Jan 2024 18:54:36 +0000 Subject: [PATCH 070/139] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 368d0ee4..a7986854 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -108,4 +108,11 @@ 短信将仅保存在此应用内并阻止其他应用访问。 电话号码 你想将消息分为多个更小的消息发送吗? + 添加网站 + 添加电话号码 + 添加备注 + 日期 + 添加电子邮件 + 添加地址 + 添加事件 \ No newline at end of file From bd802d23a3ad220545cfedc2dc33fd92f15f90d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 21 Jan 2024 20:38:01 +0000 Subject: [PATCH 071/139] Translated using Weblate (Estonian) Currently translated at 14.6% (17 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/et/ --- app/src/main/res/values-et/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 220fa249..3ca08d71 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -2,4 +2,18 @@ Otsi Kopeeritud lõikelauale + Kustuta kontakt + Kas sa oled kindel? Seda tegevust ei saa tagasi pöörata! + Lähtesta + Lisa kontakt + Proovi uuesti + Jaga + Lisa kontaktile + Uus kontakt + Helista + Sõnum + Kontakt + Katkesta + Sobib + Loo otsetee \ No newline at end of file From abf10d0adb8bdf24cdc419dd318ea1c39fc974aa Mon Sep 17 00:00:00 2001 From: NEXI Date: Fri, 26 Jan 2024 23:05:28 +0000 Subject: [PATCH 072/139] Translated using Weblate (Serbian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/sr/ --- app/src/main/res/values-sr/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index bcf5515f..10b6bb1b 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -119,4 +119,11 @@ SMS ће бити сачуван само у апликацији и неће бити доступан у другим апликацијама. Број телефона Желите ли да пошаљете поруку као више мањих порука? + Додај број телефона + Додај имејл + Датум + Додај адресу + Додај догађај + Додај веб-сајт + Додај белешку \ No newline at end of file From f54d6917b58cbe6f94d658dfa489e451a2671c86 Mon Sep 17 00:00:00 2001 From: Luis Alfredo Figueroa Bracamontes <92luisalfredo@protonmail.com> Date: Sun, 28 Jan 2024 01:16:46 +0000 Subject: [PATCH 073/139] Translated using Weblate (Spanish) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/es/ --- app/src/main/res/values-es/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 58f0b33e..7fcd120e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -11,11 +11,11 @@ Correo electrónico Dirección Evento - Ordenación + Ordenar por Nombre Apellido Tipo - Inicio + Casa Coche Asistente Cumpleaños @@ -71,7 +71,7 @@ Contraer la barra inferior al desplazarse Varios Crear un acceso directo - Dial + Llamar Mensaje Nuevo contacto Contacto From f03be8159aa1a0cfa158cebb708a7fb73f3a3b08 Mon Sep 17 00:00:00 2001 From: Aspen Date: Sun, 28 Jan 2024 18:36:05 +0000 Subject: [PATCH 074/139] Translated using Weblate (Persian) Currently translated at 99.1% (115 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fa/ --- app/src/main/res/values-fa/strings.xml | 95 +++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6f8db568..e37fa6ae 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -8,7 +8,7 @@ تلاش دوباره درباره ما انصراف - آیا مطمئن هستید؟ این کار برگشت‌ناپذیر است. + آیا مطمئن هستید؟ این کار برگشت‌ناپذیر است! باشه جستجو هم‌رسانی @@ -21,4 +21,97 @@ تنظیمات نویسنده سامانه + پیام شما بسیار بلند است! + پشتیبان گیری + نوع + تصویر + نام + بیشینه تعداد پشتیبان گیری ها + مسیر + نسخه + ظاهر + رمز + افزودن به مخاطبین + ایجاد میان بر + تماس + پیام + مخاطب + ویرایش + پالایه + بیشتر + نمایش جستار های بیشتر + نام نمی تواند خالی باشد! + برون بُرد شد. + درون بُرد شد. + خانه + پیشه + شماره تلفن + دیگر + برزنی + انتخاب مخاطب + وبسایت + فاکس محل کار + دستیار + سازمان + درون بردن از سیم کارت + هر دو + مجوز + مخاطبین + زادروز + چینش + سالگرد + صفحه نخست + افزودن وبسایت + افزودن شماره تلفن + افزودن ایمیل + افزودن یادداشت + افزودن رویداد + تاریخ + نمایش کمتر + ذخیره + ماشین + فاکس خانه + رویداد + متصل نشد. خواهشمند است از خاموش بودن حالت هواپیما مطمئن شوید. + آیا می خواهید پیام را با چند پیام کوتاه تر بفرستید؟ + این پیامک تنها درون این برنامه ذخیره می شود و در برنامه های دیگر غیرقابل دسترس است. + نو + پاسخ + دستگاه + پشتیبان گیری (بکاپ) خودکار + متفرقه (بقیه) + رفتار + رمزگذاری پشتیبان گیری به عنوان فایل زیپ + افزودن نشانی + درون بردن vCard + برون بردن vCard + آغاز برگه + فاصله پشتیبان گیری ها + هیچ کدام + رگارنگ کردن پرتره مخاطبین + وبلاگ + مدیریت گروه ها + پاک کردن گروه برای همه + سر تیتر + گروه از پیش وجود دارد + گروه ها + اینجا چیزی نیست. + تلفن + ایمیل + نشانی + شماره تلفن + نوع حساب + نام + نام خانوادگی + آوازه + سفارشی + یادداشت + جابجا کردن با FTP + پاک کردن گفت و گو + جابجا + %1$s انتخاب شده + پاک کردن پیامک + پیام ها + فرستادن پیامک + دیتابیس پیامک خصوصی \ No newline at end of file From 00bde0e02e27401ce8e30cde656de946d75a772f Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 31 Jan 2024 18:11:20 +0100 Subject: [PATCH 075/139] chore: bump version to v9.0 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5d67ed1a..58892360 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.bnyro.contacts" minSdk = 21 targetSdk = 33 - versionCode = 27 - versionName = "8.1" + versionCode = 28 + versionName = "9.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 4b5aaf44714081be2dfe0328996d161a361bf08c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 1 Feb 2024 19:35:06 +0100 Subject: [PATCH 076/139] fix: importing vcard opens sms thread (closes #351) --- .../main/java/com/bnyro/contacts/ui/activities/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index 19aee1b4..b0bac8ea 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -97,7 +97,7 @@ class MainActivity : BaseActivity() { } private fun getInitialSmsAddressAndBody(): Pair? { - if (intent?.action !in smsSendIntents) return null + if (intent?.action !in smsSendIntents || intent?.type in BackupHelper.vCardMimeTypes) return null val address = intent?.dataString ?.split(":") @@ -112,6 +112,7 @@ class MainActivity : BaseActivity() { private fun handleVcfShareAction(contactsModel: ContactsModel) { if (intent?.type !in BackupHelper.vCardMimeTypes) return + val uri = when (intent.action) { Intent.ACTION_VIEW -> intent?.data Intent.ACTION_SEND -> intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri From a4bdcb8d9ab98709465bbaacaf2967fdd25f817c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 5 Feb 2024 16:24:11 +0100 Subject: [PATCH 077/139] refactor: normalize stored phone numbers --- .../com/bnyro/contacts/repo/DeviceSmsRepo.kt | 3 ++- .../java/com/bnyro/contacts/repo/LocalSmsRepo.kt | 11 ++++++----- .../contacts/ui/components/ContactEditor.kt | 16 ++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt index 9ea63876..2767781a 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt @@ -14,6 +14,7 @@ import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.ext.intValue import com.bnyro.contacts.ext.longValue import com.bnyro.contacts.ext.stringValue +import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.PermissionHelper import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers @@ -115,7 +116,7 @@ class DeviceSmsRepo : SmsRepository { contentUri, arrayOf(Telephony.Sms.THREAD_ID), "${Telephony.Sms.ADDRESS} = ?", - arrayOf(address), + arrayOf(ContactsHelper.normalizePhoneNumber(address)), null ) ?.use { diff --git a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt index eca809d0..61fc5a35 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt @@ -3,6 +3,7 @@ package com.bnyro.contacts.repo import android.content.Context import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.db.obj.SmsData +import com.bnyro.contacts.util.ContactsHelper import kotlinx.coroutines.flow.Flow import kotlin.random.Random @@ -15,11 +16,11 @@ class LocalSmsRepo : SmsRepository { } override suspend fun getOrCreateThreadId(context: Context, address: String): Long { - DatabaseHolder.Db.localSmsDao().getSmsByAddress(address).firstOrNull()?.let { - return it.threadId - } - - return Random.nextLong() + return DatabaseHolder.Db.localSmsDao() + .getSmsByAddress(ContactsHelper.normalizePhoneNumber(address)) + .firstOrNull() + ?.threadId + ?: Random.nextLong() } override suspend fun deleteSms(context: Context, id: Long) { diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index d01b8b61..3fd7d687 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -87,19 +87,17 @@ fun ContactEditor( val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) fun List?.fillIfEmpty(): List { - return if (this.isNullOrEmpty()) { - listOf(ValueWithType("", 0)) - } else { - this + if (this.isNullOrEmpty()) { + return listOf(ValueWithType("", 0)) } + + return this } fun List>.clean(): List { - return this.filter { it.value.value.isNotBlank() }.map { it.value } + return this.filter { it.value.value.isNotBlank() }.map { it.value }.distinct() } - fun emptyMutable() = mutableStateOf(ValueWithType("", 0)) - var showAdvanced by remember { mutableStateOf(false) } @@ -192,7 +190,9 @@ fun ContactEditor( it.accountType = selectedAccount.first it.accountName = selectedAccount.second it.websites = websites.clean() - it.numbers = phoneNumber.clean() + it.numbers = phoneNumber.clean().map { number -> + ValueWithType(ContactsHelper.normalizePhoneNumber(number.value), number.type) + } it.emails = emails.clean() it.addresses = addresses.clean() it.events = events.clean() From 6221767d47e2aecbf22b5860d4820fa99b884f7b Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 6 Feb 2024 14:11:24 +0100 Subject: [PATCH 078/139] feat: add confirm import dialog when opening vcf file in app --- .../contacts/ui/activities/MainActivity.kt | 16 ++++------ .../ui/components/ConfirmImportContacts.kt | 32 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/ConfirmImportContacts.kt diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index b0bac8ea..7e610ca1 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -6,12 +6,11 @@ import android.os.Bundle import android.os.Parcelable import android.provider.ContactsContract.Intents import android.provider.ContactsContract.QuickContact -import android.util.Log import androidx.activity.compose.setContent import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType +import com.bnyro.contacts.ui.components.ConfirmImportContactsDialog import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog -import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper @@ -30,7 +29,6 @@ class MainActivity : BaseActivity() { contactsModel.initialContactId = getInitialContactId() contactsModel.initialContactData = getInsertContactData() - handleVcfShareAction(contactsModel) smsModel.initialAddressAndBody = getInitialSmsAddressAndBody() @@ -40,6 +38,9 @@ class MainActivity : BaseActivity() { getInsertOrEditNumber()?.let { AddToContactDialog(it) } + getSharedVcfUri()?.let { + ConfirmImportContactsDialog(contactsModel, it) + } } } } @@ -110,8 +111,8 @@ class MainActivity : BaseActivity() { return ContactsHelper.normalizePhoneNumber(address) to body } - private fun handleVcfShareAction(contactsModel: ContactsModel) { - if (intent?.type !in BackupHelper.vCardMimeTypes) return + private fun getSharedVcfUri(): Uri? { + if (intent?.type !in BackupHelper.vCardMimeTypes) return null val uri = when (intent.action) { Intent.ACTION_VIEW -> intent?.data @@ -119,9 +120,6 @@ class MainActivity : BaseActivity() { else -> null } - uri?.let { - Log.d("VCF Intent", "Received a valid intent with uri : $it") - contactsModel.importVcf(this, it) - } + return uri } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ConfirmImportContacts.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ConfirmImportContacts.kt new file mode 100644 index 00000000..da30908b --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ConfirmImportContacts.kt @@ -0,0 +1,32 @@ +package com.bnyro.contacts.ui.components + +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.bnyro.contacts.R +import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.models.ContactsModel + +@Composable +fun ConfirmImportContactsDialog(contactsModel: ContactsModel, contactsUri: Uri) { + val context = LocalContext.current + + var showDialog by remember { + mutableStateOf(true) + } + + if (showDialog) { + ConfirmationDialog( + onDismissRequest = { showDialog = false }, + title = stringResource(R.string.import_vcf), + text = stringResource(R.string.import_confirm) + ) { + contactsModel.importVcf(context, contactsUri) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bef30069..5bf28677 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,6 +93,7 @@ Import vCard Export vCard Import from SIM + Do you want to import the selected VCF file? Settings Start tab From 1936026e97c883ee567d9dfd03b706db6d407e83 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 6 Feb 2024 14:16:25 +0100 Subject: [PATCH 079/139] fix: deprecated Intent#getParcelableExtra method --- app/src/main/java/com/bnyro/contacts/ext/Parcelable.kt | 8 ++++++++ .../java/com/bnyro/contacts/ui/activities/MainActivity.kt | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/ext/Parcelable.kt diff --git a/app/src/main/java/com/bnyro/contacts/ext/Parcelable.kt b/app/src/main/java/com/bnyro/contacts/ext/Parcelable.kt new file mode 100644 index 00000000..3f827a1b --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ext/Parcelable.kt @@ -0,0 +1,8 @@ +package com.bnyro.contacts.ext + +import android.content.Intent +import androidx.core.content.IntentCompat + +inline fun Intent.parcelable(key: String): T? { + return IntentCompat.getParcelableExtra(this, key, T::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index 7e610ca1..e465ea2c 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -7,6 +7,7 @@ import android.os.Parcelable import android.provider.ContactsContract.Intents import android.provider.ContactsContract.QuickContact import androidx.activity.compose.setContent +import com.bnyro.contacts.ext.parcelable import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.ConfirmImportContactsDialog @@ -115,8 +116,8 @@ class MainActivity : BaseActivity() { if (intent?.type !in BackupHelper.vCardMimeTypes) return null val uri = when (intent.action) { - Intent.ACTION_VIEW -> intent?.data - Intent.ACTION_SEND -> intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri + Intent.ACTION_VIEW -> intent.data + Intent.ACTION_SEND -> intent.parcelable(Intent.EXTRA_STREAM) else -> null } From d4a8366d58b9a00e3c71e7e9f7b93e606c23b97a Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Wed, 14 Feb 2024 17:35:17 +0530 Subject: [PATCH 080/139] fix: email regex issues --- .../bnyro/contacts/util/TextFormatUtils.kt | 8 +-- .../test/java/com/bnyro/contacts/RegexTest.kt | 60 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/com/bnyro/contacts/RegexTest.kt diff --git a/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt b/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt index d6084da2..135b27ad 100644 --- a/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt +++ b/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt @@ -79,12 +79,12 @@ fun generateAnnotations(text: String): AnnotatedString { } } -private val linkRegex = Regex( +val linkRegex = Regex( "(? Date: Wed, 14 Feb 2024 15:07:44 +0100 Subject: [PATCH 081/139] feat: collapsable bottom bar in sms screen (closes #342) --- .../com/bnyro/contacts/ui/screens/MainAppContent.kt | 8 ++++---- .../com/bnyro/contacts/ui/screens/SmsListScreen.kt | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt index f003f923..37db326f 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt @@ -117,12 +117,12 @@ fun MainAppContent(smsModel: SmsModel) { color = MaterialTheme.colorScheme.background ) { Crossfade(targetState = currentPage, label = "crossfade pager") { index -> + val scrollConnectionIfEnabled = nestedScrollConnection.takeIf { themeModel.collapsableBottomBar } + when (index) { - 0 -> ContactsPage( - nestedScrollConnection.takeIf { themeModel.collapsableBottomBar } - ) + 0 -> ContactsPage(scrollConnectionIfEnabled) - 1 -> SmsListScreen(smsModel, contactsModel) + 1 -> SmsListScreen(smsModel, contactsModel, scrollConnectionIfEnabled) } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt index 7e8a3b9b..33e03617 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt @@ -43,6 +43,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -57,7 +59,11 @@ import com.bnyro.contacts.ui.models.SmsModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { +fun SmsListScreen( + smsModel: SmsModel, + contactsModel: ContactsModel, + scrollConnection: NestedScrollConnection? +) { val context = LocalContext.current var showNumberPicker by remember { mutableStateOf(false) @@ -82,6 +88,9 @@ fun SmsListScreen(smsModel: SmsModel, contactsModel: ContactsModel) { modifier = Modifier .fillMaxSize() .padding(pv) + .let { modifier -> + scrollConnection?.let { modifier.nestedScroll(it) } ?: modifier + } ) { val smsGroups = smsList.groupBy { it.threadId }.toList() .sortedBy { (_, smsList) -> smsList.maxOf { it.timestamp } } From d6fafff796017a6daa0604511175bb5d983f8d0a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 14 Feb 2024 15:30:50 +0100 Subject: [PATCH 082/139] fix: wrong chosen date in date picker (closes #334) --- .../ui/components/editor/DatePickerEditor.kt | 13 ++++++++----- .../java/com/bnyro/contacts/util/CalendarUtils.kt | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt index f09fc4af..0a4433a6 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/editor/DatePickerEditor.kt @@ -44,6 +44,7 @@ import com.bnyro.contacts.obj.TranslatedType import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.dialogs.DialogButton import com.bnyro.contacts.util.CalendarUtils +import java.util.TimeZone @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -55,20 +56,22 @@ fun DatePickerEditor( onDelete: () -> Unit, shape: Shape, ) { - val datePickerOffset = 1000 * 3600 * 23 + val datePickerOffset = remember { + TimeZone.getDefault().rawOffset + } var showPicker by remember { mutableStateOf(false) } val datePickerState = rememberDatePickerState( runCatching { - CalendarUtils.dateToMillis(state.value.value) - }.getOrDefault(null)?.plus(datePickerOffset) + CalendarUtils.dateToMillis(state.value.value)?.plus(datePickerOffset) + }.getOrDefault(null) ) LaunchedEffect(datePickerState.selectedDateMillis) { state.value.value = datePickerState.selectedDateMillis?.let { - CalendarUtils.millisToDate(it, CalendarUtils.isoDateFormat) + CalendarUtils.millisToDate(it.minus(datePickerOffset), CalendarUtils.isoDateFormat) }.orEmpty() } @@ -104,7 +107,7 @@ fun DatePickerEditor( colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.onSurfaceVariant) ) { Text(text = datePickerState.selectedDateMillis - ?.let { CalendarUtils.millisToDate(it) } + ?.let { CalendarUtils.millisToDate(it.minus(datePickerOffset)) } ?.takeIf { state.value.value.isNotEmpty() } ?: stringResource(R.string.date)) } diff --git a/app/src/main/java/com/bnyro/contacts/util/CalendarUtils.kt b/app/src/main/java/com/bnyro/contacts/util/CalendarUtils.kt index 99a0ca5f..c5ce5209 100644 --- a/app/src/main/java/com/bnyro/contacts/util/CalendarUtils.kt +++ b/app/src/main/java/com/bnyro/contacts/util/CalendarUtils.kt @@ -17,7 +17,8 @@ object CalendarUtils { private val localizedFormat get() = DateFormat.getDateInstance() fun millisToDate(milliSeconds: Long, formatter: DateFormat = localizedFormat): String { - val calendar: Calendar = Calendar.getInstance() + val calendar = Calendar.getInstance() + calendar.clear(Calendar.ZONE_OFFSET) calendar.timeInMillis = milliSeconds return formatter.format(calendar.time) } From 855680f38e1d8073c4b65006607089d144eb75f7 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 15 Feb 2024 10:54:07 +0100 Subject: [PATCH 083/139] fix: links in sms are not following the theme --- .../main/java/com/bnyro/contacts/db/obj/SmsData.kt | 6 +----- .../contacts/ui/components/conversation/Message.kt | 14 +++++++++----- .../com/bnyro/contacts/util/TextFormatUtils.kt | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt index 7ac21903..535506a5 100644 --- a/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt +++ b/app/src/main/java/com/bnyro/contacts/db/obj/SmsData.kt @@ -1,11 +1,8 @@ package com.bnyro.contacts.db.obj -import androidx.compose.ui.text.AnnotatedString import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.Ignore import androidx.room.PrimaryKey -import com.bnyro.contacts.util.generateAnnotations @Entity(tableName = "localSms") data class SmsData( @@ -15,6 +12,5 @@ data class SmsData( @ColumnInfo var timestamp: Long = 0, @ColumnInfo var threadId: Long = 0, @ColumnInfo var type: Int = 0, - @ColumnInfo(defaultValue = "NULL") var simNumber: Int? = null, - @Ignore val formatted: AnnotatedString = generateAnnotations(body) + @ColumnInfo(defaultValue = "NULL") var simNumber: Int? = null ) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt index aed85116..e108257c 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt @@ -43,6 +43,7 @@ import com.bnyro.contacts.R import com.bnyro.contacts.db.obj.SmsData import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.models.SmsModel +import com.bnyro.contacts.util.generateAnnotations @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -225,15 +226,18 @@ fun ClickableMessage( ) { SelectionContainer { val uriHandler = LocalUriHandler.current + val primary = MaterialTheme.colorScheme.primary + + val formatted = remember { + generateAnnotations(smsData.body, primary) + } + ClickableText( - text = smsData.formatted, + text = formatted, style = MaterialTheme.typography.bodyLarge.copy(color = LocalContentColor.current), onClick = { offset -> val annotation = - smsData.formatted.getStringAnnotations( - offset, - offset - ).firstOrNull() + formatted.getStringAnnotations(offset, offset).firstOrNull() annotation?.let { uriHandler.openUri(it.item) } diff --git a/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt b/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt index 135b27ad..94a4c5de 100644 --- a/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt +++ b/app/src/main/java/com/bnyro/contacts/util/TextFormatUtils.kt @@ -22,7 +22,7 @@ fun MutableList>.addKeywords( } } -fun generateAnnotations(text: String): AnnotatedString { +fun generateAnnotations(text: String, color: Color): AnnotatedString { val keywords = mutableListOf>().apply { addKeywords( text, @@ -48,7 +48,7 @@ fun generateAnnotations(text: String): AnnotatedString { val indexOf = text.indexOf(keyword) addStyle( style = SpanStyle( - color = Color.Blue, + color = color, textDecoration = when (format) { Format.LINK -> TextDecoration.Underline else -> TextDecoration.None From 9032269fb028e09fd7154efc7eb80cfc09e3528b Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 15 Feb 2024 12:41:56 +0100 Subject: [PATCH 084/139] feat(sms): add top bar and search functionality --- .../java/com/bnyro/contacts/obj/SmsThread.kt | 10 + .../contacts/ui/components/SmsThreadItem.kt | 170 ++++++++++++++ .../contacts/ui/components/TopBarMoreMenu.kt | 72 ++++++ .../ContactSearchScreen.kt | 26 +-- .../contacts/ui/screens/ContactsScreen.kt | 96 +++----- .../contacts/ui/screens/SmsListScreen.kt | 214 +++++------------- .../contacts/ui/screens/SmsSearchScreen.kt | 92 ++++++++ .../com/bnyro/contacts/util/ContactsHelper.kt | 17 ++ 8 files changed, 446 insertions(+), 251 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/obj/SmsThread.kt create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt create mode 100644 app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt rename app/src/main/java/com/bnyro/contacts/ui/{components => screens}/ContactSearchScreen.kt (70%) create mode 100644 app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt diff --git a/app/src/main/java/com/bnyro/contacts/obj/SmsThread.kt b/app/src/main/java/com/bnyro/contacts/obj/SmsThread.kt new file mode 100644 index 00000000..a8504bfc --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/obj/SmsThread.kt @@ -0,0 +1,10 @@ +package com.bnyro.contacts.obj + +import com.bnyro.contacts.db.obj.SmsData + +data class SmsThread( + val threadId: Long, + val address: String, + val contactData: ContactData?, + val smsList: List +) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt b/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt new file mode 100644 index 00000000..89b5d6b4 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt @@ -0,0 +1,170 @@ +package com.bnyro.contacts.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.DismissDirection +import androidx.compose.material3.DismissValue +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SwipeToDismiss +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDismissState +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +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.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.bnyro.contacts.R +import com.bnyro.contacts.obj.SmsThread +import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.models.SmsModel +import com.bnyro.contacts.ui.screens.SmsThreadScreen + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SmsThreadItem( + smsModel: SmsModel, + thread: SmsThread +) { + val context = LocalContext.current + + var showThreadScreen by remember { + mutableStateOf(false) + } + var showDeleteThreadDialog by remember { + mutableStateOf(false) + } + + val dismissState = rememberDismissState( + confirmValueChange = { + if (it == DismissValue.DismissedToEnd) { + showDeleteThreadDialog = true + } + return@rememberDismissState false + } + ) + + SwipeToDismiss( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp), + state = dismissState, + background = {}, + dismissContent = { + val shape = RoundedCornerShape(20.dp) + + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .clip(shape) + .clickable { + showThreadScreen = true + }, + shape = shape + ) { + Row( + modifier = Modifier.padding( + horizontal = 10.dp, + vertical = 5.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(58.dp) + .clip(CircleShape) + .background( + MaterialTheme.colorScheme.primary, + CircleShape + ), + contentAlignment = Alignment.Center + ) { + if (thread.contactData?.thumbnail != null) { + Image( + modifier = Modifier + .fillMaxSize() + .clip(CircleShape), + bitmap = thread.contactData.thumbnail!!.asImageBitmap(), + contentDescription = null, + contentScale = ContentScale.Crop + ) + } else { + Image( + modifier = Modifier + .padding(vertical = 12.dp) + .fillMaxSize(), + imageVector = Icons.Default.Person, + contentDescription = null, + colorFilter = ColorFilter.tint( + MaterialTheme.colorScheme.surfaceColorAtElevation( + 10.dp + ) + ) + ) + } + } + Spacer(modifier = Modifier.width(10.dp)) + Column( + modifier = Modifier.padding(10.dp) + ) { + Text( + text = thread.contactData?.displayName ?: thread.address, + color = MaterialTheme.colorScheme.primary, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(3.dp)) + Text( + // safe call to avoid crashes when re-rendering + text = thread.smsList.lastOrNull()?.body.orEmpty(), + maxLines = 2, + fontSize = 14.sp, + lineHeight = 18.sp + ) + } + } + } + }, + directions = setOf(DismissDirection.StartToEnd) + ) + + if (showThreadScreen) { + SmsThreadScreen(smsModel, thread.contactData, thread.address) { + showThreadScreen = false + } + } + + if (showDeleteThreadDialog) { + ConfirmationDialog( + onDismissRequest = { showDeleteThreadDialog = false }, + title = stringResource(R.string.delete_thread), + text = stringResource(R.string.irreversible) + ) { + smsModel.deleteThread(context, thread.threadId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt b/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt new file mode 100644 index 00000000..5abfcd39 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt @@ -0,0 +1,72 @@ +package com.bnyro.contacts.ui.components + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import com.bnyro.contacts.R +import com.bnyro.contacts.ui.components.base.ClickableIcon +import com.bnyro.contacts.ui.components.base.OptionMenu +import com.bnyro.contacts.ui.screens.AboutScreen +import com.bnyro.contacts.ui.screens.SettingsScreen + +@Composable +fun TopBarMoreMenu( + extraOptions: List = emptyList(), + onExtraOptionClick: (Int) -> Unit = {} +) { + var showSettings by remember { + mutableStateOf(false) + } + + var showAbout by remember { + mutableStateOf(false) + } + + var expandedOptions by remember { + mutableStateOf(false) + } + + ClickableIcon( + icon = Icons.Default.MoreVert, + contentDescription = R.string.more + ) { + expandedOptions = !expandedOptions + } + + val options = extraOptions + listOf( + stringResource(R.string.settings), + stringResource(R.string.about) + ) + OptionMenu( + expanded = expandedOptions, + options = options, + onDismissRequest = { + expandedOptions = false + }, + onSelect = { + when (it) { + options.size - 2 -> showSettings = true + options.size - 1 -> showAbout = true + else -> onExtraOptionClick(it) + } + expandedOptions = false + } + ) + + if (showSettings) { + SettingsScreen { + showSettings = false + } + } + + if (showAbout) { + AboutScreen { + showAbout = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactSearchScreen.kt similarity index 70% rename from app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt rename to app/src/main/java/com/bnyro/contacts/ui/screens/ContactSearchScreen.kt index 34e80943..b9d154f9 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactSearchScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactSearchScreen.kt @@ -1,19 +1,11 @@ -package com.bnyro.contacts.ui.components +package com.bnyro.contacts.ui.screens import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -22,17 +14,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions -import com.bnyro.contacts.ui.components.base.ClickableIcon +import com.bnyro.contacts.ui.components.ContactsList import com.bnyro.contacts.ui.components.base.ElevatedTextInputField import com.bnyro.contacts.ui.components.base.FullScreenDialog +import com.bnyro.contacts.util.ContactsHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -61,16 +52,7 @@ fun ContactSearchScreen( } LaunchedEffect(searchQuery) { withContext(Dispatchers.IO) { - val query = searchQuery.lowercase() - visibleContacts = contacts.filter { - val contactInfoStrings = listOf(it.numbers, it.emails, it.addresses, it.notes, it.websites, it.events) - .flatten() - .map { (value, _) -> value } + listOf(it.organization, it.nickName, it.displayName) - - contactInfoStrings.filterNotNull().any { str -> - str.lowercase().contains(query) - } - } + visibleContacts = ContactsHelper.filter(contacts, searchQuery) } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 7b09ad96..4719e867 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.CopyAll import androidx.compose.material.icons.filled.Create import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoveToInbox import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Share @@ -50,11 +49,10 @@ import com.bnyro.contacts.R import com.bnyro.contacts.enums.ContactsSource import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions -import com.bnyro.contacts.ui.components.ContactSearchScreen import com.bnyro.contacts.ui.components.ContactsList import com.bnyro.contacts.ui.components.NothingHere +import com.bnyro.contacts.ui.components.TopBarMoreMenu import com.bnyro.contacts.ui.components.base.ClickableIcon -import com.bnyro.contacts.ui.components.base.OptionMenu import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.components.dialogs.FilterDialog import com.bnyro.contacts.ui.components.dialogs.SimImportDialog @@ -87,18 +85,10 @@ fun ContactsPage( mutableStateOf(FilterOptions.default()) } - var showSettings by remember { - mutableStateOf(false) - } - var showSearch by remember { mutableStateOf(false) } - var showAbout by remember { - mutableStateOf(false) - } - var showFilterDialog by remember { mutableStateOf(false) } @@ -130,7 +120,9 @@ fun ContactsPage( } ) { pv -> Column( - modifier = Modifier.padding(pv).fillMaxSize() + modifier = Modifier + .padding(pv) + .fillMaxSize() ) { Crossfade( targetState = selectedContacts.isEmpty(), @@ -185,9 +177,6 @@ fun ContactsPage( } }, actions = { - var expandedOptions by remember { - mutableStateOf(false) - } ClickableIcon( icon = Icons.Default.Search, contentDescription = R.string.search @@ -200,49 +189,6 @@ fun ContactsPage( ) { showFilterDialog = true } - ClickableIcon( - icon = Icons.Default.MoreVert, - contentDescription = R.string.more - ) { - expandedOptions = !expandedOptions - } - OptionMenu( - expanded = expandedOptions, - options = listOf( - stringResource(R.string.import_vcf), - stringResource(R.string.export_vcf), - stringResource(R.string.import_sim), - stringResource(R.string.settings), - stringResource(R.string.about) - ), - onDismissRequest = { - expandedOptions = false - }, - onSelect = { - when (it) { - 0 -> { - importVcard.launch(BackupHelper.openMimeTypes) - } - - 1 -> { - exportVcard.launch(BackupHelper.backupFileName) - } - - 2 -> { - showImportSimDialog = true - } - - 3 -> { - showSettings = true - } - - 4 -> { - showAbout = true - } - } - expandedOptions = false - } - ) } ) } @@ -302,6 +248,28 @@ fun ContactsPage( ) { showDelete = true } + TopBarMoreMenu( + extraOptions = listOf( + stringResource(R.string.import_vcf), + stringResource(R.string.export_vcf), + stringResource(R.string.import_sim) + ), + onExtraOptionClick = { index -> + when (index) { + 0 -> { + importVcard.launch(BackupHelper.openMimeTypes) + } + + 1 -> { + exportVcard.launch(BackupHelper.backupFileName) + } + + 2 -> { + showImportSimDialog = true + } + } + } + ) } ) } @@ -360,18 +328,6 @@ fun ContactsPage( ) } - if (showSettings) { - SettingsScreen { - showSettings = false - } - } - - if (showAbout) { - AboutScreen { - showAbout = false - } - } - if (showDelete) { ConfirmationDialog( onDismissRequest = { diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt index 33e03617..8d89e738 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt @@ -1,59 +1,36 @@ package com.bnyro.contacts.ui.screens -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.Person -import androidx.compose.material3.DismissDirection -import androidx.compose.material3.DismissValue -import androidx.compose.material3.ElevatedCard +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SwipeToDismiss import androidx.compose.material3.Text -import androidx.compose.material3.rememberDismissState -import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState 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.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.bnyro.contacts.R +import com.bnyro.contacts.obj.SmsThread import com.bnyro.contacts.ui.components.NothingHere import com.bnyro.contacts.ui.components.NumberPickerDialog -import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.components.SmsThreadItem +import com.bnyro.contacts.ui.components.TopBarMoreMenu +import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.models.SmsModel @@ -64,7 +41,6 @@ fun SmsListScreen( contactsModel: ContactsModel, scrollConnection: NestedScrollConnection? ) { - val context = LocalContext.current var showNumberPicker by remember { mutableStateOf(false) } @@ -73,17 +49,51 @@ fun SmsListScreen( mutableStateOf(null) } - Scaffold(floatingActionButton = { - FloatingActionButton( - onClick = { - showNumberPicker = true + var showSearch by remember { + mutableStateOf(false) + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text(text = stringResource(R.string.messages)) + }, + actions = { + ClickableIcon( + icon = Icons.Default.Search, + contentDescription = R.string.search + ) { + showSearch = true + } + TopBarMoreMenu() + } + ) + }, + floatingActionButton = { + FloatingActionButton( + onClick = { + showNumberPicker = true + } + ) { + Icon(Icons.Default.Edit, null) } - ) { - Icon(Icons.Default.Edit, null) - } - }) { pv -> + }) { pv -> val smsList by smsModel.smsList.collectAsState() if (smsList.isNotEmpty()) { + val threadList = smsList.groupBy { it.threadId } + .map { (threadId, smsList) -> + val address = smsList.first().address + SmsThread( + threadId = threadId, + contactData = contactsModel.getContactByNumber(address), + address = address, + smsList = smsList + ) + } + .sortedBy { thread -> thread.smsList.maxOf { it.timestamp } } + .reversed() + LazyColumn( modifier = Modifier .fillMaxSize() @@ -92,127 +102,13 @@ fun SmsListScreen( scrollConnection?.let { modifier.nestedScroll(it) } ?: modifier } ) { - val smsGroups = smsList.groupBy { it.threadId }.toList() - .sortedBy { (_, smsList) -> smsList.maxOf { it.timestamp } } - .reversed() - - items(smsGroups) { (threadId, smsList) -> - var showThreadScreen by remember { - mutableStateOf(false) - } - var showDeleteThreadDialog by remember { - mutableStateOf(false) - } - - // safe call to avoid crashes when re-rendering - val address = smsList.firstOrNull()?.address.orEmpty() - val contactData = contactsModel.getContactByNumber(address) - - val dismissState = rememberDismissState( - confirmValueChange = { - if (it == DismissValue.DismissedToEnd) { - showDeleteThreadDialog = true - } - return@rememberDismissState false - } - ) - - SwipeToDismiss( - modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp), - state = dismissState, - background = {}, - dismissContent = { - val shape = RoundedCornerShape(20.dp) - - ElevatedCard( - modifier = Modifier - .fillMaxWidth() - .clip(shape) - .clickable { - showThreadScreen = true - }, - shape = shape - ) { - Row( - modifier = Modifier.padding( - horizontal = 10.dp, - vertical = 5.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(58.dp) - .clip(CircleShape) - .background( - MaterialTheme.colorScheme.primary, - CircleShape - ), - contentAlignment = Alignment.Center - ) { - if (contactData?.thumbnail != null) { - Image( - modifier = Modifier - .fillMaxSize() - .clip(CircleShape), - bitmap = contactData.thumbnail!!.asImageBitmap(), - contentDescription = null, - contentScale = ContentScale.Crop - ) - } else { - Image( - modifier = Modifier - .padding(vertical = 12.dp) - .fillMaxSize(), - imageVector = Icons.Default.Person, - contentDescription = null, - colorFilter = ColorFilter.tint( - MaterialTheme.colorScheme.surfaceColorAtElevation( - 10.dp - ) - ) - ) - } - } - Spacer(modifier = Modifier.width(10.dp)) - Column( - modifier = Modifier.padding(10.dp) - ) { - Text( - text = contactData?.displayName ?: address, - color = MaterialTheme.colorScheme.primary, - fontSize = 16.sp - ) - Spacer(modifier = Modifier.height(3.dp)) - Text( - // safe call to avoid crashes when re-rendering - text = smsList.lastOrNull()?.body.orEmpty(), - maxLines = 2, - fontSize = 14.sp, - lineHeight = 18.sp - ) - } - } - } - }, - directions = setOf(DismissDirection.StartToEnd) - ) - - if (showThreadScreen) { - SmsThreadScreen(smsModel, contactData, address) { - showThreadScreen = false - } - } - - if (showDeleteThreadDialog) { - ConfirmationDialog( - onDismissRequest = { showDeleteThreadDialog = false }, - title = stringResource(R.string.delete_thread), - text = stringResource(R.string.irreversible) - ) { - smsModel.deleteThread(context, threadId) - } - } + items(threadList) { thread -> + SmsThreadItem(smsModel, thread) + } + } + if (showSearch) { + SmsSearchScreen(smsModel, threadList) { + showSearch = false } } } else { diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt new file mode 100644 index 00000000..4201bdf0 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt @@ -0,0 +1,92 @@ +package com.bnyro.contacts.ui.screens + +import androidx.compose.foundation.layout.Column +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.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import com.bnyro.contacts.R +import com.bnyro.contacts.obj.SmsThread +import com.bnyro.contacts.ui.components.SmsThreadItem +import com.bnyro.contacts.ui.components.base.ElevatedTextInputField +import com.bnyro.contacts.ui.components.base.FullScreenDialog +import com.bnyro.contacts.ui.models.SmsModel +import com.bnyro.contacts.util.ContactsHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext + +@Composable +fun SmsSearchScreen( + smsModel: SmsModel, + threadList: List, + onDismissRequest: () -> Unit +) { + FullScreenDialog(onDismissRequest) { + val focusRequester = remember { + FocusRequester() + } + + LaunchedEffect(Unit) { + delay(100) + focusRequester.requestFocus() + } + + var searchQuery by remember { + mutableStateOf("") + } + var visibleThreads by remember { + mutableStateOf(threadList) + } + + LaunchedEffect(searchQuery) { + withContext(Dispatchers.IO) { + val query = searchQuery.lowercase() + + visibleThreads = threadList.filter { + it.address.lowercase().contains(query) || it.contactData?.let { contact -> + ContactsHelper.matches(contact, query) + } ?: false || it.smsList.any { sms -> sms.body.lowercase().contains(query) } + } + } + } + + Column(Modifier.fillMaxSize()) { + ElevatedTextInputField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .padding(top = 8.dp), + query = searchQuery, + onQueryChange = { searchQuery = it }, + leadingIcon = Icons.Default.Search, + placeholder = stringResource(id = R.string.search), + imeAction = ImeAction.Done, + focusRequester = focusRequester, + singleLine = true + ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + ) { + items(visibleThreads) { thread -> + SmsThreadItem(smsModel, thread) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt index e4a2e51c..fdcccc9b 100644 --- a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt @@ -2,6 +2,7 @@ package com.bnyro.contacts.util import android.provider.ContactsContract import com.bnyro.contacts.R +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.TranslatedType import com.google.i18n.phonenumbers.PhoneNumberUtil import ezvcard.parameter.AddressType @@ -101,4 +102,20 @@ object ContactsHelper { .getOrElse { return number } return phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL) } + + fun matches(contactData: ContactData, query: String): Boolean { + val contactInfoStrings = listOf(contactData.numbers, contactData.emails, contactData.addresses, contactData.notes, contactData.websites, contactData.events) + .flatten() + .map { (value, _) -> value } + listOf(contactData.organization, contactData.nickName, contactData.displayName) + + return contactInfoStrings.filterNotNull().any { str -> + str.lowercase().contains(query) + } + } + + fun filter(contacts: List, searchQuery: String): List { + val query = searchQuery.lowercase() + + return contacts.filter { matches(it, query) } + } } From 5edebb98149d1b22092914f06a65e7b40b018a61 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Feb 2024 14:38:18 +0100 Subject: [PATCH 085/139] feat: allow empty contact names (closes #374) --- app/src/main/java/com/bnyro/contacts/obj/ContactData.kt | 2 ++ .../java/com/bnyro/contacts/ui/screens/EditorScreen.kt | 3 ++- .../main/java/com/bnyro/contacts/util/ContactsHelper.kt | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt b/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt index b701f7a6..339f068f 100644 --- a/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt +++ b/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt @@ -31,6 +31,8 @@ data class ContactData( SortOrder.FIRSTNAME -> displayName SortOrder.LASTNAME -> alternativeName SortOrder.NICKNAME -> nickName ?: displayName + }?.ifBlank { + organization ?: numbers.firstOrNull()?.value } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/EditorScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/EditorScreen.kt index f95917ec..685b9391 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/EditorScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/EditorScreen.kt @@ -20,6 +20,7 @@ import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.ContactEditor import com.bnyro.contacts.ui.components.base.FullScreenDialog +import com.bnyro.contacts.util.ContactsHelper @Composable fun EditorScreen( @@ -60,7 +61,7 @@ fun EditorScreen( contact = contact?.copy(), isCreatingNewDeviceContact = isCreatingNewDeviceContact, onSave = { - if (it.displayName.orEmpty().isBlank()) { + if (ContactsHelper.isContactEmpty(it)) { Toast.makeText(context, R.string.empty_name, Toast.LENGTH_SHORT).show() return@ContactEditor } diff --git a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt index fdcccc9b..6f0e6573 100644 --- a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt @@ -118,4 +118,11 @@ object ContactsHelper { return contacts.filter { matches(it, query) } } + + fun isContactEmpty(contactData: ContactData): Boolean { + val stringProperties = listOf(contactData.firstName, contactData.surName, contactData.nickName, contactData.organization) + val listProperties = listOf(contactData.numbers, contactData.emails, contactData.events, contactData.addresses, contactData.notes) + + return stringProperties.none { !it.isNullOrBlank() } && listProperties.flatten().isEmpty() + } } From 3f192308736d6b7e6eca7b4c7cd593ad77f037fe Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Feb 2024 15:16:02 +0100 Subject: [PATCH 086/139] refactor: support all accounts available on the device for storing contacts --- app/src/main/AndroidManifest.xml | 6 +++ app/src/main/java/com/bnyro/contacts/App.kt | 9 ++--- .../com/bnyro/contacts/obj/AccountType.kt | 18 +++++++++ .../contacts/repo/DeviceContactsRepository.kt | 39 ++++++++++++------- .../contacts/ui/activities/BaseActivity.kt | 4 +- .../contacts/ui/components/ContactEditor.kt | 24 ++++++------ .../ui/components/dialogs/FilterDialog.kt | 14 +++---- .../bnyro/contacts/ui/models/ContactsModel.kt | 15 +++---- .../contacts/ui/screens/ContactsScreen.kt | 2 +- .../com/bnyro/contacts/util/Preferences.kt | 8 ++-- 10 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/obj/AccountType.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4feee5fb..cf3d4b43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,8 +6,14 @@ android:name="android.hardware.telephony" android:required="false" /> + + + + + + diff --git a/app/src/main/java/com/bnyro/contacts/App.kt b/app/src/main/java/com/bnyro/contacts/App.kt index 2e928277..9a5eb5c7 100644 --- a/app/src/main/java/com/bnyro/contacts/App.kt +++ b/app/src/main/java/com/bnyro/contacts/App.kt @@ -1,6 +1,7 @@ package com.bnyro.contacts import android.app.Application +import android.util.Log import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.repo.DeviceContactsRepository import com.bnyro.contacts.repo.DeviceSmsRepo @@ -19,12 +20,6 @@ class App : Application() { val localContactsRepository by lazy { LocalContactsRepository(this) } - val localSmsRepo by lazy { - LocalSmsRepo() - } - val deviceSmsRepo by lazy { - DeviceSmsRepo() - } lateinit var smsRepo: SmsRepository @@ -50,5 +45,7 @@ class App : Application() { NotificationHelper.createChannels(this) initSmsRepo() + + Log.e("types", DeviceContactsRepository(this).getAccountTypes().toString()) } } diff --git a/app/src/main/java/com/bnyro/contacts/obj/AccountType.kt b/app/src/main/java/com/bnyro/contacts/obj/AccountType.kt new file mode 100644 index 00000000..3f035622 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/obj/AccountType.kt @@ -0,0 +1,18 @@ +package com.bnyro.contacts.obj + +data class AccountType( + val name: String, + val type: String, +) { + val identifier = "$type|$name" + + companion object { + private const val ANDROID_ACCOUNT_TYPE = "com.android.contacts" + private const val ANDROID_ACCOUNT_NAME = "DEVICE" + + val androidDefault = AccountType( + ANDROID_ACCOUNT_NAME, + ANDROID_ACCOUNT_TYPE + ) + } +} diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt index f81f71e3..542cb2a4 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt @@ -1,8 +1,10 @@ package com.bnyro.contacts.repo import android.Manifest +import android.accounts.AccountManager import android.annotation.SuppressLint import android.content.ContentProviderOperation +import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.graphics.Bitmap @@ -32,6 +34,7 @@ import com.bnyro.contacts.ext.intValue import com.bnyro.contacts.ext.longValue import com.bnyro.contacts.ext.notAName import com.bnyro.contacts.ext.stringValue +import com.bnyro.contacts.obj.AccountType import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ContactsGroup import com.bnyro.contacts.obj.ValueWithType @@ -45,7 +48,9 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor override val label: String = context.getString(R.string.device) private val contentResolver = context.contentResolver - private val contactsUri = Data.CONTENT_URI + private val contentUri = Data.CONTENT_URI + + private val authority = ContactsContract.AUTHORITY private val projection = arrayOf( Data.RAW_CONTACT_ID, @@ -69,7 +74,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor @Suppress("SameParameterValue") val cursor = contentResolver.query( - contactsUri, + contentUri, projection, null, null, @@ -187,8 +192,8 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor ContentProviderOperation.newInsert(ContactsContract.Groups.CONTENT_URI).apply { withValue(ContactsContract.Groups.TITLE, groupName) withValue(ContactsContract.Groups.GROUP_VISIBLE, 1) - withValue(ContactsContract.Groups.ACCOUNT_NAME, ANDROID_CONTACTS_NAME) - withValue(ContactsContract.Groups.ACCOUNT_TYPE, ANDROID_ACCOUNT_TYPE) + withValue(ContactsContract.Groups.ACCOUNT_NAME, AccountType.androidDefault.name) + withValue(ContactsContract.Groups.ACCOUNT_TYPE, AccountType.androidDefault.type) operations.add(build()) } @@ -272,7 +277,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor val projection = arrayOf(Data.CONTACT_ID, valueIndex, typeIndex ?: "data2") contentResolver.query( - contactsUri, + contentUri, projection, "${Data.MIMETYPE} = ? AND ${Data.CONTACT_ID} = ?", arrayOf(itemType, contactId.toString()), @@ -314,8 +319,8 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor val lastChosenAccount = Preferences.getLastChosenAccount() val ops = listOfNotNull( getCreateAction( - contact.accountType ?: lastChosenAccount.first, - contact.accountName ?: lastChosenAccount.second + contact.accountType ?: lastChosenAccount.type, + contact.accountName ?: lastChosenAccount.name ), getInsertAction( StructuredName.CONTENT_ITEM_TYPE, @@ -421,7 +426,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor val rawContactId = contact.rawContactId.toString() val selection = "${Data.RAW_CONTACT_ID} = ? AND ${Data.MIMETYPE} = ?" - ContentProviderOperation.newUpdate(contactsUri).apply { + ContentProviderOperation.newUpdate(contentUri).apply { val selectionArgs = arrayOf(rawContactId, StructuredName.CONTENT_ITEM_TYPE) withSelection(selection, selectionArgs) withValue(StructuredName.GIVEN_NAME, contact.firstName) @@ -528,6 +533,14 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor } } + fun getAccountTypes(): List { + val accounts = AccountManager.get(context).accounts.filter { + ContentResolver.getIsSyncable(it, authority) > 0 && ContentResolver.getSyncAutomatically(it, authority) + } + + return listOf(AccountType.androidDefault) + accounts.map { AccountType(it.name, it.type) } + } + private fun getCreateAction(accountType: String, accountName: String): ContentProviderOperation { return ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, accountType) @@ -543,7 +556,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor type: Int? = null, rawContactId: Int? = null ): ContentProviderOperation { - return ContentProviderOperation.newInsert(contactsUri) + return ContentProviderOperation.newInsert(contentUri) .let { builder -> // if creating a new contact, the previous contact id is going to be taken // if updating an already existing contact, don't worry about the previous batch id @@ -587,14 +600,14 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor val selection = "${Data.RAW_CONTACT_ID} = ? AND ${Data.MIMETYPE} = ?" val selectionArgs = arrayOf(contactId, mimeType) - ContentProviderOperation.newDelete(contactsUri).apply { + ContentProviderOperation.newDelete(contentUri).apply { withSelection(selection, selectionArgs) operations.add(build()) } // add new entries entries.forEach { - ContentProviderOperation.newInsert(contactsUri).apply { + ContentProviderOperation.newInsert(contentUri).apply { withValue(Data.RAW_CONTACT_ID, contactId) withValue(Data.MIMETYPE, mimeType) withValue(valueIndex, it.value) @@ -647,7 +660,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor } private fun deletePhoto(rawContactId: Int): ContentProviderOperation { - return ContentProviderOperation.newDelete(contactsUri).apply { + return ContentProviderOperation.newDelete(contentUri).apply { val selection = "${Data.RAW_CONTACT_ID} = ? AND ${Data.MIMETYPE} = ?" val selectionArgs = arrayOf(rawContactId.toString(), Photo.CONTENT_ITEM_TYPE) withSelection(selection, selectionArgs) @@ -656,7 +669,5 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor companion object { const val MAX_PHOTO_SIZE = 700f - const val ANDROID_ACCOUNT_TYPE = "com.android.contacts" - const val ANDROID_CONTACTS_NAME = "DEVICE" } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt index 8d5026b8..ed20c1ff 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt @@ -73,7 +73,9 @@ abstract class BaseActivity : ComponentActivity() { ) private val contactPermissions = arrayOf( Manifest.permission.WRITE_CONTACTS, - Manifest.permission.READ_CONTACTS + Manifest.permission.READ_CONTACTS, + Manifest.permission.GET_ACCOUNTS, + Manifest.permission.READ_SYNC_SETTINGS ) } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index 3fd7d687..599a3fc0 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R +import com.bnyro.contacts.obj.AccountType import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.base.LabeledTextField @@ -156,14 +157,14 @@ fun ContactEditor( var selectedAccount by remember { val lastChosenAccount = Preferences.getLastChosenAccount() - mutableStateOf( - (contact?.accountType ?: lastChosenAccount.first) to - (contact?.accountName ?: lastChosenAccount.second) - ) + val account = contact?.let { + AccountType(it.accountName.orEmpty(), it.accountType.orEmpty()) + } ?: lastChosenAccount + mutableStateOf(account) } val availableAccounts = remember { - contactsModel.getAvailableAccounts() + contactsModel.getAvailableAccounts(context) } val uploadImage = rememberLauncherForActivityResult( @@ -187,8 +188,8 @@ fun ContactEditor( it.organization = organization.value.takeIf { o -> o.isNotBlank() }?.trim() it.displayName = "${firstName.value.trim()} ${surName.value.trim()}" it.photo = profilePicture - it.accountType = selectedAccount.first - it.accountName = selectedAccount.second + it.accountType = selectedAccount.type + it.accountName = selectedAccount.name it.websites = websites.clean() it.numbers = phoneNumber.clean().map { number -> ValueWithType(ContactsHelper.normalizePhoneNumber(number.value), number.type) @@ -220,7 +221,7 @@ fun ContactEditor( verticalAlignment = Alignment.CenterVertically ) { Text( - text = selectedAccount.second.ifBlank { selectedAccount.first } + text = selectedAccount.name.ifBlank { selectedAccount.type } ) Icon( imageVector = Icons.Default.ArrowDropDown, @@ -234,14 +235,11 @@ fun ContactEditor( ) { availableAccounts.forEach { DropdownMenuItem( - text = { Text(it.second) }, + text = { Text(it.name) }, onClick = { selectedAccount = it Preferences.edit { - putString( - Preferences.lastChosenAccount, - "${it.first}|${it.second}" - ) + putString(Preferences.lastChosenAccount, it.identifier) } expanded = false } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/FilterDialog.kt b/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/FilterDialog.kt index 08484974..2be3a40f 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/FilterDialog.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/dialogs/FilterDialog.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.bnyro.contacts.R import com.bnyro.contacts.enums.SortOrder +import com.bnyro.contacts.obj.AccountType import com.bnyro.contacts.obj.ContactsGroup import com.bnyro.contacts.obj.FilterOptions import com.bnyro.contacts.ui.components.base.ChipSelector @@ -21,7 +22,7 @@ import com.bnyro.contacts.ui.components.base.ChipSelector @Composable fun FilterDialog( initialFilters: FilterOptions, - availableAccountTypes: List>, + availableAccountTypes: List, availableGroups: List, onDismissRequest: () -> Unit, onFilterChanged: (FilterOptions) -> Unit @@ -73,17 +74,16 @@ fun FilterDialog( Spacer(modifier = Modifier.height(10.dp)) ChipSelector( title = stringResource(R.string.account_type), - entries = availableAccountTypes.map { it.second }, + entries = availableAccountTypes.map { it.type }, selections = availableAccountTypes.filter { - !hiddenAccountNames.contains(it.first + "|" + it.second) - }.map { it.second }, + !hiddenAccountNames.contains(it.identifier) + }.map { it.type }, onSelectionChanged = { index, newValue -> val selection = availableAccountTypes[index] - val selectedAccountName = selection.first + "|" + selection.second hiddenAccountNames = if (newValue) { - hiddenAccountNames - selectedAccountName + hiddenAccountNames - selection.identifier } else { - hiddenAccountNames + selectedAccountName + hiddenAccountNames + selection.identifier } } ) diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt index db301222..c13b1e5f 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt @@ -16,6 +16,7 @@ import com.bnyro.contacts.App import com.bnyro.contacts.R import com.bnyro.contacts.enums.ContactsSource import com.bnyro.contacts.ext.toast +import com.bnyro.contacts.obj.AccountType import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.repo.ContactsRepository import com.bnyro.contacts.repo.DeviceContactsRepository @@ -204,15 +205,11 @@ class ContactsModel( /** * Returns a list of account type to account name */ - fun getAvailableAccounts(): List> { - if (contacts.isEmpty()) { - return listOf( - DeviceContactsRepository.ANDROID_ACCOUNT_TYPE to DeviceContactsRepository.ANDROID_CONTACTS_NAME - ) - } - return contacts.mapNotNull { - it.accountType?.let { type -> type to it.accountName.orEmpty() } - }.distinct().toMutableList() + fun getAvailableAccounts(context: Context): List { + if (!PermissionHelper.hasPermission(context, Manifest.permission.GET_ACCOUNTS)) + return listOf(AccountType.androidDefault) + + return deviceContactsRepository.getAccountTypes() } fun getAvailableGroups() = contacts.map { it.groups }.flatten().distinct() diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 4719e867..68af087e 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -354,7 +354,7 @@ fun ContactsPage( filterOptions = it }, initialFilters = filterOptions, - availableAccountTypes = viewModel.getAvailableAccounts(), + availableAccountTypes = viewModel.getAvailableAccounts(context), availableGroups = viewModel.getAvailableGroups() ) } diff --git a/app/src/main/java/com/bnyro/contacts/util/Preferences.kt b/app/src/main/java/com/bnyro/contacts/util/Preferences.kt index c538349f..7464feea 100644 --- a/app/src/main/java/com/bnyro/contacts/util/Preferences.kt +++ b/app/src/main/java/com/bnyro/contacts/util/Preferences.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.SnapshotMutationPolicy import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.bnyro.contacts.enums.BackupType -import com.bnyro.contacts.repo.DeviceContactsRepository +import com.bnyro.contacts.obj.AccountType object Preferences { private const val prefFile = "preferences" @@ -47,15 +47,15 @@ object Preferences { return BackupType.fromInt(getInt(backupTypeKey, BackupType.NONE.ordinal)) } - fun getLastChosenAccount(): Pair { + fun getLastChosenAccount(): AccountType { getString(lastChosenAccount, "") .takeIf { !it.isNullOrBlank() } ?.let { val split = it.split("|") - return split.first() to split.last() + return AccountType(split.last(), split.first()) } - return DeviceContactsRepository.ANDROID_ACCOUNT_TYPE to DeviceContactsRepository.ANDROID_CONTACTS_NAME + return AccountType.androidDefault } } From a77b8b5a2a574b83d4e3f3f0b24404aab8f2b27b Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Feb 2024 15:17:47 +0100 Subject: [PATCH 087/139] fix: contacts screen is missing menu for more options --- .../contacts/ui/screens/ContactsScreen.kt | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 68af087e..fb02d5a8 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -189,6 +189,28 @@ fun ContactsPage( ) { showFilterDialog = true } + TopBarMoreMenu( + extraOptions = listOf( + stringResource(R.string.import_vcf), + stringResource(R.string.export_vcf), + stringResource(R.string.import_sim) + ), + onExtraOptionClick = { index -> + when (index) { + 0 -> { + importVcard.launch(BackupHelper.openMimeTypes) + } + + 1 -> { + exportVcard.launch(BackupHelper.backupFileName) + } + + 2 -> { + showImportSimDialog = true + } + } + } + ) } ) } @@ -248,28 +270,6 @@ fun ContactsPage( ) { showDelete = true } - TopBarMoreMenu( - extraOptions = listOf( - stringResource(R.string.import_vcf), - stringResource(R.string.export_vcf), - stringResource(R.string.import_sim) - ), - onExtraOptionClick = { index -> - when (index) { - 0 -> { - importVcard.launch(BackupHelper.openMimeTypes) - } - - 1 -> { - exportVcard.launch(BackupHelper.backupFileName) - } - - 2 -> { - showImportSimDialog = true - } - } - } - ) } ) } From e2e7b731b3963d0458d0e6fd85633963ada05694 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Feb 2024 15:27:20 +0100 Subject: [PATCH 088/139] fix: dropdown to select account type not visible --- app/src/main/java/com/bnyro/contacts/App.kt | 3 --- .../main/java/com/bnyro/contacts/ui/models/ContactsModel.kt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/App.kt b/app/src/main/java/com/bnyro/contacts/App.kt index 9a5eb5c7..7cdcf386 100644 --- a/app/src/main/java/com/bnyro/contacts/App.kt +++ b/app/src/main/java/com/bnyro/contacts/App.kt @@ -1,7 +1,6 @@ package com.bnyro.contacts import android.app.Application -import android.util.Log import com.bnyro.contacts.db.DatabaseHolder import com.bnyro.contacts.repo.DeviceContactsRepository import com.bnyro.contacts.repo.DeviceSmsRepo @@ -45,7 +44,5 @@ class App : Application() { NotificationHelper.createChannels(this) initSmsRepo() - - Log.e("types", DeviceContactsRepository(this).getAccountTypes().toString()) } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt index c13b1e5f..e10dff10 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/ContactsModel.kt @@ -206,7 +206,7 @@ class ContactsModel( * Returns a list of account type to account name */ fun getAvailableAccounts(context: Context): List { - if (!PermissionHelper.hasPermission(context, Manifest.permission.GET_ACCOUNTS)) + if (!PermissionHelper.hasPermission(context, Manifest.permission.READ_SYNC_SETTINGS)) return listOf(AccountType.androidDefault) return deviceContactsRepository.getAccountTypes() From 0370616a49a2883c9ef4e4ba226bf45d2c6dd344 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 21 Feb 2024 15:38:51 +0100 Subject: [PATCH 089/139] fix: restore full compatibility with syncing via nextcloud, davx5, ... --- .../com/bnyro/contacts/ui/components/ContactEditor.kt | 2 +- .../java/com/bnyro/contacts/ui/screens/ContactsScreen.kt | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index 599a3fc0..e8da8e1d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -209,7 +209,7 @@ fun ContactEditor( ) } }, topBar = { - if (isCreatingNewDeviceContact && availableAccounts.size > 1) { + if (isCreatingNewDeviceContact) { TopAppBar(title = { var expanded by remember { mutableStateOf(false) } Row( diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index fb02d5a8..4074fbe3 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -77,6 +77,10 @@ fun ContactsPage( mutableStateOf(viewModel.initialContactData) } + var showEditorScreen by remember { + mutableStateOf(false) + } + var showDelete by remember { mutableStateOf(false) } @@ -112,7 +116,7 @@ fun ContactsPage( floatingActionButton = { FloatingActionButton( onClick = { - newContactToInsert = ContactData() + showEditorScreen = true } ) { Icon(Icons.Default.Create, null) @@ -315,11 +319,12 @@ fun ContactsPage( } } - if (newContactToInsert != null) { + if (showEditorScreen || newContactToInsert != null) { EditorScreen( contact = newContactToInsert, onClose = { newContactToInsert = null + showEditorScreen = false }, isCreatingNewDeviceContact = (viewModel.contactsSource == ContactsSource.DEVICE), onSave = { From 0fc5b111407b12c48aa6ba7eb5b40c04b6d21eb3 Mon Sep 17 00:00:00 2001 From: topminipie <145812405+topminipie@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:45:20 +0000 Subject: [PATCH 090/139] ci: update actions --- .github/workflows/ci.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed1931bb..591523e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,20 @@ jobs: debug-builds: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 + - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 17 distribution: "temurin" cache: "gradle" + - name: Compile run: | ./gradlew assembleDebug + - name: Sign Apk continue-on-error: true id: sign_apk @@ -40,12 +43,14 @@ jobs: keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} + - name: Remove file that aren't signed continue-on-error: true run: | ls | grep 'signed\.apk$' && find . -type f -name '*.apk' ! -name '*-signed.apk' -delete + - name: Upload APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: app path: app/build/outputs/apk/debug/*.apk From bb251884ab58335211407ece1c1af350c74de852 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 13 Mar 2024 16:24:11 +0100 Subject: [PATCH 091/139] fix: reply from push notification sent to a different number (closes #355) --- .../com/bnyro/contacts/receivers/DeleteSmsReceiver.kt | 1 - .../java/com/bnyro/contacts/receivers/SmsReceiver.kt | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt index bb39aeb4..877980c9 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/DeleteSmsReceiver.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.launch class DeleteSmsReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val smsId = intent.getLongExtra(SmsReceiver.KEY_EXTRA_SMS_ID, -1) - val threadId = intent.getLongExtra(SmsReceiver.KEY_EXTRA_THREAD_ID, -1) val notificationId = intent.getIntExtra(SmsReceiver.KEY_EXTRA_NOTIFICATION_ID, -1) NotificationManagerCompat.from(context).cancel(notificationId) diff --git a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt index a33d281d..0459e6a2 100644 --- a/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt +++ b/app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt @@ -27,10 +27,10 @@ class SmsReceiver : BroadcastReceiver() { if (intent.action != SMS_DELIVER) return Telephony.Sms.Intents.getMessagesFromIntent(intent).forEach { message -> - val notificationId = message.timestampMillis.hashCode() val address = message.displayOriginatingAddress val body = message.displayMessageBody val timestamp = message.timestampMillis + val threadId = runBlocking(Dispatchers.IO) { (context.applicationContext as App).smsRepo.getOrCreateThreadId( @@ -41,7 +41,7 @@ class SmsReceiver : BroadcastReceiver() { val bareSmsData = SmsData(0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX) - createNotification(context, notificationId, bareSmsData) + createNotification(context, bareSmsData.hashCode(), bareSmsData) runBlocking(Dispatchers.IO) { (context.applicationContext as App).smsRepo.persistSms(context, bareSmsData) } @@ -62,7 +62,7 @@ class SmsReceiver : BroadcastReceiver() { val replyPendingIntent = PendingIntent.getBroadcast( context, - 0, + notificationId + 1, replyIntent, PendingIntent.FLAG_MUTABLE ) @@ -82,7 +82,7 @@ class SmsReceiver : BroadcastReceiver() { val deletePendingIntent = PendingIntent.getBroadcast( context, - 1, + notificationId + 2, deleteIntent, PendingIntent.FLAG_MUTABLE ) @@ -97,7 +97,7 @@ class SmsReceiver : BroadcastReceiver() { val smsThreadPendingIntent = PendingIntent.getActivity( context, - 2, + notificationId + 3, smsThreadIntent, PendingIntent.FLAG_IMMUTABLE ) From d098c415112aa62b16e2accb6f83bcf9cca6bd71 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 13 Mar 2024 16:53:54 +0100 Subject: [PATCH 092/139] feat: support for copying verification codes from SMS notification --- app/src/main/AndroidManifest.xml | 4 +++ .../contacts/receivers/CopyTextReceiver.kt | 13 ++++++++ .../bnyro/contacts/receivers/SmsReceiver.kt | 30 ++++++++++++++++--- .../bnyro/contacts/util/ClipboardHelper.kt | 2 +- .../com/bnyro/contacts/ExampleUnitTest.kt | 12 ++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/receivers/CopyTextReceiver.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cf3d4b43..92ce6102 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -207,6 +207,10 @@ android:name=".receivers.DeleteSmsReceiver" android:exported="false" /> + + + val copyIntent = Intent(context, CopyTextReceiver::class.java) + .putExtra(KEY_EXTRA_TEXT, code.value) + + val copyPendingIntent = PendingIntent.getBroadcast( + context, + notificationId + 4, + copyIntent, + PendingIntent.FLAG_IMMUTABLE + ) + + val copyMessageAction = NotificationCompat.Action.Builder( + R.drawable.ic_delete, + "${context.getString(R.string.copy)} ${code.value}", + copyPendingIntent + ).build() + + builder.addAction(copyMessageAction) + } if (!PermissionHelper.checkPermissions( context, @@ -128,7 +147,7 @@ class SmsReceiver : BroadcastReceiver() { ) { return } - NotificationManagerCompat.from(context).notify(notificationId, notification) + NotificationManagerCompat.from(context).notify(notificationId, builder.build()) } companion object { @@ -138,5 +157,8 @@ class SmsReceiver : BroadcastReceiver() { const val KEY_EXTRA_SMS_ID = "key_extra_sms_id" const val KEY_EXTRA_THREAD_ID = "key_extra_thread_id" const val KEY_EXTRA_NOTIFICATION_ID = "notification_id" + const val KEY_EXTRA_TEXT = "key_extra_text" + + val verificationCodeRegex = Regex("(? Date: Fri, 2 Feb 2024 14:33:33 +0000 Subject: [PATCH 093/139] Translated using Weblate (Italian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/it/ --- app/src/main/res/values-it/strings.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index aba5873d..811d695b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -4,7 +4,7 @@ Cerca Condividi Telefono - Sei sicuro\? Quest\'azione non può essere annullata. + Sei sicuro? Quest\'azione non può essere annullata! Esportato. Annulla Crea un contatto @@ -103,4 +103,16 @@ Password Impossibile connettersi. Assicurati che la modalità aereo sia disattivata. Vuoi inviare il messaggio come messaggi più piccoli? + Elimina messaggio + Crittografa i backup come zip + Aggiungi indirizzo + Aggiungi evento + Aggiungi sito web + Aggiungi numero di telefono + Aggiungi email + Aggiungi nota + Data + Database SMS privato + Gli SMS saranno memorizzati solo all\'interno di quest\'app e non saranno accessibili in altre app. + Elimina thread \ No newline at end of file From cc5685b01be175547eaf4c7259861f8bcc20c37c Mon Sep 17 00:00:00 2001 From: Aspen Date: Fri, 2 Feb 2024 16:02:52 +0000 Subject: [PATCH 094/139] Translated using Weblate (Persian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fa/ --- app/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index e37fa6ae..0b6dd406 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -114,4 +114,5 @@ پیام ها فرستادن پیامک دیتابیس پیامک خصوصی + فروریختن کادر پایینی هنگام پیمایش \ No newline at end of file From 6614237914b5d002726dd4ef6cf9ebe53fb3ecb3 Mon Sep 17 00:00:00 2001 From: Htet Oo Hlaing Date: Sat, 3 Feb 2024 17:41:17 +0100 Subject: [PATCH 095/139] Added translation using Weblate (Burmese) --- app/src/main/res/values-my/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-my/strings.xml diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-my/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 1472300acb06e5d7af7758263095cb90aa56ee30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 4 Feb 2024 08:16:42 +0000 Subject: [PATCH 096/139] Translated using Weblate (Turkish) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/tr/ --- app/src/main/res/values-tr/strings.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3594491c..2a614549 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -34,7 +34,7 @@ Telefon Connect You Panoya kopyalandı - Emin misin\? Bu geri alınamaz. + Emin misiniz? Bu geri alınamaz! İptal Arama Tamam @@ -108,4 +108,12 @@ Mesajı sil SMS\'ler yalnızca uygulama içinde saklanacak ve diğer uygulamalardan erişilemeyecektir. Telefon numarası + Mesajı birden fazla küçük mesaj olarak göndermek istiyor musunuz? + Web Sitesi Ekle + E-posta Adresi Ekle + Adres Ekle + Telefon Numarası Ekle + Not Ekle + Etkinlik Ekle + Tarih \ No newline at end of file From f0841cbf5127df8cbcab4d0e3e0802d5298ad577 Mon Sep 17 00:00:00 2001 From: --//-- Date: Sat, 3 Feb 2024 16:43:18 +0000 Subject: [PATCH 097/139] Translated using Weblate (Burmese) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/my/ --- app/src/main/res/values-my/strings.xml | 118 ++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index a6b3daec..ae6f1282 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -1,2 +1,118 @@ - \ No newline at end of file + + ကလစ်ဘုတ် သို့ ကူးယူပြီးပါပြီ + အဆက်အသွယ်ထဲသို့ ထည့်မည် + အဆက်အသွယ် ကိုဖျက်မည် + သေချာပါသလား?ပြန်လည်လုပ်ဆောင်၍မရပါ + ပယ်ဖျက်မည် + ထပ်ကြိုးစားပါ + အဆက်အသွယ်သစ် + မျှဝေမည် + ရှော့ကတ်ဖန်တီးမည် + ဖုန်း + စာ + အဆက်အသွယ် + အဆက်အသွယ်များ + ပြုပြင်မည် + စစ်ထုတ်ရန် + လျှော့၍ပြပါ + အဆက်အသွယ်ကိုရွေးမည် + သိမ်းမည် + အမည်ပေးရန်လိုသည်! + ပို၍ပြပါ + ထည့်သွင်းပြီး။ + ထုတ်ပြီး။ + ဖုန်းနံပါတ် + အမျိုးအစား + အစီအစဥ် + အကောင့်အမျိုးအစား + အမည် + အလုပ် + အက်ဖ်တီပီ + မိုဘိုင်း + အခြား + စိတ်ကြိုက် + ကား + အုပ်စုများကိုစီမံမည် + အားလုံးအတွက်အုပ်စုကိုဖျက်မည် + အသစ် + အုပ်စုများ + စက် + ကူးယူမည် + စာများ + စာပို့မည် + အုပ်စုရှိပြီးသား + အဲပလိန်းမုဒ်ပိတ်ရန်လိုသည်။ + အိုကေ + ပြန်စမည် + အဆက်အသွယ်ထည့်မည် + ပို၍ + ဘာမှမရှိပါ။ + ဖုန်း + အီးမေးလ် + လိပ်စာ + အဖြစ်အပျက် + ဝဘ်ဆိုဒ် + ပုံ + အမည် အစ + အမည် အဆုံး + အမည်ပြောင် + အဖွဲ့အစည်း + အိမ် + အိမ် ဖက် + အလုပ် ဖက် + လက်ထောက် + မွေးနေ့ + နှစ်ပတ်လည် + မှတ်စု + အဓိက + ဘလောဘ့် + ခေါင်းစဥ် + အတွင်း + ရွှေ့မည် + ဖျက်မည် + %1$s ရွေးထား + စာပြန်မည် + စာရှည်လွန်းသည်! + စာကိုသေးငယ်တဲ့စာစုများအဖြစ်ပို့ချင်ပါသလား။ + လုံခြုံသောစာသိမ်းဆည်းရာ + စာစုကိုဖျက်ရန် + စာကိုဖျက်မည် + ဗွီကတ်ထည့်မည် + ဆက်တင်များ + သင်း + အလင်း + အမှောင် + အလိုအလျောက်မှတ်တမ်းတင်ခြင်း + အများဆုံးထားရှိမည့်မှတ်တမ်းများ + ဖိုင်လမ်းကြောင်း + နှစ်ခုလုံး + တစ်ခုမျှမဟုတ် + အမ်အိုင်အက်စ်စီ + စကားဝှက် + အကြောင်း + ဖန်တီးသူ + ဗားရှင်း + ဝဘ်ဆိုဒ်ထည့်မည် + ဖုန်းနံပါတ်ထည့်မည် + လိပ်စာထည့်မည် + မှတ်စုထည့်မည် + ရက်စွဲ + ရှာမည် + SMS ကိုအက်ပလီကေးရှင်းတွင်သာသိမ်းဆည်းထားပြီးအခြားအက်ပ်များတွင်မတွေ့နိုင်ပါ။ + ဗွီကတ်ထုတ်မည် + စနစ် + ဆင်းမှသွင်းမည် + တက်ဘ်ဖွင့်ရန် + မှတ်တမ်းတင်မှုကြားကာလ + အပြုအမူ + အောက်ဘားတန်းအားဝှက်ထားပါ + ရောင်စုံအိုင်ကွန်များ + အသွင်အပြင် + မှတ်တမ်း + မှတ်တမ်းဝှက်များကိုဖိုင်ချုံ့သိမ်းမည် + ဘာသာပြန် + အီးမေးလ်ထည့်မည် + အဖြစ်အပျက်ထည့်မည် + လိုင်စင် + \ No newline at end of file From df417fefaf770235e8cf161db15a21813850d0f0 Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Sun, 4 Feb 2024 19:08:45 +0000 Subject: [PATCH 098/139] Translated using Weblate (Interlingua) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 9e22dedb..3a50977e 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -1,7 +1,7 @@ Comportamento - Salvar + Salveguardar Filtrar Reinitialisar Message From fc8366f399a012265160aa2e2bd61aa5002e2489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 7 Feb 2024 10:20:13 +0000 Subject: [PATCH 099/139] Translated using Weblate (Estonian) Currently translated at 100.0% (116 of 116 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/et/ --- app/src/main/res/values-et/strings.xml | 99 ++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 3ca08d71..3598fe79 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -16,4 +16,103 @@ Katkesta Sobib Loo otsetee + Inimesed + Muuda + Filtreeri + Veel + Näita täiendavaid välju + Näita vähem + Vali kontakt + Salvesta + Nime väli ei saa olla tühi! + Importimine õnnestus. + E-posti aadress + Aadress + Sündmus + Telefoninumber + Sortimise järjekord + Konto tüüp + Nimi + Eesnimi + Perenimi + Hüüdnimi + Organisatsioon + Tüüp + Töö + Mobiiltelefon + Muu + Kohandatud + Auto + Telefaks kodus + Telefaks tööl + Abiline + Sünnipäev + Aastapäev + Märkus + Avaleht + FTP + Ajaveeb + Halda gruppe + Eemalda grupp kõigilt + Pealkiri + Uus + Selline grupp on juba olemas + Grupid + Kohalik andmekogu + Kopeeri + Teisalda + Kustuta + %1$s valitud + Sõnumid + Ühenduse loomine ei õnnestunud. Palun kontrolli, et lennurežiim ei oleks kasutusel. + Sõnum on liiga pikk! + Kas sa soovi selle sõnumi saata mitme väiksema sõnumina? + SMS-sõnumid salvestatakse vaid selles rakenduses ja pole muudele rakendustele kättesaadavad. + Kustuta sõnum + Impordi vCard vormingus kontakt + Ekspordi vCard vormingus kontakt + Impordi kontaktkirjed SIM-kaardilt + Seadistused + Esimesena kuvatav sisuleht + Teema + Tume kujundus + Kasuta automaatset varundust + Varunduse välp + Varukoopiate arv + Kaust + Mõlemad + Pole kasutusel + Muud + Vertikaalsel kerimisel lappa alumine riba kokku + Kasuta kontaktide juures värvilisi ikoone + Tegevused + Välimus + Varukoopia + Krüpti varukoopia zip-failina + Salasõna + Litsents + Autor + Versioon + Tõlked + Lisa veebisait + Lisa telefoninumber + Lisa e-posti aadress + Lisa aadress + Lisa märkus + Lisa sündmus + Kuupäev + Siin pole mitte midagi. + Eksportimine õnnestus. + Telefon + Veebisait + Foto + Kodu + Saada sõnum + Vasta + Seade + Privaatne SMS-sõnumite andmekogu + Kustuta jutulõng + Süsteemi kujundus + Hele kujundus + Rakenduse teave \ No newline at end of file From 55b83ba046564ddea6c082f56a0e39f95a1560d8 Mon Sep 17 00:00:00 2001 From: v1s7 Date: Thu, 8 Feb 2024 11:25:02 +0000 Subject: [PATCH 100/139] Translated using Weblate (Russian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ru/ --- app/src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e63fb47f..586eb363 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -115,4 +115,5 @@ Добавить примечание Добавить адрес Дата + Импортировать выбранный VCF файл? \ No newline at end of file From 42d86f317d6fcc44b2812a58374cbdd4102d2139 Mon Sep 17 00:00:00 2001 From: psychosocial Date: Fri, 9 Feb 2024 08:08:09 +0000 Subject: [PATCH 101/139] Translated using Weblate (Turkish) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/tr/ --- app/src/main/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2a614549..54154197 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -116,4 +116,5 @@ Not Ekle Etkinlik Ekle Tarih + Seçilen vCard\'ı içe aktarmak istiyor musunuz? \ No newline at end of file From 1aaf6d91ee827f80c77c641d433076ee9850ff15 Mon Sep 17 00:00:00 2001 From: Raymond Nee Date: Fri, 9 Feb 2024 05:31:49 +0000 Subject: [PATCH 102/139] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a7986854..4e6c197d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -115,4 +115,5 @@ 添加电子邮件 添加地址 添加事件 + 您是否要导入选定的 VCF 文件? \ No newline at end of file From 64cd45bb127b161baa3f4af5d989dd37840684be Mon Sep 17 00:00:00 2001 From: Fjuro Date: Thu, 8 Feb 2024 18:34:41 +0000 Subject: [PATCH 103/139] Translated using Weblate (Czech) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/cs/ --- app/src/main/res/values-cs/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index db6a59b4..04c6f764 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -115,4 +115,5 @@ Přidat webové stránky Přidat telefonní číslo Datum + Chcete importovat vybraný soubor VCF? \ No newline at end of file From 99d4a36d447bb1eac51b4eab40166445717ef545 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Thu, 8 Feb 2024 21:04:45 +0000 Subject: [PATCH 104/139] Translated using Weblate (Spanish) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/es/ --- app/src/main/res/values-es/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7fcd120e..815d50ad 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -115,4 +115,5 @@ Añadir evento Añadir nota Fecha + ¿Desea importar el archivo VCF seleccionado? \ No newline at end of file From c86b428db7d7e570fff8940e15b3a607d5227605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jos=C3=A9=20m?= Date: Thu, 8 Feb 2024 14:01:52 +0000 Subject: [PATCH 105/139] Translated using Weblate (Galician) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/gl/ --- app/src/main/res/values-gl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 67dd6bc1..099d7f66 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -115,4 +115,5 @@ Engadir enderezo Engadir evento Data + Queres importar o ficheiro VCF seleccionado? \ No newline at end of file From aabee47f8b3ebf47b9a175838b8d754d68c77eee Mon Sep 17 00:00:00 2001 From: Software In Interlingua Date: Fri, 9 Feb 2024 00:26:17 +0000 Subject: [PATCH 106/139] Translated using Weblate (Interlingua) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ia/ --- app/src/main/res/values-ia/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 3a50977e..812874d7 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -115,4 +115,5 @@ Adder nota Data Adder evento + Desira tu importar le file VCF seligite? \ No newline at end of file From 6b201b1a2d0673f6d30e3d6f27a11679c01f7872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 8 Feb 2024 13:02:46 +0000 Subject: [PATCH 107/139] Translated using Weblate (Estonian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/et/ --- app/src/main/res/values-et/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 3598fe79..d0302cba 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -115,4 +115,5 @@ Süsteemi kujundus Hele kujundus Rakenduse teave + Kas sa soovid importida valitud VCF-faili? \ No newline at end of file From a8b5254525f0b329eef693ec7236d2bcaf0436b6 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Sat, 10 Feb 2024 06:18:54 +0000 Subject: [PATCH 108/139] Translated using Weblate (Arabic) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ar/ --- app/src/main/res/values-ar/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 85615783..2b694d41 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -115,4 +115,5 @@ اضف ملاحظة أضف حدثًا ‪التاريخ + هل ترغب في استيراد ملف VCF المحدد؟ \ No newline at end of file From 29269ec9a89e5bef9597482ec14feb126896d2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Gr=C3=B6nroos?= Date: Sat, 10 Feb 2024 18:12:51 +0000 Subject: [PATCH 109/139] Translated using Weblate (Finnish) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fi/ --- app/src/main/res/values-fi/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 958ec717..e7d2110c 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -115,4 +115,5 @@ Lisää tapahtuma Päiväys Lisää muistiinpano + Haluatko tuoda valitun VCF-tiedoston? \ No newline at end of file From 6ab45da077883965828f4e054ff0eb7483bd670d Mon Sep 17 00:00:00 2001 From: brochard Date: Sun, 11 Feb 2024 17:23:52 +0000 Subject: [PATCH 110/139] Translated using Weblate (French) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fr/ --- app/src/main/res/values-fr/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 736edbd2..c2e5c541 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -109,4 +109,12 @@ Mot de passe Impossible de se connecter. Veuillez vous assurer que le mode avion est désactivé. Souhaitez-vous envoyer le message sous la forme de plusieurs petits messages ? + Voulez-vous importer le fichier VCF sélectionné ? + Ajouter un site Web + Ajouter un évènement + Date + Ajouter un numéro de téléphone + Ajouter une adresse mail + Ajouter une adresse + Ajouter une note \ No newline at end of file From e34dc05b7fdd309a543935562a7423e58e8c2144 Mon Sep 17 00:00:00 2001 From: Giovanni Donisi Date: Sun, 11 Feb 2024 12:08:04 +0000 Subject: [PATCH 111/139] Translated using Weblate (Italian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/it/ --- app/src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 811d695b..af09ab3d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -115,4 +115,5 @@ Database SMS privato Gli SMS saranno memorizzati solo all\'interno di quest\'app e non saranno accessibili in altre app. Elimina thread + Vuoi importare il file VCF selezionato? \ No newline at end of file From e3ef5db36928ab3c755f771acfc87043569d6b7e Mon Sep 17 00:00:00 2001 From: NEXI Date: Tue, 13 Feb 2024 00:07:12 +0000 Subject: [PATCH 112/139] Translated using Weblate (Serbian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/sr/ --- app/src/main/res/values-sr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 10b6bb1b..79bcfaae 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -126,4 +126,5 @@ Додај догађај Додај веб-сајт Додај белешку + Желите ли да увезете изабрани VCF фајл? \ No newline at end of file From 2a64865a775da74a82c9b73f743c5a6899acf60c Mon Sep 17 00:00:00 2001 From: Pere O Date: Tue, 13 Feb 2024 15:51:52 +0100 Subject: [PATCH 113/139] Added translation using Weblate (Catalan) --- app/src/main/res/values-ca/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-ca/strings.xml diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From f6157679942a2981c44f86ba4f834e3ef59e0808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D1=96=D0=B9?= Date: Tue, 13 Feb 2024 11:41:46 +0000 Subject: [PATCH 114/139] Translated using Weblate (Ukrainian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/uk/ --- app/src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e82a7d48..02d2f982 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -116,4 +116,5 @@ Дата Додати примітку Додати подію + Ви хочете імпортувати вибраний VCF-файл? \ No newline at end of file From d391a48163819ae89cdc099679fff31c1926a6a4 Mon Sep 17 00:00:00 2001 From: Pere O Date: Tue, 13 Feb 2024 14:52:18 +0000 Subject: [PATCH 115/139] Translated using Weblate (Catalan) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/ca/ --- app/src/main/res/values-ca/strings.xml | 119 ++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a6b3daec..97aac223 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -1,2 +1,119 @@ - \ No newline at end of file + + Contrasenya + Quant a + Llicència + Autor + Versió + Traducció + Afegeix un lloc web + Afegeix un número de telèfon + Afegeix un correu electrònic + Afegeix una adreça + Afegeix una nota + Afegeix un esdeveniment + Data + Cerca + S\'ha copiat al porta-retalls + Suprimeix el contacte + Esteu segur? No es pot desfer. + Cancel·la + D\'acord + Restableix + Crea un contacte + Reintenta + Comparteix + Afegeix a un contacte + Nou contacte + Crea una drecera + Marca + Missatge + Contacte + Contactes + Edita + Filtre + Més + Mostra més camps + Mostra menys + Escull un contacte + Desa + El nom no pot estar buit. + Importat. + Exportat. + No hi ha res aquí. + Telèfon + Correu electrònic + Adreça + Esdeveniment + Lloc web + Foto + Número de telèfon + Ordre de classificació + Tipus de compte + Nom + Nom + Cognom + Àlies + Organització + Tipus + Domicili + Feina + Mòbil + Altres + Personalitzat + Cotxe + Fax de casa + Fax de la feina + Assistent + Aniversari + Aniversari (de casament) + Nota + Pàgina principal + FTP + Blog + Gestiona els grups + Suprimeix el grup per a tothom + Títol + Nou + El grup ja existeix + Grups + Dispositiu + Local + Copia + Mou + Suprimeix + %1$s seleccionats + Missatges + Envia + Respon + No s\'ha pogut connectar. Assegureu-vos que el mode avió està desactivat. + El missatge és massa llarg. + Voleu enviar el missatge en múltiples missatges més petits? + Base de dades de SMS privada + L\'SMS només s\'emmagatzemarà dins l\'aplicació i no serà accessible per altres aplicacions. + Suprimeix el fil de conversa + Suprimeix el missatge + Importa vCard + Exporta vCard + Importa des de la SIM + Voleu importar l\'arxiu VCF seleccionat? + Configuració + Pestanya d\'inici + Tema + Sistema + Clar + Fosc + Còpia de seguretat automàtica + Interval de còpia de seguretat + Quantitat màxima de còpies de seguretat + Directori + Tots dos + Cap + Miscel·lània + Redueix la barra inferior en desplaçar-se + Icones de contacte acolorides + Comportament + Aspecte + Còpia de seguretat + Xifra les còpies de seguretat amb zip + \ No newline at end of file From 0b5323594196f6e9c86dd4e3e26c884307a235fa Mon Sep 17 00:00:00 2001 From: Nitin Khalia Date: Wed, 14 Feb 2024 18:00:47 +0100 Subject: [PATCH 116/139] Added translation using Weblate (Hindi) --- app/src/main/res/values-hi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-hi/strings.xml diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 1c73925cea4b072037f3ef35ef535a2b319142ca Mon Sep 17 00:00:00 2001 From: Aspen Date: Wed, 14 Feb 2024 17:41:07 +0000 Subject: [PATCH 117/139] Translated using Weblate (Persian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/fa/ --- app/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 0b6dd406..0d6a5f54 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -115,4 +115,5 @@ فرستادن پیامک دیتابیس پیامک خصوصی فروریختن کادر پایینی هنگام پیمایش + آیا میخواهید فایل VCF انتخاب شده را درون بری کنید؟ \ No newline at end of file From 8396ebb40a860dd951c8d7335b4eec2fdbd30bd4 Mon Sep 17 00:00:00 2001 From: Nitin Khalia Date: Thu, 15 Feb 2024 14:36:18 +0000 Subject: [PATCH 118/139] Translated using Weblate (Hindi) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/hi/ --- app/src/main/res/values-hi/strings.xml | 119 ++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index a6b3daec..5b96fa21 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,2 +1,119 @@ - \ No newline at end of file + + क्या आप निश्चित हैं? इसे पूर्ववत नहीं किया जा सकता! + रद्द करें + नया संपर्क + संपर्क बनाएं + डायल + संदेश + फोन + पता + संगठन + घर + वर्षगांठ + टिप्पणी + ब्लॉग + FTP + मुखपृष्ठ + समूह प्रबंधित करें + सभी के लिए समूह हटाएं + समूह पहले से मौजूद है + क्या आप संदेश को अनेक छोटे-छोटे रूपों में भेजना चाहते हैं? + प्रारंभ टैब + स्वचालित बैकअप + बैकअप की अधिकतम मात्रा + रंगीन संपर्क चिह्न + अनुवाद + vCard आयात करें + क्या आप चयनित VCF फ़ाइल आयात करना चाहते हैं? + क्लिपबोर्ड पर कॉपी किया गया + संपर्क हटाएँ + ठीक है + रीसेट + पुन: प्रयास + शेयर + संपर्क में जोडें + शॉर्टकट बनाएं + संपर्क + संपर्क + संपादित करें + फिल्टर + अधिक + और फ़ील्ड दिखाएं + कम दिखाएं + संपर्क चुनें + सहेजें + नाम खाली नहीं हो सकता! + आयातित। + निर्यात किया गया। + यहाँ कुछ नहीं। + ई-मेल + आयोजन + वेबसाइट + तस्वीर + फोन नंबर + क्रमबद्ध करें + खाते का प्रकार + नाम + पहला नाम + उपनाम + अंतिम नाम + प्रकार + कार्य + मोबाइल + अन्य + कस्टम + कार + घर फैक्स + फैक्स कार्य + सहायक + जन्मदिन + शीर्षक + नया + समूह + डिवाइस + स्थानीय + कॉपी + स्थान परिवर्तन + हटाएं + %1$s चयनित + संदेश + संदेश भेजें + उत्तर + कनेक्ट नहीं हो सका। कृपया सुनिश्चित करें कि हवाई जहाज़ मोड बंद है। + संदेश बहुत लंबा है! + निजी SMS डेटाबेस + थ्रेड मिटाएं + संदेश हटाएं + सेटिंग्स + थीम + सिस्टम + हल्की + गहरी + बैकअप अंतराल + डॉयरेक्टरी + दोनों + कोई नहीं + विविध + स्क्रॉल पर निचली पट्टी को संक्षिप्त करें + व्यवहार + दिखावट + बैकअप + पासवर्ड + बारे में + लाइसेंस + लेखक + संस्करण + वेबसाइट जोड़ें + फोन नंबर डालें + SMS केवल ऐप के भीतर संग्रहीत किया जाएगा और अन्य ऐप्स में पहुंच योग्य नहीं होगा। + vCard निर्यात करें + SIM से आयात करें + बैकअप को zip के रूप में एन्क्रिप्ट करें + ई-मेल जोड़ें + पता जोड़ें + टिप्पणी जोड़ें + कार्यक्रम जोड़ें + तारीख + खोजें + \ No newline at end of file From 4f1fb9a4e78aabd7d2fb09f92cc5b23871dd6779 Mon Sep 17 00:00:00 2001 From: Andre Bastos Date: Wed, 28 Feb 2024 20:43:14 +0000 Subject: [PATCH 119/139] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/pt_BR/ --- app/src/main/res/values-pt-rBR/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 77893e35..81568705 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -9,7 +9,7 @@ Pesquisa Copiado para a área de transferência Excluir contato - Você tem certeza\? Isso não pode ser desfeito. + Você tem certeza? Isso não pode ser desfeito! Cancelar Ok Redefinir @@ -107,4 +107,13 @@ Número de telefone Senha Não foi possível conectar. Por favor, verifique se o modo avião está desligado. + Você quer importar o arquivo VCF selecionado? + Adicionar Website + Adicionar número de telefone + Adicionar Email + Adicionar Endereço + Adicionar Observação + Adicionar Evento + Data + Você quer enviar a mensagem como múltiplas mensagens pequenas? \ No newline at end of file From 05bb1078ad5c7d521d196a69eaa48f752d971178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=B5=E5=8F=AF=E6=8F=9A?= Date: Sat, 2 Mar 2024 15:59:45 +0000 Subject: [PATCH 120/139] Translated using Weblate (Chinese (Traditional)) Currently translated at 36.7% (43 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 28f7022e..7b7fa396 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -35,4 +35,11 @@ 電話號碼 已匯入。 已匯出。 + 帳戶類型 + 名稱 + 名字 + 姓氏 + 組織 + 類型 + 其他 \ No newline at end of file From 5d4c64bc9592fac1bba1b19bbd8105739be2d105 Mon Sep 17 00:00:00 2001 From: Argo Carpathians Date: Mon, 11 Mar 2024 03:05:14 +0100 Subject: [PATCH 121/139] Added translation using Weblate (Indonesian) --- app/src/main/res/values-in/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-in/strings.xml diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-in/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 465723ac2f579944511ebabe1ce00641803136fd Mon Sep 17 00:00:00 2001 From: Argo Carpathians Date: Mon, 11 Mar 2024 02:05:57 +0000 Subject: [PATCH 122/139] Translated using Weblate (Indonesian) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/id/ --- app/src/main/res/values-in/strings.xml | 119 ++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index a6b3daec..e8fa4002 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -1,2 +1,119 @@ - \ No newline at end of file + + Yakin? Tindakan ini tidak dapat dibatalkan! + Batal + Oke + Atur ulang + Buat kontak + Coba lagi + Bagikan + Kontak baru + Buat jalan pintas + Telepon + Pesan + Kontak + Filter + Lainnya + Tampilkan kolom lainnya + Tampilkan lebih sedikit + Pilih kontak + Simpan + Kolom nama tidak boleh kosong! + Telah diimpor. + Telah diekspor. + Tidak ada apa pun di sini. + Telepon + Alamat + Peristiwa + Situs web + Foto + Nomor telepon + Urutan urut + Jenis akun + Nama depan + Nama belakang + Julukan + Organisasi + Jenis + Rumah + Kerja + Ponsel + Lainnya + Kustom + Mobil + Faks rumah + Faks kerja + Asisten + Ulang tahun + Sunting + Layar depan + FTP + Blog + Atur kelompok + Hapus kelompok untuk semua + Baru + Kelompok dengan nama ini sudah ada + Kelompok + Lokal + Salin + Pindah + Hapus + %1$s dipilih + Pesan + Kirim pesan + Balas + Tidak dapat tersambung. Pastikan mode pesawat sedang tidak aktif. + Pesan ini terlalu panjang! + Apakah Anda ingin mengirimkan pesan panjang ini menjadi sejumlah pesan pendek? + Basis data SMS pribadi + Hapus utas + Hapus pesan + Impor vCard + Ekspor vCard + Impor dari kartu SIM + Apakah Anda ingin mengimpor berkas VCF yang dipilih? + Tab mulai + Tema + Sistem + Cerah + Gelap + Simpan data cadangan secara otomatis + Jangka waktu data cadangan + Serbaneka + Tutup bilah bawah saat menggulir + Ikon kontak berwarna-warni + Tindakan + Penampilan + Data cadangan + Enkripsi data cadangan dalam format ZIP + Kata sandi + Tentang + Lisensi + Cari + Pencipta + Versi + Terjemahan + Tambah surel + Tambah situs web + Tambah nomor telepon + Tambah alamat + Tambah catatan + Tambah peristiwa + Tanggal + Salin ke papan klip + Hapus kontak + Tambahkan ke kontak + Kontak + Surel + Nama + Hari ulang tahun + Catatan + Judul + Perangkat + Pengaturan + SMS ini hanya akan disimpan di dalam aplikasi ini dan tidak dapat diakses pada aplikasi lainnya. + Keduanya + Jumlah maksimal data cadangan yang diizinkan + Direktori + Tidak ada + \ No newline at end of file From 3e6e9dccc3e97fb555b5dac2df0674a9eed9240e Mon Sep 17 00:00:00 2001 From: Michal L Date: Sat, 16 Mar 2024 15:40:02 +0000 Subject: [PATCH 123/139] Translated using Weblate (Polish) Currently translated at 100.0% (117 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/pl/ --- app/src/main/res/values-pl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 67736195..ac2e418c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -115,4 +115,5 @@ Dodaj notatkę Dodaj wydarzenie Data + Czy chcesz zaimportować wybrany plik VCF? \ No newline at end of file From 893d0212970b3b201565dcc1108cd4ccaca80368 Mon Sep 17 00:00:00 2001 From: cat <158170307+cultcats@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:22:53 +0100 Subject: [PATCH 124/139] Added translation using Weblate (Danish) --- app/src/main/res/values-da/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-da/strings.xml diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-da/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From a7b0c406337c3aa17bbeb3a427efd97899fbd6e1 Mon Sep 17 00:00:00 2001 From: cat <158170307+cultcats@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:23:14 +0000 Subject: [PATCH 125/139] Translated using Weblate (Danish) Currently translated at 98.2% (115 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/da/ --- app/src/main/res/values-da/strings.xml | 117 ++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index a6b3daec..5e8d443d 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1,2 +1,117 @@ - \ No newline at end of file + + Kopieret til udklipsholder + Annullér + Nulstil + Prøv igen + Ny kontakt + Opret genvej + Ring + Kontakt + Kontakter + Filtrér + Mere + Gem + Navn kan ikke være tomt! + Importeret. + Telefon + Telefonnummer + Fornavn + Efternavn + Organisation + Hjem + Assistent + Årsdag + Note + Ny + Gruppe eksisterer allerede + Grupper + Enhed + Lokal + Kopiér + Flyt + Slet + Kunne ikke oprette forbindelse. Sørg for, at flytilstand er slået fra. + Privat SMS-database + SMS\'en gemmes kun i appen og er ikke tilgængelig i andre apps. + Slet tråd + Eksportér vCard + Indstillinger + Backup-interval + Skjul bundlinjen ved scroll + Søg + Slet kontakt + Er du sikker? Dette kan ikke fortrydes! + Del + Okay + Opret kontakt + Føj til kontakt + Vis flere felter + Besked + Redigér + Maks. antal backups + Autor + Mappe + Diverse + Begge + Ingen + Adfærd + Farverige kontakt-ikoner + Backup + Udseende + Licens + Kryptér backups som ZIP + Adgangskode + Om + Version + Tilføj Telefonnummer + Oversættelse + Tilføj E-mail + Tilføj Adresse + Tilføj Hjemmeside + Tilføj Note + Tilføj Begivenhed + Dato + Vis mindre + Vælg kontakt + Eksporteret. + Intet her. + E-mail + Adresse + Hjemmeside + Begivenhed + Foto + Sortér efter + Kontotype + Navn + Kaldenavn + Andet + Type + Arbejde + Mobil + Fødselsdag + Bil + Brugerdefineret + Startside + FTP + Administrér grupper + Blog + Slet gruppe for alle + Titel + Beskeder + %1$s valgt + Send besked + Svar + Besked er for lang! + Vil du sende beskeden som flere mindre? + Slet besked + Importér vCard + Importér fra SIM + Vil du importere den valgte VCF-fil? + Tema + System + Lys + Mørk + Automatisk backup + Start-fane + \ No newline at end of file From 1b81e382c8c424243c516df8e7a99961ee4022ee Mon Sep 17 00:00:00 2001 From: Dominik Masson <73367871+dominik-masson@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:18:39 +0100 Subject: [PATCH 126/139] start message list from bottom --- .../ui/components/conversation/Message.kt | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt index e108257c..c8decce8 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/conversation/Message.kt @@ -2,37 +2,15 @@ package com.bnyro.contacts.ui.components.conversation import android.provider.Telephony import android.text.format.DateUtils -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material3.DismissValue -import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.SwipeToDismiss -import androidx.compose.material3.Text -import androidx.compose.material3.rememberDismissState -import androidx.compose.material3.surfaceColorAtElevation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -52,6 +30,10 @@ fun ColumnScope.Messages( scrollState: LazyListState, smsModel: SmsModel ) { + LaunchedEffect(Unit) { + scrollState.scrollToItem(messages.size + 5) + } + val timestamped = messages.groupBy { DateUtils.getRelativeDateTimeString( LocalContext.current, @@ -61,6 +43,7 @@ fun ColumnScope.Messages( DateUtils.FORMAT_ABBREV_ALL ).split(", ").first() } + LazyColumn( state = scrollState, modifier = Modifier From 73f8420d92d49ec0e1363f924b9107334c5388d3 Mon Sep 17 00:00:00 2001 From: Dominik Masson <73367871+dominik-masson@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:19:56 +0100 Subject: [PATCH 127/139] feat: amoled theme --- .../com/bnyro/contacts/enums/ThemeMode.kt | 3 ++- .../contacts/ui/screens/SettingsScreen.kt | 5 +---- .../java/com/bnyro/contacts/ui/theme/Theme.kt | 19 ++++++++++++------- app/src/main/res/values/strings.xml | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/enums/ThemeMode.kt b/app/src/main/java/com/bnyro/contacts/enums/ThemeMode.kt index 20ea1b51..fb86fd2a 100644 --- a/app/src/main/java/com/bnyro/contacts/enums/ThemeMode.kt +++ b/app/src/main/java/com/bnyro/contacts/enums/ThemeMode.kt @@ -3,7 +3,8 @@ package com.bnyro.contacts.enums enum class ThemeMode { SYSTEM, LIGHT, - DARK; + DARK, + AMOLED; companion object { fun fromInt(value: Int) = ThemeMode.values().first { it.ordinal == value } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt index eebeb715..1f8481e7 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -40,8 +39,6 @@ fun SettingsScreen(onDismissRequest: () -> Unit) { val themeModel: ThemeModel = viewModel() val smsModel: SmsModel = viewModel() - val context = LocalContext.current - FullScreenDialog(onClose = onDismissRequest) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( rememberTopAppBarState() @@ -78,7 +75,7 @@ fun SettingsScreen(onDismissRequest: () -> Unit) { Text(stringResource(R.string.theme)) BlockPreference( preferenceKey = Preferences.themeKey, - entries = listOf(R.string.system, R.string.light, R.string.dark).map { + entries = listOf(R.string.system, R.string.light, R.string.dark, R.string.amoled).map { stringResource(it) } ) { diff --git a/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt b/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt index e46101f6..fa04da70 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt @@ -3,14 +3,10 @@ package com.bnyro.contacts.ui.theme import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -40,15 +36,24 @@ fun ConnectYouTheme( val darkTheme = when (themeMode) { ThemeMode.LIGHT -> false ThemeMode.DARK -> true + ThemeMode.AMOLED -> false else -> isSystemInDarkTheme() } + val amoledDark = themeMode == ThemeMode.AMOLED + val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + if (darkTheme) dynamicDarkColorScheme(context) + else if (amoledDark) dynamicDarkColorScheme(context).copy( + background = Color.Black, + surface = Color.Black + ) else dynamicLightColorScheme(context) } + darkTheme -> DarkColorScheme + amoledDark -> DarkColorScheme.copy(background = Color.Black, surface = Color.Black) else -> LightColorScheme } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bf28677..2db7a5f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,4 +128,5 @@ Add Note Add Event Date + Amoled \ No newline at end of file From 41fef6f71ed80323c6abd1813b616e2e6194fb85 Mon Sep 17 00:00:00 2001 From: Dominik Masson <73367871+dominik-masson@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:35:41 +0100 Subject: [PATCH 128/139] fix: show status bar --- app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt b/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt index fa04da70..71adea1d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/theme/Theme.kt @@ -67,7 +67,7 @@ fun ConnectYouTheme( WindowCompat.getInsetsController( activity.window, view - ).isAppearanceLightStatusBars = !darkTheme + ).isAppearanceLightStatusBars = !darkTheme && !amoledDark WindowCompat.getInsetsController( activity.window, view From b48c050908bc7aecd133f3c0a0e476050cd17c42 Mon Sep 17 00:00:00 2001 From: Dominik Date: Sun, 24 Mar 2024 18:25:54 +0000 Subject: [PATCH 129/139] Translated using Weblate (German) Currently translated at 89.7% (105 of 117 strings) Translation: You Apps/Connect You Translate-URL: https://hosted.weblate.org/projects/you-apps/connect-you/de/ --- app/src/main/res/values-de/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 03710494..8b444d6e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -102,4 +102,9 @@ Nachricht senden Nachrichten Verbindungsfehler! Stellen sie sicher, dass der Flugmodus ausgeschaltet ist! + Telefonnummer + Unterhaltung löschen + Nachricht löschen + Private SMS Datenbank + Speichern \ No newline at end of file From 2c384e2f2feaf766245cb9d20f5bdfde3007240a Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Thu, 28 Mar 2024 21:46:07 +0530 Subject: [PATCH 130/139] refactor: navigation --- app/build.gradle.kts | 1 + .../com/bnyro/contacts/nav/NavContainer.kt | 125 +++++++++ .../java/com/bnyro/contacts/nav/NavHost.kt | 88 ++++++ .../java/com/bnyro/contacts/nav/NavRoutes.kt | 28 ++ .../contacts/ui/activities/MainActivity.kt | 8 +- .../ui/components/NumberPickerDialog.kt | 28 +- .../contacts/ui/components/SmsThreadItem.kt | 16 +- .../contacts/ui/components/TopBarMoreMenu.kt | 37 +-- .../com/bnyro/contacts/ui/models/SmsModel.kt | 3 + .../bnyro/contacts/ui/screens/AboutScreen.kt | 117 ++++---- .../contacts/ui/screens/ContactsScreen.kt | 20 +- .../contacts/ui/screens/MainAppContent.kt | 258 +++++++++--------- .../contacts/ui/screens/SettingsScreen.kt | 169 ++++++------ .../contacts/ui/screens/SmsListScreen.kt | 58 ++-- .../contacts/ui/screens/SmsSearchScreen.kt | 6 +- .../contacts/ui/screens/SmsThreadScreen.kt | 215 ++++++++------- 16 files changed, 701 insertions(+), 476 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/nav/NavContainer.kt create mode 100644 app/src/main/java/com/bnyro/contacts/nav/NavHost.kt create mode 100644 app/src/main/java/com/bnyro/contacts/nav/NavRoutes.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 58892360..ee82ede1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,6 +94,7 @@ dependencies { // Room database implementation("androidx.room:room-ktx:2.5.1") + implementation("androidx.navigation:navigation-compose:2.5.2") kapt("androidx.room:room-compiler:2.5.1") // Testing diff --git a/app/src/main/java/com/bnyro/contacts/nav/NavContainer.kt b/app/src/main/java/com/bnyro/contacts/nav/NavContainer.kt new file mode 100644 index 00000000..e3254b86 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/nav/NavContainer.kt @@ -0,0 +1,125 @@ +package com.bnyro.contacts.nav + +import android.content.res.Configuration +import androidx.activity.addCallback +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationRail +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.bnyro.contacts.ui.activities.MainActivity + +val bottomNavItems = listOf( + NavRoutes.Contacts, + NavRoutes.Messages +) + +@Composable +fun NavContainer( + initialTabIndex: Int +) { + val context = LocalContext.current + val navController = rememberNavController() + + val initialTab = bottomNavItems[initialTabIndex.coerceIn(0, 1)] + var selectedRoute by remember { + mutableStateOf(initialTab) + } + LaunchedEffect(Unit) { + val activity = context as MainActivity + activity.onBackPressedDispatcher.addCallback { + if (selectedRoute != NavRoutes.Settings && selectedRoute != NavRoutes.About) { + activity.finish() + } else { + navController.popBackStack() + } + } + } + + // listen for destination changes (e.g. back presses) + DisposableEffect(Unit) { + val listener = NavController.OnDestinationChangedListener { _, destination, _ -> + allRoutes.firstOrNull { it.route == destination.route?.split("/")?.first() } + ?.let { selectedRoute = it } + } + navController.addOnDestinationChangedListener(listener) + + onDispose { + navController.removeOnDestinationChangedListener(listener) + } + } + + val orientation = LocalConfiguration.current.orientation + Scaffold( + bottomBar = { + if (orientation == Configuration.ORIENTATION_PORTRAIT && (selectedRoute == NavRoutes.Contacts || selectedRoute == NavRoutes.Messages)) { + NavigationBar( + tonalElevation = 5.dp + ) { + bottomNavItems.forEach { + NavigationBarItem( + label = { + Text(stringResource(it.stringRes!!)) + }, + icon = { + Icon(it.icon!!, null) + }, + selected = it == selectedRoute, + onClick = { + selectedRoute = it + navController.navigate(it.route) + } + ) + } + } + } + } + ) { pV -> + Row( + Modifier + .fillMaxSize() + .padding(pV) + ) { + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + NavigationRail { + bottomNavItems.forEach { + NavigationRailItem(selected = it == selectedRoute, + onClick = { + selectedRoute = it + navController.navigate(it.route) + }, + icon = { Icon(it.icon!!, null) }, + label = { + Text(stringResource(it.stringRes!!)) + }) + } + } + } + AppNavHost( + navController, + startDestination = initialTab, + modifier = Modifier + .fillMaxSize() + ) + } + } +} diff --git a/app/src/main/java/com/bnyro/contacts/nav/NavHost.kt b/app/src/main/java/com/bnyro/contacts/nav/NavHost.kt new file mode 100644 index 00000000..9b0b130e --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/nav/NavHost.kt @@ -0,0 +1,88 @@ +package com.bnyro.contacts.nav + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import com.bnyro.contacts.ui.models.ContactsModel +import com.bnyro.contacts.ui.models.SmsModel +import com.bnyro.contacts.ui.models.ThemeModel +import com.bnyro.contacts.ui.screens.AboutScreen +import com.bnyro.contacts.ui.screens.ContactsPage +import com.bnyro.contacts.ui.screens.SettingsScreen +import com.bnyro.contacts.ui.screens.SmsListScreen +import com.bnyro.contacts.ui.screens.SmsThreadScreen + +@Composable +fun AppNavHost( + navController: NavHostController, + startDestination: NavRoutes, + modifier: Modifier = Modifier +) { + val smsModel: SmsModel = viewModel(factory = SmsModel.Factory) + val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) + val themeModel: ThemeModel = viewModel() + + val viewModelStoreOwner: ViewModelStoreOwner = LocalViewModelStoreOwner.current!! + + NavHost(navController, startDestination = startDestination.route, modifier = modifier) { + composable(NavRoutes.About.route) { + AboutScreen { + navController.popBackStack() + } + } + composable(NavRoutes.Settings.route) { + SettingsScreen(themeModel = themeModel, smsModel = smsModel) { + navController.popBackStack() + } + } + composable(NavRoutes.Contacts.route) { + CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { + ContactsPage(null, + onNavigate = { + navController.navigate(it.route) + }) + } + } + composable(NavRoutes.Messages.route) { + CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { + SmsListScreen( + smsModel = smsModel, + contactsModel = contactsModel, + scrollConnection = null, + onNavigate = { + navController.navigate(it.route) + }, + onClickMessage = { address, contactData -> + smsModel.currentContactData = contactData + navController.navigate("${NavRoutes.MessageThread.route}/$address") + } + ) + } + } + composable( + "${NavRoutes.MessageThread.route}/{address}", + listOf(navArgument("address") { type = NavType.StringType }) + ) { + val address = it.arguments?.getString("address") + CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { + SmsThreadScreen( + smsModel = smsModel, + contactsModel = contactsModel, + contactsData = remember { smsModel.currentContactData }, + address = address.orEmpty() + ) { + navController.popBackStack() + } + } + } + } +} diff --git a/app/src/main/java/com/bnyro/contacts/nav/NavRoutes.kt b/app/src/main/java/com/bnyro/contacts/nav/NavRoutes.kt new file mode 100644 index 00000000..d72a5e44 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/nav/NavRoutes.kt @@ -0,0 +1,28 @@ +package com.bnyro.contacts.nav + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Message +import androidx.compose.material.icons.rounded.Person +import androidx.compose.ui.graphics.vector.ImageVector +import com.bnyro.contacts.R + +sealed class NavRoutes( + val route: String, + @StringRes val stringRes: Int? = null, + val icon: ImageVector? = null +) { + object About : NavRoutes("about", null, null) + object Settings : NavRoutes("settings", null, null) + object Contacts : NavRoutes("contacts", R.string.contacts, Icons.Rounded.Person) + object Messages : NavRoutes("messages", R.string.messages, Icons.Rounded.Message) + object MessageThread : NavRoutes("message_thread", null, null) +} + +val allRoutes = listOf( + NavRoutes.About, + NavRoutes.Settings, + NavRoutes.Contacts, + NavRoutes.Messages, + NavRoutes.MessageThread, +) \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index e465ea2c..dc5ce4bd 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -3,19 +3,19 @@ package com.bnyro.contacts.ui.activities import android.content.Intent import android.net.Uri import android.os.Bundle -import android.os.Parcelable import android.provider.ContactsContract.Intents import android.provider.ContactsContract.QuickContact import androidx.activity.compose.setContent import com.bnyro.contacts.ext.parcelable +import com.bnyro.contacts.nav.NavContainer import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.ConfirmImportContactsDialog import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog -import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper import com.bnyro.contacts.util.ContactsHelper +import com.bnyro.contacts.util.Preferences import java.net.URLDecoder class MainActivity : BaseActivity() { @@ -33,9 +33,11 @@ class MainActivity : BaseActivity() { smsModel.initialAddressAndBody = getInitialSmsAddressAndBody() + val initialTabIndex = smsModel.initialAddressAndBody?.let { 1 } + ?: Preferences.getInt(Preferences.homeTabKey, 0) setContent { ConnectYouTheme(themeModel.themeMode) { - MainAppContent(smsModel) + NavContainer(initialTabIndex) getInsertOrEditNumber()?.let { AddToContactDialog(it) } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/NumberPickerDialog.kt b/app/src/main/java/com/bnyro/contacts/ui/components/NumberPickerDialog.kt index 287392d0..132622b0 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/NumberPickerDialog.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/NumberPickerDialog.kt @@ -21,22 +21,22 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R import com.bnyro.contacts.enums.SortOrder +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.dialogs.DialogButton import com.bnyro.contacts.ui.models.ContactsModel @Composable fun NumberPickerDialog( + contactsModel: ContactsModel, onDismissRequest: () -> Unit, - onNumberSelect: (number: String) -> Unit + onNumberSelect: (number: String, contactData: ContactData?) -> Unit, ) { - val contactsModel: ContactsModel = viewModel() - var numbersToSelectFrom by remember { - mutableStateOf(listOf()) + var numbersToSelectFrom: Pair> by remember { + mutableStateOf(null to listOf()) } AlertDialog( @@ -71,7 +71,7 @@ fun NumberPickerDialog( modifier = Modifier.padding(top = 3.dp), icon = Icons.Default.Send ) { - onNumberSelect.invoke(numberInput) + onNumberSelect.invoke(numberInput, null) } } Spacer(modifier = Modifier.height(10.dp)) @@ -83,11 +83,11 @@ fun NumberPickerDialog( selected = false, onSinglePress = { if (it.numbers.size > 1) { - numbersToSelectFrom = it.numbers.map { num -> num.value } + numbersToSelectFrom = it to it.numbers.map { num -> num.value } return@ContactItem true } - onNumberSelect.invoke(it.numbers.first().value) + onNumberSelect.invoke(it.numbers.first().value, it) onDismissRequest.invoke() true }, @@ -98,15 +98,15 @@ fun NumberPickerDialog( } ) - if (numbersToSelectFrom.isNotEmpty()) { + if (numbersToSelectFrom.second.isNotEmpty()) { AlertDialog( - onDismissRequest = { numbersToSelectFrom = emptyList() }, + onDismissRequest = { numbersToSelectFrom = null to emptyList() }, text = { LazyColumn { - items(numbersToSelectFrom) { + items(numbersToSelectFrom.second) { ClickableText(text = it) { - onNumberSelect.invoke(it) - numbersToSelectFrom = emptyList() + onNumberSelect.invoke(it, numbersToSelectFrom.first) + numbersToSelectFrom = null to emptyList() onDismissRequest.invoke() } } @@ -114,7 +114,7 @@ fun NumberPickerDialog( }, confirmButton = { DialogButton(stringResource(R.string.cancel)) { - numbersToSelectFrom = emptyList() + numbersToSelectFrom = null to emptyList() } } ) diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt b/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt index 89b5d6b4..1db135a9 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/SmsThreadItem.kt @@ -42,22 +42,20 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.bnyro.contacts.R +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.SmsThread import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.models.SmsModel -import com.bnyro.contacts.ui.screens.SmsThreadScreen @OptIn(ExperimentalMaterial3Api::class) @Composable fun SmsThreadItem( smsModel: SmsModel, - thread: SmsThread + thread: SmsThread, + onClick: (address: String, contactData: ContactData?) -> Unit ) { val context = LocalContext.current - var showThreadScreen by remember { - mutableStateOf(false) - } var showDeleteThreadDialog by remember { mutableStateOf(false) } @@ -83,7 +81,7 @@ fun SmsThreadItem( .fillMaxWidth() .clip(shape) .clickable { - showThreadScreen = true + onClick.invoke(thread.address, thread.contactData) }, shape = shape ) { @@ -152,12 +150,6 @@ fun SmsThreadItem( directions = setOf(DismissDirection.StartToEnd) ) - if (showThreadScreen) { - SmsThreadScreen(smsModel, thread.contactData, thread.address) { - showThreadScreen = false - } - } - if (showDeleteThreadDialog) { ConfirmationDialog( onDismissRequest = { showDeleteThreadDialog = false }, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt b/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt index 5abfcd39..23649415 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/TopBarMoreMenu.kt @@ -7,26 +7,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.res.stringResource import com.bnyro.contacts.R import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.base.OptionMenu -import com.bnyro.contacts.ui.screens.AboutScreen -import com.bnyro.contacts.ui.screens.SettingsScreen @Composable fun TopBarMoreMenu( - extraOptions: List = emptyList(), - onExtraOptionClick: (Int) -> Unit = {} + options: List = emptyList(), + onOptionClick: (Int) -> Unit = {} ) { - var showSettings by remember { - mutableStateOf(false) - } - - var showAbout by remember { - mutableStateOf(false) - } - var expandedOptions by remember { mutableStateOf(false) } @@ -38,10 +27,6 @@ fun TopBarMoreMenu( expandedOptions = !expandedOptions } - val options = extraOptions + listOf( - stringResource(R.string.settings), - stringResource(R.string.about) - ) OptionMenu( expanded = expandedOptions, options = options, @@ -49,24 +34,8 @@ fun TopBarMoreMenu( expandedOptions = false }, onSelect = { - when (it) { - options.size - 2 -> showSettings = true - options.size - 1 -> showAbout = true - else -> onExtraOptionClick(it) - } + onOptionClick(it) expandedOptions = false } ) - - if (showSettings) { - SettingsScreen { - showSettings = false - } - } - - if (showAbout) { - AboutScreen { - showAbout = false - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt index 3f471754..0266ec13 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/models/SmsModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.bnyro.contacts.App +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.util.SmsUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted @@ -25,6 +26,8 @@ class SmsModel(val app: App) : ViewModel() { initialValue = listOf() ) + var currentContactData: ContactData? = null + var initialAddressAndBody by mutableStateOf?>(null) var currentSubscription: SubscriptionInfo? = null diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/AboutScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/AboutScreen.kt index f2aaa9ea..3e40657d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/AboutScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/AboutScreen.kt @@ -23,69 +23,68 @@ import com.bnyro.contacts.BuildConfig import com.bnyro.contacts.R import com.bnyro.contacts.ui.components.about.AboutRow import com.bnyro.contacts.ui.components.base.ClickableIcon -import com.bnyro.contacts.ui.components.base.FullScreenDialog @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AboutScreen(onDismissRequest: () -> Unit) { - FullScreenDialog(onClose = onDismissRequest) { - Scaffold( - topBar = { - TopAppBar( - title = { - Text(stringResource(R.string.about)) - }, - navigationIcon = { - ClickableIcon( - icon = Icons.Default.ArrowBack, - contentDescription = R.string.okay - ) { - onDismissRequest.invoke() - } +fun AboutScreen(onBackPress: () -> Unit) { + Scaffold( + topBar = { + TopAppBar( + title = { + Text(stringResource(R.string.about)) + }, + navigationIcon = { + ClickableIcon( + icon = Icons.Default.ArrowBack, + contentDescription = R.string.okay + ) { + onBackPress.invoke() } - ) - } - ) { pV -> - Column( - modifier = Modifier.padding(pV), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - modifier = Modifier - .fillMaxWidth() - .height(210.dp), - painter = painterResource(R.drawable.ic_launcher_foreground), - contentDescription = null - ) - Divider( - modifier = Modifier.fillMaxWidth() - .height(2.dp) - ) - Spacer(Modifier.height(10.dp)) - AboutRow( - title = stringResource(R.string.version), - summary = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" - ) - AboutRow( - title = "GitHub", - url = "https://github.com/you-apps/ConnectYou/" - ) - AboutRow( - title = stringResource(R.string.author), - summary = "You Apps", - url = "https://github.com/you-apps/" - ) - AboutRow( - title = stringResource(R.string.translation), - summary = "Weblate", - url = "https://hosted.weblate.org/projects/you-apps/connect-you/" - ) - AboutRow( - title = stringResource(R.string.license), - summary = "GPL-3.0", - url = "https://www.gnu.org/licenses/gpl-3.0.html" - ) - } + } + ) + } + ) { pV -> + Column( + modifier = Modifier.padding(pV), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + modifier = Modifier + .fillMaxWidth() + .height(210.dp), + painter = painterResource(R.drawable.ic_launcher_foreground), + contentDescription = null + ) + Divider( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + ) + Spacer(Modifier.height(10.dp)) + AboutRow( + title = stringResource(R.string.version), + summary = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + ) + AboutRow( + title = "GitHub", + url = "https://github.com/you-apps/ConnectYou/" + ) + AboutRow( + title = stringResource(R.string.author), + summary = "You Apps", + url = "https://github.com/you-apps/" + ) + AboutRow( + title = stringResource(R.string.translation), + summary = "Weblate", + url = "https://hosted.weblate.org/projects/you-apps/connect-you/" + ) + AboutRow( + title = stringResource(R.string.license), + summary = "GPL-3.0", + url = "https://www.gnu.org/licenses/gpl-3.0.html" + ) } } + } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 4074fbe3..11d7b9e4 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R import com.bnyro.contacts.enums.ContactsSource +import com.bnyro.contacts.nav.NavRoutes import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.FilterOptions import com.bnyro.contacts.ui.components.ContactsList @@ -64,7 +65,8 @@ import com.bnyro.contacts.util.Preferences @OptIn(ExperimentalMaterial3Api::class) @Composable fun ContactsPage( - scrollConnection: NestedScrollConnection? + scrollConnection: NestedScrollConnection?, + onNavigate: (NavRoutes) -> Unit ) { val viewModel: ContactsModel = viewModel(factory = ContactsModel.Factory) val context = LocalContext.current @@ -194,12 +196,14 @@ fun ContactsPage( showFilterDialog = true } TopBarMoreMenu( - extraOptions = listOf( + options = listOf( stringResource(R.string.import_vcf), stringResource(R.string.export_vcf), - stringResource(R.string.import_sim) + stringResource(R.string.import_sim), + stringResource(R.string.settings), + stringResource(R.string.about) ), - onExtraOptionClick = { index -> + onOptionClick = { index -> when (index) { 0 -> { importVcard.launch(BackupHelper.openMimeTypes) @@ -212,6 +216,14 @@ fun ContactsPage( 2 -> { showImportSimDialog = true } + + 3 -> { + onNavigate.invoke(NavRoutes.Settings) + } + + 4 -> { + onNavigate.invoke(NavRoutes.About) + } } } ) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt index 37db326f..57f62766 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt @@ -1,130 +1,130 @@ package com.bnyro.contacts.ui.screens - -import android.annotation.SuppressLint -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Message -import androidx.compose.material.icons.rounded.People -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import com.bnyro.contacts.R -import com.bnyro.contacts.obj.NavBarItem -import com.bnyro.contacts.ui.models.ContactsModel -import com.bnyro.contacts.ui.models.SmsModel -import com.bnyro.contacts.ui.models.ThemeModel -import com.bnyro.contacts.util.Preferences - -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") -@Composable -fun MainAppContent(smsModel: SmsModel) { - val themeModel: ThemeModel = viewModel() - val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) - - val bottomBarHeight = 80.dp - val bottomBarHeightPx = LocalDensity.current.run { bottomBarHeight.toPx() } - var bottomBarOffset by remember { mutableFloatStateOf(0f) } - - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val newOffset = bottomBarOffset + available.y - bottomBarOffset = newOffset.coerceIn(-bottomBarHeightPx, 0f) - return Offset.Zero - } - } - } - - val navItems = listOf( - NavBarItem( - stringResource(R.string.contacts), - Icons.Rounded.People - ), - NavBarItem( - stringResource(R.string.messages), - Icons.Rounded.Message - ) - ) - - var currentPage by remember { - mutableIntStateOf( - smsModel.initialAddressAndBody?.let { 1 } ?: Preferences.getInt( - Preferences.homeTabKey, - 0 - ) - ) - } - - Scaffold( - bottomBar = { - NavigationBar( - modifier = if (themeModel.collapsableBottomBar) { - Modifier - .clipToBounds() - .height( - LocalDensity.current.run { - (bottomBarHeightPx + bottomBarOffset).toDp() - } - ) - } else { - Modifier - }, - tonalElevation = 10.dp - ) { - navItems.forEachIndexed { index, navItem -> - NavigationBarItem( - selected = (index == currentPage), - onClick = { - currentPage = index - }, - icon = { - Icon(navItem.icon, null) - }, - label = { - Text(navItem.label) - } - ) - } - } - } - ) { pV -> - Surface( - modifier = Modifier - .fillMaxSize() - .padding(pV), - color = MaterialTheme.colorScheme.background - ) { - Crossfade(targetState = currentPage, label = "crossfade pager") { index -> - val scrollConnectionIfEnabled = nestedScrollConnection.takeIf { themeModel.collapsableBottomBar } - - when (index) { - 0 -> ContactsPage(scrollConnectionIfEnabled) - - 1 -> SmsListScreen(smsModel, contactsModel, scrollConnectionIfEnabled) - } - } - } - } -} +// +//import android.annotation.SuppressLint +//import androidx.compose.animation.Crossfade +//import androidx.compose.foundation.layout.fillMaxSize +//import androidx.compose.foundation.layout.height +//import androidx.compose.foundation.layout.padding +//import androidx.compose.material.icons.Icons +//import androidx.compose.material.icons.rounded.Message +//import androidx.compose.material.icons.rounded.People +//import androidx.compose.material3.Icon +//import androidx.compose.material3.MaterialTheme +//import androidx.compose.material3.NavigationBar +//import androidx.compose.material3.NavigationBarItem +//import androidx.compose.material3.Scaffold +//import androidx.compose.material3.Surface +//import androidx.compose.material3.Text +//import androidx.compose.runtime.Composable +//import androidx.compose.runtime.getValue +//import androidx.compose.runtime.mutableFloatStateOf +//import androidx.compose.runtime.mutableIntStateOf +//import androidx.compose.runtime.remember +//import androidx.compose.runtime.setValue +//import androidx.compose.ui.Modifier +//import androidx.compose.ui.draw.clipToBounds +//import androidx.compose.ui.geometry.Offset +//import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +//import androidx.compose.ui.input.nestedscroll.NestedScrollSource +//import androidx.compose.ui.platform.LocalDensity +//import androidx.compose.ui.res.stringResource +//import androidx.compose.ui.unit.dp +//import androidx.lifecycle.viewmodel.compose.viewModel +//import com.bnyro.contacts.R +//import com.bnyro.contacts.obj.NavBarItem +//import com.bnyro.contacts.ui.models.ContactsModel +//import com.bnyro.contacts.ui.models.SmsModel +//import com.bnyro.contacts.ui.models.ThemeModel +//import com.bnyro.contacts.util.Preferences +// +//@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +//@Composable +//fun MainAppContent(smsModel: SmsModel) { +// val themeModel: ThemeModel = viewModel() +// val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) +// +// val bottomBarHeight = 80.dp +// val bottomBarHeightPx = LocalDensity.current.run { bottomBarHeight.toPx() } +// var bottomBarOffset by remember { mutableFloatStateOf(0f) } +// +// val nestedScrollConnection = remember { +// object : NestedScrollConnection { +// override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { +// val newOffset = bottomBarOffset + available.y +// bottomBarOffset = newOffset.coerceIn(-bottomBarHeightPx, 0f) +// return Offset.Zero +// } +// } +// } +// +// val navItems = listOf( +// NavBarItem( +// stringResource(R.string.contacts), +// Icons.Rounded.People +// ), +// NavBarItem( +// stringResource(R.string.messages), +// Icons.Rounded.Message +// ) +// ) +// +// var currentPage by remember { +// mutableIntStateOf( +// smsModel.initialAddressAndBody?.let { 1 } ?: Preferences.getInt( +// Preferences.homeTabKey, +// 0 +// ) +// ) +// } +// +// Scaffold( +// bottomBar = { +// NavigationBar( +// modifier = if (themeModel.collapsableBottomBar) { +// Modifier +// .clipToBounds() +// .height( +// LocalDensity.current.run { +// (bottomBarHeightPx + bottomBarOffset).toDp() +// } +// ) +// } else { +// Modifier +// }, +// tonalElevation = 10.dp +// ) { +// navItems.forEachIndexed { index, navItem -> +// NavigationBarItem( +// selected = (index == currentPage), +// onClick = { +// currentPage = index +// }, +// icon = { +// Icon(navItem.icon, null) +// }, +// label = { +// Text(navItem.label) +// } +// ) +// } +// } +// } +// ) { pV -> +// Surface( +// modifier = Modifier +// .fillMaxSize() +// .padding(pV), +// color = MaterialTheme.colorScheme.background +// ) { +// Crossfade(targetState = currentPage, label = "crossfade pager") { index -> +// val scrollConnectionIfEnabled = nestedScrollConnection.takeIf { themeModel.collapsableBottomBar } +// +// when (index) { +// 0 -> ContactsPage(scrollConnectionIfEnabled) +// +// 1 -> SmsListScreen(scrollConnectionIfEnabled) +// } +// } +// } +// } +//} diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt index 1f8481e7..7f3d605d 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt @@ -19,11 +19,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R import com.bnyro.contacts.enums.ThemeMode import com.bnyro.contacts.ui.components.base.ClickableIcon -import com.bnyro.contacts.ui.components.base.FullScreenDialog import com.bnyro.contacts.ui.components.prefs.AutoBackupPref import com.bnyro.contacts.ui.components.prefs.BlockPreference import com.bnyro.contacts.ui.components.prefs.EncryptBackupsPref @@ -35,95 +33,96 @@ import com.bnyro.contacts.util.Preferences @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsScreen(onDismissRequest: () -> Unit) { - val themeModel: ThemeModel = viewModel() - val smsModel: SmsModel = viewModel() +fun SettingsScreen(themeModel: ThemeModel, smsModel: SmsModel, onBackPress: () -> Unit) { - FullScreenDialog(onClose = onDismissRequest) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( - rememberTopAppBarState() - ) + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + rememberTopAppBarState() + ) - Scaffold( - modifier = Modifier.nestedScroll((scrollBehavior.nestedScrollConnection)), - topBar = { - LargeTopAppBar( - title = { - Text(stringResource(R.string.settings)) - }, - navigationIcon = { - ClickableIcon( - icon = Icons.Default.ArrowBack, - contentDescription = R.string.okay - ) { - onDismissRequest.invoke() - } - }, - scrollBehavior = scrollBehavior - ) - } - ) { pV -> - val scrollState = rememberScrollState() - - Column( - modifier = Modifier - .padding(pV) - .padding(horizontal = 16.dp) - .verticalScroll(scrollState) - ) { - SettingsCategory(title = stringResource(R.string.appearance)) - Text(stringResource(R.string.theme)) - BlockPreference( - preferenceKey = Preferences.themeKey, - entries = listOf(R.string.system, R.string.light, R.string.dark, R.string.amoled).map { - stringResource(it) + Scaffold( + modifier = Modifier.nestedScroll((scrollBehavior.nestedScrollConnection)), + topBar = { + LargeTopAppBar( + title = { + Text(stringResource(R.string.settings)) + }, + navigationIcon = { + ClickableIcon( + icon = Icons.Default.ArrowBack, + contentDescription = R.string.okay + ) { + onBackPress.invoke() } - ) { - themeModel.themeMode = ThemeMode.fromInt(it) - } - SwitchPref( - prefKey = Preferences.colorfulContactIconsKey, - title = stringResource(R.string.colorful_contact_icons) - ) { - themeModel.colorfulIcons = it - } - Divider( - modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), - color = MaterialTheme.colorScheme.surfaceVariant - ) - SettingsCategory(title = stringResource(R.string.messages)) - SwitchPref( - prefKey = Preferences.storeSmsLocallyKey, - title = stringResource(R.string.private_sms_database), - summary = stringResource(R.string.private_sms_database_desc) - ) { - smsModel.refreshLocalSmsPreference() + }, + scrollBehavior = scrollBehavior + ) + } + ) { pV -> + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .padding(pV) + .padding(horizontal = 16.dp) + .verticalScroll(scrollState) + ) { + SettingsCategory(title = stringResource(R.string.appearance)) + Text(stringResource(R.string.theme)) + BlockPreference( + preferenceKey = Preferences.themeKey, + entries = listOf( + R.string.system, + R.string.light, + R.string.dark, + R.string.amoled + ).map { + stringResource(it) } - Divider( - modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), - color = MaterialTheme.colorScheme.surfaceVariant - ) - SettingsCategory(title = stringResource(R.string.behavior)) - Text(stringResource(R.string.start_tab)) - BlockPreference( - preferenceKey = Preferences.homeTabKey, - entries = listOf(R.string.contacts, R.string.messages).map { - stringResource(it) - } - ) - SwitchPref( - prefKey = Preferences.collapseBottomBarKey, - title = stringResource(R.string.collapse_bottom_bar) - ) { - themeModel.collapsableBottomBar = it + ) { + themeModel.themeMode = ThemeMode.fromInt(it) + } + SwitchPref( + prefKey = Preferences.colorfulContactIconsKey, + title = stringResource(R.string.colorful_contact_icons) + ) { + themeModel.colorfulIcons = it + } + Divider( + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.surfaceVariant + ) + SettingsCategory(title = stringResource(R.string.messages)) + SwitchPref( + prefKey = Preferences.storeSmsLocallyKey, + title = stringResource(R.string.private_sms_database), + summary = stringResource(R.string.private_sms_database_desc) + ) { + smsModel.refreshLocalSmsPreference() + } + Divider( + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.surfaceVariant + ) + SettingsCategory(title = stringResource(R.string.behavior)) + Text(stringResource(R.string.start_tab)) + BlockPreference( + preferenceKey = Preferences.homeTabKey, + entries = listOf(R.string.contacts, R.string.messages).map { + stringResource(it) } - Divider( - modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), - color = MaterialTheme.colorScheme.surfaceVariant - ) - AutoBackupPref() - EncryptBackupsPref() + ) + SwitchPref( + prefKey = Preferences.collapseBottomBarKey, + title = stringResource(R.string.collapse_bottom_bar) + ) { + themeModel.collapsableBottomBar = it } + Divider( + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp), + color = MaterialTheme.colorScheme.surfaceVariant + ) + AutoBackupPref() + EncryptBackupsPref() } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt index 8d89e738..317ad026 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsListScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,6 +26,8 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import com.bnyro.contacts.R +import com.bnyro.contacts.nav.NavRoutes +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.SmsThread import com.bnyro.contacts.ui.components.NothingHere import com.bnyro.contacts.ui.components.NumberPickerDialog @@ -39,20 +42,22 @@ import com.bnyro.contacts.ui.models.SmsModel fun SmsListScreen( smsModel: SmsModel, contactsModel: ContactsModel, - scrollConnection: NestedScrollConnection? + scrollConnection: NestedScrollConnection?, + onNavigate: (NavRoutes) -> Unit, + onClickMessage: (address: String, contactData: ContactData?) -> Unit ) { var showNumberPicker by remember { mutableStateOf(false) } - - var smsAddress by remember { - mutableStateOf(null) - } - var showSearch by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + smsModel.initialAddressAndBody?.let { + onClickMessage(it.first, null) + } + } Scaffold( topBar = { TopAppBar( @@ -66,7 +71,21 @@ fun SmsListScreen( ) { showSearch = true } - TopBarMoreMenu() + TopBarMoreMenu(options = listOf( + stringResource(R.string.settings), + stringResource(R.string.about) + ), + onOptionClick = { index -> + when (index) { + 0 -> { + onNavigate.invoke(NavRoutes.Settings) + } + + 1 -> { + onNavigate.invoke(NavRoutes.About) + } + } + }) } ) }, @@ -103,13 +122,11 @@ fun SmsListScreen( } ) { items(threadList) { thread -> - SmsThreadItem(smsModel, thread) + SmsThreadItem(smsModel, thread, onClick = onClickMessage) } } if (showSearch) { - SmsSearchScreen(smsModel, threadList) { - showSearch = false - } + SmsSearchScreen(smsModel, threadList, { showSearch = false }, onClickMessage) } } else { Column(Modifier.padding(pv)) { @@ -119,25 +136,10 @@ fun SmsListScreen( if (showNumberPicker) { NumberPickerDialog( + contactsModel, onDismissRequest = { showNumberPicker = false }, - onNumberSelect = { - smsAddress = it - } + onNumberSelect = onClickMessage ) } - - smsAddress?.let { - val contactData = contactsModel.getContactByNumber(it) - SmsThreadScreen(smsModel, contactData, it) { - smsAddress = null - } - } - - smsModel.initialAddressAndBody?.let { - val contactData = contactsModel.getContactByNumber(it.first) - SmsThreadScreen(smsModel, contactData, it.first) { - smsModel.initialAddressAndBody = null - } - } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt index 4201bdf0..e2e97fed 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsSearchScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.bnyro.contacts.R +import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.SmsThread import com.bnyro.contacts.ui.components.SmsThreadItem import com.bnyro.contacts.ui.components.base.ElevatedTextInputField @@ -34,7 +35,8 @@ import kotlinx.coroutines.withContext fun SmsSearchScreen( smsModel: SmsModel, threadList: List, - onDismissRequest: () -> Unit + onDismissRequest: () -> Unit, + onClickMessage: (address: String, contactData: ContactData?) -> Unit ) { FullScreenDialog(onDismissRequest) { val focusRequester = remember { @@ -84,7 +86,7 @@ fun SmsSearchScreen( .fillMaxSize() ) { items(visibleThreads) { thread -> - SmsThreadItem(smsModel, thread) + SmsThreadItem(smsModel, thread, onClickMessage) } } } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt index 04e66684..de664b24 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SmsThreadScreen.kt @@ -43,10 +43,10 @@ import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.base.ClickableIcon import com.bnyro.contacts.ui.components.base.ElevatedTextInputField -import com.bnyro.contacts.ui.components.base.FullScreenDialog import com.bnyro.contacts.ui.components.conversation.Messages import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog +import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.ui.models.SmsModel import com.bnyro.contacts.util.SmsUtil @@ -55,9 +55,10 @@ import com.bnyro.contacts.util.SmsUtil @Composable fun SmsThreadScreen( smsModel: SmsModel, - contactData: ContactData?, + contactsModel: ContactsModel, address: String, initialText: String = "", + contactsData: ContactData? = null, onClose: () -> Unit ) { val context = LocalContext.current @@ -72,6 +73,10 @@ fun SmsThreadScreen( } } + val contactData = remember { + contactsData ?: contactsModel.getContactByNumber(address) + } + var showContactScreen by remember { mutableStateOf(false) } @@ -79,125 +84,123 @@ fun SmsThreadScreen( mutableStateOf(false) } - FullScreenDialog(onClose = onClose) { - Scaffold( - topBar = { - TopAppBar( - title = { - val interactionSource = remember { - MutableInteractionSource() - } - PlainTooltipBox( - tooltip = { Text(address) } - ) { - Text( - modifier = Modifier - .tooltipTrigger() - .clickable(interactionSource, null) { - if (contactData != null) showContactScreen = true - }, - text = contactData?.displayName ?: address - ) - } - }, - navigationIcon = { - ClickableIcon( - icon = Icons.Default.ArrowBack, - contentDescription = R.string.okay - ) { - onClose.invoke() - } - }, - actions = { - if (contactData == null) { - ClickableIcon(icon = Icons.Default.PersonAddAlt1) { - showAddToContactDialog = true - } - } + Scaffold( + topBar = { + TopAppBar( + title = { + val interactionSource = remember { + MutableInteractionSource() } - ) - } - ) { pV -> - Column( - modifier = Modifier - .padding(pV) - ) { - val state = rememberLazyListState() - Messages(messages = smsList, scrollState = state, smsModel = smsModel) - - Spacer(modifier = Modifier.height(10.dp)) - if (subscriptions != null && subscriptions.size >= 2) { - var currentSub by remember { mutableIntStateOf(0) } - LaunchedEffect(Unit) { - currentSub = 0 - smsModel.currentSubscription = subscriptions[currentSub] + PlainTooltipBox( + tooltip = { Text(address) } + ) { + Text( + modifier = Modifier + .tooltipTrigger() + .clickable(interactionSource, null) { + if (contactData != null) showContactScreen = true + }, + text = contactData?.displayName ?: address + ) + } + }, + navigationIcon = { + ClickableIcon( + icon = Icons.Default.ArrowBack, + contentDescription = R.string.okay + ) { + onClose.invoke() } - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - OutlinedButton(onClick = { - currentSub = if (currentSub == 0) 1 else 0 - smsModel.currentSubscription = subscriptions[currentSub] - }) { - Text( - text = "SIM ${subscriptions[currentSub].simSlotIndex + 1} - ${subscriptions[currentSub].displayName}" - ) + }, + actions = { + if (contactData == null) { + ClickableIcon(icon = Icons.Default.PersonAddAlt1) { + showAddToContactDialog = true } } } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 10.dp, end = 5.dp, bottom = 20.dp), - verticalAlignment = Alignment.CenterVertically - ) { - var showConfirmSendMultipleSmsDialog by remember { - mutableStateOf(false) + ) + } + ) { pV -> + Column( + modifier = Modifier + .padding(pV) + ) { + val state = rememberLazyListState() + Messages(messages = smsList, scrollState = state, smsModel = smsModel) + + Spacer(modifier = Modifier.height(10.dp)) + if (subscriptions != null && subscriptions.size >= 2) { + var currentSub by remember { mutableIntStateOf(0) } + LaunchedEffect(Unit) { + currentSub = 0 + smsModel.currentSubscription = subscriptions[currentSub] + } + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + OutlinedButton(onClick = { + currentSub = if (currentSub == 0) 1 else 0 + smsModel.currentSubscription = subscriptions[currentSub] + }) { + Text( + text = "SIM ${subscriptions[currentSub].simSlotIndex + 1} - ${subscriptions[currentSub].displayName}" + ) } + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 10.dp, end = 5.dp, bottom = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + var showConfirmSendMultipleSmsDialog by remember { + mutableStateOf(false) + } - var text by remember { - mutableStateOf(initialText) - } + var text by remember { + mutableStateOf(initialText) + } - ElevatedTextInputField( - modifier = Modifier.weight(1f), - query = text, - onQueryChange = { text = it }, - placeholder = stringResource(R.string.send) - ) + ElevatedTextInputField( + modifier = Modifier.weight(1f), + query = text, + onQueryChange = { text = it }, + placeholder = stringResource(R.string.send) + ) - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(8.dp)) - FilledIconButton( - modifier = Modifier.size(48.dp), - onClick = { - if (text.isBlank()) return@FilledIconButton - if (!SmsUtil.isShortEnoughForSms(text)) { - showConfirmSendMultipleSmsDialog = true - return@FilledIconButton - } + FilledIconButton( + modifier = Modifier.size(48.dp), + onClick = { + if (text.isBlank()) return@FilledIconButton + if (!SmsUtil.isShortEnoughForSms(text)) { + showConfirmSendMultipleSmsDialog = true + return@FilledIconButton + } - smsModel.sendSms(context, address, text) + smsModel.sendSms(context, address, text) - text = "" - } - ) { - Icon( - imageVector = Icons.Default.Send, - contentDescription = stringResource(R.string.send) - ) + text = "" } + ) { + Icon( + imageVector = Icons.Default.Send, + contentDescription = stringResource(R.string.send) + ) + } - if (showConfirmSendMultipleSmsDialog) { - ConfirmationDialog( - onDismissRequest = { showConfirmSendMultipleSmsDialog = false }, - title = stringResource(R.string.message_too_long), - text = stringResource(R.string.send_message_as_multiple) - ) { - SmsUtil.splitSmsText(text).forEach { - smsModel.sendSms(context, address, it) - } - - text = "" + if (showConfirmSendMultipleSmsDialog) { + ConfirmationDialog( + onDismissRequest = { showConfirmSendMultipleSmsDialog = false }, + title = stringResource(R.string.message_too_long), + text = stringResource(R.string.send_message_as_multiple) + ) { + SmsUtil.splitSmsText(text).forEach { + smsModel.sendSms(context, address, it) } + + text = "" } } } From c2de4ae7c1a18f6dc8d9811d6a0bd64a4f672a2b Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Thu, 28 Mar 2024 21:49:58 +0530 Subject: [PATCH 131/139] fix: Messages writing box hidden under keyboard --- app/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 92ce6102..e6791615 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,8 @@ + android:exported="true" + android:windowSoftInputMode="adjustResize"> From b2e0f98f2f1e8bf8dafef4ec4dce5330d86c8602 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Fri, 29 Mar 2024 12:06:01 +0530 Subject: [PATCH 132/139] refactor: remove unused MainAppContent --- .../contacts/ui/screens/MainAppContent.kt | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt deleted file mode 100644 index 57f62766..00000000 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/MainAppContent.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.bnyro.contacts.ui.screens -// -//import android.annotation.SuppressLint -//import androidx.compose.animation.Crossfade -//import androidx.compose.foundation.layout.fillMaxSize -//import androidx.compose.foundation.layout.height -//import androidx.compose.foundation.layout.padding -//import androidx.compose.material.icons.Icons -//import androidx.compose.material.icons.rounded.Message -//import androidx.compose.material.icons.rounded.People -//import androidx.compose.material3.Icon -//import androidx.compose.material3.MaterialTheme -//import androidx.compose.material3.NavigationBar -//import androidx.compose.material3.NavigationBarItem -//import androidx.compose.material3.Scaffold -//import androidx.compose.material3.Surface -//import androidx.compose.material3.Text -//import androidx.compose.runtime.Composable -//import androidx.compose.runtime.getValue -//import androidx.compose.runtime.mutableFloatStateOf -//import androidx.compose.runtime.mutableIntStateOf -//import androidx.compose.runtime.remember -//import androidx.compose.runtime.setValue -//import androidx.compose.ui.Modifier -//import androidx.compose.ui.draw.clipToBounds -//import androidx.compose.ui.geometry.Offset -//import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -//import androidx.compose.ui.input.nestedscroll.NestedScrollSource -//import androidx.compose.ui.platform.LocalDensity -//import androidx.compose.ui.res.stringResource -//import androidx.compose.ui.unit.dp -//import androidx.lifecycle.viewmodel.compose.viewModel -//import com.bnyro.contacts.R -//import com.bnyro.contacts.obj.NavBarItem -//import com.bnyro.contacts.ui.models.ContactsModel -//import com.bnyro.contacts.ui.models.SmsModel -//import com.bnyro.contacts.ui.models.ThemeModel -//import com.bnyro.contacts.util.Preferences -// -//@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") -//@Composable -//fun MainAppContent(smsModel: SmsModel) { -// val themeModel: ThemeModel = viewModel() -// val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) -// -// val bottomBarHeight = 80.dp -// val bottomBarHeightPx = LocalDensity.current.run { bottomBarHeight.toPx() } -// var bottomBarOffset by remember { mutableFloatStateOf(0f) } -// -// val nestedScrollConnection = remember { -// object : NestedScrollConnection { -// override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { -// val newOffset = bottomBarOffset + available.y -// bottomBarOffset = newOffset.coerceIn(-bottomBarHeightPx, 0f) -// return Offset.Zero -// } -// } -// } -// -// val navItems = listOf( -// NavBarItem( -// stringResource(R.string.contacts), -// Icons.Rounded.People -// ), -// NavBarItem( -// stringResource(R.string.messages), -// Icons.Rounded.Message -// ) -// ) -// -// var currentPage by remember { -// mutableIntStateOf( -// smsModel.initialAddressAndBody?.let { 1 } ?: Preferences.getInt( -// Preferences.homeTabKey, -// 0 -// ) -// ) -// } -// -// Scaffold( -// bottomBar = { -// NavigationBar( -// modifier = if (themeModel.collapsableBottomBar) { -// Modifier -// .clipToBounds() -// .height( -// LocalDensity.current.run { -// (bottomBarHeightPx + bottomBarOffset).toDp() -// } -// ) -// } else { -// Modifier -// }, -// tonalElevation = 10.dp -// ) { -// navItems.forEachIndexed { index, navItem -> -// NavigationBarItem( -// selected = (index == currentPage), -// onClick = { -// currentPage = index -// }, -// icon = { -// Icon(navItem.icon, null) -// }, -// label = { -// Text(navItem.label) -// } -// ) -// } -// } -// } -// ) { pV -> -// Surface( -// modifier = Modifier -// .fillMaxSize() -// .padding(pV), -// color = MaterialTheme.colorScheme.background -// ) { -// Crossfade(targetState = currentPage, label = "crossfade pager") { index -> -// val scrollConnectionIfEnabled = nestedScrollConnection.takeIf { themeModel.collapsableBottomBar } -// -// when (index) { -// 0 -> ContactsPage(scrollConnectionIfEnabled) -// -// 1 -> SmsListScreen(scrollConnectionIfEnabled) -// } -// } -// } -// } -//} From c3c5e166939fc4d8a5ef987cfb4ce0ac1ed56cd1 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Fri, 29 Mar 2024 13:33:02 +0530 Subject: [PATCH 133/139] fix: contact picker (closes #265) --- .../ui/activities/PickContactActivity.kt | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/PickContactActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/PickContactActivity.kt index d6f44a6e..11715fca 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/PickContactActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/PickContactActivity.kt @@ -2,9 +2,14 @@ package com.bnyro.contacts.ui.activities import android.app.Activity import android.content.ContentUris +import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import android.provider.ContactsContract +import android.provider.ContactsContract.CommonDataKinds.Email +import android.provider.ContactsContract.CommonDataKinds.Phone +import android.provider.ContactsContract.Data import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -33,10 +38,18 @@ import com.bnyro.contacts.ui.theme.ConnectYouTheme class PickContactActivity : BaseActivity() { + private var specialMimeType: String? = null + @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + specialMimeType = when (intent.data) { + Email.CONTENT_URI -> Email.CONTENT_ITEM_TYPE + Phone.CONTENT_URI -> Phone.CONTENT_ITEM_TYPE + else -> null + } + setContent { ConnectYouTheme(themeModel.themeMode) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -94,14 +107,48 @@ class PickContactActivity : BaseActivity() { if (contact == null) { setResult(Activity.RESULT_CANCELED, Intent()) } else { - val intent = Intent().apply { - data = ContentUris.withAppendedId( + val uri = when { + specialMimeType != null -> { + val contactId = getContactMimeTypeId( + this, + contact.contactId.toString(), + specialMimeType!! + ) + Uri.withAppendedPath(Data.CONTENT_URI, contactId) + } + + else -> ContentUris.withAppendedId( ContactsContract.Contacts.CONTENT_URI, contact.contactId ) } + val intent = Intent().apply { + data = uri + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } setResult(Activity.RESULT_OK, intent) } finish() } + + private fun getContactMimeTypeId( + context: Context, + contactId: String, + mimeType: String + ): String { + val uri = Data.CONTENT_URI + val projection = arrayOf(Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE) + val selection = "${Data.MIMETYPE} = ? AND ${Data.RAW_CONTACT_ID} = ?" + val selectionArgs = arrayOf(mimeType, contactId) + + val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) + cursor?.use { + if (it.moveToFirst()) { + val index = it.getColumnIndex(Data._ID) + return it.getString(index) + } + } + return "" + } + } From 169ec43cbd429c1e07360a48b3d514b43a0f3152 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 1 Apr 2024 22:35:02 +0200 Subject: [PATCH 134/139] fix: support secondary and teritary fields when importing contact (addresses #383) --- .../contacts/ui/activities/MainActivity.kt | 30 +------------ .../com/bnyro/contacts/util/IntentHelper.kt | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt index e465ea2c..a459c5eb 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt @@ -16,6 +16,7 @@ import com.bnyro.contacts.ui.screens.MainAppContent import com.bnyro.contacts.ui.theme.ConnectYouTheme import com.bnyro.contacts.util.BackupHelper import com.bnyro.contacts.util.ContactsHelper +import com.bnyro.contacts.util.IntentHelper import java.net.URLDecoder class MainActivity : BaseActivity() { @@ -49,34 +50,7 @@ class MainActivity : BaseActivity() { private fun getInsertContactData(): ContactData? { return when { intent?.action == Intent.ACTION_INSERT -> { - val name = intent.getStringExtra(Intents.Insert.NAME) - ?: intent.getStringExtra(Intents.Insert.PHONETIC_NAME) - ContactData( - displayName = name, - firstName = name?.split(" ")?.firstOrNull(), - surName = name?.split(" ", limit = 2)?.lastOrNull(), - organization = intent.getStringExtra(Intents.Insert.COMPANY), - numbers = listOfNotNull( - intent.getStringExtra(Intents.Insert.PHONE)?.let { - ValueWithType(it, 0) - } - ), - emails = listOfNotNull( - intent.getStringExtra(Intents.Insert.EMAIL)?.let { - ValueWithType(it, 0) - } - ), - notes = listOfNotNull( - intent.getStringExtra(Intents.Insert.NOTES)?.let { - ValueWithType(it, 0) - } - ), - addresses = listOfNotNull( - intent.getStringExtra(Intents.Insert.POSTAL)?.let { - ValueWithType(it, 0) - } - ) - ) + IntentHelper.extractContactFromIntent(intent) } intent?.getStringExtra("action") == "create" -> ContactData() diff --git a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt index 2a6ee4ce..a5293602 100644 --- a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt @@ -8,6 +8,9 @@ import android.provider.ContactsContract import androidx.core.net.toUri import com.bnyro.contacts.R import com.bnyro.contacts.enums.IntentActionType +import com.bnyro.contacts.obj.ContactData +import com.bnyro.contacts.obj.TranslatedType +import com.bnyro.contacts.obj.ValueWithType object IntentHelper { fun launchAction(context: Context, type: IntentActionType, argument: String) { @@ -67,4 +70,43 @@ object IntentHelper { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) context.startActivity(intent) } + + fun extractContactFromIntent(intent: Intent): ContactData { + val name = intent.getStringExtra(ContactsContract.Intents.Insert.NAME) + ?: intent.getStringExtra(ContactsContract.Intents.Insert.PHONETIC_NAME) + + return ContactData( + displayName = name, + firstName = name?.split(" ")?.firstOrNull(), + surName = name?.split(" ", limit = 2)?.lastOrNull(), + organization = intent.getStringExtra(ContactsContract.Intents.Insert.COMPANY), + numbers = extractIntentValue(intent, ContactsContract.Intents.Insert.PHONE, ContactsContract.Intents.Insert.PHONE_TYPE) + + extractIntentValue(intent, ContactsContract.Intents.Insert.SECONDARY_PHONE, ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE) + + extractIntentValue(intent, ContactsContract.Intents.Insert.TERTIARY_PHONE, ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE), + emails = extractIntentValue(intent, ContactsContract.Intents.Insert.EMAIL, ContactsContract.Intents.Insert.EMAIL_TYPE) + + extractIntentValue(intent, ContactsContract.Intents.Insert.SECONDARY_EMAIL, ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE) + + extractIntentValue(intent, ContactsContract.Intents.Insert.TERTIARY_EMAIL, ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE), + notes = extractIntentValue(intent, ContactsContract.Intents.Insert.NOTES), + addresses = extractIntentValue(intent, ContactsContract.Intents.Insert.POSTAL) + ) + } + + private fun extractIntentValue( + intent: Intent, + key: String, + typeKey: String? = null, + types: List = emptyList() + ): List { + val entry = intent.getStringExtra(key) ?: return emptyList() + + val type = if (typeKey != null) { + val typeIdentifier = intent.getStringExtra(typeKey) + + types.firstOrNull { + it.vcardType?.value?.uppercase() == typeIdentifier + }?.id + } else null + + return listOf(ValueWithType(entry, type ?: 0)) + } } From c962ff87413aaa3e89f408f506f39041a15e2281 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 1 Apr 2024 22:49:58 +0200 Subject: [PATCH 135/139] feat: support for job titles (closes #383) --- .../com.bnyro.contacts.db.AppDatabase/5.json | 171 ++++++++++++++++++ .../java/com/bnyro/contacts/db/AppDatabase.kt | 8 +- .../com/bnyro/contacts/db/obj/LocalContact.kt | 1 + .../com/bnyro/contacts/obj/ContactData.kt | 1 + .../contacts/repo/DeviceContactsRepository.kt | 19 +- .../contacts/repo/LocalContactsRepository.kt | 2 + .../contacts/ui/components/ContactEditor.kt | 15 ++ .../contacts/ui/components/ShareDialog.kt | 3 + .../ui/screens/SingleContactScreen.kt | 5 + .../com/bnyro/contacts/util/IntentHelper.kt | 1 + 10 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 app/schemas/com.bnyro.contacts.db.AppDatabase/5.json diff --git a/app/schemas/com.bnyro.contacts.db.AppDatabase/5.json b/app/schemas/com.bnyro.contacts.db.AppDatabase/5.json new file mode 100644 index 00000000..e0aa2a74 --- /dev/null +++ b/app/schemas/com.bnyro.contacts.db.AppDatabase/5.json @@ -0,0 +1,171 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "d90f0abe705f87ae3f5682c2f4b7c8a8", + "entities": [ + { + "tableName": "localContacts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `displayName` TEXT, `firstName` TEXT, `surName` TEXT, `nickName` TEXT, `title` TEXT, `organization` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surName", + "columnName": "surName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "nickName", + "columnName": "nickName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "organization", + "columnName": "organization", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "valuableTypes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `contactId` INTEGER NOT NULL, `category` INTEGER NOT NULL, `value` TEXT NOT NULL, `type` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contactId", + "columnName": "contactId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "localSms", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `address` TEXT NOT NULL, `body` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `threadId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `simNumber` INTEGER DEFAULT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "body", + "columnName": "body", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "threadId", + "columnName": "threadId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "simNumber", + "columnName": "simNumber", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "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, 'd90f0abe705f87ae3f5682c2f4b7c8a8')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt b/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt index 52c4280f..b7dbf4cb 100644 --- a/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt +++ b/app/src/main/java/com/bnyro/contacts/db/AppDatabase.kt @@ -11,8 +11,12 @@ import com.bnyro.contacts.db.obj.SmsData @Database( entities = [LocalContact::class, DbDataItem::class, SmsData::class], - autoMigrations = [AutoMigration(2, 3), AutoMigration(3, 4)], - version = 4 + autoMigrations = [ + AutoMigration(2, 3), + AutoMigration(3, 4), + AutoMigration(4, 5) + ], + version = 5 ) abstract class AppDatabase : RoomDatabase() { abstract fun localContactsDao(): LocalContactsDao diff --git a/app/src/main/java/com/bnyro/contacts/db/obj/LocalContact.kt b/app/src/main/java/com/bnyro/contacts/db/obj/LocalContact.kt index cc764186..6b29a9d4 100644 --- a/app/src/main/java/com/bnyro/contacts/db/obj/LocalContact.kt +++ b/app/src/main/java/com/bnyro/contacts/db/obj/LocalContact.kt @@ -12,5 +12,6 @@ data class LocalContact( @ColumnInfo val firstName: String? = null, @ColumnInfo val surName: String? = null, @ColumnInfo val nickName: String? = null, + @ColumnInfo val title: String? = null, @ColumnInfo val organization: String? = null ) diff --git a/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt b/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt index 339f068f..dc45cdf6 100644 --- a/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt +++ b/app/src/main/java/com/bnyro/contacts/obj/ContactData.kt @@ -14,6 +14,7 @@ data class ContactData( var firstName: String? = null, var surName: String? = null, var nickName: String? = null, + var title: String? = null, var organization: String? = null, var photo: Bitmap? = null, var thumbnail: Bitmap? = null, diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt index 542cb2a4..cccdad20 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt @@ -50,7 +50,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor private val contentResolver = context.contentResolver private val contentUri = Data.CONTENT_URI - private val authority = ContactsContract.AUTHORITY + private val authority = AUTHORITY private val projection = arrayOf( Data.RAW_CONTACT_ID, @@ -60,6 +60,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor StructuredName.GIVEN_NAME, StructuredName.FAMILY_NAME, Nickname.NAME, + Organization.TITLE, Organization.COMPANY, RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_NAME @@ -131,6 +132,7 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor photo = getContactPhoto(contactId) ?: thumbnail groups = getGroups(contactId, storedContactGroups) nickName = getEntry(contactId, Nickname.CONTENT_ITEM_TYPE, Nickname.NAME) + title = getEntry(contactId, Organization.CONTENT_ITEM_TYPE, Organization.TITLE) organization = getEntry(contactId, Organization.CONTENT_ITEM_TYPE, Organization.COMPANY) events = getExtras( contactId, @@ -344,6 +346,13 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor it ) }, + contact.title?.let { + getInsertAction( + Organization.CONTENT_ITEM_TYPE, + Organization.TITLE, + it + ) + }, contact.organization?.let { getInsertAction( Organization.CONTENT_ITEM_TYPE, @@ -443,6 +452,14 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor contact.nickName ) ) + operations.addAll( + getUpdateSingleAction( + rawContactId, + Organization.CONTENT_ITEM_TYPE, + Organization.TITLE, + contact.title + ) + ) operations.addAll( getUpdateSingleAction( rawContactId, diff --git a/app/src/main/java/com/bnyro/contacts/repo/LocalContactsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/LocalContactsRepository.kt index 685d1a41..d61006fa 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/LocalContactsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/LocalContactsRepository.kt @@ -32,6 +32,7 @@ class LocalContactsRepository(context: Context) : ContactsRepository { firstName = contact.firstName, surName = contact.surName, nickName = contact.nickName, + title = contact.title, organization = contact.organization ) val contactId = DatabaseHolder.Db.localContactsDao().insertContact(localContact) @@ -86,6 +87,7 @@ class LocalContactsRepository(context: Context) : ContactsRepository { firstName = it.contact.firstName, surName = it.contact.surName, nickName = it.contact.nickName, + title = it.contact.title, organization = it.contact.organization, photo = profileImage, thumbnail = profileImage, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index e8da8e1d..79f512b6 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -119,6 +119,10 @@ fun ContactEditor( mutableStateOf(contact?.nickName.orEmpty()) } + val title = remember { + mutableStateOf(contact?.title.orEmpty()) + } + val organization = remember { mutableStateOf(contact?.organization.orEmpty()) } @@ -186,6 +190,7 @@ fun ContactEditor( it.surName = surName.value.trim() it.nickName = nickName.value.takeIf { n -> n.isNotBlank() }?.trim() it.organization = organization.value.takeIf { o -> o.isNotBlank() }?.trim() + it.title = title.value.takeIf { o -> o.isNotBlank() }?.trim() it.displayName = "${firstName.value.trim()} ${surName.value.trim()}" it.photo = profilePicture it.accountType = selectedAccount.type @@ -341,6 +346,16 @@ fun ContactEditor( ) } ) + LabeledTextField( + label = R.string.title, + state = title, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.Cases, + contentDescription = null + ) + } + ) TextFieldGroup( items = websites, label = R.string.website, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt index 8213a9ce..9907bcf2 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt @@ -30,6 +30,7 @@ fun ShareDialog( val shareName = remember { mutableStateOf(true) } val sharePhoto = remember { mutableStateOf(true) } val shareNickName = remember { mutableStateOf(true) } + val shareTitle = remember { mutableStateOf(true) } val shareOrganization = remember { mutableStateOf(true) } val shareWebsite = remember { mutableStateOf(true) } val sharePhone = remember { mutableStateOf(true) } @@ -40,6 +41,7 @@ fun ShareDialog( val options = listOf( R.string.name to shareName, R.string.photo to sharePhoto, + R.string.title to shareTitle, R.string.nick_name to shareNickName, R.string.organization to shareOrganization, R.string.website to shareWebsite, @@ -59,6 +61,7 @@ fun ShareDialog( } if (!sharePhoto.value) photo = null if (!shareNickName.value) nickName = null + if (!shareTitle.value) title = null if (!shareOrganization.value) organization = null if (!shareAddress.value) addresses = emptyList() if (!shareEmail.value) emails = emptyList() diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt index eec4abea..a5e2aede 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt @@ -219,6 +219,11 @@ fun SingleContactScreen(contact: ContactData, onClose: () -> Unit) { entries = listOfNotNull(contact.nickName) ) + ContactEntryTextGroup( + label = stringResource(R.string.title), + entries = listOfNotNull(contact.title) + ) + ContactEntryTextGroup( label = stringResource(R.string.organization), entries = listOfNotNull(contact.organization) diff --git a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt index a5293602..5106db99 100644 --- a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt @@ -80,6 +80,7 @@ object IntentHelper { firstName = name?.split(" ")?.firstOrNull(), surName = name?.split(" ", limit = 2)?.lastOrNull(), organization = intent.getStringExtra(ContactsContract.Intents.Insert.COMPANY), + title = intent.getStringExtra(ContactsContract.Intents.Insert.JOB_TITLE), numbers = extractIntentValue(intent, ContactsContract.Intents.Insert.PHONE, ContactsContract.Intents.Insert.PHONE_TYPE) + extractIntentValue(intent, ContactsContract.Intents.Insert.SECONDARY_PHONE, ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE) + extractIntentValue(intent, ContactsContract.Intents.Insert.TERTIARY_PHONE, ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE), From 6b216580a2329d3ac548a362c402183e90bf1be9 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Tue, 2 Apr 2024 15:32:07 +0200 Subject: [PATCH 136/139] refactor: simplify contacts logic and remove duplicated code --- .../bnyro/contacts/enums/ContactAttributes.kt | 164 ++++++++++++ .../contacts/repo/DeviceContactsRepository.kt | 246 ++++-------------- .../contacts/ui/components/ContactEditor.kt | 1 + .../contacts/ui/components/ShareDialog.kt | 85 +++--- .../contacts/ui/components/ShareOption.kt | 6 +- .../ui/screens/SingleContactScreen.kt | 3 +- .../com/bnyro/contacts/util/ContactsHelper.kt | 14 + .../com/bnyro/contacts/util/IntentHelper.kt | 33 ++- 8 files changed, 293 insertions(+), 259 deletions(-) create mode 100644 app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt diff --git a/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt b/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt new file mode 100644 index 00000000..6e3322dc --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt @@ -0,0 +1,164 @@ +package com.bnyro.contacts.enums + +import android.provider.ContactsContract +import android.provider.ContactsContract.Intents +import com.bnyro.contacts.R +import com.bnyro.contacts.obj.ContactData +import com.bnyro.contacts.obj.TranslatedType +import com.bnyro.contacts.obj.ValueWithType +import com.bnyro.contacts.util.ContactsHelper + +abstract class ContactAttribute(){ + abstract val stringRes: Int + abstract val androidValueColumn: String + abstract val androidContentType: String + abstract fun set(contact: ContactData, value: T) + abstract fun get(contact: ContactData): T +} + +abstract class StringAttribute : ContactAttribute() { + abstract val insertKey: String? +} + +abstract class ListAttribute: ContactAttribute>() { + abstract val androidTypeColumn: String + abstract val types: List + abstract val insertKeys: List> +} + +class Organization : StringAttribute() { + override val stringRes: Int = R.string.organization + override val insertKey: String = Intents.Insert.COMPANY + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Organization.COMPANY + override val androidContentType: String = ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + + override fun set(contact: ContactData, value: String?) { + contact.organization = value + } + + override fun get(contact: ContactData) = contact.organization +} + +class Title : StringAttribute() { + override val stringRes: Int = R.string.title + override val insertKey: String = Intents.Insert.JOB_TITLE + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Organization.TITLE + override val androidContentType: String = ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + + override fun set(contact: ContactData, value: String?) { + contact.organization = value + } + + override fun get(contact: ContactData) = contact.organization +} + +class Nickname : StringAttribute() { + override val stringRes: Int = R.string.nick_name + override val insertKey: String? = null + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Nickname.NAME + override val androidContentType: String = ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE + + override fun set(contact: ContactData, value: String?) { + contact.nickName = value + } + + override fun get(contact: ContactData) = contact.nickName +} + +class Events : ListAttribute() { + override val stringRes: Int = R.string.event + override val insertKeys: List> = emptyList() + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Event.START_DATE + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Event.TYPE + override val androidContentType: String = ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.events = value + } + + override fun get(contact: ContactData) = contact.events + + override val types: List = ContactsHelper.eventTypes +} + +class Numbers : ListAttribute() { + override val stringRes: Int = R.string.phone_number + override val insertKeys: List> = listOf( + Intents.Insert.PHONE to Intents.Insert.PHONE_TYPE, + Intents.Insert.SECONDARY_PHONE to Intents.Insert.SECONDARY_PHONE, + Intents.Insert.TERTIARY_EMAIL to Intents.Insert.TERTIARY_EMAIL_TYPE + ) + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Phone.NUMBER + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Phone.TYPE + override val androidContentType: String = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.numbers = value + } + + override fun get(contact: ContactData) = contact.numbers + + override val types: List = ContactsHelper.phoneNumberTypes +} + +class Emails : ListAttribute() { + override val stringRes: Int = R.string.email + override val insertKeys: List> = listOf( + Intents.Insert.EMAIL to Intents.Insert.EMAIL_TYPE, + Intents.Insert.SECONDARY_EMAIL to Intents.Insert.SECONDARY_EMAIL_TYPE, + Intents.Insert.TERTIARY_EMAIL to Intents.Insert.TERTIARY_EMAIL_TYPE + ) + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Email.ADDRESS + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Email.TYPE + override val androidContentType: String = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.emails = value + } + + override fun get(contact: ContactData) = contact.emails + + override val types: List = ContactsHelper.emailTypes +} + +class Addresses : ListAttribute() { + override val stringRes: Int = R.string.address + override val insertKeys: List> = listOf(Intents.Insert.POSTAL to null) + override val androidValueColumn: String = ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.StructuredPostal.TYPE + override val androidContentType: String = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.addresses = value + } + + override fun get(contact: ContactData) = contact.addresses + + override val types: List = ContactsHelper.addressTypes +} + +class Notes : ListAttribute() { + override val stringRes: Int = R.string.note + override val insertKeys: List> = listOf(Intents.Insert.NOTES to null) + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Note.NOTE + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Note.DATA2 + override val androidContentType: String = ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.notes = value + } + + override fun get(contact: ContactData) = contact.notes + + override val types: List = emptyList() +} + +class Websites : ListAttribute() { + override val stringRes: Int = R.string.website + override val insertKeys: List> = emptyList() + override val androidValueColumn: String = ContactsContract.CommonDataKinds.Website.URL + override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Website.TYPE + override val androidContentType: String = ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { + contact.websites = value + } + + override fun get(contact: ContactData) = contact.websites + + override val types: List = ContactsHelper.websiteTypes +} diff --git a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt index cccdad20..123b288f 100644 --- a/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/repo/DeviceContactsRepository.kt @@ -13,23 +13,20 @@ import android.net.Uri import android.provider.ContactsContract import android.provider.ContactsContract.AUTHORITY import android.provider.ContactsContract.CALLER_IS_SYNCADAPTER -import android.provider.ContactsContract.CommonDataKinds.Email -import android.provider.ContactsContract.CommonDataKinds.Event import android.provider.ContactsContract.CommonDataKinds.GroupMembership import android.provider.ContactsContract.CommonDataKinds.Nickname -import android.provider.ContactsContract.CommonDataKinds.Note import android.provider.ContactsContract.CommonDataKinds.Organization import android.provider.ContactsContract.CommonDataKinds.Phone import android.provider.ContactsContract.CommonDataKinds.Photo import android.provider.ContactsContract.CommonDataKinds.StructuredName -import android.provider.ContactsContract.CommonDataKinds.StructuredPostal -import android.provider.ContactsContract.CommonDataKinds.Website import android.provider.ContactsContract.Contacts import android.provider.ContactsContract.Data import android.provider.ContactsContract.RawContacts import androidx.annotation.RequiresPermission import com.bnyro.contacts.R import com.bnyro.contacts.enums.BackupType +import com.bnyro.contacts.enums.ListAttribute +import com.bnyro.contacts.enums.StringAttribute import com.bnyro.contacts.ext.intValue import com.bnyro.contacts.ext.longValue import com.bnyro.contacts.ext.notAName @@ -59,9 +56,6 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor Contacts.DISPLAY_NAME_ALTERNATIVE, StructuredName.GIVEN_NAME, StructuredName.FAMILY_NAME, - Nickname.NAME, - Organization.TITLE, - Organization.COMPANY, RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_NAME ) @@ -131,45 +125,21 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor thumbnail = getContactPhotoThumbnail(contactId) photo = getContactPhoto(contactId) ?: thumbnail groups = getGroups(contactId, storedContactGroups) - nickName = getEntry(contactId, Nickname.CONTENT_ITEM_TYPE, Nickname.NAME) - title = getEntry(contactId, Organization.CONTENT_ITEM_TYPE, Organization.TITLE) - organization = getEntry(contactId, Organization.CONTENT_ITEM_TYPE, Organization.COMPANY) - events = getExtras( - contactId, - Event.START_DATE, - Event.TYPE, - Event.CONTENT_ITEM_TYPE - ) - numbers = getExtras( - contactId, - Phone.NUMBER, - Phone.TYPE, - Phone.CONTENT_ITEM_TYPE - ) - emails = getExtras( - contactId, - Email.ADDRESS, - Email.TYPE, - Email.CONTENT_ITEM_TYPE - ) - addresses = getExtras( - contactId, - StructuredPostal.FORMATTED_ADDRESS, - StructuredPostal.TYPE, - StructuredPostal.CONTENT_ITEM_TYPE - ) - notes = getExtras( - contactId, - Note.NOTE, - Note.DATA2, - Note.CONTENT_ITEM_TYPE - ) - websites = getExtras( - contactId, - Website.URL, - Website.TYPE, - Website.CONTENT_ITEM_TYPE - ) + + ContactsHelper.contactAttributesTypes.forEach { attribute -> + if (attribute is StringAttribute) { + val dataStr = getEntry(contactId, attribute.androidContentType, attribute.androidValueColumn) + attribute.set(this, dataStr) + } else if (attribute is ListAttribute) { + val dataEntries = getExtras( + contactId, + attribute.androidValueColumn, + attribute.androidTypeColumn, + attribute.androidContentType + ) + attribute.set(this, dataEntries) + } + } } } @@ -339,89 +309,32 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor StructuredName.FAMILY_NAME, contact.surName.orEmpty() ), - contact.nickName?.let { - getInsertAction( - Nickname.CONTENT_ITEM_TYPE, - Nickname.NAME, - it - ) - }, - contact.title?.let { - getInsertAction( - Organization.CONTENT_ITEM_TYPE, - Organization.TITLE, - it - ) - }, - contact.organization?.let { - getInsertAction( - Organization.CONTENT_ITEM_TYPE, - Organization.COMPANY, - it - ) - }, contact.photo?.let { getInsertAction(Photo.CONTENT_ITEM_TYPE, Photo.PHOTO, getBitmapBytes(it)) }, - *contact.websites.map { - getInsertAction( - Website.CONTENT_ITEM_TYPE, - Website.URL, - it.value, - Website.TYPE, - it.type - ) - }.toTypedArray(), - *contact.numbers.map { - getInsertAction( - Phone.CONTENT_ITEM_TYPE, - Phone.NUMBER, - it.value, - Phone.TYPE, - it.type - ) - }.toTypedArray(), - *contact.emails.map { - getInsertAction( - Email.CONTENT_ITEM_TYPE, - Email.ADDRESS, - it.value, - Email.TYPE, - it.type - ) - }.toTypedArray(), - *contact.addresses.map { - getInsertAction( - StructuredPostal.CONTENT_ITEM_TYPE, - StructuredPostal.FORMATTED_ADDRESS, - it.value, - StructuredPostal.TYPE, - it.type - ) - }.toTypedArray(), - *contact.events.map { - getInsertAction( - Event.CONTENT_ITEM_TYPE, - Event.START_DATE, - it.value, - Event.TYPE, - it.type - ) - }.toTypedArray(), - *contact.notes.map { - getInsertAction( - Note.CONTENT_ITEM_TYPE, - Note.NOTE, - it.value - ) - }.toTypedArray(), *contact.groups.map { getInsertAction( GroupMembership.CONTENT_ITEM_TYPE, GroupMembership.GROUP_ROW_ID, it.rowId.toString() ) - }.toTypedArray() + }.toTypedArray(), + *ContactsHelper.contactAttributesTypes.filterIsInstance().map { attribute -> + attribute.get(contact)?.let { + getInsertAction(attribute.androidContentType, attribute.androidValueColumn, it) + } + }.toTypedArray(), + *ContactsHelper.contactAttributesTypes.filterIsInstance().map { attribute -> + attribute.get(contact).map { + getInsertAction( + attribute.androidContentType, + attribute.androidValueColumn, + it.value, + attribute.androidTypeColumn, + it.type + ) + } + }.flatten().toTypedArray() ).let { ArrayList(it) } contentResolver.applyBatch(AUTHORITY, ops) @@ -444,85 +357,20 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor operations.add(build()) } - operations.addAll( - getUpdateSingleAction( - rawContactId, - Nickname.CONTENT_ITEM_TYPE, - Nickname.NAME, - contact.nickName - ) - ) - operations.addAll( - getUpdateSingleAction( - rawContactId, - Organization.CONTENT_ITEM_TYPE, - Organization.TITLE, - contact.title - ) - ) - operations.addAll( - getUpdateSingleAction( - rawContactId, - Organization.CONTENT_ITEM_TYPE, - Organization.COMPANY, - contact.organization - ) - ) + for (attribute in ContactsHelper.contactAttributesTypes) { + if (attribute is StringAttribute) { + operations.addAll(getUpdateSingleAction( + rawContactId, attribute.androidContentType, + attribute.androidValueColumn, attribute.get(contact) + )) + } else if (attribute is ListAttribute) { + operations.addAll(getUpdateMultipleAction( + rawContactId, attribute.androidContentType, attribute.get(contact), + attribute.androidValueColumn, attribute.androidTypeColumn + )) + } + } - operations.addAll( - getUpdateMultipleAction( - rawContactId, - Website.CONTENT_ITEM_TYPE, - contact.websites, - Website.URL, - Website.TYPE - ) - ) - operations.addAll( - getUpdateMultipleAction( - rawContactId, - Phone.CONTENT_ITEM_TYPE, - contact.numbers, - Phone.NUMBER, - Phone.TYPE - ) - ) - operations.addAll( - getUpdateMultipleAction( - rawContactId, - Email.CONTENT_ITEM_TYPE, - contact.emails, - Email.ADDRESS, - Email.TYPE - ) - ) - operations.addAll( - getUpdateMultipleAction( - rawContactId, - StructuredPostal.CONTENT_ITEM_TYPE, - contact.addresses, - StructuredPostal.FORMATTED_ADDRESS, - StructuredPostal.TYPE - ) - ) - operations.addAll( - getUpdateMultipleAction( - rawContactId, - Event.CONTENT_ITEM_TYPE, - contact.events, - Event.START_DATE, - Event.TYPE - ) - ) - operations.addAll( - getUpdateMultipleAction( - rawContactId, - Note.CONTENT_ITEM_TYPE, - contact.notes, - Note.NOTE, - null - ) - ) operations.addAll( getUpdateMultipleAction( rawContactId, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt index 79f512b6..7c3afaf8 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEditor.kt @@ -359,6 +359,7 @@ fun ContactEditor( TextFieldGroup( items = websites, label = R.string.website, + types = ContactsHelper.websiteTypes, addActionText = R.string.add_website, keyboardType = KeyboardType.Uri, leadingIcon = Icons.Outlined.Web diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt index 9907bcf2..98c8073c 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ShareDialog.kt @@ -4,20 +4,23 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Row import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R +import com.bnyro.contacts.enums.ListAttribute +import com.bnyro.contacts.enums.StringAttribute import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.ui.components.dialogs.DialogButton import com.bnyro.contacts.ui.models.ContactsModel import com.bnyro.contacts.util.BackupHelper +import com.bnyro.contacts.util.ContactsHelper @Composable fun ShareDialog( @@ -27,48 +30,46 @@ fun ShareDialog( val contactsModel: ContactsModel = viewModel(factory = ContactsModel.Factory) val context = LocalContext.current - val shareName = remember { mutableStateOf(true) } - val sharePhoto = remember { mutableStateOf(true) } - val shareNickName = remember { mutableStateOf(true) } - val shareTitle = remember { mutableStateOf(true) } - val shareOrganization = remember { mutableStateOf(true) } - val shareWebsite = remember { mutableStateOf(true) } - val sharePhone = remember { mutableStateOf(true) } - val shareEmail = remember { mutableStateOf(true) } - val shareAddress = remember { mutableStateOf(true) } - val shareNote = remember { mutableStateOf(true) } + val baseOptions = remember { + listOf(R.string.name, R.string.photo) + } - val options = listOf( - R.string.name to shareName, - R.string.photo to sharePhoto, - R.string.title to shareTitle, - R.string.nick_name to shareNickName, - R.string.organization to shareOrganization, - R.string.website to shareWebsite, - R.string.phone to sharePhone, - R.string.email to shareEmail, - R.string.address to shareAddress, - R.string.note to shareNote - ) + val options = remember { + baseOptions + ContactsHelper.contactAttributesTypes.map { it.stringRes } + } + + val selected = remember { + SnapshotStateList().apply { + for (i in options.indices) add(true) + } + } fun getContactData(): ContactData { - return contact.copy().apply { - if (!shareName.value) { - displayName = null - alternativeName = null - firstName = null - surName = null + val data = ContactData() + + if (selected[0]) { + data.displayName = contact.displayName + data.alternativeName = contact.alternativeName + data.firstName = contact.firstName + data.surName = contact.surName + } + if (selected[1]) { + data.photo = contact.photo + } + + ContactsHelper.contactAttributesTypes.forEachIndexed { index, contactAttribute -> + if (selected[index + baseOptions.size]) { + if (contactAttribute is StringAttribute) { + val value = contactAttribute.get(contact) + contactAttribute.set(data, value) + } else if (contactAttribute is ListAttribute) { + val value = contactAttribute.get(contact) + contactAttribute.set(data, value) + } } - if (!sharePhoto.value) photo = null - if (!shareNickName.value) nickName = null - if (!shareTitle.value) title = null - if (!shareOrganization.value) organization = null - if (!shareAddress.value) addresses = emptyList() - if (!shareEmail.value) emails = emptyList() - if (!shareNote.value) notes = emptyList() - if (!sharePhone.value) numbers = emptyList() - if (!shareWebsite.value) websites = emptyList() } + + return data } val openFilePicker = rememberLauncherForActivityResult( @@ -86,8 +87,10 @@ fun ShareDialog( title = { Text(stringResource(R.string.share)) }, text = { LazyColumn { - items(options) { - ShareOption(title = it.first, isChecked = it.second) + itemsIndexed(options) { index, it -> + ShareOption(title = it, isChecked = selected[index]) { + selected[index] = it + } } } }, diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ShareOption.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ShareOption.kt index 2be4edcb..5524d4ec 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ShareOption.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ShareOption.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.Checkbox import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -17,13 +16,14 @@ import androidx.compose.ui.unit.dp @Composable fun ShareOption( @StringRes title: Int, - isChecked: MutableState + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { - Checkbox(checked = isChecked.value, onCheckedChange = { isChecked.value = it }) + Checkbox(checked = isChecked, onCheckedChange = onCheckedChange) Spacer(modifier = Modifier.width(10.dp)) Text(text = stringResource(title)) } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt index a5e2aede..6caa4960 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -66,7 +65,7 @@ import com.bnyro.contacts.util.CalendarUtils import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.IntentHelper -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SingleContactScreen(contact: ContactData, onClose: () -> Unit) { val viewModel: ContactsModel = viewModel() diff --git a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt index 6f0e6573..d177728f 100644 --- a/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/ContactsHelper.kt @@ -2,6 +2,15 @@ package com.bnyro.contacts.util import android.provider.ContactsContract import com.bnyro.contacts.R +import com.bnyro.contacts.enums.Addresses +import com.bnyro.contacts.enums.Emails +import com.bnyro.contacts.enums.Events +import com.bnyro.contacts.enums.Nickname +import com.bnyro.contacts.enums.Notes +import com.bnyro.contacts.enums.Numbers +import com.bnyro.contacts.enums.Organization +import com.bnyro.contacts.enums.Title +import com.bnyro.contacts.enums.Websites import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.TranslatedType import com.google.i18n.phonenumbers.PhoneNumberUtil @@ -77,6 +86,11 @@ object ContactsHelper { TranslatedType(ContactsContract.CommonDataKinds.Website.TYPE_OTHER, R.string.other) ) + val contactAttributesTypes = listOf( + Nickname(), Title(), Organization(), + Numbers(), Addresses(), Emails(), Events(), Websites(), Notes() + ) + fun splitFullName(displayName: String?): Pair { val displayNameParts = displayName.orEmpty().split(" ") return when { diff --git a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt index 5106db99..cc9d9f82 100644 --- a/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt +++ b/app/src/main/java/com/bnyro/contacts/util/IntentHelper.kt @@ -8,6 +8,8 @@ import android.provider.ContactsContract import androidx.core.net.toUri import com.bnyro.contacts.R import com.bnyro.contacts.enums.IntentActionType +import com.bnyro.contacts.enums.ListAttribute +import com.bnyro.contacts.enums.StringAttribute import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.TranslatedType import com.bnyro.contacts.obj.ValueWithType @@ -75,21 +77,24 @@ object IntentHelper { val name = intent.getStringExtra(ContactsContract.Intents.Insert.NAME) ?: intent.getStringExtra(ContactsContract.Intents.Insert.PHONETIC_NAME) - return ContactData( + val data = ContactData( displayName = name, firstName = name?.split(" ")?.firstOrNull(), surName = name?.split(" ", limit = 2)?.lastOrNull(), - organization = intent.getStringExtra(ContactsContract.Intents.Insert.COMPANY), - title = intent.getStringExtra(ContactsContract.Intents.Insert.JOB_TITLE), - numbers = extractIntentValue(intent, ContactsContract.Intents.Insert.PHONE, ContactsContract.Intents.Insert.PHONE_TYPE) + - extractIntentValue(intent, ContactsContract.Intents.Insert.SECONDARY_PHONE, ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE) + - extractIntentValue(intent, ContactsContract.Intents.Insert.TERTIARY_PHONE, ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE), - emails = extractIntentValue(intent, ContactsContract.Intents.Insert.EMAIL, ContactsContract.Intents.Insert.EMAIL_TYPE) + - extractIntentValue(intent, ContactsContract.Intents.Insert.SECONDARY_EMAIL, ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE) + - extractIntentValue(intent, ContactsContract.Intents.Insert.TERTIARY_EMAIL, ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE), - notes = extractIntentValue(intent, ContactsContract.Intents.Insert.NOTES), - addresses = extractIntentValue(intent, ContactsContract.Intents.Insert.POSTAL) ) + + ContactsHelper.contactAttributesTypes.forEach { attribute -> + if (attribute is StringAttribute) { + attribute.set(data, intent.getStringExtra(attribute.insertKey)) + } else if (attribute is ListAttribute) { + val values = attribute.insertKeys.mapNotNull { insertKey -> + extractIntentValue(intent, insertKey.first, insertKey.second) + } + attribute.set(data, values) + } + } + + return data } private fun extractIntentValue( @@ -97,8 +102,8 @@ object IntentHelper { key: String, typeKey: String? = null, types: List = emptyList() - ): List { - val entry = intent.getStringExtra(key) ?: return emptyList() + ): ValueWithType? { + val entry = intent.getStringExtra(key) ?: return null val type = if (typeKey != null) { val typeIdentifier = intent.getStringExtra(typeKey) @@ -108,6 +113,6 @@ object IntentHelper { }?.id } else null - return listOf(ValueWithType(entry, type ?: 0)) + return ValueWithType(entry, type ?: 0) } } From 49814110a13f10852bdd73f16af762826940f6c5 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 6 Apr 2024 14:24:07 +0200 Subject: [PATCH 137/139] fix: strange behavior when creating new contact via intent --- .../java/com/bnyro/contacts/ui/screens/ContactsScreen.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt index 11d7b9e4..e9b042ea 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/ContactsScreen.kt @@ -381,9 +381,4 @@ fun ContactsPage( showImportSimDialog = false } } - viewModel.initialContactData?.let { - SingleContactScreen(it) { - viewModel.initialContactData = null - } - } } From cafb4ed1ea1c19d10702b2f8da84f816aec78140 Mon Sep 17 00:00:00 2001 From: Arthur Gayot <116185937+Arthur-GYT@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:51:45 +0200 Subject: [PATCH 138/139] feat: automatic per app languages file (#386) --- app/build.gradle.kts | 4 ++++ app/src/main/res/resources.properties | 1 + build.gradle.kts | 4 ++-- gradle.properties | 4 +++- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/resources.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ee82ede1..48419cba 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,10 @@ android { namespace = "com.bnyro.contacts" compileSdk = 33 + androidResources { + generateLocaleConfig = true + } + defaultConfig { applicationId = "com.bnyro.contacts" minSdk = 21 diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 00000000..d5a3ddc9 --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en-US \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f4e65b8b..f08f5d79 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ buildscript { val compose_version by extra("1.5.0-beta01") } // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "7.4.2" apply false - id("com.android.library") version "7.4.2" apply false + id("com.android.application") version "8.2.2" apply false + id("com.android.library") version "8.2.2" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false } diff --git a/gradle.properties b/gradle.properties index 3c5031eb..a2e90d87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c4308c8e..a586e9e2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jan 24 15:00:58 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From f5ebcb5303799659d326c5ccd1ee1b9506d00171 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 10 Apr 2024 20:19:16 +0200 Subject: [PATCH 139/139] refactor: use ContactAttributes in SingleContactsScreen --- .../bnyro/contacts/enums/ContactAttributes.kt | 21 ++++- .../ui/components/ContactEntryGroup.kt | 4 +- .../ui/screens/SingleContactScreen.kt | 83 +++++-------------- 3 files changed, 43 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt b/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt index 6e3322dc..713bce6a 100644 --- a/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt +++ b/app/src/main/java/com/bnyro/contacts/enums/ContactAttributes.kt @@ -6,14 +6,16 @@ import com.bnyro.contacts.R import com.bnyro.contacts.obj.ContactData import com.bnyro.contacts.obj.TranslatedType import com.bnyro.contacts.obj.ValueWithType +import com.bnyro.contacts.util.CalendarUtils import com.bnyro.contacts.util.ContactsHelper -abstract class ContactAttribute(){ +abstract class ContactAttribute { abstract val stringRes: Int abstract val androidValueColumn: String abstract val androidContentType: String abstract fun set(contact: ContactData, value: T) abstract fun get(contact: ContactData): T + open fun display(contact: ContactData): T = get(contact) } abstract class StringAttribute : ContactAttribute() { @@ -24,6 +26,7 @@ abstract class ListAttribute: ContactAttribute>() { abstract val androidTypeColumn: String abstract val types: List abstract val insertKeys: List> + open val intentActionType: IntentActionType? = null } class Organization : StringAttribute() { @@ -71,12 +74,17 @@ class Events : ListAttribute() { override val androidValueColumn: String = ContactsContract.CommonDataKinds.Event.START_DATE override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Event.TYPE override val androidContentType: String = ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE + override fun set(contact: ContactData, value: List) { contact.events = value } override fun get(contact: ContactData) = contact.events + override fun display(contact: ContactData) = super.display(contact).map { + it.copy(value = CalendarUtils.localizeIsoDate(it.value)) + } + override val types: List = ContactsHelper.eventTypes } @@ -90,11 +98,16 @@ class Numbers : ListAttribute() { override val androidValueColumn: String = ContactsContract.CommonDataKinds.Phone.NUMBER override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Phone.TYPE override val androidContentType: String = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + override val intentActionType = IntentActionType.DIAL + override fun set(contact: ContactData, value: List) { contact.numbers = value } override fun get(contact: ContactData) = contact.numbers + override fun display(contact: ContactData) = super.display(contact).map { + ValueWithType(ContactsHelper.normalizePhoneNumber(it.value), it.type) + } override val types: List = ContactsHelper.phoneNumberTypes } @@ -109,6 +122,8 @@ class Emails : ListAttribute() { override val androidValueColumn: String = ContactsContract.CommonDataKinds.Email.ADDRESS override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Email.TYPE override val androidContentType: String = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + override val intentActionType = IntentActionType.EMAIL + override fun set(contact: ContactData, value: List) { contact.emails = value } @@ -124,6 +139,8 @@ class Addresses : ListAttribute() { override val androidValueColumn: String = ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS override val androidTypeColumn: String = ContactsContract.CommonDataKinds.StructuredPostal.TYPE override val androidContentType: String = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + override val intentActionType = IntentActionType.ADDRESS + override fun set(contact: ContactData, value: List) { contact.addresses = value } @@ -154,6 +171,8 @@ class Websites : ListAttribute() { override val androidValueColumn: String = ContactsContract.CommonDataKinds.Website.URL override val androidTypeColumn: String = ContactsContract.CommonDataKinds.Website.TYPE override val androidContentType: String = ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE + override val intentActionType = IntentActionType.WEBSITE + override fun set(contact: ContactData, value: List) { contact.websites = value } diff --git a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEntryGroup.kt b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEntryGroup.kt index 6078a194..1fb5a346 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/components/ContactEntryGroup.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/components/ContactEntryGroup.kt @@ -48,13 +48,13 @@ fun ContactEntryTextGroup( label: String, entries: List, types: List = listOf(), - onClick: (ValueWithType) -> Unit = {} + onClick: (String) -> Unit = {} ) { ContactEntryGroup( label, entries.map { ValueWithType(it, null) }, types, false, - onClick + onClick = { onClick.invoke(it.value) } ) } diff --git a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt index 6caa4960..5db01eaa 100644 --- a/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/ui/screens/SingleContactScreen.kt @@ -47,8 +47,10 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.bnyro.contacts.R import com.bnyro.contacts.enums.IntentActionType +import com.bnyro.contacts.enums.ListAttribute +import com.bnyro.contacts.enums.Notes +import com.bnyro.contacts.enums.StringAttribute import com.bnyro.contacts.obj.ContactData -import com.bnyro.contacts.obj.ValueWithType import com.bnyro.contacts.ui.components.ContactEntryGroup import com.bnyro.contacts.ui.components.ContactEntryTextGroup import com.bnyro.contacts.ui.components.ContactProfilePicture @@ -61,7 +63,6 @@ import com.bnyro.contacts.ui.components.dialogs.ConfirmationDialog import com.bnyro.contacts.ui.components.dialogs.ShortcutDialog import com.bnyro.contacts.ui.components.shapes.curlyCornerShape import com.bnyro.contacts.ui.models.ContactsModel -import com.bnyro.contacts.util.CalendarUtils import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.IntentHelper @@ -213,68 +214,26 @@ fun SingleContactScreen(contact: ContactData, onClose: () -> Unit) { } } - ContactEntryTextGroup( - label = stringResource(R.string.nick_name), - entries = listOfNotNull(contact.nickName) - ) - - ContactEntryTextGroup( - label = stringResource(R.string.title), - entries = listOfNotNull(contact.title) - ) - - ContactEntryTextGroup( - label = stringResource(R.string.organization), - entries = listOfNotNull(contact.organization) - ) - - ContactEntryGroup( - label = stringResource(R.string.website), - entries = contact.websites - ) { - IntentHelper.launchAction(context, IntentActionType.WEBSITE, it.value) - } - - ContactEntryGroup( - label = stringResource(R.string.phone), - entries = contact.numbers.map { - ValueWithType(ContactsHelper.normalizePhoneNumber(it.value), it.type) - }, - types = ContactsHelper.phoneNumberTypes - ) { - IntentHelper.launchAction(context, IntentActionType.DIAL, it.value) - } - - ContactEntryGroup( - label = stringResource(R.string.email), - entries = contact.emails, - types = ContactsHelper.emailTypes - ) { - IntentHelper.launchAction(context, IntentActionType.EMAIL, it.value) - } - - ContactEntryGroup( - label = stringResource(R.string.address), - entries = contact.addresses, - types = ContactsHelper.addressTypes - ) { - IntentHelper.launchAction(context, IntentActionType.ADDRESS, it.value) + for (attributesType in ContactsHelper.contactAttributesTypes) { + if (attributesType is StringAttribute) { + ContactEntryTextGroup( + label = stringResource(attributesType.stringRes), + entries = listOfNotNull(attributesType.display(contact)) + ) + } else if (attributesType is ListAttribute) { + ContactEntryGroup( + label = stringResource(attributesType.stringRes), + entries = attributesType.display(contact), + types = attributesType.types, + useMarkdown = attributesType is Notes + ) { + attributesType.intentActionType?.let { intentActionType -> + IntentHelper.launchAction(context, intentActionType, it.value) + } + } + } } - ContactEntryGroup( - label = stringResource(R.string.event), - entries = contact.events.map { - ValueWithType(CalendarUtils.localizeIsoDate(it.value), it.type) - }, - types = ContactsHelper.eventTypes - ) - - ContactEntryGroup( - label = stringResource(R.string.note), - entries = contact.notes, - useMarkdown = true - ) - ContactEntryTextGroup( label = stringResource(R.string.groups), entries = contact.groups.map { it.title }