From 32a0197f1f197e410a2c25f3ec4051b692a347e8 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 12 May 2018 08:46:34 +0200 Subject: [PATCH 001/135] change the way accounts are fetched --- app/build.gradle | 2 +- .../contacts/helpers/ContactsHelper.kt | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ca15f081..dfe8c477 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.0.0' + implementation 'com.simplemobiletools:commons:4.0.3' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index e52181fa..7ffa94f4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1,5 +1,6 @@ package com.simplemobiletools.contacts.helpers +import android.accounts.Account import android.accounts.AccountManager import android.app.Activity import android.content.* @@ -61,6 +62,33 @@ class ContactsHelper(val activity: Activity) { }.start() } + private fun getContentResolverAccounts(): HashSet { + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.RawContacts.ACCOUNT_NAME, + ContactsContract.RawContacts.ACCOUNT_TYPE + ) + + val sources = HashSet() + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, null, null, null) + if (cursor?.moveToFirst() == true) { + do { + val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: "" + val type = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) ?: "" + val source = ContactSource(name, type) + sources.add(source) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + } finally { + cursor?.close() + } + + return sources + } + private fun getDeviceContacts(contacts: SparseArray) { if (!activity.hasContactPermissions()) { return @@ -662,14 +690,17 @@ class ContactsHelper(val activity: Activity) { return sources } - val accountManager = AccountManager.get(activity) - accountManager.accounts.filter { it.name.contains("@") || localAccountTypes.contains(it.type) }.forEach { - if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) { + val accounts = AccountManager.get(activity).accounts + accounts.forEach { + if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1 && ContentResolver.getSyncAutomatically(it, ContactsContract.AUTHORITY)) { val contactSource = ContactSource(it.name, it.type) sources.add(contactSource) } } + val contentResolverAccounts = getContentResolverAccounts().filter { !accounts.contains(Account(it.name, it.type)) } + sources.addAll(contentResolverAccounts) + if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { sources.add(ContactSource("", "")) } From ed982f2b1f6df29c52ecbfea7f3c8ee743bb413d Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 12 May 2018 09:08:08 +0200 Subject: [PATCH 002/135] update version to 4.0.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dfe8c477..c03ab4fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 21 - versionName "4.0.1" + versionCode 22 + versionName "4.0.2" setProperty("archivesBaseName", "contacts") } From 3ebe057930c7f82b0dfe282a9198ba9964787cb9 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 12 May 2018 09:08:14 +0200 Subject: [PATCH 003/135] updating changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9854e29..1a53bf69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========== +Version 4.0.2 *(2018-05-12)* +---------------------------- + + * Make sure all relevant contact sources are visible + Version 4.0.1 *(2018-05-09)* ---------------------------- From 41acf03519520bf4a7174ff607dfc57dcaabde71 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 12 May 2018 20:58:03 +0200 Subject: [PATCH 004/135] adding some new lines at the adaptive icon files --- app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml | 2 +- app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 64419c94..a77f7223 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml index ddd51757..dab4c0c5 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml index 0bb69cf2..37bf057f 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml index aa10bae8..3e4d0696 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml index 8d2fc23f..9786d7bf 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml index 89f7a275..afb3d0d3 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml index 0822e55f..1846b81d 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml index 0735af68..4152801a 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml index 06e60b88..e55d1092 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml index 4765f7ec..40d07450 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml index 37f5249f..601d8170 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml index eed925f1..01f2fead 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml index a051acce..d37b24c4 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml index d6df0e23..9fd7bc5b 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml index 4b6fc188..d2adf9a0 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml index e7e2cb97..32c838cd 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml index 972ad3de..a6e93599 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml index c3020ef7..18492d28 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml index 0c336264..854427e0 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + From 477742a549450db0524ca193194dee00fcbfeb08 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 13 May 2018 18:23:54 +0200 Subject: [PATCH 005/135] remove the check for ContentResolver.getSyncAutomatically at getting accounts --- .../com/simplemobiletools/contacts/helpers/ContactsHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 7ffa94f4..722ed3af 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -692,7 +692,7 @@ class ContactsHelper(val activity: Activity) { val accounts = AccountManager.get(activity).accounts accounts.forEach { - if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1 && ContentResolver.getSyncAutomatically(it, ContactsContract.AUTHORITY)) { + if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) { val contactSource = ContactSource(it.name, it.type) sources.add(contactSource) } From c0ad8662f4166d3f9d20fbda6ee7953839fc3089 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 13 May 2018 21:36:44 +0200 Subject: [PATCH 006/135] update commons to 4.0.9 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c03ab4fc..51137338 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.0.3' + implementation 'com.simplemobiletools:commons:4.0.9' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' From c8dba03e71ef546dc65f6e858ba567863c6ea578 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 13 May 2018 21:37:43 +0200 Subject: [PATCH 007/135] avoid creating Account object with empty name or type --- .../com/simplemobiletools/contacts/helpers/ContactsHelper.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 722ed3af..8d5e4d40 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -698,7 +698,9 @@ class ContactsHelper(val activity: Activity) { } } - val contentResolverAccounts = getContentResolverAccounts().filter { !accounts.contains(Account(it.name, it.type)) } + val contentResolverAccounts = getContentResolverAccounts().filter { + it.name.isNotEmpty() && it.type.isNotEmpty() && !accounts.contains(Account(it.name, it.type)) + } sources.addAll(contentResolverAccounts) if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { From e46e2a95e0fc4d01a1b898624fd3c24c49493909 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 13 May 2018 21:37:52 +0200 Subject: [PATCH 008/135] update version to 4.0.3 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 51137338..21d41971 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 22 - versionName "4.0.2" + versionCode 23 + versionName "4.0.3" setProperty("archivesBaseName", "contacts") } From a9520c93ab4db029f469fb6f46007494d827701e Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 13 May 2018 21:37:58 +0200 Subject: [PATCH 009/135] updating changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a53bf69..9b1a038d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========== +Version 4.0.3 *(2018-05-13)* +---------------------------- + + * Show a couple additional contact sources + Version 4.0.2 *(2018-05-12)* ---------------------------- From b49a75b73e116889d42cf774a32afdc72f383d86 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 15 May 2018 10:34:40 +0200 Subject: [PATCH 010/135] remove a redundant variable --- .../com/simplemobiletools/contacts/adapters/ContactsAdapter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt index fd9db502..de6d2591 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt @@ -250,8 +250,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList Date: Sun, 20 May 2018 15:22:01 +0300 Subject: [PATCH 011/135] Create strings.xml --- app/src/main/res/values-tr/strings.xml | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/src/main/res/values-tr/strings.xml diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 00000000..9b926f57 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,127 @@ + + Basit Kişiler + Kişiler + Adres + Ekleniyor… + Güncelleniyor… + Telefon belleği + Telefon belleği (diğer uygulamalar tarafından görülmez) + Şirket + İş pozisyonu + Web sitesi + Kişilere SMS gönder + Kişilere e-posta gönder + Gruba SMS gönder + Gruba e-posta gönder + + Yeni kişi + Kişiyi düzenle + Kişi seç + Kişileri seç + Adı + Göbek adı + Soyadı + + + Grup yok + Yeni grup oluştur + Gruptan kaldır + Bu grup boş + Kişi ekle + Cihazda kişi grubu yok + Grup oluştur + Gruba ekle + Hesap altında grup oluştur + + + Fotoğraf çek + Fotoğraf seç + Fotoğrafı kaldır + + + Soyadı ile başla + Ana ekranda telefon numaralarını göster + Kişi küçük resimlerini göster + Kişi tıklandığında + Kişiyi ara + Kişi bilgilerini göster + Favoriler sekmesini göster + Gruplar sekmesini göster + Görüntülenecek kişi alanlarını yönet + Çift kişileri filtrelemeyi dene + + + E-posta + Ev + İş + Diğer + + + Numara + Cep + Ana + İş Faksı + Ev Faksı + Çağrı Cihazı + Telefon numarası bulunamadı + + + Doğum günü + Yıldönümü + + + Henüz hiç favori kişi eklemediniz gibi görünüyor. + Favorilerini ekle + Favorilere ekle + Favorilerden kaldır + Bir kişiyi değiştirmek için Düzen ekranında olmalısınız + + + Kişileri ara + Favorileri ara + + + Kişileri içe aktar + Kişileri dışa aktar + Kişileri bir .vcf dosyasından içe aktar + Kişileri bir .vcf dosyasına aktar + Hedef kişi kaynağı + Kişi kaynaklarını dahil et + Dosya adı (.vcf olmadan) + + + Görüntülenecek alanları seç + Önek + Sonek + Telefon numaraları + E-postalar + Adresler + Etkinlikler (doğum günleri, yıldönümleri) + Notlar + Organizasyon + Web siteleri + Gruplar + Kişi kaynağı + + + Rehberde görüntülenecek alanları değiştirmek istiyorum. Bunu yapabilir miyim? + Evet, tek yapmanız gereken Ayarlar -> Görüntülenecek kişi alanlarını yönet\'e gitmek. Orada hangi alanların görüntüleneceğini seçebilirsiniz. Bazıları varsayılan olarak devre dışı bile olsa, orada bazı yenilerini bulabilirsiniz. + + + + Kişilerinizi reklamsız yönetmek için bir kişiler uygulaması. + + Kişilerinizi herhangi bir kaynaktan oluşturmak veya yönetmek için basit bir uygulama. Kişiler yalnızca cihazınızda saklanabilir, aynı zamanda Google veya diğer hesaplarla senkronize edilebilir. Favori kişilerinizi ayrı bir listede görüntüleyebilirsiniz. + + Kullanıcı e-postalarını ve etkinliklerini yönetmek için de kullanabilirsiniz. Birden çok parametreye göre sıralama/filtreleme, isteğe bağlı olarak soyadı ilk ad olarak görüntüleme yeteneğine sahiptir. + + Reklam veya gereksiz izinler içermez. Tamamen açık kaynaktır, özelleştirilebilir renkler sağlar. + + Bu uygulama, daha büyük bir uygulama serisinden sadece bir parça. Geri kalanı http://www.simplemobiletools.com adresinde bulabilirsiniz + + + + From 8501379d1881eb99c0140905be7d1ec4c786d038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Marques?= Date: Mon, 21 May 2018 13:40:27 +0100 Subject: [PATCH 012/135] Update strings.xml --- 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 486feb5a..cb37c1e3 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -48,7 +48,7 @@ Mostrar favoritos Mostrar grupos Gerir campos a exibir - Try filtering out duplicate contacts + Tentar filtrar contactos duplicados E-mail From d78de3f0e1511b74dc08e614bebbb47662887bc5 Mon Sep 17 00:00:00 2001 From: fricyo <30796677+fricyo@users.noreply.github.com> Date: Mon, 4 Jun 2018 17:39:21 +0800 Subject: [PATCH 013/135] Update Translation --- app/src/main/res/values-zh-rTW/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 79766c2a..b81a503a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -8,11 +8,11 @@ 手機空間 (其他程式不可見) 公司 職位 - Website - Send SMS to contacts - Send email to contacts - Send SMS to group - Send email to group + 網站 + 發送簡訊給聯絡人 + 發送電子郵件給聯絡人 + 發送簡訊給群組 + 發送電子郵件給群組 新聯絡人 編輯聯絡人 @@ -48,7 +48,7 @@ 顯示我的最愛頁面 顯示群組頁面 管理顯示的聯絡人欄位 - Try filtering out duplicate contacts + 試著過濾重複的聯絡人 電子信箱 @@ -74,7 +74,7 @@ 添加我的最愛 加入我的最愛 從我的最愛移除 - You must be at the Edit screen to modify a contact + 你必須在編輯畫面去修改聯絡人 搜尋聯絡人 @@ -99,7 +99,7 @@ 活動 (生日、紀念日) 筆記 組織 - Websites + 網站 群組 聯絡人來源 From 5c2bdf204c0b28e8e929bdddf7f6fa6147f86f04 Mon Sep 17 00:00:00 2001 From: Jonas Schubert Date: Mon, 4 Jun 2018 22:16:46 +0200 Subject: [PATCH 014/135] Updated german translations --- app/src/main/res/values-de/strings.xml | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 58b24d96..331e7fd6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,18 +1,18 @@ - Schlichte Kontakte + Simple Contacts Kontakte Adresse Einfügen… Aktualisiere… Gerätespeicher Gerätespeicher (nicht sichtbar für andere Apps) - Company - Job position + Unternehmen + Arbeitsstelle Website - Send SMS to contacts - Send email to contacts - Send SMS to group - Send email to group + Sende SMS zu Kontakten + Sende E-Mail zu Kontakten + Sende SMS zu Gruppe + Sende E-Mail zu Gruppe Neuer Kontakt Kontakt bearbeiten @@ -45,13 +45,13 @@ Beim Klicken auf den Kontakt Kontakt anrufen Kontaktdetails ansehen - Show favorites tab - Show groups tab - Manage shown contact fields - Try filtering out duplicate contacts + Zeige Favoriten + Zeige Gruppen + Bearbeite sichtbare Kontaktfelder + Versuche Kontaktdupplikate herauszufiltern - Email + E-Mail Privat Arbeit Sonstiges @@ -74,7 +74,7 @@ Favoriten hinzufügen Zu Favoriten hinzufügen Aus Favoriten entfernen - You must be at the Edit screen to modify a contact + Sie müssen sich im Editier-Modus befinden, um einen Kontakt zu bearbeiten Kontakte durchsuchen @@ -90,34 +90,34 @@ Dateiname (ohne .vcf) - Select fields to show + Sichtbare Felder selektieren Prefix Suffix - Phone numbers - Emails - Addresses - Events (birthdays, anniversaries) - Notes - Organization + Telefonnummern + E-Mails + Addressen + Termine (Geburtstage, Jahrestage) + Notizen + Organisation Websites - Groups - Contact source + Gruppen + Kontaktquelle - I want to change what fields are visible at contacts. Can I do it? - Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Ich möchte die sichtbaren Kontaktfelder ändern. Kann ich das machen? + Ja, alles, was Sie machen müssen ist folgendes: Gehen Sie zu Einstellungen -> Bearbeite sichtbare Kontaktfelder. Hier können die sichtbaren Felder bearbeitet werden. Einige sind standardmäßig deaktiviert, weshalb hier neue gefunden werden können. - Eine App zum Verwalten von Kontakten, ganz ohne Werbung. + Eine Anwendung zum Verwalten von Kontakten, ganz ohne Werbung. - Eine schlichte App um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt. + Eine einfache Anwendung um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt. - Du kannst diese App auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen. + Du kannst diese Anwendung auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen. Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, alle verwendeten Farben sind anpassbar. - Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf https://www.simplemobiletools.com + Diese Anwendung ist nur eine aus einer größeren Serie. Der Rest davon findet sich auf https://www.simplemobiletools.com - Eine Anwendung zum Verwalten von Kontakten, ganz ohne Werbung. + Eine App zum Verwalten von Kontakten, ganz ohne Werbung. - Eine einfache Anwendung um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt. + Eine schlichte App um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt. - Du kannst diese Anwendung auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen. + Du kannst diese App auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen. Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, alle verwendeten Farben sind anpassbar. - Diese Anwendung ist nur eine aus einer größeren Serie. Der Rest davon findet sich auf https://www.simplemobiletools.com + Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf https://www.simplemobiletools.com Keine Gruppen Eine neue Gruppe erstellen - Von Gruppe entfernen + Aus Gruppe entfernen Diese Gruppe ist leer Kontakte hinzufügen Es sind keine Kontaktgruppen auf diesem Gerät vorhanden @@ -34,21 +34,21 @@ Gruppe in diesem Konto erstellen - Foto machen + Foto aufnehmen Foto auswählen Foto entfernen Namen mit Nachnamen beginnen Zeige Telefonnummern im Hauptmenü - Zeige Vorschaubilder für Kontakte + Zeige Vorschaubilder der Kontakte Beim Klicken auf den Kontakt Kontakt anrufen - Kontaktdetails ansehen - Zeige Favoriten - Zeige Gruppen + Kontaktdetails anzeigen + Zeige Favoriten-Reiter + Zeige Gruppen-Reiter Bearbeite sichtbare Kontaktfelder - Versuche Kontaktdupplikate herauszufiltern + Versuche Kontaktduplikate herauszufiltern E-Mail @@ -74,7 +74,7 @@ Favoriten hinzufügen Zu Favoriten hinzufügen Aus Favoriten entfernen - Sie müssen sich im Editier-Modus befinden, um einen Kontakt zu bearbeiten + Sie müssen sich im Bearbeitungsmodus befinden, um einen Kontakt zu bearbeiten Kontakte durchsuchen @@ -90,8 +90,8 @@ Dateiname (ohne .vcf) - Sichtbare Felder selektieren - Prefix + Sichtbare Felder auswählen + Titel Suffix Telefonnummern E-Mails @@ -99,13 +99,13 @@ Termine (Geburtstage, Jahrestage) Notizen Organisation - Websites + Webseiten Gruppen Kontaktquelle Ich möchte die sichtbaren Kontaktfelder ändern. Kann ich das machen? - Ja, alles, was Sie machen müssen ist folgendes: Gehen Sie zu Einstellungen -> Bearbeite sichtbare Kontaktfelder. Hier können die sichtbaren Felder bearbeitet werden. Einige sind standardmäßig deaktiviert, weshalb hier neue gefunden werden können. + Ja, alles, was Sie tun müssen ist folgendes: Gehen Sie zu Einstellungen -> Bearbeite sichtbare Kontaktfelder. Hier können die sichtbaren Felder ausgewählt werden. Einige sind standardmäßig deaktiviert, weshalb hier neue gefunden werden können. From 8708c800e68cfc57d3ab877a48b5783da2d239d4 Mon Sep 17 00:00:00 2001 From: Tibor Kaputa Date: Sun, 17 Jun 2018 19:49:18 +0200 Subject: [PATCH 019/135] reverting app name translation --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a0176161..c0387162 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,5 +1,5 @@ - Simple Kontakte + Schlichte Kontakte Kontakte Adresse Einfügen… From d3051b66caa9fb6d360a4a27b528cc54bf717bd2 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 17 Jun 2018 19:51:31 +0200 Subject: [PATCH 020/135] updating german app descriptions by gembutterfly --- app/src/main/res/values-de/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c0387162..1990f405 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -109,15 +109,15 @@ - Eine App zum Verwalten von Kontakten, ganz ohne Werbung. + Eine Kontakte-App zur Verwaltung Ihrer Kontake. Ohne Werbung. - Eine schlichte App um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt. + Eine einfache App, mit der Sie Kontakte erstellen oder von jeder Quelle verwalten können. Die Kontakte können entweder nur auf Ihrem Gerät gespeichert, oder mittels Google oder anderer Konten synchronisiert werden. Sie können Ihre Lieblingskontake in einer separaten Liste anzeigen. - Du kannst diese App auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen. + Sie können es auch zur Verwaltung von Nutzer-E-Mails und Ereignisse nutzen. Es kann nach mehreren Parametern sortieren oder filtern, oder optional den Nachnamen zuerst anzeigen. - Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, alle verwendeten Farben sind anpassbar. + Enthält keine Werbung oder unnötige Berechtigungen. Vollständig quelloffen. Die Farben sind anpassbar. - Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf https://www.simplemobiletools.com + Diese App ist nur ein Teil einer größeren App-Familie. Die übrigen finden Sie unter https://www.simplemobiletools.com E-post @@ -74,7 +74,7 @@ Lägg till favoriter Lägg till i favoriter Ta bort från favoriter - You must be at the Edit screen to modify a contact + Kontakter kan bara redigeras i redigeringsvyn Sök efter kontakter From 19119b51c92a560951e90874e4a281c2ae2e2bdd Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 16 Jul 2018 21:54:07 +0200 Subject: [PATCH 038/135] fix some glitches at importing contacts from vcf, if Charset is mentioned --- app/build.gradle | 2 +- .../contacts/helpers/Constants.kt | 8 ++--- .../contacts/helpers/VcfExporter.kt | 8 ++--- .../contacts/helpers/VcfImporter.kt | 29 +++++++++++++++---- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 25b89ecc..e1d218b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.3.29' + implementation 'com.simplemobiletools:commons:4.4.24' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 5409d2dd..a2239943 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -46,10 +46,10 @@ const val ANNIVERSARY = "ANNIVERSARY:" const val PHOTO = "PHOTO" const val EMAIL = "EMAIL" const val ADR = "ADR" -const val NOTE = "NOTE:" -const val ORG = "ORG:" -const val TITLE = "TITLE:" -const val URL = "URL:" +const val NOTE = "NOTE" +const val ORG = "ORG" +const val TITLE = "TITLE" +const val URL = "URL" const val ENCODING = "ENCODING" const val BASE64 = "BASE64" const val JPEG = "JPEG" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt index 88f077f3..e53cecd4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -65,16 +65,16 @@ class VcfExporter { } if (contact.notes.isNotEmpty()) { - out.writeLn("$NOTE${contact.notes.replace("\n", "\\n")}") + out.writeLn("$NOTE:${contact.notes.replace("\n", "\\n")}") } if (!contact.organization.isEmpty()) { - out.writeLn("$ORG${contact.organization.company.replace("\n", "\\n")}") - out.writeLn("$TITLE${contact.organization.jobPosition.replace("\n", "\\n")}") + out.writeLn("$ORG:${contact.organization.company.replace("\n", "\\n")}") + out.writeLn("$TITLE:${contact.organization.jobPosition.replace("\n", "\\n")}") } contact.websites.forEach { - out.writeLn("$URL$it") + out.writeLn("$URL:$it") } if (contact.thumbnailUri.isNotEmpty()) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index 44c3106d..f8c9ec67 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -59,7 +59,8 @@ class VcfImporter(val activity: SimpleActivity) { inputStream.bufferedReader().use { while (true) { - val line = it.readLine() ?: break + var line = it.readLine() ?: break + line = line.trim() if (line.trim().isEmpty()) { if (isGettingPhoto) { savePhoto() @@ -128,6 +129,7 @@ class VcfImporter(val activity: SimpleActivity) { val nameParts = currentNameString.split(";") curSurname = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[0]) else nameParts[0] curFirstName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[1]) else nameParts[1] + if (nameParts.size > 2) { curMiddleName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[2]) else nameParts[2] curPrefix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[3]) else nameParts[3] @@ -244,26 +246,43 @@ class VcfImporter(val activity: SimpleActivity) { } private fun addNotes(notes: String) { - currentNotesSB.append(notes) + if (notes.startsWith(";CHARSET", true)) { + currentNotesSB.append(notes.substringAfter(":")) + } else { + currentNotesSB.append(notes.substring(1)) + } isGettingNotes = true } private fun addCompany(company: String) { - curCompany = company + curCompany = if (company.startsWith(";")) { + company.substringAfter(":").trim(';') + } else { + company + } } private fun addJobPosition(jobPosition: String) { - curJobPosition = jobPosition + curJobPosition = if (jobPosition.startsWith(";")) { + jobPosition.substringAfter(":") + } else { + jobPosition + } } private fun addWebsite(website: String) { - curWebsites.add(website) + if (website.startsWith(";")) { + curWebsites.add(website.substringAfter(":")) + } else { + curWebsites.add(website) + } } private fun saveContact(source: String) { val organization = Organization(curCompany, curJobPosition) val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents, source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites) + if (ContactsHelper(activity).insertContact(contact)) { contactsImported++ } From eb8c667bc282217a43cb420d82ced4ceda57cabf Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 16 Jul 2018 22:55:47 +0200 Subject: [PATCH 039/135] improve address parsing at importing in some cases --- .../simplemobiletools/contacts/helpers/VcfImporter.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index f8c9ec67..b7d98142 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.contacts.helpers import android.graphics.Bitmap import android.graphics.BitmapFactory import android.provider.ContactsContract.CommonDataKinds +import android.text.TextUtils import android.util.Base64 import android.widget.Toast import com.simplemobiletools.commons.extensions.showErrorToast @@ -190,10 +191,16 @@ class VcfImporter(val activity: SimpleActivity) { if (rawType.contains('=')) { rawType = rawType.split('=').last() } + val type = getAddressTypeId(rawType.toUpperCase()) val addresses = addressParts[1].split(";") if (addresses.size == 7) { - curAddresses.add(Address(addresses[2].replace("\\n", "\n"), type)) + if (address.contains(";CHARSET=UTF-8:")) { + val fullAddress = TextUtils.join(", ", addresses.filter { it.trim().isNotEmpty() }) + curAddresses.add(Address(fullAddress, type)) + } else { + curAddresses.add(Address(addresses[2].replace("\\n", "\n"), type)) + } } } From c229211dcbb9be7a834faaecf3d195e35e5bcd58 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 16 Jul 2018 23:02:03 +0200 Subject: [PATCH 040/135] update commons to 4.4.26 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e1d218b3..1b82b2b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.4.24' + implementation 'com.simplemobiletools:commons:4.4.26' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' From 03dbf5a882a083d59b703ca53c4bd9771fefddbf Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 16 Jul 2018 23:07:32 +0200 Subject: [PATCH 041/135] adding an orange splash activity declaration --- app/src/main/AndroidManifest.xml | 33 +++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bb5331be..65956686 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,13 +26,7 @@ - - - - - - + android:theme="@style/SplashTheme"/> @@ -193,19 +187,6 @@ android:resource="@xml/provider_paths"/> - - - - - - - - + + + + + + + Date: Mon, 16 Jul 2018 23:08:41 +0200 Subject: [PATCH 042/135] show a Purchase Simple Thank You button in the settings --- .../contacts/activities/SettingsActivity.kt | 10 +++++++++ app/src/main/res/layout/activity_settings.xml | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt index 1cf3bb24..19eb91db 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt @@ -3,6 +3,8 @@ package com.simplemobiletools.contacts.activities import android.os.Bundle import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.extensions.isThankYouInstalled +import com.simplemobiletools.commons.extensions.launchPurchaseThankYouIntent import com.simplemobiletools.commons.extensions.updateTextColors import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R @@ -23,6 +25,7 @@ class SettingsActivity : SimpleActivity() { override fun onResume() { super.onResume() + setupPurchaseThankYou() setupCustomizeColors() setupManageShownContactFields() setupUseEnglish() @@ -36,6 +39,13 @@ class SettingsActivity : SimpleActivity() { updateTextColors(settings_holder) } + private fun setupPurchaseThankYou() { + settings_purchase_thank_you_holder.beVisibleIf(config.appRunCount > 10 && !isThankYouInstalled()) + settings_purchase_thank_you_holder.setOnClickListener { + launchPurchaseThankYouIntent() + } + } + private fun setupCustomizeColors() { settings_customize_colors_holder.setOnClickListener { startCustomizationActivity() diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 18addc99..b1f7cccc 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -11,6 +11,28 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + Date: Mon, 16 Jul 2018 23:48:42 +0200 Subject: [PATCH 043/135] fix #200, close search at changing tab --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 806fa429..c72b5fdc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -296,6 +296,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { it.icon?.applyColorFilter(config.textColor) }, tabSelectedAction = { + if (isSearchOpen) { + getCurrentFragment()?.onSearchQueryChanged("") + searchMenuItem?.collapseActionView() + } viewpager.currentItem = it.position it.icon?.applyColorFilter(getAdjustedPrimaryColor()) } From 920cb6d882300095b10e6cd64130f6a51e4fddca Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 17 Jul 2018 00:09:39 +0200 Subject: [PATCH 044/135] shorten some contact name comparison code --- .../kotlin/com/simplemobiletools/contacts/models/Contact.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 85b448fd..b35c5130 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -45,9 +45,9 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m -1 } else { if (firstString.toLowerCase() == secondString.toLowerCase()) { - getFullName().compareTo(other.getFullName()) + getFullName().compareTo(other.getFullName(), true) } else { - firstString.toLowerCase().compareTo(secondString.toLowerCase()) + firstString.compareTo(secondString, true) } } } From e584a38f4125c60f26053382b4f08edf09c0c0d3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 17 Jul 2018 00:13:30 +0200 Subject: [PATCH 045/135] update version to 4.1.0 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1b82b2b0..7646472d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 25 - versionName "4.0.5" + versionCode 26 + versionName "4.1.0" setProperty("archivesBaseName", "contacts") } From 58bb8a7327b0227985c1befbd7960f67e088df5a Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 17 Jul 2018 00:13:37 +0200 Subject: [PATCH 046/135] updating changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7f19c6..ab1302de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog ========== +Version 4.1.0 *(2018-07-16)* +---------------------------- + + * Fixed a couple issues related to importing contacts from .vcf files + * Couple other UX and stability improvements + Version 4.0.5 *(2018-07-05)* ---------------------------- From 3ca7f939863babe0ad31beaa835fbb76a457cbf0 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 17 Jul 2018 10:30:17 +0200 Subject: [PATCH 047/135] adding some tablet screenshots --- .../images/sevenInchScreenshots/tablet-7.png | Bin 0 -> 97735 bytes .../images/tenInchScreenshots/tablet-10.png | Bin 0 -> 108468 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png create mode 100644 fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png new file mode 100644 index 0000000000000000000000000000000000000000..aeb53b51f69e176ac4698414359bd8f3a434f52a GIT binary patch literal 97735 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�yqFbp`9glI{ydo6qj#~ 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 literal 0 HcmV?d00001 From 04df9ef079140ab6fabd52b97f8d892da62d69b0 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 17 Jul 2018 20:35:37 +0200 Subject: [PATCH 048/135] ignore NICKNAME tag at importing contacts for now --- .../kotlin/com/simplemobiletools/contacts/helpers/Constants.kt | 1 + .../kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index a2239943..4eb29248 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -40,6 +40,7 @@ const val PHOTO_UNCHANGED = 4 const val BEGIN_VCARD = "BEGIN:VCARD" const val END_VCARD = "END:VCARD" const val N = "N" +const val NICKNAME = "NICKNAME" const val TEL = "TEL" const val BDAY = "BDAY:" const val ANNIVERSARY = "ANNIVERSARY:" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index b7d98142..0e27d8cd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -84,6 +84,7 @@ class VcfImporter(val activity: SimpleActivity) { when { line.toUpperCase() == BEGIN_VCARD -> resetValues() line.toUpperCase().startsWith(NOTE) -> addNotes(line.substring(NOTE.length)) + line.toUpperCase().startsWith(NICKNAME) -> { } line.toUpperCase().startsWith(N) -> addNames(line.substring(N.length)) line.toUpperCase().startsWith(TEL) -> addPhoneNumber(line.substring(TEL.length)) line.toUpperCase().startsWith(EMAIL) -> addEmail(line.substring(EMAIL.length)) From f12d240e498657f13a0ca1700ed9b418bd0d2948 Mon Sep 17 00:00:00 2001 From: Gozzwip <36863088+Gozzwip@users.noreply.github.com> Date: Fri, 20 Jul 2018 19:56:48 +0500 Subject: [PATCH 049/135] adding translations --- app/src/main/res/values-az/strings.xml | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/src/main/res/values-az/strings.xml diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml new file mode 100644 index 00000000..58ab4d54 --- /dev/null +++ b/app/src/main/res/values-az/strings.xml @@ -0,0 +1,127 @@ + + Sadə Kontaktlar + Kontaktlar + Ünvan + Daxil edilir… + Yenilənir… + Telefon yaddaşı + Telefon yaddaşı (digər tətbiqlərə görünmür) + Şirkət + İş vəziyyəti + Vebsayt + Kontaktlara SMS göndər + Kontaktlara e-poçt göndər + Grupa SMS göndər + Grupa e-poçt göndər + + Yeni kontakt + Redaktə et + Kontakt seç + Kontaktları seç + Ad + Orta Ad + Soyad + + + Qruplar yoxdur + Yeni qrup yarat + Qrupdan sil + Bu qrup boşdur + Kontaktlar əlavə et + Cihazda heçbir kontakt qrupu yoxdur + Qrup yarat + Qrupa əlavə et + Hesab altında qrup yarat + + + Şəkil çək + Şəkil seç + Şəkli sil + + + Ada soyaddan başla + Telefon nömrələrini əsas ekranda göstər + Kontakt görüntülərini göstər + Kontakta toxunduqda + Kontakta zəng et + Kontakt detallarına bax + Sevimlilər nişanını göstər + Qruplar nişanını göstər + Göstərilən kontakt sahəsini idarə et + Təkrarlanmış kontaktları filtrləməyə çalış + + + E-poçt + Ev + İş + Başqa + + + Nömrə + Mobil + Əsas + İş Faksı + Ev Faksı + Zəng cihazı + Heçbir telefon nömrəsi tapılmadı + + + Ad günü + İl dönümü + + + Görünür, hələlik heçbir sevimli kontakt əlavə etməmisiniz. + Sevimlilər əlavlə et + Sevimlilərə əlavə et + Sevimlilərdən sil + Kontaktı dəyişmək üçün İdarə et ekranında olmalısınız + + + Kontaktları axtar + Sevimliləri axtar + + + Kontaktları daxil et + Kontaktları xaric et + .vcf faylından kontaktları daxil et + .vcf faylından kontaktları xaric et + Kontakt kökünü nişanla + Kontakt köklərini daxil et + Fayl adı (.vcf olmadan) + + + Göstərmək üçün sahəni seç + Ön şəkilçi + Orta şəkilçi + Telefon nömrələri + E-poçtlar + Ünvanlar + Hadisələr (ad günləri, il dönümləri) + Qeydlər + Təşkilat + Vebsayatlar + Qruplar + Kontakt kökü + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + + + + A contacts app for managing your contacts without ads. + + A simple app for creating or managing your contacts from any source. The contacts can be stored on your device only, but also synchronized via Google, or other accounts. You can display your favorite contacts on a separate list. + + You can use it for managing user emails and events too. It has the ability to sort/filter by multiple parameters, optionally display surname as the first name. + + Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + + This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com + + + + From 1e38cd80370edee25732bdccee3d31a29bbda628 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 19:13:39 +0200 Subject: [PATCH 050/135] catch and show exceptions thrown at saving contacts --- .../simplemobiletools/contacts/helpers/ContactsHelper.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 8d5e4d40..896e8685 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -993,7 +993,12 @@ class ContactsHelper(val activity: Activity) { operations.clear() } } - activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + + try { + activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + } catch (e: Exception) { + activity.showErrorToast(e) + } } fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) { From d79c65b334e2dbaa2af5f5500a0a3f7104b1e423 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 19:52:50 +0200 Subject: [PATCH 051/135] update commons to 4.5.12 --- app/build.gradle | 2 +- .../simplemobiletools/contacts/activities/MainActivity.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7646472d..4bf4f41d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.4.26' + implementation 'com.simplemobiletools:commons:4.5.12' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index c72b5fdc..353571bf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -403,13 +403,14 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } private fun launchAbout() { + val licenses = LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO + val faqItems = arrayListOf( FAQItem(R.string.faq_1_title, R.string.faq_1_text), FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons) ) - startAboutActivity(R.string.app_name, LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO, - BuildConfig.VERSION_NAME, faqItems) + startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) } override fun refreshContacts(refreshTabsMask: Int) { From 692f964479d7fbc97981000f5a0194994382eac6 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 20:24:21 +0200 Subject: [PATCH 052/135] fix #207, avoid trying to delete device contacts without having the permission --- .../simplemobiletools/contacts/helpers/ContactsHelper.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 896e8685..a2829ff7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -14,10 +14,7 @@ import android.provider.MediaStore import android.text.TextUtils import android.util.SparseArray import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME -import com.simplemobiletools.commons.helpers.SORT_BY_SURNAME -import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.models.* @@ -1300,7 +1297,9 @@ class ContactsHelper(val activity: Activity) { } } - activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + if (activity.hasPermission(PERMISSION_WRITE_CONTACTS)) { + activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + } } catch (e: Exception) { activity.showErrorToast(e) } From 2e3b63cb4879932239acd00faedf4befc7c2c35b Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 20:32:29 +0200 Subject: [PATCH 053/135] add some strings related to customizing tab visibility --- app/src/main/res/values-az/strings.xml | 5 +++-- app/src/main/res/values-de/strings.xml | 5 +++-- app/src/main/res/values-el/strings.xml | 5 +++-- app/src/main/res/values-fr/strings.xml | 5 +++-- app/src/main/res/values-hr/strings.xml | 5 +++-- app/src/main/res/values-ko-rKR/strings.xml | 5 +++-- app/src/main/res/values-lt/strings.xml | 5 +++-- app/src/main/res/values-pt/strings.xml | 5 +++-- app/src/main/res/values-ru/strings.xml | 5 +++-- app/src/main/res/values-sk/strings.xml | 5 +++-- app/src/main/res/values-sv/strings.xml | 5 +++-- app/src/main/res/values-tr/strings.xml | 5 +++-- app/src/main/res/values-zh-rTW/strings.xml | 5 +++-- app/src/main/res/values/strings.xml | 5 +++-- 14 files changed, 42 insertions(+), 28 deletions(-) diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 58ab4d54..f50ea841 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -45,10 +45,11 @@ Kontakta toxunduqda Kontakta zəng et Kontakt detallarına bax - Sevimlilər nişanını göstər - Qruplar nişanını göstər Göstərilən kontakt sahəsini idarə et Təkrarlanmış kontaktları filtrləməyə çalış + Manage shown tabs + Contacts + Favorites E-poçt diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1990f405..15b0caa8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -45,10 +45,11 @@ Beim Klicken auf den Kontakt Kontakt anrufen Kontaktdetails anzeigen - Zeige Favoriten-Reiter - Zeige Gruppen-Reiter Bearbeite sichtbare Kontaktfelder Versuche Kontaktduplikate herauszufiltern + Manage shown tabs + Contacts + Favorites E-Mail diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 717a7ce1..3c9a6db9 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -45,10 +45,11 @@ Στην επιλογή επαφής Κλήση επαφής Εμφάνιση λεπτομερειών επαφής - Εμφάνιση καρτέλας αγαπημένων - Εμφάνιση καρτέλας ομάδων Manage shown contact fields Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites Email diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index aaded17e..4c4e8087 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -45,10 +45,11 @@ Sur appui du contact Appeler le contact Voir les détails du contact - Afficher l\'onglet favoris - Afficher l\'onglet groupes Configurer l\'affichage des champs des contacts Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites E-mail diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 08d418e7..89206f1d 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -45,10 +45,11 @@ Prilikom dodira kontakta Nazovi kontakt Prikaži pojedinosti o kontaktu - Prikaži karticu favorita - Prikaži karticu grupa Manage shown contact fields Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites E-pošta diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 99536a44..af409ac3 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -45,10 +45,11 @@ On contact click Call contact View contact details - Show favorites tab - Show groups tab Manage shown contact fields Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites 이메일 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index f5ee1255..d3ef8368 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -45,10 +45,11 @@ Ant kontakto paspaudimo Skambinti kontaktui Žiūrėti kontakto detales - Rodyti mėgiamiausiųjų skirtuką - Rodyti grupių skirtuką Manage shown contact fields Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites Elektroninis paštas diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index cb37c1e3..b210f007 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -45,10 +45,11 @@ Ao tocar no contacto Ligar Ver detalhes - Mostrar favoritos - Mostrar grupos Gerir campos a exibir Tentar filtrar contactos duplicados + Manage shown tabs + Contacts + Favorites E-mail diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b107fb1e..988c00bd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -45,10 +45,11 @@ При нажатии на контакт Позвонить контакту Просмотреть подробности о контакте - Показывать вкладку избранного - Показывать вкладку групп Управление отображаемыми полями контактов Отфильтровывать повторяющиеся контакты + Manage shown tabs + Contacts + Favorites Эл. почта diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index c1f6d985..3749269b 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -45,10 +45,11 @@ Po kliknutí na kontakt Zavolať kontakt Zobraziť údaje kontaktu - Zobraziť okno s obľúbenými - Zobraziť okno so skupinami Spravovať zobrazené polia kontaktov Pokúsiť sa vyfiltrovať duplicitné kontakty + Spravovať zobrazené karty + Kontakty + Obľúbené Email diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 98a3fa21..95e65d53 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -45,10 +45,11 @@ Vid kontakttryckning Ring kontakt Visa kontaktuppgifter - Visa fliken Favoriter - Visa fliken Grupper Hantera visade kontaktfält Försök filtrera bort dubblettkontakter + Manage shown tabs + Contacts + Favorites E-post diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9b926f57..e8e413cd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -45,10 +45,11 @@ Kişi tıklandığında Kişiyi ara Kişi bilgilerini göster - Favoriler sekmesini göster - Gruplar sekmesini göster Görüntülenecek kişi alanlarını yönet Çift kişileri filtrelemeyi dene + Manage shown tabs + Contacts + Favorites E-posta diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b81a503a..51a02b91 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -45,10 +45,11 @@ 點擊聯絡人 打電話給聯絡人 顯示聯絡人資料 - 顯示我的最愛頁面 - 顯示群組頁面 管理顯示的聯絡人欄位 試著過濾重複的聯絡人 + Manage shown tabs + Contacts + Favorites 電子信箱 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3c3eede..fc2c4716 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,10 +45,11 @@ On contact click Call contact View contact details - Show favorites tab - Show groups tab Manage shown contact fields Try filtering out duplicate contacts + Manage shown tabs + Contacts + Favorites Email From dbf3b09b26ed91cd228631d50d694ce837b9e1b4 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 20:58:02 +0200 Subject: [PATCH 054/135] add a settings item for managing visible tabs --- .../contacts/activities/SettingsActivity.kt | 8 ++++ .../dialogs/ManageVisibleFieldsDialog.kt | 2 +- .../dialogs/ManageVisibleTabsDialog.kt | 47 +++++++++++++++++++ .../contacts/helpers/Config.kt | 4 ++ .../contacts/helpers/Constants.kt | 1 + app/src/main/res/layout/activity_settings.xml | 22 +++++++++ .../res/layout/dialog_manage_visible_tabs.xml | 42 +++++++++++++++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt create mode 100644 app/src/main/res/layout/dialog_manage_visible_tabs.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt index 19eb91db..ffadba2f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt @@ -9,6 +9,7 @@ import com.simplemobiletools.commons.extensions.updateTextColors import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.dialogs.ManageVisibleFieldsDialog +import com.simplemobiletools.contacts.dialogs.ManageVisibleTabsDialog import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.helpers.ON_CLICK_CALL_CONTACT import com.simplemobiletools.contacts.helpers.ON_CLICK_EDIT_CONTACT @@ -28,6 +29,7 @@ class SettingsActivity : SimpleActivity() { setupPurchaseThankYou() setupCustomizeColors() setupManageShownContactFields() + setupManageShownTabs() setupUseEnglish() setupAvoidWhatsNew() setupShowInfoBubble() @@ -58,6 +60,12 @@ class SettingsActivity : SimpleActivity() { } } + private fun setupManageShownTabs() { + settings_manage_tabs_holder.setOnClickListener { + ManageVisibleTabsDialog(this) + } + } + private fun setupUseEnglish() { settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en") settings_use_english.isChecked = config.useEnglish diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt index cb0b8424..dfd89e5c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt @@ -36,7 +36,7 @@ class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) { } AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() }) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } .setNegativeButton(R.string.cancel, null) .create().apply { activity.setupDialogStuff(view, this) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt new file mode 100644 index 00000000..65c75389 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt @@ -0,0 +1,47 @@ +package com.simplemobiletools.contacts.dialogs + +import android.support.v7.app.AlertDialog +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.views.MyAppCompatCheckbox +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.helpers.CONTACTS_TAB_MASK +import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK +import com.simplemobiletools.contacts.helpers.GROUPS_TAB_MASK + +class ManageVisibleTabsDialog(val activity: BaseSimpleActivity) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_visible_tabs, null) + private val tabs = LinkedHashMap() + + init { + tabs.apply { + put(CONTACTS_TAB_MASK, R.id.manage_visible_tabs_contacts) + put(FAVORITES_TAB_MASK, R.id.manage_visible_tabs_favorites) + put(GROUPS_TAB_MASK, R.id.manage_visible_tabs_groups) + } + + val showTabs = activity.config.showTabs + for ((key, value) in tabs) { + view.findViewById(value).isChecked = showTabs and key != 0 + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + var result = 0 + for ((key, value) in tabs) { + if (view.findViewById(value).isChecked) { + result += key + } + } + + activity.config.showTabs = result + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt index de1bb860..64d4ac77 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt @@ -48,4 +48,8 @@ class Config(context: Context) : BaseConfig(context) { var filterDuplicates: Boolean get() = prefs.getBoolean(FILTER_DUPLICATES, true) set(filterDuplicates) = prefs.edit().putBoolean(FILTER_DUPLICATES, filterDuplicates).apply() + + var showTabs: Int + get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK) + set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 4eb29248..eb14c935 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -12,6 +12,7 @@ const val LOCAL_ACCOUNT_NAME = "local_account_name" const val LOCAL_ACCOUNT_TYPE = "local_account_type" const val ON_CONTACT_CLICK = "on_contact_click" const val SHOW_CONTACT_FIELDS = "show_contact_fields" +const val SHOW_TABS = "show_tabs" const val FILTER_DUPLICATES = "filter_duplicates" const val CONTACT_ID = "contact_id" diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index b1f7cccc..f950dd49 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -77,6 +77,28 @@ + + + + + + + + + + + + + + + + + + From 6f9265a8f46f52e554c3273adf73dc7dc4e43ed3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 30 Jul 2018 21:11:42 +0200 Subject: [PATCH 055/135] show all tabs, if the user selects none of them --- .../contacts/dialogs/ManageVisibleTabsDialog.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt index 65c75389..788b2f23 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt @@ -6,6 +6,7 @@ import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.views.MyAppCompatCheckbox import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.helpers.ALL_TABS_MASK import com.simplemobiletools.contacts.helpers.CONTACTS_TAB_MASK import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK import com.simplemobiletools.contacts.helpers.GROUPS_TAB_MASK @@ -42,6 +43,10 @@ class ManageVisibleTabsDialog(val activity: BaseSimpleActivity) { } } + if (result == 0) { + result = ALL_TABS_MASK + } + activity.config.showTabs = result } } From 399ae4c0b6c134af257e7717879483389c281d46 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 12:05:21 +0200 Subject: [PATCH 056/135] show/hide contact tabs as appropriate --- app/build.gradle | 2 +- .../contacts/activities/MainActivity.kt | 74 +++++++++++++++++-- .../contacts/adapters/ViewPagerAdapter.kt | 35 +++++++-- .../contacts/helpers/Constants.kt | 5 ++ build.gradle | 2 +- 5 files changed, 105 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4bf4f41d..071698b2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.12' + implementation 'com.simplemobiletools:commons:4.5.14' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 353571bf..a59f39ce 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -45,6 +45,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private var werePermissionsHandled = false private var isFirstResume = true private var isGettingContacts = false + private var handledShowTabs = 0 private var storedTextColor = 0 private var storedBackgroundColor = 0 @@ -53,6 +54,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private var storedShowPhoneNumbers = false private var storedStartNameWithSurname = false private var storedFilterDuplicates = true + private var storedShowTabs = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -92,6 +94,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { return } + if (storedShowTabs != config.showTabs) { + viewpager.adapter = null + } + val configShowContactThumbnails = config.showContactThumbnails if (storedShowContactThumbnails != configShowContactThumbnails) { getAllFragments().forEach { @@ -191,6 +197,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { storedShowPhoneNumbers = showPhoneNumbers storedStartNameWithSurname = startNameWithSurname storedFilterDuplicates = filterDuplicates + storedShowTabs = showTabs } } @@ -235,6 +242,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } private fun setupTabColors() { + handledShowTabs = config.showTabs val lastUsedPage = config.lastUsedViewPagerPage main_tabs_holder.apply { background = ColorDrawable(config.backgroundColor) @@ -250,10 +258,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private fun storeLocalAccountData() { if (config.localAccountType == "-1") { - ContactsHelper(this).getContactSources { + ContactsHelper(this).getContactSources { sources -> var localAccountType = "" var localAccountName = "" - it.forEach { + sources.forEach { if (localAccountTypes.contains(it.type)) { localAccountType = it.type localAccountName = it.name @@ -309,6 +317,60 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { tryImportContactsFromFile(intent.data) intent.data = null } + + val showTabs = config.showTabs + val indexesToRemove = ArrayList() + tabsList.forEachIndexed { index, value -> + if (showTabs and value == 0) { + if (main_tabs_holder?.getTabAt(index) != null) { + indexesToRemove.add(index) + } + } + } + + indexesToRemove.reversed().forEach { + main_tabs_holder.removeTabAt(it) + } + + tabsList.forEachIndexed { index, value -> + if (showTabs and value != 0 && handledShowTabs and value == 0) { + main_tabs_holder.addTab(main_tabs_holder.newTab().setIcon(getTabIcon(value)), getTabPosition(value, showTabs)) + } + } + + handledShowTabs = config.showTabs + } + + private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { + CONTACTS_TAB_MASK -> R.drawable.ic_person + FAVORITES_TAB_MASK -> R.drawable.ic_star_on + else -> R.drawable.ic_group + }) + + private fun getTabPosition(value: Int, showTabs: Int): Int { + return when (value) { + CONTACTS_TAB_MASK -> 0 + FAVORITES_TAB_MASK -> { + if (showTabs and CONTACTS_TAB_MASK != 0) { + 1 + } else { + 0 + } + } + else -> { + if (showTabs and CONTACTS_TAB_MASK != 0) { + if (showTabs and FAVORITES_TAB_MASK != 0) { + 2 + } else { + 1 + } + } else if (showTabs and FAVORITES_TAB_MASK != 0) { + 1 + } else { + 0 + } + } + } } private fun showSortingDialog() { @@ -383,13 +445,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { FilePickerDialog(this, pickFile = false, showFAB = true) { ExportContactsDialog(this, it) { file, contactSources -> Thread { - ContactsHelper(this).getContacts { - val contacts = it.filter { contactSources.contains(it.source) } + ContactsHelper(this).getContacts { allContacts -> + val contacts = allContacts.filter { contactSources.contains(it.source) } if (contacts.isEmpty()) { toast(R.string.no_entries_for_exporting) } else { - VcfExporter().exportContacts(this, file, contacts as ArrayList, true) { - toast(when (it) { + VcfExporter().exportContacts(this, file, contacts as ArrayList, true) { result -> + toast(when (result) { VcfExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful VcfExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed else -> R.string.exporting_failed diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt index beb17f42..4ef918b3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt @@ -5,15 +5,21 @@ import android.view.View import android.view.ViewGroup import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.MainActivity +import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.fragments.MyViewPagerFragment +import com.simplemobiletools.contacts.helpers.CONTACTS_TAB_MASK +import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK +import com.simplemobiletools.contacts.helpers.tabsList import com.simplemobiletools.contacts.models.Contact class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList) : PagerAdapter() { + val showTabs = activity.config.showTabs override fun instantiateItem(container: ViewGroup, position: Int): Any { val layout = getFragment(position) val view = activity.layoutInflater.inflate(layout, container, false) container.addView(view) + (view as MyViewPagerFragment).apply { setupFragment(activity) refreshContacts(contacts) @@ -25,12 +31,31 @@ class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList R.layout.fragment_contacts - 1 -> R.layout.fragment_favorites - else -> R.layout.fragment_groups + private fun getFragment(position: Int): Int { + return when (position) { + 0 -> { + when { + showTabs and CONTACTS_TAB_MASK != 0 -> R.layout.fragment_contacts + showTabs and FAVORITES_TAB_MASK != 0 -> R.layout.fragment_favorites + else -> R.layout.fragment_groups + } + } + 1 -> { + if (showTabs and CONTACTS_TAB_MASK != 0) { + if (showTabs and FAVORITES_TAB_MASK != 0) { + R.layout.fragment_favorites + } else { + R.layout.fragment_groups + } + } else { + R.layout.fragment_groups + } + } + else -> R.layout.fragment_groups + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index eb14c935..9dd91933 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -31,6 +31,11 @@ const val FAVORITES_TAB_MASK = 2 const val GROUPS_TAB_MASK = 4 const val ALL_TABS_MASK = 7 +val tabsList = arrayListOf(CONTACTS_TAB_MASK, + FAVORITES_TAB_MASK, + GROUPS_TAB_MASK +) + // contact photo changes const val PHOTO_ADDED = 1 const val PHOTO_REMOVED = 2 diff --git a/build.gradle b/build.gradle index 94d76296..939d19d8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.51' + ext.kotlin_version = '1.2.60' repositories { google() From e1d21a9ed462d2945cfebfa3df26f21e2003c4bf Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 13:43:59 +0200 Subject: [PATCH 057/135] alwas recreate the viewpager tabs to avoid glitching --- .../contacts/activities/MainActivity.kt | 54 +++---------------- app/src/main/res/layout/activity_main.xml | 22 +------- 2 files changed, 9 insertions(+), 67 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index a59f39ce..a386bcc6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -318,61 +318,23 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { intent.data = null } - val showTabs = config.showTabs - val indexesToRemove = ArrayList() + main_tabs_holder.removeAllTabs() + var skippedTabs = 0 tabsList.forEachIndexed { index, value -> - if (showTabs and value == 0) { - if (main_tabs_holder?.getTabAt(index) != null) { - indexesToRemove.add(index) - } + if (config.showTabs and value == 0) { + skippedTabs++ + } else { + main_tabs_holder.addTab(main_tabs_holder.newTab().setIcon(getTabIcon(index)), index - skippedTabs, config.lastUsedViewPagerPage == index - skippedTabs) } } - - indexesToRemove.reversed().forEach { - main_tabs_holder.removeTabAt(it) - } - - tabsList.forEachIndexed { index, value -> - if (showTabs and value != 0 && handledShowTabs and value == 0) { - main_tabs_holder.addTab(main_tabs_holder.newTab().setIcon(getTabIcon(value)), getTabPosition(value, showTabs)) - } - } - - handledShowTabs = config.showTabs } private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { - CONTACTS_TAB_MASK -> R.drawable.ic_person - FAVORITES_TAB_MASK -> R.drawable.ic_star_on + LOCATION_CONTACTS_TAB -> R.drawable.ic_person + LOCATION_FAVORITES_TAB -> R.drawable.ic_star_on else -> R.drawable.ic_group }) - private fun getTabPosition(value: Int, showTabs: Int): Int { - return when (value) { - CONTACTS_TAB_MASK -> 0 - FAVORITES_TAB_MASK -> { - if (showTabs and CONTACTS_TAB_MASK != 0) { - 1 - } else { - 0 - } - } - else -> { - if (showTabs and CONTACTS_TAB_MASK != 0) { - if (showTabs and FAVORITES_TAB_MASK != 0) { - 2 - } else { - 1 - } - } else if (showTabs and FAVORITES_TAB_MASK != 0) { - 1 - } else { - 0 - } - } - } - } - private fun showSortingDialog() { ChangeSortingDialog(this) { refreshContacts(CONTACTS_TAB_MASK or FAVORITES_TAB_MASK) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 95c89e52..1c26ff25 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,27 +14,7 @@ app:tabIndicatorColor="@android:color/white" app:tabIndicatorHeight="2dp" app:tabMinWidth="150dp" - app:tabSelectedTextColor="@android:color/white"> - - - - - - - - + app:tabSelectedTextColor="@android:color/white"/> Date: Thu, 2 Aug 2018 14:38:30 +0200 Subject: [PATCH 058/135] hide the viewpager tabs if only one is visible --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index a386bcc6..a1225c5d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -327,6 +327,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { main_tabs_holder.addTab(main_tabs_holder.newTab().setIcon(getTabIcon(index)), index - skippedTabs, config.lastUsedViewPagerPage == index - skippedTabs) } } + + main_tabs_holder.beVisibleIf(skippedTabs < 2) } private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { From 7b577aa082132df9480b461fb1b96d0a0f0be638 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 15:16:24 +0200 Subject: [PATCH 059/135] do not spend time fetching data for hidden fragments --- .../contacts/fragments/MyViewPagerFragment.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 805eb5ee..defca5ad 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -86,6 +86,12 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } fun refreshContacts(contacts: ArrayList) { + if ((config.showTabs and CONTACTS_TAB_MASK == 0 && this is ContactsFragment) || + (config.showTabs and FAVORITES_TAB_MASK == 0 && this is FavoritesFragment) || + (config.showTabs and GROUPS_TAB_MASK == 0 && this is GroupsFragment)) { + return + } + if (config.lastUsedContactSource.isEmpty()) { val grouped = contacts.groupBy { it.source }.maxWith(compareBy { it.value.size }) config.lastUsedContactSource = grouped?.key ?: "" From 7e10bbce6d7ef07ecf6142d58a281d49728e8717 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 16:17:49 +0200 Subject: [PATCH 060/135] show company contacts if they have at least company or title filled --- .../simplemobiletools/contacts/helpers/ContactsHelper.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index a2829ff7..0d7ad2d1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -380,8 +380,12 @@ class ContactsHelper(val activity: Activity) { if (cursor?.moveToFirst() == true) { do { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) - val company = cursor.getStringValue(CommonDataKinds.Organization.COMPANY) ?: continue - val title = cursor.getStringValue(CommonDataKinds.Organization.TITLE) ?: continue + val company = cursor.getStringValue(CommonDataKinds.Organization.COMPANY) ?: "" + val title = cursor.getStringValue(CommonDataKinds.Organization.TITLE) ?: "" + if (company.isEmpty() && title.isEmpty()) { + continue + } + val organization = Organization(company, title) organizations.put(id, organization) } while (cursor.moveToNext()) From 473dcc946d54ec5208f3d66f934709dd1795f882 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 16:28:08 +0200 Subject: [PATCH 061/135] trim colons at importing company fields --- .../com/simplemobiletools/contacts/helpers/VcfImporter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index 0e27d8cd..8de88544 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -266,7 +266,7 @@ class VcfImporter(val activity: SimpleActivity) { curCompany = if (company.startsWith(";")) { company.substringAfter(":").trim(';') } else { - company + company.trimStart(':') } } @@ -274,7 +274,7 @@ class VcfImporter(val activity: SimpleActivity) { curJobPosition = if (jobPosition.startsWith(";")) { jobPosition.substringAfter(":") } else { - jobPosition + jobPosition.trimStart(':') } } From 73ff7f440e75138556fbf63fdb7fb456fb32fd2f Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 16:39:35 +0200 Subject: [PATCH 062/135] recheck visible option items at creating the fragments --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index a1225c5d..6f10a401 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -329,6 +329,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } main_tabs_holder.beVisibleIf(skippedTabs < 2) + invalidateOptionsMenu() } private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { From 23d6e5471855d971bc8f5278f5e42c151aad951f Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 22:02:10 +0200 Subject: [PATCH 063/135] fix the sorting of organization contacts --- .../com/simplemobiletools/contacts/models/Contact.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index b35c5130..dd5f3250 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -16,8 +16,8 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m } override fun compareTo(other: Contact): Int { - val firstString: String - val secondString: String + var firstString: String + var secondString: String when { sorting and SORT_BY_FIRST_NAME != 0 -> { @@ -34,6 +34,14 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m } } + if (firstString.isEmpty() && firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty() && organization.company.isNotEmpty()) { + firstString = organization.company + } + + if (secondString.isEmpty() && other.firstName.isEmpty() && other.middleName.isEmpty() && other.surname.isEmpty() && other.organization.company.isNotEmpty()) { + secondString = other.organization.company + } + var result = if (firstString.firstOrNull()?.isLetter() == true && secondString.firstOrNull()?.isLetter() == false) { -1 } else if (firstString.firstOrNull()?.isLetter() == false && secondString.firstOrNull()?.isLetter() == true) { From 39b0171bc26bb76480745bc99991c27c9b8ec733 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 23:37:20 +0200 Subject: [PATCH 064/135] adding a string for call confirmation dialog --- app/src/main/res/values-az/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hr/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 14 files changed, 14 insertions(+) diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index f50ea841..ae62308d 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-poçt diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 15b0caa8..64591683 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-Mail diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3c9a6db9..440efcb3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call Email diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4c4e8087..97c2c5ae 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-mail diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 89206f1d..58b743d9 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-pošta diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index af409ac3..83faac29 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call 이메일 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index d3ef8368..c191ccb9 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call Elektroninis paštas diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b210f007..d2445a9e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-mail diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 988c00bd..55577789 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call Эл. почта diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3749269b..764e5951 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -50,6 +50,7 @@ Spravovať zobrazené karty Kontakty Obľúbené + Zobraziť pred spustením hovoru okno na jeho potvrdenie Email diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 95e65d53..49ea418d 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-post diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e8e413cd..9b6057a9 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call E-posta diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 51a02b91..0ae83827 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call 電子信箱 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc2c4716..c5296c61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Manage shown tabs Contacts Favorites + Show a call confirmation dialog before initiating a call Email From 096b3888c217b809250c02c193b2653f2b7f23d7 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 2 Aug 2018 23:42:52 +0200 Subject: [PATCH 065/135] adding a settings toggle for call confirmation dialog --- .../contacts/activities/SettingsActivity.kt | 9 ++++ .../contacts/helpers/Config.kt | 4 ++ .../contacts/helpers/Constants.kt | 1 + app/src/main/res/layout/activity_settings.xml | 46 ++++++++++++++++--- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt index ffadba2f..37811085 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt @@ -37,6 +37,7 @@ class SettingsActivity : SimpleActivity() { setupShowPhoneNumbers() setupStartNameWithSurname() setupFilterDuplicates() + setupShowCallConfirmation() setupOnContactClick() updateTextColors(settings_holder) } @@ -144,4 +145,12 @@ class SettingsActivity : SimpleActivity() { ON_CLICK_VIEW_CONTACT -> R.string.view_contact else -> R.string.edit_contact }) + + private fun setupShowCallConfirmation() { + settings_show_call_confirmation.isChecked = config.showCallConfirmation + settings_show_call_confirmation_holder.setOnClickListener { + settings_show_call_confirmation.toggle() + config.showCallConfirmation = settings_show_call_confirmation.isChecked + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt index 64d4ac77..2370665b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt @@ -52,4 +52,8 @@ class Config(context: Context) : BaseConfig(context) { var showTabs: Int get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK) set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply() + + var showCallConfirmation: Boolean + get() = prefs.getBoolean(SHOW_CALL_CONFIRMATION, false) + set(showCallConfirmation) = prefs.edit().putBoolean(SHOW_CALL_CONFIRMATION, showCallConfirmation).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 9dd91933..a9bf3b56 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -14,6 +14,7 @@ const val ON_CONTACT_CLICK = "on_contact_click" const val SHOW_CONTACT_FIELDS = "show_contact_fields" const val SHOW_TABS = "show_tabs" const val FILTER_DUPLICATES = "filter_duplicates" +const val SHOW_CALL_CONFIRMATION = "show_call_confirmation" const val CONTACT_ID = "contact_id" const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index f950dd49..8f260f80 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1,6 +1,7 @@ @@ -118,7 +119,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/use_english_language"/> + android:text="@string/use_english_language" + app:switchPadding="@dimen/medium_margin"/> @@ -141,7 +143,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/avoid_whats_new"/> + android:text="@string/avoid_whats_new" + app:switchPadding="@dimen/medium_margin"/> @@ -164,7 +167,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/show_info_bubble"/> + android:text="@string/show_info_bubble" + app:switchPadding="@dimen/medium_margin"/> @@ -187,7 +191,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/show_contact_thumbnails"/> + android:text="@string/show_contact_thumbnails" + app:switchPadding="@dimen/medium_margin"/> @@ -210,7 +215,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/show_phone_numbers"/> + android:text="@string/show_phone_numbers" + app:switchPadding="@dimen/medium_margin"/> @@ -233,7 +239,8 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/start_name_with_surname"/> + android:text="@string/start_name_with_surname" + app:switchPadding="@dimen/medium_margin"/> @@ -256,7 +263,32 @@ android:clickable="false" android:paddingLeft="@dimen/medium_margin" android:paddingStart="@dimen/medium_margin" - android:text="@string/filter_duplicates"/> + android:text="@string/filter_duplicates" + app:switchPadding="@dimen/medium_margin"/> + + + + + + From 46e81d6268204e7447342987b58509eeb582c598 Mon Sep 17 00:00:00 2001 From: ScratchBuild Date: Fri, 3 Aug 2018 17:39:22 +0900 Subject: [PATCH 066/135] Add Japanese translation --- app/src/main/res/values-ja/strings.xml | 129 +++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 app/src/main/res/values-ja/strings.xml diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..00b4b2c0 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,129 @@ + + Simple Contacts + 連絡先 + 住所 + 挿入中… + 更新中… + 内部ストレージ + 内部ストレージ (他のアプリからは表示されません) + 会社 + 役職 + ウェブサイト + 連絡先にSMSを送信 + 連絡先にメールを送信 + グループにSMSを送信 + グループにメールを送信 + + 新しい連絡先 + 連絡先を編集 + 連絡先を選択 + 連絡先を選択 + + ミドルネーム + + + + グループなし + 新しいグループを作成 + グループから削除 + このグループは空です + 連絡先を追加 + 連絡先グループがありません + グループを作成 + グループに追加 + アカウントの下にグループを作成 + + + 写真を撮影 + 写真を選択 + 写真を削除 + + + 姓を先に表示 + メイン画面に電話番号を表示 + 連絡先のサムネイルを表示 + 連絡先をタップ + 連絡先に発信 + 連絡先の詳細を表示 + 連絡先に表示するフィールドを管理 + 重複した連絡先を除外する + 表示するタブを管理 + 連絡先 + お気に入り + 発信する前に確認ダイアログを表示する + + + メール + 自宅 + 職場 + その他 + + + 番号 + 携帯 + Main + 職場FAX + 自宅FAX + ポケベル + 電話番号が見つかりません + + + 誕生日 + 記念日 + + + お気に入りの連絡先はまだありません + お気に入りを追加 + お気に入りに追加 + お気に入りから削除 + 連絡先を編集するには編集画面に切り替えてください + + + 連絡先を検索 + お気に入りを検索 + + + 連絡先をインポート + 連絡先をエクスポート + .vcfファイルから連絡先をインポート + 連絡先を.vcfファイルにエクスポート + Target contact source + Include contact sources + ファイル名 (.vcfを含めない) + + + 表示する項目を選択 + 敬称(名前の前) + 敬称(名前の後) + 電話番号 + メール + 住所 + 予定 (誕生日、記念日) + メモ + 所属 + ウェブサイト + グループ + インポート元 + + + 連絡先に表示される項目(フィールド)を変更することはできますか? + 可能です。[設定] -> [連絡先に表示するフィールドを管理] から、表示されるフィールドを選択することができます。これらの中にはデフォルトで無効になっているものもあるので、あなたは新しいものを見つけるかもしれません。 + + + + 連絡先を管理するシンプルなアプリ (広告表示なし)。 + + 連絡先を作成または管理するためのシンプルなアプリです。連絡先はお使いの端末上にのみ保存されますが、Googleや他のアカウントと同期することもできます。お気に入りの連絡先を別のリストとして表示することができます。 + + 友人のメールアドレスや予定の管理にも使用できます。これらは複数の項目で並べ替えやフィルタリングする機能があり、名前を\"姓 名\"の順に表示することもできます。 + + 広告や不要なアクセス許可は含まれていません。完全にオープンソースで、色のカスタマイズも可能です。 + + このアプリは大きな一連のアプリの一つです。 その他のアプリは http://www.simplemobiletools.com で見つけることができます。 + + + + From 828c3337cc5cee15a6c32b959d97fe2aa566eef3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 11:58:53 +0200 Subject: [PATCH 067/135] adding the call confirmation dialog --- app/build.gradle | 2 +- .../dialogs/CallConfirmationDialog.kt | 30 ++++++++++++++++++ .../contacts/extensions/Activity.kt | 11 +++++++ app/src/main/res/anim/pulsing_animation.xml | 14 ++++++++ .../main/res/drawable-hdpi/ic_phone_big.png | Bin 0 -> 566 bytes .../main/res/drawable-xhdpi/ic_phone_big.png | Bin 0 -> 716 bytes .../main/res/drawable-xxhdpi/ic_phone_big.png | Bin 0 -> 993 bytes .../res/drawable-xxxhdpi/ic_phone_big.png | Bin 0 -> 1367 bytes .../res/layout/dialog_call_confirmation.xml | 18 +++++++++++ app/src/main/res/values-az/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hr/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 24 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt create mode 100644 app/src/main/res/anim/pulsing_animation.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_phone_big.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_phone_big.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_phone_big.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_phone_big.png create mode 100644 app/src/main/res/layout/dialog_call_confirmation.xml diff --git a/app/build.gradle b/app/build.gradle index 071698b2..c58036a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.14' + implementation 'com.simplemobiletools:commons:4.5.16' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt new file mode 100644 index 00000000..bad59311 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt @@ -0,0 +1,30 @@ +package com.simplemobiletools.contacts.dialogs + +import android.support.v7.app.AlertDialog +import android.view.animation.AnimationUtils +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.models.Contact +import kotlinx.android.synthetic.main.dialog_call_confirmation.view.* + +class CallConfirmationDialog(val activity: BaseSimpleActivity, val contact: Contact, private val callback: () -> Unit) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_call_confirmation, null) + + init { + AlertDialog.Builder(activity) + .setNegativeButton(R.string.cancel, null) + .create().apply { + val title = String.format(activity.getString(R.string.call_person), contact.getFullName()) + activity.setupDialogStuff(view, this, titleText = title) { + view.call_confirm_phone.apply { + startAnimation(AnimationUtils.loadAnimation(activity, R.anim.pulsing_animation)) + setOnClickListener { + callback.invoke() + dismiss() + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt index ee5be77c..60ead47a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt @@ -14,6 +14,7 @@ import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity +import com.simplemobiletools.contacts.dialogs.CallConfirmationDialog import com.simplemobiletools.contacts.helpers.ContactsHelper import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.helpers.VcfExporter @@ -36,6 +37,16 @@ fun SimpleActivity.startCallIntent(recipient: String) { } fun SimpleActivity.tryStartCall(contact: Contact) { + if (config.showCallConfirmation) { + CallConfirmationDialog(this, contact) { + startCall(contact) + } + } else { + startCall(contact) + } +} + +fun SimpleActivity.startCall(contact: Contact) { val numbers = contact.phoneNumbers if (numbers.size == 1) { startCallIntent(numbers.first().value) diff --git a/app/src/main/res/anim/pulsing_animation.xml b/app/src/main/res/anim/pulsing_animation.xml new file mode 100644 index 00000000..119d4b04 --- /dev/null +++ b/app/src/main/res/anim/pulsing_animation.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/drawable-hdpi/ic_phone_big.png b/app/src/main/res/drawable-hdpi/ic_phone_big.png new file mode 100644 index 0000000000000000000000000000000000000000..61d59bd472a6143e571679dffb46189ac517bfd0 GIT binary patch literal 566 zcmV-60?GY}P)Gx6~8e8ySEe*e-WJ{*gYnb z_=~i>!tM%5#b4CnGj>-Y6bOzFlMBzvpgxhqYhs;@v$sKZpXUo{_AQ{DhsGd2N{o&dj`GnJJ+FA2S zqOgoV^IpP(YP`X12kC$2EZ~nxxKM_V;dtOFYl>JkRsI zApF8Ct`L(e%;Fc(iElWIjdOe}8Ziu8BSa(4VC$@C#6#@8FZyr=dtZt^9LL^Iq7S>U z_n+v)8ti2heVC8E3Zf5_u-97jp+ELUi9Xc9-d@p%oY)KTsc6F=L?W~3!vpNK6Mfi@ zy$zxdlhFd?fl%9Xpr!Uo_%x%lUCdD-Q5H z*N7{6(T14aAdzUs??lBoKe=efa-wtFn6DGn2zHk=;sQSBKH(maQCz|3fce?Q9VEmw zUEIPfOo_xLq{OsP+`>{!Im9Jo$Fz(0#U-rA)Ja@IBAyrlA<~OW=#A+lpNTv8g!7mt zh)XC&xO(Ce=3#nFI&lX-a~abGz7uzlgD6bf-1?b25pF5(iz|4S)r6bGyKespFpzh| z6=dTf;l@Pv7I>cwG$Q|7`GY7(h;TFbKr9iLQvkq27SVttX8vnNRe~(x3$Z`~u3@^( z3c6CAEd0(p^7_$|AP4v_^7(J+DvZ7axk+~W`EU6uj6MVj(Uf;A=4-eLqcy=c6VGD4 zhO02D6Gf0mbl^jI21%{w8|3w(6psjYh+@yozX@6b4CLV!(b+&6W4?+21OKp#=*{9L zXbUj#5d*QHE5JZ`?zt9VAU-=>3o!5w)wt+dfPpXQ&Lh_X4E)P%_W}&0W}bTi2GX*? z$p9l_J|Wiv4E#njcDWZ|AQ9a-Ph`%BF1$m0YB7St*cmQb@dHKZ#saPqy>omc`tcKa yXwFzxvz2`u=K?o*%w=Ztvv{88d7kHa0iFVp^xjFRk^X4_0000DtVbPZ(v|#l*76jbBLfoB75_(u_3U zXd+dOdB#{$Eoh!Gj8sdTXAB_Ks^%FzNwvOtMh8-DYo75lsg5wuc%M`knP)shst3$7 z?jY5B<{6ie>TmOmqewN0O7n{?NVkFc#bTs8!8~I;sU9}Z_>WY-n`gXCs!7bN3w_vr z<`;XB?m_d5rAgP>JYxvyRxrQ#kaVY+UtCVQ$ILHwAl<9x7Y$4z)xG8!zmjSv^Nj0A z^Nj`QrAJ2=Hs7dV5QXZ@Qsy5oC{GZFDNhg$j8mQx#c5QYLcfv?VL1~vAWJjrnV9hvSw1r{V^6Z}Y+}YgWNA&M z342CnfrOvPGLU)`Hj9k?3h$6@7ZcW?i7b6tz=X%h_M8bzFotY z-%zPU+Yxf32@jI3g}p3DhuLtJRidqECfgs>DA7%XJgz|1{7uM}O0)`N2su%Sjv%C& z1C;0qLMF1El{f17kC0JpY~_uW7!^sht(7-+p^=aY>^IBiZm;7#-sD=gq0&kRhto_* zGsjvY7ULVHHk6k+nuV=2a3V36B_7DVnVA+k@;aBZ7fY4?T>c|er{@V6&Y!%-%^c6p zY{ptF&%#t$@!%<9{$SZzk*7o#s8Y%M#0+IO%L~f|@|4;FRciT>m}ah{GLJkx2q?J) zsw~9s#C^w-h2?RuK$Ql*A#NxK6qd)q0##~wn_RrcB8B8}ut1dx9wZlo*?W%V0j!vC zF}Zn;MdnN%D82`fiR5A!=TV)WC(eouTQij0bmE}YJaJZhSb=}Y)pu;jQuF|SfdZ)G zNpjV~An+O}fa4iY5wPOL>inTVK~!-yjS3XRCUj7sAZodpMg0o|Ppa?eQMH&?-h$Xp*wh9!%<~+s-1qz~$ z!}yR!W|h(A5sPphpD>1WznNduuq{{fEfXl*@#Y`(?92`P$VBq>D-~8gs9`M*;3A&j zd-@UAo@K0jF&`UqBv)}KkMa`l@io8GmYxh{;_RCb5)ly*5fKp)5fPaT{t(GNvbJp+ P00000NkvXXu0mjf)A`04 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_phone_big.png b/app/src/main/res/drawable-xxxhdpi/ic_phone_big.png new file mode 100644 index 0000000000000000000000000000000000000000..707350b0057539085e1c7932d5d4d042e3e79cd0 GIT binary patch literal 1367 zcmV-d1*rOoP)G0000FZNklI+diMinpn4*$^^xB~3laSUBMk()NUf z5|*KYv{kaKg%NJX!z~s@=!S50PS4={1B|h4jgy1xM zj4=tpLHMZB=eQtjg_jjflCcC{4lqeZ0TrO%k`XZwA77Y+;8T3eGzq~Yx(!Fg#dtZy zBpG|-FP{f3n!ePa{RSTvcc+#KQcxu{;0T9@khmm{Cn+U#;-}M?Wke76K+!cQL&cdkBq*G zKOYAwwov>jp$TO<#GkRW2yZL?sMuZcM@0eS6n|7)Pna(hzYCV6JShGW6CXTJvUNInBH1L3fp)_hRFUi{6E8eUh#gIQ(16(_8%J#uA6!6)hfI7>!Vp4iW#WUq z2r-n}CO&wR5Fb%s;(=z&B*e`oJ~&=0=0-*$F%lu6WW&@0%@Bz+XqjxY<@#KdLz?o`6eU@epMu^p9!naDCG z>EjAQ4WpS!+9=?2LJfB~3xxU%Bh)xnHc1;RGmB7DS;ria;Y_}vC7<&Ym$07wKd>!j zgqp=>=7hZ&$M22h3HGI?Rd*agxcMH#S196f{?0-^=OVVIhTe>SRK~ukzZSkjvI=_f z1ShdJh1KzS=yk$Xa%rmZe?)f57)1x(;sGw@Xm(^X)@4yE2dLm2nUC z!s1r}30Q}Tl>ou{TpyKvXp4D-{qK7cgH1M8&dvqXHu1NT#ZQsA$NuDj+hprLPKz ziXu*BiVBE|y4*>b3W$mocufUF#=5+x0wQBWK2ZUYu@Ud9fXG;rXDL$wQLzNKGg}2j zMQu)JpbChLt$2||Dj+HvasdNXKxAylJxtQz+^8sIC!S@FhNnkGDcf@kU8&Lpji_kE zAw0=o(pAASCfT4V`*A1jSVS5gH^~j9Y{8j4L1z|{tL`aGQx%F>gTuL*7x;$3!F5#e zF15{t-Xzv#R}SYauHk-Oa60<31DlNF^*K5Csh=_=Yh=_=Yh=_=Yh=_=Y Z$hQiM&3h@-rdR*~002ovPDHLkV1i)GZ65#t literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/dialog_call_confirmation.xml b/app/src/main/res/layout/dialog_call_confirmation.xml new file mode 100644 index 00000000..9d406f6b --- /dev/null +++ b/app/src/main/res/layout/dialog_call_confirmation.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index ae62308d..316127b9 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -13,6 +13,7 @@ Kontaktlara e-poçt göndər Grupa SMS göndər Grupa e-poçt göndər + Call %s Yeni kontakt Redaktə et diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 64591683..0ff4ffd4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,6 +13,7 @@ Sende E-Mail an Kontakte Sende SMS an Gruppe Sende E-Mail an Gruppe + Call %s Neuer Kontakt Kontakt bearbeiten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 440efcb3..74ec8836 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -13,6 +13,7 @@ Send email to contacts Send SMS to group Send email to group + Call %s Νέα επαφή Επεξεργασία επαφής diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 97c2c5ae..f26a7906 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -13,6 +13,7 @@ Envoyer un e-mail aux contacts Envoyer un SMS au groupe Envoyer un e-mail au groupe + Call %s Nouveau contact Modifier contact diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 58b743d9..f5c127bc 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -13,6 +13,7 @@ Send email to contacts Send SMS to group Send email to group + Call %s Novi kontakt Uredi kontakt diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 00b4b2c0..6088a8c7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -13,6 +13,7 @@ 連絡先にメールを送信 グループにSMSを送信 グループにメールを送信 + Call %s 新しい連絡先 連絡先を編集 diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 83faac29..0d26d31d 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -13,6 +13,7 @@ Send email to contacts Send SMS to group Send email to group + Call %s 새로운 연락처 연락처 수정 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index c191ccb9..856c888f 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -13,6 +13,7 @@ Send email to contacts Send SMS to group Send email to group + Call %s Naujas kontaktas Redaguoti kontaktą diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d2445a9e..d1c90985 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -13,6 +13,7 @@ Enviar e-mail aos contactos Enviar SMS para o grupo Enviar e-mail para o grupo + Call %s Novo contacto Editar contacto diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 55577789..9d6a161d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -13,6 +13,7 @@ Отправить письмо контактам Отправить SMS группе Отправить письмо группе + Call %s Новый контакт Редактировать контакт diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 764e5951..8efa53a1 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -13,6 +13,7 @@ Poslať kontaktom email Poslať skupine SMS Poslať skupine email + Zavolať %s Nový kontakt Upraviť kontakt diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 49ea418d..5ded4f45 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -13,6 +13,7 @@ Skicka e-post till kontakter Skicka sms till grupp Skicka e-post till grupp + Call %s Ny kontakt Redigera kontakt diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9b6057a9..aa343df4 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -13,6 +13,7 @@ Kişilere e-posta gönder Gruba SMS gönder Gruba e-posta gönder + Call %s Yeni kişi Kişiyi düzenle diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0ae83827..9383c834 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -13,6 +13,7 @@ 發送電子郵件給聯絡人 發送簡訊給群組 發送電子郵件給群組 + Call %s 新聯絡人 編輯聯絡人 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5296c61..1ce3a4b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ Send email to contacts Send SMS to group Send email to group + Call %s New contact Edit contact From 19d7ed33b312bb95624859494f4202ebedc7a235 Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 13:11:08 +0200 Subject: [PATCH 068/135] adding a new string for Recent Calls tab --- app/src/main/res/values-az/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hr/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 15 files changed, 15 insertions(+) diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 316127b9..3e362b2d 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0ff4ffd4..a1527c07 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 74ec8836..7af6ada3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f26a7906..3dec8f48 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index f5c127bc..4bc2b827 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6088a8c7..703b96d8 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -51,6 +51,7 @@ 表示するタブを管理 連絡先 お気に入り + Recent calls 発信する前に確認ダイアログを表示する diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 0d26d31d..7b62c688 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 856c888f..be456240 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d1c90985..db97baf4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9d6a161d..3505d21b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 8efa53a1..e99c2230 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -51,6 +51,7 @@ Spravovať zobrazené karty Kontakty Obľúbené + Predošlé hovory Zobraziť pred spustením hovoru okno na jeho potvrdenie diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5ded4f45..4b3ba2cd 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index aa343df4..aeb4f28a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9383c834..f41009c8 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1ce3a4b0..4d456010 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Manage shown tabs Contacts Favorites + Recent calls Show a call confirmation dialog before initiating a call From a83886a5bd9ff62ee9ecd8261d21f66947fb6dda Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 13:52:44 +0200 Subject: [PATCH 069/135] creating a Recents tab --- .../contacts/activities/MainActivity.kt | 12 +++-- .../contacts/adapters/ViewPagerAdapter.kt | 41 ++++++++---------- .../dialogs/ManageVisibleTabsDialog.kt | 6 +-- .../contacts/fragments/MyViewPagerFragment.kt | 20 ++++++--- .../contacts/fragments/RecentsFragment.kt | 14 ++++++ .../contacts/helpers/Constants.kt | 11 +++-- app/src/main/res/drawable-hdpi/ic_clock.png | Bin 0 -> 487 bytes app/src/main/res/drawable-xhdpi/ic_clock.png | Bin 0 -> 655 bytes app/src/main/res/drawable-xxhdpi/ic_clock.png | Bin 0 -> 947 bytes .../main/res/drawable-xxxhdpi/ic_clock.png | Bin 0 -> 1279 bytes .../res/layout/dialog_manage_visible_tabs.xml | 8 ++++ app/src/main/res/layout/fragment_recents.xml | 10 +++++ 12 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_clock.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_clock.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_clock.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_clock.png create mode 100644 app/src/main/res/layout/fragment_recents.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 6f10a401..c675ddb3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -37,6 +37,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_contacts.* import kotlinx.android.synthetic.main.fragment_favorites.* import kotlinx.android.synthetic.main.fragment_groups.* +import kotlinx.android.synthetic.main.fragment_recents.* import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { @@ -168,7 +169,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { val currentPage = viewpager?.currentItem menu.apply { findItem(R.id.search).isVisible = currentPage != LOCATION_GROUPS_TAB - findItem(R.id.sort).isVisible = currentPage != LOCATION_GROUPS_TAB + findItem(R.id.sort).isVisible = currentPage != LOCATION_GROUPS_TAB && currentPage != LOCATION_RECENTS_TAB findItem(R.id.filter).isVisible = currentPage != LOCATION_GROUPS_TAB } setupSearch(menu) @@ -278,7 +279,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private fun initFragments() { refreshContacts(ALL_TABS_MASK) - viewpager.offscreenPageLimit = 2 + viewpager.offscreenPageLimit = 3 viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { if (isSearchOpen) { @@ -328,13 +329,14 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } - main_tabs_holder.beVisibleIf(skippedTabs < 2) + main_tabs_holder.beVisibleIf(skippedTabs < 3) invalidateOptionsMenu() } private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { LOCATION_CONTACTS_TAB -> R.drawable.ic_person LOCATION_FAVORITES_TAB -> R.drawable.ic_star_on + LOCATION_RECENTS_TAB -> R.drawable.ic_clock else -> R.drawable.ic_group }) @@ -465,6 +467,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { favorites_fragment?.refreshContacts(it) } + if (refreshTabsMask and RECENTS_TAB_MASK != 0) { + recents_fragment?.refreshContacts(it) + } + if (refreshTabsMask and GROUPS_TAB_MASK != 0) { if (refreshTabsMask == GROUPS_TAB_MASK) { groups_fragment.skipHashComparing = true diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt index 4ef918b3..5a61132d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt @@ -7,9 +7,7 @@ import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.MainActivity import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.fragments.MyViewPagerFragment -import com.simplemobiletools.contacts.helpers.CONTACTS_TAB_MASK -import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK -import com.simplemobiletools.contacts.helpers.tabsList +import com.simplemobiletools.contacts.helpers.* import com.simplemobiletools.contacts.models.Contact class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList) : PagerAdapter() { @@ -36,26 +34,23 @@ class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList { - when { - showTabs and CONTACTS_TAB_MASK != 0 -> R.layout.fragment_contacts - showTabs and FAVORITES_TAB_MASK != 0 -> R.layout.fragment_favorites - else -> R.layout.fragment_groups - } - } - 1 -> { - if (showTabs and CONTACTS_TAB_MASK != 0) { - if (showTabs and FAVORITES_TAB_MASK != 0) { - R.layout.fragment_favorites - } else { - R.layout.fragment_groups - } - } else { - R.layout.fragment_groups - } - } - else -> R.layout.fragment_groups + val fragments = arrayListOf() + if (showTabs and CONTACTS_TAB_MASK != 0) { + fragments.add(R.layout.fragment_contacts) } + + if (showTabs and FAVORITES_TAB_MASK != 0) { + fragments.add(R.layout.fragment_favorites) + } + + if (showTabs and RECENTS_TAB_MASK != 0) { + fragments.add(R.layout.fragment_recents) + } + + if (showTabs and GROUPS_TAB_MASK != 0) { + fragments.add(R.layout.fragment_groups) + } + + return fragments[position] } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt index 788b2f23..241c62cf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt @@ -6,10 +6,7 @@ import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.views.MyAppCompatCheckbox import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.config -import com.simplemobiletools.contacts.helpers.ALL_TABS_MASK -import com.simplemobiletools.contacts.helpers.CONTACTS_TAB_MASK -import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK -import com.simplemobiletools.contacts.helpers.GROUPS_TAB_MASK +import com.simplemobiletools.contacts.helpers.* class ManageVisibleTabsDialog(val activity: BaseSimpleActivity) { private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_visible_tabs, null) @@ -19,6 +16,7 @@ class ManageVisibleTabsDialog(val activity: BaseSimpleActivity) { tabs.apply { put(CONTACTS_TAB_MASK, R.id.manage_visible_tabs_contacts) put(FAVORITES_TAB_MASK, R.id.manage_visible_tabs_favorites) + put(RECENTS_TAB_MASK, R.id.manage_visible_tabs_recents) put(GROUPS_TAB_MASK, R.id.manage_visible_tabs_groups) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index defca5ad..378e3640 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -47,12 +47,16 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fragment_placeholder_2.underlineText() updateViewStuff() - if (this is FavoritesFragment) { - fragment_placeholder.text = activity.getString(R.string.no_favorites) - fragment_placeholder_2.text = activity.getString(R.string.add_favorites) - } else if (this is GroupsFragment) { - fragment_placeholder.text = activity.getString(R.string.no_group_created) - fragment_placeholder_2.text = activity.getString(R.string.create_group) + when { + this is FavoritesFragment -> { + fragment_placeholder.text = activity.getString(R.string.no_favorites) + fragment_placeholder_2.text = activity.getString(R.string.add_favorites) + } + this is GroupsFragment -> { + fragment_placeholder.text = activity.getString(R.string.no_group_created) + fragment_placeholder_2.text = activity.getString(R.string.create_group) + } + this is RecentsFragment -> fragment_fab.beGone() } } } @@ -88,6 +92,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fun refreshContacts(contacts: ArrayList) { if ((config.showTabs and CONTACTS_TAB_MASK == 0 && this is ContactsFragment) || (config.showTabs and FAVORITES_TAB_MASK == 0 && this is FavoritesFragment) || + (config.showTabs and RECENTS_TAB_MASK == 0 && this is RecentsFragment) || (config.showTabs and GROUPS_TAB_MASK == 0 && this is GroupsFragment)) { return } @@ -105,6 +110,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) val filtered = when { this is GroupsFragment -> contacts this is FavoritesFragment -> contacts.filter { it.starred == 1 } as ArrayList + this is RecentsFragment -> ArrayList() else -> { val contactSources = activity!!.getVisibleContactSources() contacts.filter { contactSources.contains(it.source) } as ArrayList @@ -170,7 +176,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } private fun setupContactsFavoritesAdapter(contacts: ArrayList) { - fragment_placeholder_2.beVisibleIf(contacts.isEmpty()) + fragment_placeholder_2.beVisibleIf(contacts.isEmpty() && this !is RecentsFragment) fragment_placeholder.beVisibleIf(contacts.isEmpty()) fragment_list.beVisibleIf(contacts.isNotEmpty()) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt new file mode 100644 index 00000000..791d4db0 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -0,0 +1,14 @@ +package com.simplemobiletools.contacts.fragments + +import android.content.Context +import android.util.AttributeSet + +class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) { + override fun fabClicked() { + finishActMode() + } + + override fun placeholderClicked() { + + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index a9bf3b56..f1395a13 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -24,16 +24,19 @@ const val FIRST_GROUP_ID = 10000 const val LOCATION_CONTACTS_TAB = 0 const val LOCATION_FAVORITES_TAB = 1 -const val LOCATION_GROUPS_TAB = 2 -const val LOCATION_GROUP_CONTACTS = 3 +const val LOCATION_RECENTS_TAB = 2 +const val LOCATION_GROUPS_TAB = 3 +const val LOCATION_GROUP_CONTACTS = 4 const val CONTACTS_TAB_MASK = 1 const val FAVORITES_TAB_MASK = 2 -const val GROUPS_TAB_MASK = 4 -const val ALL_TABS_MASK = 7 +const val RECENTS_TAB_MASK = 4 +const val GROUPS_TAB_MASK = 8 +const val ALL_TABS_MASK = 15 val tabsList = arrayListOf(CONTACTS_TAB_MASK, FAVORITES_TAB_MASK, + RECENTS_TAB_MASK, GROUPS_TAB_MASK ) diff --git a/app/src/main/res/drawable-hdpi/ic_clock.png b/app/src/main/res/drawable-hdpi/ic_clock.png new file mode 100644 index 0000000000000000000000000000000000000000..43b00e0aae69e29432a03207c09bc33ad32e0b2c GIT binary patch literal 487 zcmVkQ>ZW^yxwweK+NyifdLT1bc@X`*Q)l*s-b#=AfD*&@IX*L7y8m&k| zX;Guy2W&~FSpwLnc$)d!4OkSCV<=!v$bi*=p~+i%0S+n<E|rNR4bht|gzyf;&kq(i$Fhvz{1 zkOb8n1J|^P@^&N)+cK~!q(Be%z+P4VW~-7de`9bvq(lh|eZ^U1fehM?2!@C?f`C|3GFy36tE){mIV!iTLD>JR83a dn2JXt0027ct^>_&xyv28cwq;| z3)+YewBrTFMsderjBC^opQyz(j6vdzGQ2@OrLqJ-6`rErxU2+gP)VAI4FC{ZG~)wm zwb-K~Y1CMeAPp$SqSBNVJIqBrCX!PF5gwuDiWT{JiW(*ov;oCX)Kl_X6WgHDM00K+ zMjF-Hnm7@4NL0WE6o*k0tVw%Nqe3+>61Cf!bPm-iR0Ew*XRS%kPz^#g&;a$!nv{ns z9;$)jsE^j93@YK(04>E42~@_K^a54Svw=J|P)=-75B0*DbO!H5?#WHm8^%z`Ws%P~ zZ_^Rtf+9>NiMmc#OZsrs9&6DFs6*bN4Erz+P)mNmA=G$lv9{K$!Wmt-hRQHiL=h#8 zYGZfVJ+WSP&M3@y-UG16a@jo~zgRIJ^@xb?cWeg_&4`Kc*x|ZXR3eQUFTPPqL@^$f zri|B<`GIB<5KTzBJei7;KRo(Xf(6N*kG>^rxe_33slipe!xqKXz=2mAUSMn!FBIV@ zMwZ#0q2p|_0Yb-+!EdSD!}q=Pr7_ir5u+-N>2C?&*AbB*=oR(@68;ONQp_R}?h|;$ pTqCFsmI&gNS5qHHrMkmb*-m-bqXaM${9_f zO;qP%NrKC$#%2-?rCfsJG^S9|TK?i>$9Mn5dMcXCDH06%Ou%a$zfmSWa00)v4zE$< zi+`lk1+R&mEdg*&!Vl|Ay7N;&a6exEk|rUL#=m&oCGI$ZRhX(u z1gL~*IVXrK>SAg~nnZv!+G46DZnzQCL<%G+_W9P~T$+gKCUL6e5Ir1%cg?UD*zP;~;sez76vZx|qt;LuOQ1ql zqdt{n7=Zd$B+5XFe^LF#jvTh2u8l3wHK;A*hz)n6mWo6jNU_A}A;PDqrm+TUiuy!s zsEPV8)<7SiYKRR3QTN6g=w7G$H8W5b#2V-V)O4|7De8n+1D)V>`LG?88*89k)HbnU zGb+n>pufZ!S*Xoo!*W!)I6^h#ROfzAEf*VR2Ry0OT0SFFQsR8nY_Xvq>OOJA1I)lQ zEp?T{eW<=-Lv_?g;*1==VQU_0u435W9)z$|2$Hnvlp7pjt%tm$f+bDh` zA@PuS@Qq`mXgRSraWnW)h8}Jnr?>4GHV1de-$+O^Tvg;LRnhS=@R! ztvKsZbT}=6pN?-${_gp literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clock.png b/app/src/main/res/drawable-xxxhdpi/ic_clock.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd0dc3e43a3d5d42d2aee0cb2942d96757b89a7 GIT binary patch literal 1279 zcmV@SyN}oy6U71|;`wv!}s!~OHBAzjPKserIB(dTLim;I*m~en~6qLL~6jL~XNk^F|d4;-sh!@_amUxfBq~n!~ z^c61=%?|8j@D4Yzh+Z_J9El`RmWK3Z5jXM<8Q9rMlz4+=o+dXxv5`(BNG?dEBb)e% z+&oUAm`5=_=WdzNDA?c&SE#g{X#`ChjJVwm;l$1 zBw-+hs|gUyUP_z0c+v=FHyy+PI*>*%KT<@(MMtH*1Xg!QXy}O5 z7V#Kcuxc&tk-%^0o+C;e;%{7jsy~PB2javn=AiqLR1xz5bfoelx|!k*u^dDdpr(jt z0Tk3GfGUkxaf8X|t{3q%fPx#)O%ONu3DqeI=4pUJoI>@ZI7fSQ>qR^dpkO_^_Tm(G zqB=)vo(Cw%IaGH#|HMs4wNu3V06KP}x^DRIPoT03#dl>8elZ47sL)B@L?=+;t@wgX($re*>E(L?p15OXxD#Mxr>xX7`^Gb5T7aVW0w! zVYQc`;s_6;nj?1Tj_QWF;5SWwMK8$p!V%rHVbi zMzvH7pezq!wVx3#eks2cC%6{X$6^TW`2njpFLyEV3973?-DIdNh7iXR&Y{a^RqPRgU2T%oQD0zhX z1W+9$R@`A8y01xDZC3M$;sta+5G(G{$(N1p%~-XP&~P_a!^A@j z!RpR%R%MQ&OQ*Sbgl43pJ4R6n9o?}y>sj6AELL5;DLhT|sqo|;iF`pY_ZY04<>xMf z`IJO6FZ|yKW;g8({Uz)+U;0x}jspY};Ht1|msfdSyR<33?q;?h#aHvy<9hyAk8P05 z^S0&hjt0u#Pm$dPJB%h){6%AKHam*w1M)M*#W3it_QRk)p&Em= paoltc^8+7xF^(G%5fKp)@jFaBLm`ZzP5}S_002ovPDHLkV1jBOMo9nw literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/dialog_manage_visible_tabs.xml b/app/src/main/res/layout/dialog_manage_visible_tabs.xml index 65e8978c..e690b1dc 100644 --- a/app/src/main/res/layout/dialog_manage_visible_tabs.xml +++ b/app/src/main/res/layout/dialog_manage_visible_tabs.xml @@ -30,6 +30,14 @@ android:paddingTop="@dimen/activity_margin" android:text="@string/favorites"/> + + + + + + + From 79e8234d4393cade5353f92d83a436ae4a81d91b Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 15:59:24 +0200 Subject: [PATCH 070/135] adding the function fetching recent calls --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 + .../contacts/activities/MainActivity.kt | 16 ++++--- .../contacts/helpers/ContactsHelper.kt | 45 +++++++++++++++++++ .../contacts/models/RecentCall.kt | 3 ++ 5 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt diff --git a/app/build.gradle b/app/build.gradle index c58036a0..21f30e97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.16' + implementation 'com.simplemobiletools:commons:4.5.17' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 65956686..4422660d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ + + ) -> Unit) { + Thread { + val calls = ArrayList() + if (!activity.hasPermission(PERMISSION_WRITE_CALL_LOG)) { + callback(calls) + return@Thread + } + + val uri = android.provider.CallLog.Calls.CONTENT_URI + val projection = arrayOf( + CallLog.Calls._ID, + CallLog.Calls.NUMBER, + CallLog.Calls.DATE, + CallLog.Calls.DURATION, + CallLog.Calls.CACHED_NAME, + CallLog.Calls.TYPE + ) + + val sorting = "${CallLog.Calls._ID} DESC LIMIT 50" + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, null, null, sorting) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(CallLog.Calls._ID) + val number = cursor.getStringValue(CallLog.Calls.NUMBER) + val date = cursor.getLongValue(CallLog.Calls.DATE) + val duration = cursor.getIntValue(CallLog.Calls.DURATION) + val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) ?: "" + val type = cursor.getIntValue(CallLog.Calls.TYPE) + val recentCall = RecentCall(id, number, date, duration, name, type) + calls.add(recentCall) + } while (cursor.moveToNext()) + } + } finally { + cursor?.close() + } + callback(calls) + }.start() + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt new file mode 100644 index 00000000..f2bbfdf5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.contacts.models + +data class RecentCall(var id: Int, var number: String, var date: Long, var duration: Int, var name: String, var type: Int) From 71f3e8cb0ccf4b23b11622277c24d8a75218ce50 Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 16:36:17 +0200 Subject: [PATCH 071/135] list the last 100 calls at the Recent calls list --- .../contacts/activities/MainActivity.kt | 9 +-- .../contacts/adapters/RecentCallsAdapter.kt | 67 +++++++++++++++++++ .../contacts/fragments/MyViewPagerFragment.kt | 4 +- .../contacts/fragments/RecentsFragment.kt | 26 +++++++ .../contacts/helpers/ContactsHelper.kt | 4 +- .../contacts/models/RecentCall.kt | 2 +- app/src/main/res/layout/item_recent_call.xml | 31 +++++++++ 7 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt create mode 100644 app/src/main/res/layout/item_recent_call.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index aaea9ac9..a3f57512 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -37,6 +37,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_contacts.* import kotlinx.android.synthetic.main.fragment_favorites.* import kotlinx.android.synthetic.main.fragment_groups.* +import kotlinx.android.synthetic.main.fragment_recents.* import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { @@ -477,11 +478,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } groups_fragment?.refreshContacts(it) } - } - - if (refreshTabsMask and RECENTS_TAB_MASK != 0) { - ContactsHelper(this).getRecents { + if (refreshTabsMask and RECENTS_TAB_MASK != 0) { + ContactsHelper(this).getRecents { + recents_fragment?.updateRecentCalls(it) + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt new file mode 100644 index 00000000..e5e16914 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -0,0 +1,67 @@ +package com.simplemobiletools.contacts.adapters + +import android.view.Menu +import android.view.View +import android.view.ViewGroup +import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.views.FastScroller +import com.simplemobiletools.commons.views.MyRecyclerView +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.activities.SimpleActivity +import com.simplemobiletools.contacts.models.RecentCall +import kotlinx.android.synthetic.main.item_recent_call.view.* +import java.util.* + +class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, + itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { + + init { + setupDragListener(true) + } + + override fun getActionMenuId() = 0 + + override fun prepareActionMode(menu: Menu) {} + + override fun prepareItemSelection(viewHolder: ViewHolder) {} + + override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {} + + override fun actionItemPressed(id: Int) { + if (selectedPositions.isEmpty()) { + return + } + } + + override fun getSelectableItemCount() = recentCalls.size + + override fun getIsItemSelectable(position: Int) = true + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_recent_call, parent) + + override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) { + val recentCall = recentCalls[position] + val view = holder.bindView(recentCall, true, true) { itemView, layoutPosition -> + setupView(itemView, recentCall) + } + bindViewHolder(holder, position, view) + } + + override fun getItemCount() = recentCalls.size + + fun updateItems(newItems: ArrayList) { + recentCalls = newItems + notifyDataSetChanged() + finishActMode() + fastScroller?.measureRecyclerView() + } + + private fun setupView(view: View, recentCall: RecentCall) { + view.apply { + recent_call_name.apply { + text = recentCall.name ?: recentCall.number + setTextColor(textColor) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 378e3640..9b37a550 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -129,7 +129,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) private fun setupContacts(contacts: ArrayList) { if (this is GroupsFragment) { setupGroupsAdapter(contacts) - } else { + } else if (this !is RecentsFragment) { setupContactsFavoritesAdapter(contacts) } } @@ -176,7 +176,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } private fun setupContactsFavoritesAdapter(contacts: ArrayList) { - fragment_placeholder_2.beVisibleIf(contacts.isEmpty() && this !is RecentsFragment) + fragment_placeholder_2.beVisibleIf(contacts.isEmpty()) fragment_placeholder.beVisibleIf(contacts.isEmpty()) fragment_list.beVisibleIf(contacts.isNotEmpty()) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index 791d4db0..f4cd5ae6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -2,6 +2,11 @@ package com.simplemobiletools.contacts.fragments import android.content.Context import android.util.AttributeSet +import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.extensions.isActivityDestroyed +import com.simplemobiletools.contacts.adapters.RecentCallsAdapter +import com.simplemobiletools.contacts.models.RecentCall +import kotlinx.android.synthetic.main.fragment_layout.view.* class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) { override fun fabClicked() { @@ -11,4 +16,25 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage override fun placeholderClicked() { } + + fun updateRecentCalls(recentCalls: ArrayList) { + if (activity == null || activity!!.isActivityDestroyed()) { + return + } + + fragment_placeholder.beVisibleIf(recentCalls.isEmpty()) + fragment_list.beVisibleIf(recentCalls.isNotEmpty()) + + val currAdapter = fragment_list.adapter + if (currAdapter == null) { + RecentCallsAdapter(activity!!, recentCalls, fragment_list, fragment_fastscroller) { + + }.apply { + addVerticalDividers(true) + fragment_list.adapter = this + } + } else { + (currAdapter as RecentCallsAdapter).updateItems(recentCalls) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 84b04848..dc99ee92 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1331,7 +1331,7 @@ class ContactsHelper(val activity: Activity) { CallLog.Calls.TYPE ) - val sorting = "${CallLog.Calls._ID} DESC LIMIT 50" + val sorting = "${CallLog.Calls._ID} DESC LIMIT 100" var cursor: Cursor? = null try { @@ -1342,7 +1342,7 @@ class ContactsHelper(val activity: Activity) { val number = cursor.getStringValue(CallLog.Calls.NUMBER) val date = cursor.getLongValue(CallLog.Calls.DATE) val duration = cursor.getIntValue(CallLog.Calls.DURATION) - val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) ?: "" + val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) val type = cursor.getIntValue(CallLog.Calls.TYPE) val recentCall = RecentCall(id, number, date, duration, name, type) calls.add(recentCall) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt index f2bbfdf5..1c82e451 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class RecentCall(var id: Int, var number: String, var date: Long, var duration: Int, var name: String, var type: Int) +data class RecentCall(var id: Int, var number: String, var date: Long, var duration: Int, var name: String?, var type: Int) diff --git a/app/src/main/res/layout/item_recent_call.xml b/app/src/main/res/layout/item_recent_call.xml new file mode 100644 index 00000000..a272d6a7 --- /dev/null +++ b/app/src/main/res/layout/item_recent_call.xml @@ -0,0 +1,31 @@ + + + + + + + + + From 29c59f498ac304449c80b082b82631e4d1f664e1 Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 3 Aug 2018 23:48:14 +0200 Subject: [PATCH 072/135] show phone number and date at Recents too --- .../contacts/activities/MainActivity.kt | 4 ++- .../contacts/adapters/RecentCallsAdapter.kt | 12 ++++++++ .../contacts/fragments/RecentsFragment.kt | 10 +++---- .../contacts/helpers/ContactsHelper.kt | 8 +++-- .../contacts/models/RecentCall.kt | 2 +- app/src/main/res/layout/item_recent_call.xml | 29 ++++++++++++++++--- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index a3f57512..fd99a331 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -481,7 +481,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (refreshTabsMask and RECENTS_TAB_MASK != 0) { ContactsHelper(this).getRecents { - recents_fragment?.updateRecentCalls(it) + runOnUiThread { + recents_fragment?.updateRecentCalls(it) + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt index e5e16914..3e7f4340 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -4,6 +4,7 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.contacts.R @@ -62,6 +63,17 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList) { if (activity == null || activity!!.isActivityDestroyed()) { @@ -33,6 +29,8 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage addVerticalDividers(true) fragment_list.adapter = this } + + fragment_fastscroller.setViews(fragment_list) {} } else { (currAdapter as RecentCallsAdapter).updateItems(recentCalls) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index dc99ee92..3956fd53 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -21,6 +21,8 @@ import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.models.* import com.simplemobiletools.contacts.overloads.times +import java.text.SimpleDateFormat +import java.util.* class ContactsHelper(val activity: Activity) { private val BATCH_SIZE = 100 @@ -1326,7 +1328,6 @@ class ContactsHelper(val activity: Activity) { CallLog.Calls._ID, CallLog.Calls.NUMBER, CallLog.Calls.DATE, - CallLog.Calls.DURATION, CallLog.Calls.CACHED_NAME, CallLog.Calls.TYPE ) @@ -1341,10 +1342,11 @@ class ContactsHelper(val activity: Activity) { val id = cursor.getIntValue(CallLog.Calls._ID) val number = cursor.getStringValue(CallLog.Calls.NUMBER) val date = cursor.getLongValue(CallLog.Calls.DATE) - val duration = cursor.getIntValue(CallLog.Calls.DURATION) val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) val type = cursor.getIntValue(CallLog.Calls.TYPE) - val recentCall = RecentCall(id, number, date, duration, name, type) + + val formattedDate = SimpleDateFormat("dd.MM.yyyy, HH:mm", Locale.getDefault()).format(Date(date)) + val recentCall = RecentCall(id, number, formattedDate, name, type) calls.add(recentCall) } while (cursor.moveToNext()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt index 1c82e451..f3e92d4c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class RecentCall(var id: Int, var number: String, var date: Long, var duration: Int, var name: String?, var type: Int) +data class RecentCall(var id: Int, var number: String, var date: String, var name: String?, var type: Int) diff --git a/app/src/main/res/layout/item_recent_call.xml b/app/src/main/res/layout/item_recent_call.xml index a272d6a7..0e9f0666 100644 --- a/app/src/main/res/layout/item_recent_call.xml +++ b/app/src/main/res/layout/item_recent_call.xml @@ -13,19 +13,40 @@ + + + + From 2a987a640e91ba9d5bea0602eb5f08e982dcf6a9 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 09:50:01 +0200 Subject: [PATCH 073/135] tweak the DateTime field at recent calls --- .../contacts/adapters/RecentCallsAdapter.kt | 2 +- .../contacts/helpers/ContactsHelper.kt | 16 ++++++++++++++-- .../contacts/models/RecentCall.kt | 2 +- app/src/main/res/layout/item_recent_call.xml | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt index 3e7f4340..7c216264 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -71,7 +71,7 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList formattedDate = formattedDate.substring(12) + datePart == yesterdayDate -> formattedDate = yesterday + formattedDate.substring(11) + formattedDate.substring(7, 11) == currentYear -> formattedDate = formattedDate.substring(0, 6) + formattedDate.substring(11) + } + val recentCall = RecentCall(id, number, formattedDate, name, type) calls.add(recentCall) } while (cursor.moveToNext()) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt index f3e92d4c..bb17f652 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class RecentCall(var id: Int, var number: String, var date: String, var name: String?, var type: Int) +data class RecentCall(var id: Int, var number: String, var dateTime: String, var name: String?, var type: Int) diff --git a/app/src/main/res/layout/item_recent_call.xml b/app/src/main/res/layout/item_recent_call.xml index 0e9f0666..f0419568 100644 --- a/app/src/main/res/layout/item_recent_call.xml +++ b/app/src/main/res/layout/item_recent_call.xml @@ -45,8 +45,8 @@ android:layout_below="@+id/recent_call_name" android:gravity="right" android:maxLines="1" - android:textSize="@dimen/bigger_text_size" - tools:text="Today, 17:00"/> + android:textSize="@dimen/normal_text_size" + tools:text="Yesterday, 17:00"/> From 3f0c87572fbb42c454cc9376f9fdce3c05277728 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 16:02:17 +0200 Subject: [PATCH 074/135] adjust the Recent Calls items, show phone number only when appropriate --- app/build.gradle | 1 + .../contacts/adapters/RecentCallsAdapter.kt | 4 ++- app/src/main/res/layout/item_recent_call.xml | 28 +++++++++++++------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 21f30e97..51461aae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation 'com.simplemobiletools:commons:4.5.17' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.2' debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt index 7c216264..d380cd05 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -9,12 +9,14 @@ import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity +import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.models.RecentCall import kotlinx.android.synthetic.main.item_recent_call.view.* import java.util.* class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { + val showPhoneNumbers = activity.config.showPhoneNumbers init { setupDragListener(true) @@ -65,7 +67,7 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList - - + From 74c7acab84260fc8bb01ff31442b13b7bd090110 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 16:07:14 +0200 Subject: [PATCH 075/135] avoid showing the same number Recent Call twice in a row --- .../simplemobiletools/contacts/helpers/ContactsHelper.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 498b113b..22ba3721 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1332,12 +1332,13 @@ class ContactsHelper(val activity: Activity) { CallLog.Calls.TYPE ) - val sorting = "${CallLog.Calls._ID} DESC LIMIT 50" + val sorting = "${CallLog.Calls._ID} DESC LIMIT 100" val currentDate = Date(System.currentTimeMillis()) val currentYear = SimpleDateFormat("yyyy", Locale.getDefault()).format(currentDate) val todayDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(currentDate) val yesterdayDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(Date(System.currentTimeMillis() - DAY_SECONDS * 1000)) val yesterday = activity.getString(R.string.yesterday) + var prevNumber = "" var cursor: Cursor? = null try { @@ -1349,6 +1350,9 @@ class ContactsHelper(val activity: Activity) { val date = cursor.getLongValue(CallLog.Calls.DATE) val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) val type = cursor.getIntValue(CallLog.Calls.TYPE) + if (number == prevNumber) { + continue + } var formattedDate = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date(date)) val datePart = formattedDate.substring(0, 11) @@ -1358,6 +1362,7 @@ class ContactsHelper(val activity: Activity) { formattedDate.substring(7, 11) == currentYear -> formattedDate = formattedDate.substring(0, 6) + formattedDate.substring(11) } + prevNumber = number val recentCall = RecentCall(id, number, formattedDate, name, type) calls.add(recentCall) } while (cursor.moveToNext()) From a192c1b028fc2dbafd5d2d5ed125c9f217edfca1 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 16:08:27 +0200 Subject: [PATCH 076/135] ignore the Type field at recent calls --- .../simplemobiletools/contacts/helpers/ContactsHelper.kt | 6 ++---- .../com/simplemobiletools/contacts/models/RecentCall.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 22ba3721..05d78abb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1328,8 +1328,7 @@ class ContactsHelper(val activity: Activity) { CallLog.Calls._ID, CallLog.Calls.NUMBER, CallLog.Calls.DATE, - CallLog.Calls.CACHED_NAME, - CallLog.Calls.TYPE + CallLog.Calls.CACHED_NAME ) val sorting = "${CallLog.Calls._ID} DESC LIMIT 100" @@ -1349,7 +1348,6 @@ class ContactsHelper(val activity: Activity) { val number = cursor.getStringValue(CallLog.Calls.NUMBER) val date = cursor.getLongValue(CallLog.Calls.DATE) val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME) - val type = cursor.getIntValue(CallLog.Calls.TYPE) if (number == prevNumber) { continue } @@ -1363,7 +1361,7 @@ class ContactsHelper(val activity: Activity) { } prevNumber = number - val recentCall = RecentCall(id, number, formattedDate, name, type) + val recentCall = RecentCall(id, number, formattedDate, name) calls.add(recentCall) } while (cursor.moveToNext()) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt index bb17f652..e009be76 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class RecentCall(var id: Int, var number: String, var dateTime: String, var name: String?, var type: Int) +data class RecentCall(var id: Int, var number: String, var dateTime: String, var name: String?) From 4743fcd73061f11e251fddd0042dcd60d1ddfbb6 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 17:43:35 +0200 Subject: [PATCH 077/135] implementing a Select All menu button at Recents --- .../contacts/adapters/RecentCallsAdapter.kt | 12 +++++++++--- app/src/main/res/menu/cab_recent_calls.xml | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/menu/cab_recent_calls.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt index d380cd05..c2fa54f3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -16,24 +16,30 @@ import java.util.* class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { - val showPhoneNumbers = activity.config.showPhoneNumbers + private val showPhoneNumbers = activity.config.showPhoneNumbers init { setupDragListener(true) } - override fun getActionMenuId() = 0 + override fun getActionMenuId() = R.menu.cab_recent_calls override fun prepareActionMode(menu: Menu) {} override fun prepareItemSelection(viewHolder: ViewHolder) {} - override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {} + override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) { + viewHolder?.itemView?.recent_call_frame?.isSelected = select + } override fun actionItemPressed(id: Int) { if (selectedPositions.isEmpty()) { return } + + when (id) { + R.id.cab_select_all -> selectAll() + } } override fun getSelectableItemCount() = recentCalls.size diff --git a/app/src/main/res/menu/cab_recent_calls.xml b/app/src/main/res/menu/cab_recent_calls.xml new file mode 100644 index 00000000..128e6362 --- /dev/null +++ b/app/src/main/res/menu/cab_recent_calls.xml @@ -0,0 +1,14 @@ + +

+ + + From 46580c362039d0fad03e0620e07e6ca714669b35 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 18:24:26 +0200 Subject: [PATCH 078/135] implement Recent Call removing --- .../contacts/adapters/GroupsAdapter.kt | 4 +-- .../contacts/adapters/RecentCallsAdapter.kt | 36 +++++++++++++++++-- .../contacts/fragments/RecentsFragment.kt | 2 +- .../contacts/helpers/ContactsHelper.kt | 27 +++++++++++++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt index fe7f7f3d..ca98b7cd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt @@ -91,11 +91,11 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val private fun askConfirmDelete() { ConfirmationDialog(activity) { - deleteContacts() + deleteGroups() } } - private fun deleteContacts() { + private fun deleteGroups() { if (selectedPositions.isEmpty()) { return } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt index c2fa54f3..e54d8d2b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt @@ -4,18 +4,22 @@ import android.view.Menu import android.view.View import android.view.ViewGroup import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter +import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.views.FastScroller import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK +import com.simplemobiletools.contacts.interfaces.RefreshContactsListener import com.simplemobiletools.contacts.models.RecentCall import kotlinx.android.synthetic.main.item_recent_call.view.* import java.util.* -class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, recyclerView: MyRecyclerView, fastScroller: FastScroller, - itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { +class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, val refreshListener: RefreshContactsListener?, recyclerView: MyRecyclerView, + fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { private val showPhoneNumbers = activity.config.showPhoneNumbers init { @@ -39,6 +43,7 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList selectAll() + R.id.cab_delete -> askConfirmDelete() } } @@ -65,6 +70,33 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList() + selectedPositions.sortedDescending().forEach { + val call = recentCalls[it] + callsToRemove.add(call) + } + ContactsHelper(activity).removeRecentCalls(callsToRemove.map { it.id } as ArrayList) + recentCalls.removeAll(callsToRemove) + + if (recentCalls.isEmpty()) { + refreshListener?.refreshContacts(RECENTS_TAB_MASK) + finishActMode() + } else { + removeSelectedItems() + } + } + private fun setupView(view: View, recentCall: RecentCall) { view.apply { recent_call_name.apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index f7e70e4d..e68a0714 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -23,7 +23,7 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage val currAdapter = fragment_list.adapter if (currAdapter == null) { - RecentCallsAdapter(activity!!, recentCalls, fragment_list, fragment_fastscroller) { + RecentCallsAdapter(activity!!, recentCalls, activity, fragment_list, fragment_fastscroller) { }.apply { addVerticalDividers(true) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 05d78abb..9b53113a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1323,7 +1323,7 @@ class ContactsHelper(val activity: Activity) { return@Thread } - val uri = android.provider.CallLog.Calls.CONTENT_URI + val uri = CallLog.Calls.CONTENT_URI val projection = arrayOf( CallLog.Calls._ID, CallLog.Calls.NUMBER, @@ -1371,4 +1371,29 @@ class ContactsHelper(val activity: Activity) { callback(calls) }.start() } + + fun removeRecentCalls(ids: ArrayList) { + Thread { + try { + val operations = ArrayList() + val selection = "${CallLog.Calls._ID} = ?" + ids.forEach { + ContentProviderOperation.newDelete(CallLog.Calls.CONTENT_URI).apply { + val selectionArgs = arrayOf(it.toString()) + withSelection(selection, selectionArgs) + operations.add(build()) + } + + if (operations.size % BATCH_SIZE == 0) { + activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations) + operations.clear() + } + } + + activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations) + } catch (e: Exception) { + activity.showErrorToast(e) + } + }.start() + } } From dc23bac57fe9e5d93feec8f3b383b5380154661c Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 18:27:12 +0200 Subject: [PATCH 079/135] show the appropriate bubble text at fastscrolling Recents --- .../simplemobiletools/contacts/fragments/RecentsFragment.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index e68a0714..eea47f9f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -30,7 +30,10 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage fragment_list.adapter = this } - fragment_fastscroller.setViews(fragment_list) {} + fragment_fastscroller.setViews(fragment_list) { + val item = (fragment_list.adapter as RecentCallsAdapter).recentCalls.getOrNull(it) + fragment_fastscroller.updateBubbleText(item?.name ?: item?.number ?: "") + } } else { (currAdapter as RecentCallsAdapter).updateItems(recentCalls) } From 3257dc7c799acf4563550889fc3bc4e0660b6a3e Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 19:42:33 +0200 Subject: [PATCH 080/135] adding some performance improvements at initial app loading --- .../contacts/activities/MainActivity.kt | 27 ++++++++++--------- .../contacts/adapters/ViewPagerAdapter.kt | 7 +++-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index fd99a331..79477df7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -283,7 +283,6 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2).filter { it != activeIndex } private fun initFragments() { - refreshContacts(ALL_TABS_MASK) viewpager.offscreenPageLimit = 3 viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { @@ -305,6 +304,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } }) + viewpager.onGlobalLayout { + refreshContacts(ALL_TABS_MASK) + } + main_tabs_holder.onTabSelectionChanged( tabUnselectedAction = { it.icon?.applyColorFilter(config.textColor) @@ -451,19 +454,19 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (isActivityDestroyed() || isGettingContacts) { return } - isGettingContacts = true + + if (viewpager.adapter == null) { + viewpager.adapter = ViewPagerAdapter(this) + viewpager.currentItem = config.lastUsedViewPagerPage + } + ContactsHelper(this).getContacts { isGettingContacts = false if (isActivityDestroyed()) { return@getContacts } - if (viewpager.adapter == null) { - viewpager.adapter = ViewPagerAdapter(this, it) - viewpager.currentItem = config.lastUsedViewPagerPage - } - if (refreshTabsMask and CONTACTS_TAB_MASK != 0) { contacts_fragment?.refreshContacts(it) } @@ -478,12 +481,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } groups_fragment?.refreshContacts(it) } + } - if (refreshTabsMask and RECENTS_TAB_MASK != 0) { - ContactsHelper(this).getRecents { - runOnUiThread { - recents_fragment?.updateRecentCalls(it) - } + if (refreshTabsMask and RECENTS_TAB_MASK != 0) { + ContactsHelper(this).getRecents { + runOnUiThread { + recents_fragment?.updateRecentCalls(it) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt index 5a61132d..c78ffaef 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt @@ -8,10 +8,9 @@ import com.simplemobiletools.contacts.activities.MainActivity import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.fragments.MyViewPagerFragment import com.simplemobiletools.contacts.helpers.* -import com.simplemobiletools.contacts.models.Contact -class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList) : PagerAdapter() { - val showTabs = activity.config.showTabs +class ViewPagerAdapter(val activity: MainActivity) : PagerAdapter() { + private val showTabs = activity.config.showTabs override fun instantiateItem(container: ViewGroup, position: Int): Any { val layout = getFragment(position) @@ -20,8 +19,8 @@ class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList Date: Sat, 4 Aug 2018 20:11:23 +0200 Subject: [PATCH 081/135] add the Recents fragment to the list of all fragments --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 79477df7..6c163f2a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -492,7 +492,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } - private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, groups_fragment) + private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, recents_fragment, groups_fragment) private fun checkWhatsNewDialog() { arrayListOf().apply { From fadfcb139ad5fbf96fae3c294170204d5ba093c4 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 20:44:05 +0200 Subject: [PATCH 082/135] trigger activityResume only at live fragments --- .../simplemobiletools/contacts/activities/MainActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 6c163f2a..ff293f75 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -150,10 +150,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { initFragments() } else { refreshContacts(ALL_TABS_MASK) - } - getAllFragments().forEach { - it?.onActivityResume() + getAllFragments().forEach { + it?.onActivityResume() + } } } isFirstResume = false From 8be0b1ab9a51b7557a7491b99af1b4b7681691bc Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 20:55:39 +0200 Subject: [PATCH 083/135] make sure we always return the proper current fragment --- .../contacts/activities/MainActivity.kt | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index ff293f75..f1386655 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -30,6 +30,7 @@ import com.simplemobiletools.contacts.dialogs.ImportContactsDialog import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.extensions.dbHelper import com.simplemobiletools.contacts.extensions.getTempFile +import com.simplemobiletools.contacts.fragments.MyViewPagerFragment import com.simplemobiletools.contacts.helpers.* import com.simplemobiletools.contacts.interfaces.RefreshContactsListener import com.simplemobiletools.contacts.models.Contact @@ -171,11 +172,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu, menu) - val currentPage = viewpager?.currentItem + val currentFragment = getCurrentFragment() menu.apply { - findItem(R.id.search).isVisible = currentPage != LOCATION_GROUPS_TAB - findItem(R.id.sort).isVisible = currentPage != LOCATION_GROUPS_TAB && currentPage != LOCATION_RECENTS_TAB - findItem(R.id.filter).isVisible = currentPage != LOCATION_GROUPS_TAB + findItem(R.id.search).isVisible = currentFragment != groups_fragment + findItem(R.id.sort).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment + findItem(R.id.filter).isVisible = currentFragment != groups_fragment } setupSearch(menu) return true @@ -213,13 +214,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { (searchMenuItem!!.actionView as SearchView).apply { setSearchableInfo(searchManager.getSearchableInfo(componentName)) isSubmitButtonEnabled = false - queryHint = getString(if (viewpager.currentItem == 0) R.string.search_contacts else R.string.search_favorites) + queryHint = getString(if (getCurrentFragment() == contacts_fragment) R.string.search_contacts else R.string.search_favorites) setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String) = false override fun onQueryTextChange(newText: String): Boolean { if (isSearchOpen) { - getCurrentFragment()?.onSearchQueryChanged(newText) + getCurrentFragment().onSearchQueryChanged(newText) } return true } @@ -228,23 +229,39 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { MenuItemCompat.setOnActionExpandListener(searchMenuItem, object : MenuItemCompat.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem?): Boolean { - getCurrentFragment()?.onSearchOpened() + getCurrentFragment().onSearchOpened() isSearchOpen = true return true } override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { - getCurrentFragment()?.onSearchClosed() + getCurrentFragment().onSearchClosed() isSearchOpen = false return true } }) } - private fun getCurrentFragment() = when (viewpager.currentItem) { - 0 -> contacts_fragment - 1 -> favorites_fragment - else -> groups_fragment + private fun getCurrentFragment(): MyViewPagerFragment { + val showTabs = config.showTabs + val fragments = arrayListOf() + if (showTabs and CONTACTS_TAB_MASK != 0) { + fragments.add(contacts_fragment) + } + + if (showTabs and FAVORITES_TAB_MASK != 0) { + fragments.add(favorites_fragment) + } + + if (showTabs and RECENTS_TAB_MASK != 0) { + fragments.add(recents_fragment) + } + + if (showTabs and GROUPS_TAB_MASK != 0) { + fragments.add(groups_fragment) + } + + return fragments[viewpager.currentItem] } private fun setupTabColors() { @@ -287,7 +304,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { if (isSearchOpen) { - getCurrentFragment()?.onSearchQueryChanged("") + getCurrentFragment().onSearchQueryChanged("") searchMenuItem?.collapseActionView() } } @@ -314,7 +331,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { }, tabSelectedAction = { if (isSearchOpen) { - getCurrentFragment()?.onSearchQueryChanged("") + getCurrentFragment().onSearchQueryChanged("") searchMenuItem?.collapseActionView() } viewpager.currentItem = it.position From 6044dab0c9d243a791b9e60bba28a93b06b1c2b5 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 21:26:55 +0200 Subject: [PATCH 084/135] lets just restart the activity at Shown Tabs change to avoid glitches --- .../contacts/activities/MainActivity.kt | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index f1386655..f0f6798f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle +import android.os.Handler import android.support.v4.app.ActivityCompat import android.support.v4.content.ContextCompat import android.support.v4.view.MenuItemCompat @@ -102,7 +103,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } if (storedShowTabs != config.showTabs) { - viewpager.adapter = null + config.lastUsedViewPagerPage = 0 + System.exit(0) + return } val configShowContactThumbnails = config.showContactThumbnails @@ -220,7 +223,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { override fun onQueryTextChange(newText: String): Boolean { if (isSearchOpen) { - getCurrentFragment().onSearchQueryChanged(newText) + getCurrentFragment()?.onSearchQueryChanged(newText) } return true } @@ -229,20 +232,20 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { MenuItemCompat.setOnActionExpandListener(searchMenuItem, object : MenuItemCompat.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem?): Boolean { - getCurrentFragment().onSearchOpened() + getCurrentFragment()?.onSearchOpened() isSearchOpen = true return true } override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { - getCurrentFragment().onSearchClosed() + getCurrentFragment()?.onSearchClosed() isSearchOpen = false return true } }) } - private fun getCurrentFragment(): MyViewPagerFragment { + private fun getCurrentFragment(): MyViewPagerFragment? { val showTabs = config.showTabs val fragments = arrayListOf() if (showTabs and CONTACTS_TAB_MASK != 0) { @@ -297,14 +300,14 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } - private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2).filter { it != activeIndex } + private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2, 3).filter { it != activeIndex } private fun initFragments() { viewpager.offscreenPageLimit = 3 viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { if (isSearchOpen) { - getCurrentFragment().onSearchQueryChanged("") + getCurrentFragment()?.onSearchQueryChanged("") searchMenuItem?.collapseActionView() } } @@ -331,7 +334,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { }, tabSelectedAction = { if (isSearchOpen) { - getCurrentFragment().onSearchQueryChanged("") + getCurrentFragment()?.onSearchQueryChanged("") searchMenuItem?.collapseActionView() } viewpager.currentItem = it.position @@ -354,6 +357,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } + // selecting the proper tab sometimes glitches, add an extra selector to make sure we have it right + main_tabs_holder.onGlobalLayout { + Handler().postDelayed({ + main_tabs_holder.getTabAt(config.lastUsedViewPagerPage)?.select() + }, 100L) + } + main_tabs_holder.beVisibleIf(skippedTabs < 3) invalidateOptionsMenu() } From c1e6feb82a237aa1f8d93f9222cda07b2e1c13a6 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 21:44:36 +0200 Subject: [PATCH 085/135] determine if a known Contact has been clicked at Recents --- .../contacts/activities/MainActivity.kt | 4 ++++ .../contacts/fragments/RecentsFragment.kt | 11 ++++++++++- .../simplemobiletools/contacts/helpers/Constants.kt | 1 + .../com/simplemobiletools/contacts/models/Contact.kt | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index f0f6798f..0c03212f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -502,6 +502,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { favorites_fragment?.refreshContacts(it) } + if (refreshTabsMask and RECENTS_TAB_MASK != 0) { + recents_fragment?.refreshContacts(it) + } + if (refreshTabsMask and GROUPS_TAB_MASK != 0) { if (refreshTabsMask == GROUPS_TAB_MASK) { groups_fragment.skipHashComparing = true diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index eea47f9f..f0e039d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -5,6 +5,8 @@ import android.util.AttributeSet import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.isActivityDestroyed import com.simplemobiletools.contacts.adapters.RecentCallsAdapter +import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN +import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.RecentCall import kotlinx.android.synthetic.main.fragment_layout.view.* @@ -24,7 +26,14 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage val currAdapter = fragment_list.adapter if (currAdapter == null) { RecentCallsAdapter(activity!!, recentCalls, activity, fragment_list, fragment_fastscroller) { - + val recentCall = (it as RecentCall).number.replace(PHONE_NUMBER_PATTERN.toRegex(), "") + var selectedContact: Contact? = null + for (contact in allContacts) { + if (contact.phoneNumbers.any { it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), "") == recentCall }) { + selectedContact = contact + break + } + } }.apply { addVerticalDividers(true) fragment_list.adapter = this diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index f1395a13..7e8b5874 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -21,6 +21,7 @@ const val SMT_PRIVATE = "smt_private" // used at the contact source of local c const val IS_PRIVATE = "is_private" const val GROUP = "group" const val FIRST_GROUP_ID = 10000 +const val PHONE_NUMBER_PATTERN = "\\D+" const val LOCATION_CONTACTS_TAB = 0 const val LOCATION_FAVORITES_TAB = 1 diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index dd5f3250..8acc0614 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, var events: ArrayList, @@ -12,7 +13,6 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m companion object { var sorting = 0 var startWithSurname = false - val pattern = "\\D+".toRegex() } override fun compareTo(other: Contact): Int { @@ -93,7 +93,7 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m fun getHashToCompare(): Int { val newPhoneNumbers = ArrayList() - phoneNumbers.mapTo(newPhoneNumbers) { PhoneNumber(it.value.replace(pattern, ""), 0) } + phoneNumbers.mapTo(newPhoneNumbers) { PhoneNumber(it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), ""), 0) } val newEmails = ArrayList() emails.mapTo(newEmails) { Email(it.value, 0) } From 57b64e63a1210d52b1069a3c9b5ff1dcc24bdfed Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 22:24:46 +0200 Subject: [PATCH 086/135] perform the selected action if a known contact gets clicked at Recents --- .../activities/GroupContactsActivity.kt | 22 +++++++------------ .../contacts/extensions/Activity.kt | 18 ++++++++++++--- .../contacts/fragments/MyViewPagerFragment.kt | 17 ++++---------- .../contacts/fragments/RecentsFragment.kt | 5 +++++ 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt index 19cc309a..3716007b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt @@ -3,12 +3,17 @@ package com.simplemobiletools.contacts.activities import android.os.Bundle import android.view.Menu import android.view.MenuItem -import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor +import com.simplemobiletools.commons.extensions.underlineText +import com.simplemobiletools.commons.extensions.updateTextColors import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.adapters.ContactsAdapter import com.simplemobiletools.contacts.dialogs.SelectContactsDialog import com.simplemobiletools.contacts.extensions.* -import com.simplemobiletools.contacts.helpers.* +import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.GROUP +import com.simplemobiletools.contacts.helpers.LOCATION_GROUP_CONTACTS import com.simplemobiletools.contacts.interfaces.RefreshContactsListener import com.simplemobiletools.contacts.interfaces.RemoveFromGroupListener import com.simplemobiletools.contacts.models.Contact @@ -102,18 +107,7 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh val currAdapter = group_contacts_list.adapter if (currAdapter == null) { ContactsAdapter(this, contacts, this, LOCATION_GROUP_CONTACTS, this, group_contacts_list, group_contacts_fastscroller) { - when (config.onContactClick) { - ON_CLICK_CALL_CONTACT -> { - val contact = it as Contact - if (contact.phoneNumbers.isNotEmpty()) { - tryStartCall(it) - } else { - toast(R.string.no_phone_number_found) - } - } - ON_CLICK_VIEW_CONTACT -> viewContact(it as Contact) - ON_CLICK_EDIT_CONTACT -> editContact(it as Contact) - } + contactClicked(it as Contact) }.apply { addVerticalDividers(true) group_contacts_list.adapter = this diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt index 60ead47a..a63eb96d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt @@ -15,9 +15,7 @@ import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.dialogs.CallConfirmationDialog -import com.simplemobiletools.contacts.helpers.ContactsHelper -import com.simplemobiletools.contacts.helpers.SMT_PRIVATE -import com.simplemobiletools.contacts.helpers.VcfExporter +import com.simplemobiletools.contacts.helpers.* import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.ContactSource import java.io.File @@ -202,3 +200,17 @@ fun Activity.getVisibleContactSources(): ArrayList { sourceNames.removeAll(config.ignoredContactSources) return sourceNames } + +fun SimpleActivity.contactClicked(contact: Contact) { + when (config.onContactClick) { + ON_CLICK_CALL_CONTACT -> { + if (contact.phoneNumbers.isNotEmpty()) { + tryStartCall(contact) + } else { + toast(R.string.no_phone_number_found) + } + } + ON_CLICK_VIEW_CONTACT -> viewContact(contact) + ON_CLICK_EDIT_CONTACT -> editContact(contact) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 9b37a550..7e0c03fe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -15,7 +15,9 @@ import com.simplemobiletools.contacts.activities.MainActivity import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.adapters.ContactsAdapter import com.simplemobiletools.contacts.adapters.GroupsAdapter -import com.simplemobiletools.contacts.extensions.* +import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.extensions.contactClicked +import com.simplemobiletools.contacts.extensions.getVisibleContactSources import com.simplemobiletools.contacts.helpers.* import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.Group @@ -185,18 +187,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) forceListRedraw = false val location = if (this is FavoritesFragment) LOCATION_FAVORITES_TAB else LOCATION_CONTACTS_TAB ContactsAdapter(activity as SimpleActivity, contacts, activity, location, null, fragment_list, fragment_fastscroller) { - when (config.onContactClick) { - ON_CLICK_CALL_CONTACT -> { - val contact = it as Contact - if (contact.phoneNumbers.isNotEmpty()) { - (activity as SimpleActivity).tryStartCall(it) - } else { - activity!!.toast(R.string.no_phone_number_found) - } - } - ON_CLICK_VIEW_CONTACT -> context!!.viewContact(it as Contact) - ON_CLICK_EDIT_CONTACT -> context!!.editContact(it as Contact) - } + activity?.contactClicked(it as Contact) }.apply { addVerticalDividers(true) fragment_list.adapter = this diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index f0e039d0..0c1f62c1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -5,6 +5,7 @@ import android.util.AttributeSet import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.isActivityDestroyed import com.simplemobiletools.contacts.adapters.RecentCallsAdapter +import com.simplemobiletools.contacts.extensions.contactClicked import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.RecentCall @@ -34,6 +35,10 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage break } } + + if (selectedContact != null) { + activity?.contactClicked(selectedContact) + } }.apply { addVerticalDividers(true) fragment_list.adapter = this From 82e6738cd7c761328694d15cdf9ac182d4998647 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 22:29:45 +0200 Subject: [PATCH 087/135] open the Edit screen at pressing an unknown Recents item --- .../contacts/activities/EditContactActivity.kt | 3 --- .../contacts/fragments/RecentsFragment.kt | 9 +++++++++ .../com/simplemobiletools/contacts/helpers/Constants.kt | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index bf153159..8d674368 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -45,9 +45,6 @@ class EditContactActivity : ContactActivity() { private val CHOOSE_PHOTO = 2 private val REMOVE_PHOTO = 3 - private val KEY_PHONE = "phone" - private val KEY_NAME = "name" - private var wasActivityInitialized = false private var lastPhotoIntentUri: Uri? = null private var isSaving = false diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index 0c1f62c1..d8ac307c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -1,11 +1,14 @@ package com.simplemobiletools.contacts.fragments import android.content.Context +import android.content.Intent import android.util.AttributeSet import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.isActivityDestroyed +import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.adapters.RecentCallsAdapter import com.simplemobiletools.contacts.extensions.contactClicked +import com.simplemobiletools.contacts.helpers.KEY_PHONE import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.RecentCall @@ -38,6 +41,12 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage if (selectedContact != null) { activity?.contactClicked(selectedContact) + } else { + Intent(context, EditContactActivity::class.java).apply { + action = Intent.ACTION_INSERT + putExtra(KEY_PHONE, recentCall) + context.startActivity(this) + } } }.apply { addVerticalDividers(true) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 7e8b5874..f433e558 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -23,6 +23,10 @@ const val GROUP = "group" const val FIRST_GROUP_ID = 10000 const val PHONE_NUMBER_PATTERN = "\\D+" +// extras used at third party intents +const val KEY_PHONE = "phone" +const val KEY_NAME = "name" + const val LOCATION_CONTACTS_TAB = 0 const val LOCATION_FAVORITES_TAB = 1 const val LOCATION_RECENTS_TAB = 2 From 210aadf2d207d0ac1428f064a070c2276e5f46a3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 22:41:53 +0200 Subject: [PATCH 088/135] hide Search at the Recents tab --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 0c03212f..f41aed05 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -177,7 +177,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { menuInflater.inflate(R.menu.menu, menu) val currentFragment = getCurrentFragment() menu.apply { - findItem(R.id.search).isVisible = currentFragment != groups_fragment + findItem(R.id.search).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment findItem(R.id.sort).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment findItem(R.id.filter).isVisible = currentFragment != groups_fragment } From acc69d4dcb1624f50db4dee155f5cb7afa22e826 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:03:34 +0200 Subject: [PATCH 089/135] use helper functions for checking some permissions --- app/build.gradle | 2 +- .../simplemobiletools/contacts/activities/MainActivity.kt | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 51461aae..6d73b7b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.17' + implementation 'com.simplemobiletools:commons:4.5.18' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index f41aed05..68991246 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -4,13 +4,11 @@ import android.Manifest import android.app.SearchManager import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.Handler import android.support.v4.app.ActivityCompat -import android.support.v4.content.ContextCompat import android.support.v4.view.MenuItemCompat import android.support.v4.view.ViewPager import android.support.v7.widget.SearchView @@ -73,13 +71,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (it) { handlePermission(PERMISSION_WRITE_CONTACTS) { // workaround for upgrading from version 3.x to 4.x as we added a new permission from an already granted permissions group - val hasGetAccountsPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED - if (!hasGetAccountsPermission) { + if (!hasPermission(PERMISSION_GET_ACCOUNTS)) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.GET_ACCOUNTS), 34) } - val hasWriteCallLogPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALL_LOG) == PackageManager.PERMISSION_GRANTED - if (!hasWriteCallLogPermission) { + if (!hasPermission(PERMISSION_WRITE_CALL_LOG)) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_CALL_LOG), 35) } From 9e4330dfa4f1c43722a8438a03c216df66632b9a Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:04:40 +0200 Subject: [PATCH 090/135] removing some magic numbers --- .../simplemobiletools/contacts/activities/MainActivity.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 68991246..7a92a04e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -41,6 +41,10 @@ import kotlinx.android.synthetic.main.fragment_recents.* import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { + // just some random constants + private val GET_ACCOUNTS_PERMISSION = 34 + private val WRITE_CALL_LOG_PERMISSION = 34 + private var isSearchOpen = false private var searchMenuItem: MenuItem? = null private var werePermissionsHandled = false @@ -72,11 +76,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { handlePermission(PERMISSION_WRITE_CONTACTS) { // workaround for upgrading from version 3.x to 4.x as we added a new permission from an already granted permissions group if (!hasPermission(PERMISSION_GET_ACCOUNTS)) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.GET_ACCOUNTS), 34) + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.GET_ACCOUNTS), GET_ACCOUNTS_PERMISSION) } if (!hasPermission(PERMISSION_WRITE_CALL_LOG)) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_CALL_LOG), 35) + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_CALL_LOG), WRITE_CALL_LOG_PERMISSION) } storeLocalAccountData() From 40777553fef32cabb294f505bff6c937b73f4e85 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:11:19 +0200 Subject: [PATCH 091/135] add a placeholder button for requesting permissions for displaying Recent Calls --- .../contacts/fragments/MyViewPagerFragment.kt | 5 ++++- .../contacts/fragments/RecentsFragment.kt | 12 +++++++++++- app/src/main/res/values-az/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hr/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 17 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 7e0c03fe..09e9b6e4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -58,7 +58,10 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fragment_placeholder.text = activity.getString(R.string.no_group_created) fragment_placeholder_2.text = activity.getString(R.string.create_group) } - this is RecentsFragment -> fragment_fab.beGone() + this is RecentsFragment -> { + fragment_fab.beGone() + fragment_placeholder_2.text = activity.getString(R.string.request_the_required_permissions) + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index d8ac307c..30b16f79 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -4,12 +4,15 @@ import android.content.Context import android.content.Intent import android.util.AttributeSet import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.extensions.hasPermission import com.simplemobiletools.commons.extensions.isActivityDestroyed +import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.adapters.RecentCallsAdapter import com.simplemobiletools.contacts.extensions.contactClicked import com.simplemobiletools.contacts.helpers.KEY_PHONE import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN +import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.RecentCall import kotlinx.android.synthetic.main.fragment_layout.view.* @@ -17,7 +20,13 @@ import kotlinx.android.synthetic.main.fragment_layout.view.* class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) { override fun fabClicked() {} - override fun placeholderClicked() {} + override fun placeholderClicked() { + activity!!.handlePermission(PERMISSION_WRITE_CALL_LOG) { + if (it) { + activity?.refreshContacts(RECENTS_TAB_MASK) + } + } + } fun updateRecentCalls(recentCalls: ArrayList) { if (activity == null || activity!!.isActivityDestroyed()) { @@ -25,6 +34,7 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage } fragment_placeholder.beVisibleIf(recentCalls.isEmpty()) + fragment_placeholder_2.beVisibleIf(recentCalls.isEmpty() && !activity!!.hasPermission(PERMISSION_WRITE_CALL_LOG)) fragment_list.beVisibleIf(recentCalls.isNotEmpty()) val currAdapter = fragment_list.adapter diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 3e362b2d..46f4c74a 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -14,6 +14,7 @@ Grupa SMS göndər Grupa e-poçt göndər Call %s + Request the required permissions Yeni kontakt Redaktə et diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a1527c07..0ebec726 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ Sende SMS an Gruppe Sende E-Mail an Gruppe Call %s + Request the required permissions Neuer Kontakt Kontakt bearbeiten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 7af6ada3..3e7c89a8 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -14,6 +14,7 @@ Send SMS to group Send email to group Call %s + Request the required permissions Νέα επαφή Επεξεργασία επαφής diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 3dec8f48..78acf3e7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -14,6 +14,7 @@ Envoyer un SMS au groupe Envoyer un e-mail au groupe Call %s + Request the required permissions Nouveau contact Modifier contact diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 4bc2b827..5e46bb0a 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -14,6 +14,7 @@ Send SMS to group Send email to group Call %s + Request the required permissions Novi kontakt Uredi kontakt diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 703b96d8..31ec9b66 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,6 +14,7 @@ グループにSMSを送信 グループにメールを送信 Call %s + Request the required permissions 新しい連絡先 連絡先を編集 diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 7b62c688..ed854ef9 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -14,6 +14,7 @@ Send SMS to group Send email to group Call %s + Request the required permissions 새로운 연락처 연락처 수정 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index be456240..09805977 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -14,6 +14,7 @@ Send SMS to group Send email to group Call %s + Request the required permissions Naujas kontaktas Redaguoti kontaktą diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index db97baf4..23fab7d4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -14,6 +14,7 @@ Enviar SMS para o grupo Enviar e-mail para o grupo Call %s + Request the required permissions Novo contacto Editar contacto diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3505d21b..a80fc3a0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -14,6 +14,7 @@ Отправить SMS группе Отправить письмо группе Call %s + Request the required permissions Новый контакт Редактировать контакт diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e99c2230..b8e165cf 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -14,6 +14,7 @@ Poslať skupine SMS Poslať skupine email Zavolať %s + Vyžiadať potrebné oprávnenia Nový kontakt Upraviť kontakt diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4b3ba2cd..2b0d0352 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -14,6 +14,7 @@ Skicka sms till grupp Skicka e-post till grupp Call %s + Request the required permissions Ny kontakt Redigera kontakt diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index aeb4f28a..48847d70 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -14,6 +14,7 @@ Gruba SMS gönder Gruba e-posta gönder Call %s + Request the required permissions Yeni kişi Kişiyi düzenle diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f41009c8..19e7a0ec 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -14,6 +14,7 @@ 發送簡訊給群組 發送電子郵件給群組 Call %s + Request the required permissions 新聯絡人 編輯聯絡人 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d456010..b6bb08ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Send SMS to group Send email to group Call %s + Request the required permissions New contact Edit contact From 8b29f9c10fde0fb29a22f81459b920d38ad9189a Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:16:05 +0200 Subject: [PATCH 092/135] allow editing contacts without permissions if our app triggers the editor --- .../contacts/activities/EditContactActivity.kt | 3 ++- .../simplemobiletools/contacts/fragments/RecentsFragment.kt | 2 ++ .../kotlin/com/simplemobiletools/contacts/helpers/Constants.kt | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index 8d674368..12f7e4b3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -58,7 +58,8 @@ class EditContactActivity : ContactActivity() { val action = intent.action isThirdPartyIntent = action == Intent.ACTION_EDIT || action == Intent.ACTION_INSERT_OR_EDIT || action == Intent.ACTION_INSERT - if (isThirdPartyIntent) { + val isFromSimpleContacts = intent.getBooleanExtra(IS_FROM_SIMPLE_CONTACTS, false) + if (isThirdPartyIntent && !isFromSimpleContacts) { handlePermission(PERMISSION_READ_CONTACTS) { if (it) { handlePermission(PERMISSION_WRITE_CONTACTS) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index 30b16f79..a6f14702 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -10,6 +10,7 @@ import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.adapters.RecentCallsAdapter import com.simplemobiletools.contacts.extensions.contactClicked +import com.simplemobiletools.contacts.helpers.IS_FROM_SIMPLE_CONTACTS import com.simplemobiletools.contacts.helpers.KEY_PHONE import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK @@ -55,6 +56,7 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage Intent(context, EditContactActivity::class.java).apply { action = Intent.ACTION_INSERT putExtra(KEY_PHONE, recentCall) + putExtra(IS_FROM_SIMPLE_CONTACTS, true) context.startActivity(this) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index f433e558..0ee6468e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -22,6 +22,7 @@ const val IS_PRIVATE = "is_private" const val GROUP = "group" const val FIRST_GROUP_ID = 10000 const val PHONE_NUMBER_PATTERN = "\\D+" +const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts" // extras used at third party intents const val KEY_PHONE = "phone" From 930d6cb149d1d4d0d65c004f58032f254ab88f2b Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:21:54 +0200 Subject: [PATCH 093/135] adding some of the new features in Whats New --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 1 + app/src/main/res/values/donottranslate.xml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 7a92a04e..07876d1f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -530,6 +530,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { add(Release(10, R.string.release_10)) add(Release(11, R.string.release_11)) add(Release(16, R.string.release_16)) + add(Release(27, R.string.release_27)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 613319f9..49c1ffcd 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,6 +2,10 @@ + + Allow customizing which tabs are visible\n + Added an optional call confirmation dialog + Added name prefix/suffix and contact organizations\n Added a settings item \"Manage shown contact fields\" for customizing visible contact details, with some fields disabled by default From e2807bf85b1d737786667a3a124bc6c3f35f2a35 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:26:47 +0200 Subject: [PATCH 094/135] fixing a constant value --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 07876d1f..2998da4c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -43,7 +43,7 @@ import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { // just some random constants private val GET_ACCOUNTS_PERMISSION = 34 - private val WRITE_CALL_LOG_PERMISSION = 34 + private val WRITE_CALL_LOG_PERMISSION = 35 private var isSearchOpen = false private var searchMenuItem: MenuItem? = null From 56646660c974a6d012e2657fc4303a6b93915428 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:42:30 +0200 Subject: [PATCH 095/135] updating the way permissions are handled --- app/build.gradle | 2 +- .../contacts/activities/MainActivity.kt | 35 ++++++++++--------- .../contacts/fragments/RecentsFragment.kt | 5 ++- .../contacts/helpers/ContactsHelper.kt | 2 +- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6d73b7b0..c4fb5b78 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.18' + implementation 'com.simplemobiletools:commons:4.5.19' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 2998da4c..1b78e8e1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -1,6 +1,5 @@ package com.simplemobiletools.contacts.activities -import android.Manifest import android.app.SearchManager import android.content.Context import android.content.Intent @@ -8,7 +7,6 @@ import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.Handler -import android.support.v4.app.ActivityCompat import android.support.v4.view.MenuItemCompat import android.support.v4.view.ViewPager import android.support.v7.widget.SearchView @@ -41,10 +39,6 @@ import kotlinx.android.synthetic.main.fragment_recents.* import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { - // just some random constants - private val GET_ACCOUNTS_PERMISSION = 34 - private val WRITE_CALL_LOG_PERMISSION = 35 - private var isSearchOpen = false private var searchMenuItem: MenuItem? = null private var werePermissionsHandled = false @@ -70,29 +64,36 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { // just get a reference to the database to make sure it is created properly dbHelper + handlePermission(PERMISSION_READ_CALL_LOG) { + if (it) { + handlePermission(PERMISSION_WRITE_CALL_LOG) { + checkContactPermissions() + } + } else { + checkContactPermissions() + } + + } + storeStateVariables() + checkWhatsNewDialog() + } + + private fun checkContactPermissions() { handlePermission(PERMISSION_READ_CONTACTS) { werePermissionsHandled = true if (it) { handlePermission(PERMISSION_WRITE_CONTACTS) { // workaround for upgrading from version 3.x to 4.x as we added a new permission from an already granted permissions group - if (!hasPermission(PERMISSION_GET_ACCOUNTS)) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.GET_ACCOUNTS), GET_ACCOUNTS_PERMISSION) + handlePermission(PERMISSION_GET_ACCOUNTS) { + storeLocalAccountData() + initFragments() } - - if (!hasPermission(PERMISSION_WRITE_CALL_LOG)) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_CALL_LOG), WRITE_CALL_LOG_PERMISSION) - } - - storeLocalAccountData() - initFragments() } } else { storeLocalAccountData() initFragments() } } - storeStateVariables() - checkWhatsNewDialog() } override fun onResume() { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index a6f14702..c1319997 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -6,6 +6,7 @@ import android.util.AttributeSet import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.hasPermission import com.simplemobiletools.commons.extensions.isActivityDestroyed +import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALL_LOG import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.adapters.RecentCallsAdapter @@ -24,7 +25,9 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage override fun placeholderClicked() { activity!!.handlePermission(PERMISSION_WRITE_CALL_LOG) { if (it) { - activity?.refreshContacts(RECENTS_TAB_MASK) + activity!!.handlePermission(PERMISSION_READ_CALL_LOG) { + activity?.refreshContacts(RECENTS_TAB_MASK) + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 9b53113a..c78e0ac2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -1318,7 +1318,7 @@ class ContactsHelper(val activity: Activity) { fun getRecents(callback: (ArrayList) -> Unit) { Thread { val calls = ArrayList() - if (!activity.hasPermission(PERMISSION_WRITE_CALL_LOG)) { + if (!activity.hasPermission(PERMISSION_WRITE_CALL_LOG) || !activity.hasPermission(PERMISSION_READ_CALL_LOG)) { callback(calls) return@Thread } From 4568263de14a8b723fd854cb433593fe6597a9c3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:44:28 +0200 Subject: [PATCH 096/135] update version to 4.2.0 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c4fb5b78..2089a453 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 26 - versionName "4.1.0" + versionCode 27 + versionName "4.2.0" setProperty("archivesBaseName", "contacts") } From efc4e777604c5ba1d8a219c74145f5ab95d897cc Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 4 Aug 2018 23:44:34 +0200 Subject: [PATCH 097/135] updating changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1302de..cfb6d668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========== +Version 4.2.0 *(2018-08-04)* +---------------------------- + + * Added a Recent Calls tab + * Allow customizing which tabs are visible + * Added an optional call confirmation dialog + * Fixed some glitches related to company contacts + * Some other performance and stability improvements + Version 4.1.0 *(2018-07-16)* ---------------------------- From 30819f0b5b488313d1a771457b7042dad04bd22a Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 5 Aug 2018 15:34:32 +0200 Subject: [PATCH 098/135] fixing some crashes --- app/build.gradle | 2 +- .../contacts/fragments/MyViewPagerFragment.kt | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2089a453..6bfaed1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.19' + implementation 'com.simplemobiletools:commons:4.5.20' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 09e9b6e4..ae3b7fbd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -15,6 +15,7 @@ import com.simplemobiletools.contacts.activities.MainActivity import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.adapters.ContactsAdapter import com.simplemobiletools.contacts.adapters.GroupsAdapter +import com.simplemobiletools.contacts.adapters.RecentCallsAdapter import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.extensions.contactClicked import com.simplemobiletools.contacts.extensions.getVisibleContactSources @@ -67,10 +68,10 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } fun textColorChanged(color: Int) { - if (this is GroupsFragment) { - (fragment_list.adapter as GroupsAdapter).updateTextColor(color) - } else { - (fragment_list.adapter as ContactsAdapter).apply { + when { + this is GroupsFragment -> (fragment_list.adapter as GroupsAdapter).updateTextColor(color) + this is RecentsFragment -> (fragment_list.adapter as RecentCallsAdapter).updateTextColor(color) + else -> (fragment_list.adapter as ContactsAdapter).apply { updateTextColor(color) initDrawables() } @@ -86,7 +87,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } fun startNameWithSurnameChanged(startNameWithSurname: Boolean) { - if (this !is GroupsFragment) { + if (this !is GroupsFragment && this !is RecentsFragment) { (fragment_list.adapter as ContactsAdapter).apply { config.sorting = if (startNameWithSurname) SORT_BY_SURNAME else SORT_BY_FIRST_NAME this@MyViewPagerFragment.activity!!.refreshContacts(CONTACTS_TAB_MASK or FAVORITES_TAB_MASK) @@ -217,7 +218,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) showContactThumbnails = showThumbnails notifyDataSetChanged() } - } else { + } else if (this !is RecentsFragment) { (fragment_list.adapter as? ContactsAdapter)?.apply { showContactThumbnails = showThumbnails notifyDataSetChanged() From 9158f5226f7977b352ea6eea289fb9ab1518c1d8 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 5 Aug 2018 15:36:02 +0200 Subject: [PATCH 099/135] make sure the Call Confirmation button has a proper color --- .../contacts/dialogs/CallConfirmationDialog.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt index bad59311..40f3ef89 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt @@ -3,8 +3,10 @@ package com.simplemobiletools.contacts.dialogs import android.support.v7.app.AlertDialog import android.view.animation.AnimationUtils import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.applyColorFilter import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.models.Contact import kotlinx.android.synthetic.main.dialog_call_confirmation.view.* @@ -12,6 +14,7 @@ class CallConfirmationDialog(val activity: BaseSimpleActivity, val contact: Cont private var view = activity.layoutInflater.inflate(R.layout.dialog_call_confirmation, null) init { + view.call_confirm_phone.applyColorFilter(activity.config.textColor) AlertDialog.Builder(activity) .setNegativeButton(R.string.cancel, null) .create().apply { From 4f16dc5c42bdffbed9c0645b4a361d0d12eaa9ea Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 5 Aug 2018 15:39:03 +0200 Subject: [PATCH 100/135] make sure the top tab icons are colored properly --- .../contacts/activities/MainActivity.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 1b78e8e1..dc7d06a4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -4,6 +4,7 @@ import android.app.SearchManager import android.content.Context import android.content.Intent import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.os.Handler @@ -369,12 +370,16 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { invalidateOptionsMenu() } - private fun getTabIcon(position: Int) = resources.getDrawable(when (position) { - LOCATION_CONTACTS_TAB -> R.drawable.ic_person - LOCATION_FAVORITES_TAB -> R.drawable.ic_star_on - LOCATION_RECENTS_TAB -> R.drawable.ic_clock - else -> R.drawable.ic_group - }) + private fun getTabIcon(position: Int): Drawable { + val drawableId = when (position) { + LOCATION_CONTACTS_TAB -> R.drawable.ic_person + LOCATION_FAVORITES_TAB -> R.drawable.ic_star_on + LOCATION_RECENTS_TAB -> R.drawable.ic_clock + else -> R.drawable.ic_group + } + + return resources.getColoredDrawableWithColor(drawableId, config.textColor) + } private fun showSortingDialog() { ChangeSortingDialog(this) { From 5f4306705bfb7d309e939465d6e65a4c297b65e8 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 5 Aug 2018 15:41:26 +0200 Subject: [PATCH 101/135] update version to 4.2.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6bfaed1d..da0dddf7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 27 - versionName "4.2.0" + versionCode 28 + versionName "4.2.1" setProperty("archivesBaseName", "contacts") } From 7151aa3d76fc2f565a2424f835c5bbb8decc6a4e Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 5 Aug 2018 15:41:34 +0200 Subject: [PATCH 102/135] updating changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb6d668..9e34645f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========== +Version 4.2.1 *(2018-08-05)* +---------------------------- + + * Added some stability and light theme UX fixes + Version 4.2.0 *(2018-08-04)* ---------------------------- From 385ab4b5197992469017157a545ac036516545dc Mon Sep 17 00:00:00 2001 From: Gozzwip <36863088+Gozzwip@users.noreply.github.com> Date: Mon, 6 Aug 2018 18:54:50 +0400 Subject: [PATCH 103/135] typo fixes and more translations --- app/src/main/res/values-az/strings.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 46f4c74a..591ffde4 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -13,8 +13,8 @@ Kontaktlara e-poçt göndər Grupa SMS göndər Grupa e-poçt göndər - Call %s - Request the required permissions + %s şəxsinə zng et + Lazım olan icazələri istə Yeni kontakt Redaktə et @@ -49,11 +49,11 @@ Kontakt detallarına bax Göstərilən kontakt sahəsini idarə et Təkrarlanmış kontaktları filtrləməyə çalış - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Göstərilən nişanları idarə et + Kontaktlar + Sevimlilər + Hazırki zənglər + Zəngə başlamazdan əvvəl zəng təsdiq pəncərəsi göstər E-poçt @@ -76,7 +76,7 @@ Görünür, hələlik heçbir sevimli kontakt əlavə etməmisiniz. - Sevimlilər əlavlə et + Sevimlilər əlavə et Sevimlilərə əlavə et Sevimlilərdən sil Kontaktı dəyişmək üçün İdarə et ekranında olmalısınız @@ -104,7 +104,7 @@ Hadisələr (ad günləri, il dönümləri) Qeydlər Təşkilat - Vebsayatlar + Vebsaytlar Qruplar Kontakt kökü From 26ad4573187546630eefd635038c85ae924c319a Mon Sep 17 00:00:00 2001 From: fricyo <30796677+fricyo@users.noreply.github.com> Date: Tue, 7 Aug 2018 22:32:22 +0800 Subject: [PATCH 104/135] Update Translation --- app/src/main/res/values-zh-rTW/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 19e7a0ec..a34ddaa1 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -13,8 +13,8 @@ 發送電子郵件給聯絡人 發送簡訊給群組 發送電子郵件給群組 - Call %s - Request the required permissions + 打電話給 %s + 請求必要的權限 新聯絡人 編輯聯絡人 @@ -49,11 +49,11 @@ 顯示聯絡人資料 管理顯示的聯絡人欄位 試著過濾重複的聯絡人 - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + 管理顯示的頁面 + 聯絡人 + 我的最愛 + 通話紀錄 + 開始通話前顯示通話確認框 電子信箱 From 670386f532cc352bfe497f4e9e08059e44cfa7e3 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 09:39:25 +0200 Subject: [PATCH 105/135] update commons to 4.6.5 --- app/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index da0dddf7..dadae90f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.5.20' + implementation 'com.simplemobiletools:commons:4.6.5' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/build.gradle b/build.gradle index 939d19d8..b0005124 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong From 197670b486696a2e93f13a761ed6ae99354a765c Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 09:52:13 +0200 Subject: [PATCH 106/135] fix #222, delay checking of the visible menu items a bit --- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index dc7d06a4..e310da95 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -178,6 +178,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu, menu) val currentFragment = getCurrentFragment() + menu.apply { findItem(R.id.search).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment findItem(R.id.sort).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment @@ -363,11 +364,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { main_tabs_holder.onGlobalLayout { Handler().postDelayed({ main_tabs_holder.getTabAt(config.lastUsedViewPagerPage)?.select() + invalidateOptionsMenu() }, 100L) } main_tabs_holder.beVisibleIf(skippedTabs < 3) - invalidateOptionsMenu() } private fun getTabIcon(position: Int): Drawable { From 991e4d1e75fb6e6b2ab22d2704f58e82c4c3fd9e Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 18:16:40 +0200 Subject: [PATCH 107/135] make sure we trim the colon from url at importing contacts --- .../com/simplemobiletools/contacts/helpers/VcfImporter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index 8de88544..33c459f3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -282,7 +282,7 @@ class VcfImporter(val activity: SimpleActivity) { if (website.startsWith(";")) { curWebsites.add(website.substringAfter(":")) } else { - curWebsites.add(website) + curWebsites.add(website.trimStart(':')) } } From 7acfde6bb023aafe16381d4c068085fb6e7377cb Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 18:42:16 +0200 Subject: [PATCH 108/135] parse quoted printable properly at importing contacts --- .../contacts/helpers/VcfImporter.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index 33c459f3..dc94f12d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -196,12 +196,17 @@ class VcfImporter(val activity: SimpleActivity) { val type = getAddressTypeId(rawType.toUpperCase()) val addresses = addressParts[1].split(";") if (addresses.size == 7) { - if (address.contains(";CHARSET=UTF-8:")) { - val fullAddress = TextUtils.join(", ", addresses.filter { it.trim().isNotEmpty() }) - curAddresses.add(Address(fullAddress, type)) + var parsedAddress = if (address.contains(";CHARSET=UTF-8:")) { + TextUtils.join(", ", addresses.filter { it.trim().isNotEmpty() }) } else { - curAddresses.add(Address(addresses[2].replace("\\n", "\n"), type)) + addresses[2].replace("\\n", "\n") } + + if (address.contains("QUOTED-PRINTABLE")) { + parsedAddress = QuotedPrintable.decode(parsedAddress) + } + + curAddresses.add(Address(parsedAddress, type)) } } From 67d0b02280e8c72988ab0e75ce932ed12c56f0f9 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 21:16:42 +0200 Subject: [PATCH 109/135] properly handle importing organizations spanning across multiple lines --- .../contacts/helpers/VcfImporter.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index dc94f12d..ae7e3dfd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -47,6 +47,10 @@ class VcfImporter(val activity: SimpleActivity) { private var isGettingNotes = false private var currentNotesSB = StringBuilder() + private var isGettingCompany = false + private var currentCompanyIsANSI = false + private var currentCompany = StringBuilder() + private var contactsImported = 0 private var contactsFailed = 0 @@ -60,9 +64,9 @@ class VcfImporter(val activity: SimpleActivity) { inputStream.bufferedReader().use { while (true) { - var line = it.readLine() ?: break - line = line.trim() - if (line.trim().isEmpty()) { + val originalLine = it.readLine() ?: break + val line = originalLine.trim() + if (line.isEmpty()) { if (isGettingPhoto) { savePhoto() isGettingPhoto = false @@ -73,12 +77,16 @@ class VcfImporter(val activity: SimpleActivity) { isGettingName = false parseNames() } else if (isGettingNotes) { - if (line.startsWith(' ')) { + if (originalLine.startsWith(' ')) { currentNotesSB.append(line.substring(1)) } else { curNotes = currentNotesSB.toString().replace("\\n", "\n").replace("\\,", ",") isGettingNotes = false } + } else if (isGettingCompany && currentCompanyIsANSI && line.startsWith("=")) { + currentCompany.append(line) + curCompany = QuotedPrintable.decode(currentCompany.toString().replace("==", "=")) + continue } when { @@ -273,6 +281,10 @@ class VcfImporter(val activity: SimpleActivity) { } else { company.trimStart(':') } + + currentCompanyIsANSI = company.toUpperCase().contains("QUOTED-PRINTABLE") + currentCompany.append(curCompany) + isGettingCompany = true } private fun addJobPosition(jobPosition: String) { @@ -328,5 +340,9 @@ class VcfImporter(val activity: SimpleActivity) { isGettingNotes = false currentNotesSB = StringBuilder() + + isGettingCompany = false + currentCompanyIsANSI = false + currentCompany = StringBuilder() } } From 32dd6aac805b3bd348f38fdd7f5ad8c277fe6e22 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 21:30:15 +0200 Subject: [PATCH 110/135] properly refresh Contacts and Favorites fragments at delete via Search --- .../simplemobiletools/contacts/adapters/ContactsAdapter.kt | 2 +- .../contacts/fragments/MyViewPagerFragment.kt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt index 3b7dc075..39bd3220 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt @@ -160,7 +160,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList) { From 6bf77d73bd647a29a2c879c9f0135ec1e6183f37 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sat, 11 Aug 2018 22:03:26 +0200 Subject: [PATCH 111/135] fix #131, properly sort all UTF8 names --- .../contacts/models/Contact.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 8acc0614..3fbb72c5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -5,6 +5,7 @@ import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN +import java.text.Normalizer data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, var events: ArrayList, @@ -13,6 +14,7 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m companion object { var sorting = 0 var startWithSurname = false + val regex = "\\p{InCombiningDiacriticalMarks}+".toRegex() } override fun compareTo(other: Contact): Int { @@ -21,25 +23,25 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m when { sorting and SORT_BY_FIRST_NAME != 0 -> { - firstString = firstName - secondString = other.firstName + firstString = normalizeString(firstName) + secondString = normalizeString(other.firstName) } sorting and SORT_BY_MIDDLE_NAME != 0 -> { - firstString = middleName - secondString = other.middleName + firstString = normalizeString(middleName) + secondString = normalizeString(other.middleName) } else -> { - firstString = surname - secondString = other.surname + firstString = normalizeString(surname) + secondString = normalizeString(other.surname) } } if (firstString.isEmpty() && firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty() && organization.company.isNotEmpty()) { - firstString = organization.company + firstString = normalizeString(organization.company) } if (secondString.isEmpty() && other.firstName.isEmpty() && other.middleName.isEmpty() && other.surname.isEmpty() && other.organization.company.isNotEmpty()) { - secondString = other.organization.company + secondString = normalizeString(other.organization.company) } var result = if (firstString.firstOrNull()?.isLetter() == true && secondString.firstOrNull()?.isLetter() == false) { @@ -67,6 +69,8 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m return result } + private fun normalizeString(string: String) = Normalizer.normalize(string, Normalizer.Form.NFD).replace(regex, "") + fun getBubbleText() = when { sorting and SORT_BY_FIRST_NAME != 0 -> firstName sorting and SORT_BY_MIDDLE_NAME != 0 -> middleName From fd775bf050725869f1b0479005312b03628b73f0 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 19:24:30 +0200 Subject: [PATCH 112/135] make string normalizing a string extension --- .../contacts/extensions/String.kt | 6 ++++++ .../contacts/helpers/Constants.kt | 2 ++ .../contacts/models/Contact.kt | 21 ++++++++----------- 3 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt new file mode 100644 index 00000000..b76c81b7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt @@ -0,0 +1,6 @@ +package com.simplemobiletools.contacts.extensions + +import com.simplemobiletools.contacts.helpers.normalizeRegex +import java.text.Normalizer + +fun String.normalizeString() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(normalizeRegex, "") diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 0ee6468e..6e3c1608 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -122,3 +122,5 @@ val localAccountTypes = arrayListOf("vnd.sec.contact.phone", "com.android.huawei.phone", "Local Phone Account" ) + +val normalizeRegex = "\\p{InCombiningDiacriticalMarks}+".toRegex() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 3fbb72c5..06cd24d4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -4,8 +4,8 @@ import android.graphics.Bitmap import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING +import com.simplemobiletools.contacts.extensions.normalizeString import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN -import java.text.Normalizer data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, var events: ArrayList, @@ -14,7 +14,6 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m companion object { var sorting = 0 var startWithSurname = false - val regex = "\\p{InCombiningDiacriticalMarks}+".toRegex() } override fun compareTo(other: Contact): Int { @@ -23,25 +22,25 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m when { sorting and SORT_BY_FIRST_NAME != 0 -> { - firstString = normalizeString(firstName) - secondString = normalizeString(other.firstName) + firstString = firstName.normalizeString() + secondString = other.firstName.normalizeString() } sorting and SORT_BY_MIDDLE_NAME != 0 -> { - firstString = normalizeString(middleName) - secondString = normalizeString(other.middleName) + firstString = middleName.normalizeString() + secondString = other.middleName.normalizeString() } else -> { - firstString = normalizeString(surname) - secondString = normalizeString(other.surname) + firstString = surname.normalizeString() + secondString = other.surname.normalizeString() } } if (firstString.isEmpty() && firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty() && organization.company.isNotEmpty()) { - firstString = normalizeString(organization.company) + firstString = organization.company.normalizeString() } if (secondString.isEmpty() && other.firstName.isEmpty() && other.middleName.isEmpty() && other.surname.isEmpty() && other.organization.company.isNotEmpty()) { - secondString = normalizeString(other.organization.company) + secondString = other.organization.company.normalizeString() } var result = if (firstString.firstOrNull()?.isLetter() == true && secondString.firstOrNull()?.isLetter() == false) { @@ -69,8 +68,6 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m return result } - private fun normalizeString(string: String) = Normalizer.normalize(string, Normalizer.Form.NFD).replace(regex, "") - fun getBubbleText() = when { sorting and SORT_BY_FIRST_NAME != 0 -> firstName sorting and SORT_BY_MIDDLE_NAME != 0 -> middleName From 9ab6f9d7df7837c2780eb1e0c8c5f5d7415ccd43 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 19:26:55 +0200 Subject: [PATCH 113/135] properly update view visibility after closing search --- .../contacts/fragments/MyViewPagerFragment.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 0fd78e68..1409077e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -186,10 +186,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } private fun setupContactsFavoritesAdapter(contacts: ArrayList) { - fragment_placeholder_2.beVisibleIf(contacts.isEmpty()) - fragment_placeholder.beVisibleIf(contacts.isEmpty()) - fragment_list.beVisibleIf(contacts.isNotEmpty()) - + setupViewVisibility(contacts) val currAdapter = fragment_list.adapter if (currAdapter == null || forceListRedraw) { forceListRedraw = false @@ -271,6 +268,8 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fun onSearchClosed() { (fragment_list.adapter as? ContactsAdapter)?.updateItems(contactsIgnoringSearch) + setupViewVisibility(contactsIgnoringSearch) + if (this is FavoritesFragment) { fragment_placeholder.text = activity?.getString(R.string.no_favorites) } @@ -283,6 +282,12 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fragment_placeholder_2.setTextColor(context.getAdjustedPrimaryColor()) } + private fun setupViewVisibility(contacts: ArrayList) { + fragment_placeholder_2.beVisibleIf(contacts.isEmpty()) + fragment_placeholder.beVisibleIf(contacts.isEmpty()) + fragment_list.beVisibleIf(contacts.isNotEmpty()) + } + abstract fun fabClicked() abstract fun placeholderClicked() From 0bcbf9bb94f4601cd199d2bc9ab525ee2fe81fc9 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 20:18:05 +0200 Subject: [PATCH 114/135] fix #132, fix searching with UTF8 chars --- app/build.gradle | 2 +- .../contacts/extensions/String.kt | 6 ------ .../contacts/fragments/MyViewPagerFragment.kt | 17 ++++++++++------- .../contacts/helpers/Constants.kt | 2 -- .../contacts/models/Contact.kt | 2 +- 5 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt diff --git a/app/build.gradle b/app/build.gradle index dadae90f..b0cacb82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.6.5' + implementation 'com.simplemobiletools:commons:4.6.6' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt deleted file mode 100644 index b76c81b7..00000000 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.simplemobiletools.contacts.extensions - -import com.simplemobiletools.contacts.helpers.normalizeRegex -import java.text.Normalizer - -fun String.normalizeString() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(normalizeRegex, "") diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 1409077e..fea5f0b4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -236,32 +236,35 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } fun onSearchQueryChanged(text: String) { + val shouldNormalize = text.normalizeString() == text (fragment_list.adapter as? ContactsAdapter)?.apply { val filtered = contactsIgnoringSearch.filter { - it.getFullName().contains(text, true) || + getProperText(it.getFullName(), shouldNormalize).contains(text, true) || it.phoneNumbers.any { it.value.contains(text, true) } || it.emails.any { it.value.contains(text, true) } || - it.addresses.any { it.value.contains(text, true) } || - it.notes.contains(text, true) || - it.organization.company.contains(text, true) || - it.organization.jobPosition.contains(text, true) || + it.addresses.any { getProperText(it.value, shouldNormalize).contains(text, true) } || + getProperText(it.notes, shouldNormalize).contains(text, true) || + getProperText(it.organization.company, shouldNormalize).contains(text, true) || + getProperText(it.organization.jobPosition, shouldNormalize).contains(text, true) || it.websites.any { it.contains(text, true) } } as ArrayList Contact.sorting = config.sorting Contact.startWithSurname = config.startNameWithSurname filtered.sort() - filtered.sortBy { !it.getFullName().startsWith(text, true) } + filtered.sortBy { !getProperText(it.getFullName(), shouldNormalize).startsWith(text, true) } if (filtered.isEmpty() && this@MyViewPagerFragment is FavoritesFragment) { fragment_placeholder.text = activity.getString(R.string.no_items_found) } fragment_placeholder.beVisibleIf(filtered.isEmpty()) - updateItems(filtered, text) + updateItems(filtered, text.normalizeString()) } } + private fun getProperText(text: String, shouldNormalize: Boolean) = if (shouldNormalize) text.normalizeString() else text + fun onSearchOpened() { contactsIgnoringSearch = (fragment_list?.adapter as? ContactsAdapter)?.contactItems ?: ArrayList() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 6e3c1608..0ee6468e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -122,5 +122,3 @@ val localAccountTypes = arrayListOf("vnd.sec.contact.phone", "com.android.huawei.phone", "Local Phone Account" ) - -val normalizeRegex = "\\p{InCombiningDiacriticalMarks}+".toRegex() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 06cd24d4..e8229f51 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -1,10 +1,10 @@ package com.simplemobiletools.contacts.models import android.graphics.Bitmap +import com.simplemobiletools.commons.extensions.normalizeString import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import com.simplemobiletools.contacts.extensions.normalizeString import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String, From 85976af2b80437582629647dcfbd5d52aa4ba244 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 20:22:48 +0200 Subject: [PATCH 115/135] adding a Nickname string --- app/src/main/res/values-az/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hr/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 15 files changed, 15 insertions(+) diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 591ffde4..d197f75f 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -23,6 +23,7 @@ Ad Orta Ad Soyad + Nickname Qruplar yoxdur diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0ebec726..85b88321 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,6 +23,7 @@ Vorname Zweiter Vorname Nachname + Nickname Keine Gruppen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3e7c89a8..caba673d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -23,6 +23,7 @@ Όνομα Μεσαίο όνομα Επώνυμο + Nickname Δεν υπάρχουν ομάδες diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 78acf3e7..c8ecf2f7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -23,6 +23,7 @@ Prénom Deuxième prénom Nom + Nickname Pas de groupe diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 5e46bb0a..e3b2bb3f 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -23,6 +23,7 @@ Ime Srednje ime Prezime + Nickname Nema grupa diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 31ec9b66..3235994a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -23,6 +23,7 @@ ミドルネーム + Nickname グループなし diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index ed854ef9..94819d86 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -23,6 +23,7 @@ 이름 중간 이름 + Nickname No groups diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 09805977..4dbd270c 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -23,6 +23,7 @@ Vardas Antras vardas Pavardė + Nickname Nėra grupių diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 23fab7d4..228623a3 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -23,6 +23,7 @@ Primeiro nome Segundo nome Apelido + Nickname Não há grupos diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a80fc3a0..86609349 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -23,6 +23,7 @@ Имя Отчество Фамилия + Nickname Нет групп diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index b8e165cf..6e3f3d25 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -23,6 +23,7 @@ Krstné meno Stredné meno Priezvisko + Prezývka Žiadne skupiny diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 2b0d0352..347d8536 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -23,6 +23,7 @@ Förnamn Mellannamn Efternamn + Nickname Inga grupper diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 48847d70..f849e887 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -23,6 +23,7 @@ Adı Göbek adı Soyadı + Nickname Grup yok diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a34ddaa1..c6f1b48b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -23,6 +23,7 @@ 名字 中間名 姓氏 + Nickname 沒有群組 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6bb08ca..d0c4b5f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ First name Middle name Surname + Nickname No groups From 041dc198561a014ccfe294dcdc85ee3f42e0eefe Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 20:27:15 +0200 Subject: [PATCH 116/135] add nickname to the visible fields customizing dialog --- .../contacts/dialogs/ManageVisibleFieldsDialog.kt | 1 + .../com/simplemobiletools/contacts/helpers/Constants.kt | 1 + app/src/main/res/layout/dialog_manage_visible_fields.xml | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt index dfd89e5c..9560f4b2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt @@ -19,6 +19,7 @@ class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) { put(SHOW_MIDDLE_NAME_FIELD, R.id.manage_visible_fields_middle_name) put(SHOW_SURNAME_FIELD, R.id.manage_visible_fields_surname) put(SHOW_SUFFIX_FIELD, R.id.manage_visible_fields_suffix) + put(SHOW_NICKNAME_FIELD, R.id.manage_visible_fields_nickname) put(SHOW_PHONE_NUMBERS_FIELD, R.id.manage_visible_fields_phone_numbers) put(SHOW_EMAILS_FIELD, R.id.manage_visible_fields_emails) put(SHOW_ADDRESSES_FIELD, R.id.manage_visible_fields_addresses) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 0ee6468e..a1c7e3a5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -104,6 +104,7 @@ const val SHOW_ORGANIZATION_FIELD = 1024 const val SHOW_GROUPS_FIELD = 2048 const val SHOW_CONTACT_SOURCE_FIELD = 4096 const val SHOW_WEBSITES_FIELD = 8192 +const val SHOW_NICKNAME_FIELD = 16384 const val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME const val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE diff --git a/app/src/main/res/layout/dialog_manage_visible_fields.xml b/app/src/main/res/layout/dialog_manage_visible_fields.xml index 2b8bbf80..599fdb88 100644 --- a/app/src/main/res/layout/dialog_manage_visible_fields.xml +++ b/app/src/main/res/layout/dialog_manage_visible_fields.xml @@ -54,6 +54,14 @@ android:paddingTop="@dimen/activity_margin" android:text="@string/suffix"/> + + Date: Sun, 12 Aug 2018 19:40:26 +0100 Subject: [PATCH 117/135] Update strings.xml --- app/src/main/res/values-pt/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 228623a3..f2a6d47f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -13,7 +13,7 @@ Enviar e-mail aos contactos Enviar SMS para o grupo Enviar e-mail para o grupo - Call %s + Ligar a %s Request the required permissions Novo contacto @@ -23,7 +23,7 @@ Primeiro nome Segundo nome Apelido - Nickname + Alcunha Não há grupos @@ -51,10 +51,10 @@ Gerir campos a exibir Tentar filtrar contactos duplicados Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Contactos + Favoritos + Chamadas recentes + Mostrar diálogo para confirmar a chamada E-mail From 87069862f89a7d55ad23ec6bccf29233104043c2 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 20:52:46 +0200 Subject: [PATCH 118/135] add a nickname field to contacts --- .../activities/EditContactActivity.kt | 4 +- .../contacts/helpers/ContactsHelper.kt | 50 +++++++++++++++++-- .../contacts/helpers/DBHelper.kt | 21 +++++--- .../contacts/helpers/VcfImporter.kt | 6 ++- .../contacts/models/Contact.kt | 6 +-- 5 files changed, 68 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index 12f7e4b3..db2bef0f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -503,8 +503,8 @@ class EditContactActivity : ContactActivity() { supportActionBar?.title = resources.getString(R.string.new_contact) originalContactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE val organization = Organization("", "") - contact = Contact(0, "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "", null, "", - ArrayList(), organization, ArrayList()) + contact = Contact(0, "", "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "", + null, "", ArrayList(), organization, ArrayList()) contact_source.text = getPublicContactSource(contact!!.source) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index c78e0ac2..b719390d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -11,6 +11,7 @@ import android.net.Uri import android.provider.CallLog import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds +import android.provider.ContactsContract.CommonDataKinds.Nickname import android.provider.ContactsContract.CommonDataKinds.Note import android.provider.MediaStore import android.text.TextUtils @@ -112,6 +113,7 @@ class ContactsHelper(val activity: Activity) { val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: "" + val nickname = "" val photoUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_URI) ?: "" val number = ArrayList() // proper value is obtained below val emails = ArrayList() @@ -125,8 +127,8 @@ class ContactsHelper(val activity: Activity) { val groups = ArrayList() val organization = Organization("", "") val websites = ArrayList() - val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, - accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, + events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) contacts.put(id, contact) } while (cursor.moveToNext()) @@ -144,6 +146,13 @@ class ContactsHelper(val activity: Activity) { contacts[key]?.phoneNumbers = phoneNumbers.valueAt(i) } + val nicknames = getNicknames() + size = nicknames.size() + for (i in 0 until size) { + val key = nicknames.keyAt(i) + contacts[key]?.nickname = nicknames.valueAt(i) + } + val emails = getEmails() size = emails.size() for (i in 0 until size) { @@ -225,6 +234,36 @@ class ContactsHelper(val activity: Activity) { return phoneNumbers } + private fun getNicknames(contactId: Int? = null): SparseArray { + val nicknames = SparseArray() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + Nickname.NAME + ) + + val selection = getSourcesSelection(true, contactId != null) + val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId) + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) + val nickname = cursor.getStringValue(Nickname.NAME) ?: continue + nicknames.put(id, nickname) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return nicknames + } + private fun getEmails(contactId: Int? = null): SparseArray> { val emails = SparseArray>() val uri = CommonDataKinds.Email.CONTENT_URI @@ -353,7 +392,7 @@ class ContactsHelper(val activity: Activity) { if (cursor?.moveToFirst() == true) { do { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) - val note = cursor.getStringValue(CommonDataKinds.Note.NOTE) ?: continue + val note = cursor.getStringValue(Note.NOTE) ?: continue notes.put(id, note) } while (cursor.moveToNext()) } @@ -654,6 +693,7 @@ class ContactsHelper(val activity: Activity) { val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: "" + val nickname = getNicknames(id)[id] ?: "" val photoUri = cursor.getStringValue(CommonDataKinds.Phone.PHOTO_URI) ?: "" val number = getPhoneNumbers(id)[id] ?: ArrayList() val emails = getEmails(id)[id] ?: ArrayList() @@ -667,8 +707,8 @@ class ContactsHelper(val activity: Activity) { val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" val organization = getOrganizations(id)[id] ?: Organization("", "") val websites = getWebsites(id)[id] ?: ArrayList() - return Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, accountName, - starred, contactId, thumbnailUri, null, notes, groups, organization, websites) + return Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events, + accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) } } finally { cursor?.close() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt index 0cdd81ae..860b3698 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt @@ -28,6 +28,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private val COL_MIDDLE_NAME = "middle_name" private val COL_SURNAME = "surname" private val COL_SUFFIX = "suffix" + private val COL_NICKNAME = "nickname" private val COL_PHOTO = "photo" private val COL_PHONE_NUMBERS = "phone_numbers" private val COL_EMAILS = "emails" @@ -48,9 +49,9 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private val mDb = writableDatabase companion object { - private const val DB_VERSION = 5 const val DB_NAME = "contacts.db" - var dbInstance: DBHelper? = null + private const val DB_VERSION = 6 + private var dbInstance: DBHelper? = null var gson = Gson() fun newInstance(context: Context): DBHelper { @@ -65,7 +66,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont db.execSQL("CREATE TABLE $CONTACTS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_FIRST_NAME TEXT, $COL_MIDDLE_NAME TEXT, " + "$COL_SURNAME TEXT, $COL_PHOTO BLOB, $COL_PHONE_NUMBERS TEXT, $COL_EMAILS TEXT, $COL_EVENTS TEXT, $COL_STARRED INTEGER, " + "$COL_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION TEXT," + - "$COL_WEBSITES TEXT)") + "$COL_WEBSITES TEXT, $COL_NICKNAME TEXT)") // start autoincrement ID from FIRST_CONTACT_ID to avoid conflicts db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$CONTACTS_TABLE_NAME', $FIRST_CONTACT_ID)") @@ -94,6 +95,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont if (oldVersion < 5) { db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_WEBSITES TEXT DEFAULT ''") } + + if (oldVersion < 6) { + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_NICKNAME TEXT DEFAULT ''") + } } private fun createGroupsTable(db: SQLiteDatabase) { @@ -135,6 +140,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont put(COL_MIDDLE_NAME, contact.middleName) put(COL_SURNAME, contact.surname) put(COL_SUFFIX, contact.suffix) + put(COL_NICKNAME, contact.nickname) put(COL_PHONE_NUMBERS, gson.toJson(contact.phoneNumbers)) put(COL_EMAILS, gson.toJson(contact.emails)) put(COL_ADDRESSES, gson.toJson(contact.addresses)) @@ -252,8 +258,8 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array? = null): ArrayList { val storedGroups = ContactsHelper(activity).getStoredGroups() val contacts = ArrayList() - val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_PHONE_NUMBERS, COL_EMAILS, - COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES) + val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS, + COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES) val phoneNumbersToken = object : TypeToken>() {}.type val emailsToken = object : TypeToken>() {}.type @@ -271,6 +277,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont val middleName = cursor.getStringValue(COL_MIDDLE_NAME) val surname = cursor.getStringValue(COL_SURNAME) val suffix = cursor.getStringValue(COL_SUFFIX) + val nickname = cursor.getStringValue(COL_NICKNAME) val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS) val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken) @@ -315,8 +322,8 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont val websites = if (websitesJson == "[]") ArrayList() else gson.fromJson>(websitesJson, websitesToken) ?: ArrayList(1) - val contact = Contact(id, prefix, firstName, middleName, surname, suffix, "", phoneNumbers, emails, addresses, events, - SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites) + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, "", phoneNumbers, emails, addresses, + events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites) contacts.add(contact) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index ae7e3dfd..3831f26f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -25,6 +25,7 @@ class VcfImporter(val activity: SimpleActivity) { private var curMiddleName = "" private var curSurname = "" private var curSuffix = "" + private var curNickname = "" private var curPhotoUri = "" private var curNotes = "" private var curCompany = "" @@ -305,8 +306,8 @@ class VcfImporter(val activity: SimpleActivity) { private fun saveContact(source: String) { val organization = Organization(curCompany, curJobPosition) - val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents, - source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites) + val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curNickname, curPhotoUri, curPhoneNumbers, + curEmails, curAddresses, curEvents, source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites) if (ContactsHelper(activity).insertContact(contact)) { contactsImported++ @@ -319,6 +320,7 @@ class VcfImporter(val activity: SimpleActivity) { curMiddleName = "" curSurname = "" curSuffix = "" + curNickname = "" curPhotoUri = "" curNotes = "" curCompany = "" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index e8229f51..700dbbba 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -7,9 +7,9 @@ import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN -data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String, - var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, var events: ArrayList, - var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String, +data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var nickname: String, + var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, + var events: ArrayList, var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String, var groups: ArrayList, var organization: Organization, var websites: ArrayList) : Comparable { companion object { var sorting = 0 From 0335bdf10d855377fb7d6059552e836c20bfc71b Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 21:08:09 +0200 Subject: [PATCH 119/135] show the contact nickname at the View screen if available --- .../contacts/activities/ViewContactActivity.kt | 6 +++++- .../contacts/fragments/MyViewPagerFragment.kt | 1 + .../contacts/models/Contact.kt | 2 +- .../main/res/layout/activity_view_contact.xml | 17 ++++++++++++++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt index aaf03ea7..573b5124 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -198,7 +198,11 @@ class ViewContactActivity : ContactActivity() { contact_suffix.text = suffix contact_suffix.beVisibleIf(suffix.isNotEmpty() && showFields and SHOW_SUFFIX_FIELD != 0) - if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone()) { + contact_nickname.text = nickname + contact_nickname.beVisibleIf(nickname.isNotEmpty() && showFields and SHOW_NICKNAME_FIELD != 0) + + if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone() + && contact_nickname.isGone()) { contact_name_image.beInvisible() (contact_photo.layoutParams as RelativeLayout.LayoutParams).bottomMargin = resources.getDimension(R.dimen.medium_margin).toInt() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index fea5f0b4..7afecd04 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -240,6 +240,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) (fragment_list.adapter as? ContactsAdapter)?.apply { val filtered = contactsIgnoringSearch.filter { getProperText(it.getFullName(), shouldNormalize).contains(text, true) || + getProperText(it.nickname, shouldNormalize).contains(text, true) || it.phoneNumbers.any { it.value.contains(text, true) } || it.emails.any { it.value.contains(text, true) } || it.addresses.any { getProperText(it.value, shouldNormalize).contains(text, true) } || diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 700dbbba..6a18a265 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -99,7 +99,7 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m val newEmails = ArrayList() emails.mapTo(newEmails) { Email(it.value, 0) } - return copy(id = 0, prefix = "", firstName = getFullName().toLowerCase(), middleName = "", surname = "", suffix = "", photoUri = "", + return copy(id = 0, prefix = "", firstName = getFullName().toLowerCase(), middleName = "", surname = "", suffix = "", nickname = "", photoUri = "", phoneNumbers = newPhoneNumbers, events = ArrayList(), addresses = ArrayList(), emails = newEmails, source = "", starred = 0, contactId = 0, thumbnailUri = "", notes = "", groups = ArrayList(), websites = ArrayList(), organization = Organization("", "")).hashCode() } diff --git a/app/src/main/res/layout/activity_view_contact.xml b/app/src/main/res/layout/activity_view_contact.xml index 8d50e8cf..e471ae49 100644 --- a/app/src/main/res/layout/activity_view_contact.xml +++ b/app/src/main/res/layout/activity_view_contact.xml @@ -166,6 +166,21 @@ android:singleLine="true" android:textSize="@dimen/bigger_text_size"/> + + From 1b28f626aa181cd4d3648b044406e826cb800086 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 22:13:28 +0200 Subject: [PATCH 120/135] fix nickname adding and editing at contacts --- .../activities/EditContactActivity.kt | 3 +++ .../contacts/helpers/ContactsHelper.kt | 24 +++++++++++++++++++ .../main/res/layout/activity_edit_contact.xml | 18 +++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index db2bef0f..ad80cb37 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -279,6 +279,7 @@ class EditContactActivity : ContactActivity() { contact_middle_name.beVisibleIf(showFields and SHOW_MIDDLE_NAME_FIELD != 0) contact_surname.beVisibleIf(showFields and SHOW_SURNAME_FIELD != 0) contact_suffix.beVisibleIf(showFields and SHOW_SUFFIX_FIELD != 0) + contact_nickname.beVisibleIf(showFields and SHOW_NICKNAME_FIELD != 0) contact_source.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0) contact_source_image.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0) @@ -346,6 +347,7 @@ class EditContactActivity : ContactActivity() { contact_middle_name.setText(middleName) contact_surname.setText(surname) contact_suffix.setText(suffix) + contact_nickname.setText(nickname) } } @@ -715,6 +717,7 @@ class EditContactActivity : ContactActivity() { middleName = contact_middle_name.value surname = contact_surname.value suffix = contact_suffix.value + nickname = contact_nickname.value photoUri = currentContactPhotoPath phoneNumbers = getFilledPhoneNumbers() emails = getFilledEmails() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index b719390d..2413682b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -825,6 +825,22 @@ class ContactsHelper(val activity: Activity) { operations.add(build()) } + // delete nickname + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " + val selectionArgs = arrayOf(contact.id.toString(), Nickname.CONTENT_ITEM_TYPE) + withSelection(selection, selectionArgs) + operations.add(build()) + } + + // add nickname + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id) + withValue(ContactsContract.Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) + withValue(Nickname.NAME, contact.nickname) + operations.add(build()) + } + // delete phone numbers ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " @@ -1089,6 +1105,14 @@ class ContactsHelper(val activity: Activity) { operations.add(build()) } + // nickname + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + withValue(ContactsContract.Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) + withValue(Nickname.NAME, contact.nickname) + operations.add(build()) + } + // phone numbers contact.phoneNumbers.forEach { ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml index 534ac995..15c77a7c 100644 --- a/app/src/main/res/layout/activity_edit_contact.xml +++ b/app/src/main/res/layout/activity_edit_contact.xml @@ -170,6 +170,22 @@ android:textCursorDrawable="@null" android:textSize="@dimen/bigger_text_size"/> + + From e0a38be028e5b3b394cebe6ac1cb66ba0008e4a4 Mon Sep 17 00:00:00 2001 From: tibbi Date: Sun, 12 Aug 2018 22:22:34 +0200 Subject: [PATCH 121/135] fix #195, properly update notes and organization fields --- .../contacts/helpers/ContactsHelper.kt | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 2413682b..116f854a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -917,20 +917,34 @@ class ContactsHelper(val activity: Activity) { } } - // notes - ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply { - val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" + // delete notes + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " val selectionArgs = arrayOf(contact.id.toString(), Note.CONTENT_ITEM_TYPE) withSelection(selection, selectionArgs) + operations.add(build()) + } + + // add notes + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id) + withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) withValue(Note.NOTE, contact.notes) operations.add(build()) } - // organization - ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply { - val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" + // delete organization + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Organization.CONTENT_ITEM_TYPE) withSelection(selection, selectionArgs) + operations.add(build()) + } + + // add organization + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company) withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE) withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition) From 441a2ac3b159bb40f05d8ae0e4d4b50dd867d3d0 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 13 Aug 2018 12:26:52 +0200 Subject: [PATCH 122/135] handle exporting and importing contact nicknames --- .../simplemobiletools/contacts/helpers/VcfExporter.kt | 4 ++++ .../simplemobiletools/contacts/helpers/VcfImporter.kt | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt index e53cecd4..5e6adf06 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -42,6 +42,10 @@ class VcfExporter { out.writeLn(VERSION_2_1) out.writeLn("$N${getNames(contact)}") + if (contact.nickname.isNotEmpty()) { + out.writeLn("$NICKNAME:${contact.nickname}") + } + contact.phoneNumbers.forEach { out.writeLn("$TEL;${getPhoneNumberLabel(it.type)}:${it.value}") } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index 3831f26f..c50168cc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -93,7 +93,7 @@ class VcfImporter(val activity: SimpleActivity) { when { line.toUpperCase() == BEGIN_VCARD -> resetValues() line.toUpperCase().startsWith(NOTE) -> addNotes(line.substring(NOTE.length)) - line.toUpperCase().startsWith(NICKNAME) -> { } + line.toUpperCase().startsWith(NICKNAME) -> addNickname(line.substring(NICKNAME.length)) line.toUpperCase().startsWith(N) -> addNames(line.substring(N.length)) line.toUpperCase().startsWith(TEL) -> addPhoneNumber(line.substring(TEL.length)) line.toUpperCase().startsWith(EMAIL) -> addEmail(line.substring(EMAIL.length)) @@ -148,6 +148,14 @@ class VcfImporter(val activity: SimpleActivity) { } } + private fun addNickname(nickname: String) { + curNickname = if (nickname.startsWith(";CHARSET", true)) { + nickname.substringAfter(":") + } else { + nickname.substring(1) + } + } + private fun addPhoneNumber(phoneNumber: String) { val phoneParts = phoneNumber.trimStart(';').split(":") var rawType = phoneParts[0] From 3b26a1ec92bd40a1b4a9f4074d8b08aff0cae2e4 Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 13 Aug 2018 13:00:46 +0200 Subject: [PATCH 123/135] adding the Nickname field in the release notes --- app/build.gradle | 2 +- .../com/simplemobiletools/contacts/activities/MainActivity.kt | 1 + app/src/main/res/values/donottranslate.xml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b0cacb82..bbf760dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.6.6' + implementation 'com.simplemobiletools:commons:4.6.7' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index e310da95..d080242b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -538,6 +538,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { add(Release(11, R.string.release_11)) add(Release(16, R.string.release_16)) add(Release(27, R.string.release_27)) + add(Release(29, R.string.release_29)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 49c1ffcd..aa83af5e 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,6 +2,7 @@ + Added an optional Nickname field Allow customizing which tabs are visible\n Added an optional call confirmation dialog From b680d538eb88bc2c9675ef3d92c3015a22fc7faf Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 13 Aug 2018 13:02:17 +0200 Subject: [PATCH 124/135] update version to 4.2.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bbf760dc..61e35152 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 28 - versionName "4.2.1" + versionCode 29 + versionName "4.2.2" setProperty("archivesBaseName", "contacts") } From 0e08fbf7ce1e60edeb683980e4edc812a5b813de Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 13 Aug 2018 13:02:22 +0200 Subject: [PATCH 125/135] updating changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e34645f..3c6a8c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========== +Version 4.2.2 *(2018-08-13)* +---------------------------- + + * Added an optional Nickname field + * Improved searching and sorting UTF8 characters + * Fixed updating Notes and Organization fields + Version 4.2.1 *(2018-08-05)* ---------------------------- From ad98c6e80edffad6923e67de6a9a537d1232092e Mon Sep 17 00:00:00 2001 From: spkprs Date: Wed, 15 Aug 2018 00:00:32 +0300 Subject: [PATCH 126/135] Update strings.xml --- app/src/main/res/values-el/strings.xml | 84 +++++++++++++------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index caba673d..9d3911ad 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -6,15 +6,15 @@ Ενημέρωση… Μνήμη τηλεφώνου Μνήμη τηλεφώνου (δεν είναι ορατή από άλλες εφαρμογές) - Company - Job position - Website - Send SMS to contacts - Send email to contacts - Send SMS to group - Send email to group - Call %s - Request the required permissions + Εταιρεία + Θέση Εργασίας + Ιστοσελίδα + Αποστολή SMS σε επαφές + Αποστολή email σε επαφές + Αποστολή SMS σε ομάδες + Αποστολή email σε ομάδες + Κλήση %s + Ζητήστε τα απαιτούμενα δικαιώματα Νέα επαφή Επεξεργασία επαφής @@ -23,7 +23,7 @@ Όνομα Μεσαίο όνομα Επώνυμο - Nickname + Ψευδώνυμο Δεν υπάρχουν ομάδες @@ -34,7 +34,7 @@ Δεν υπάρχουν ομάδες επαφών στη συσκευή Δημιουργία ομάδας Προσθήκη σε ομάδα - Δημιουργία ομάδας κάτω από λογαριασμό + Δημιουργία ομάδας υπο ενός λογαριασμού Λήψη φωτογραφίας @@ -42,23 +42,23 @@ Αφαίρεση φωτογραφίας - Το όνομα ξεκινά με το επώνυμο + Εμφάνιση πρώτα το επώνυμο Εμφάνιση τηλεφωνικών αριθμών στην κύρια οθόνη Εμφάνιση μικρογραφιών επαφής Στην επιλογή επαφής Κλήση επαφής Εμφάνιση λεπτομερειών επαφής - Manage shown contact fields - Try filtering out duplicate contacts - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Διαχείρηση εμφανιζόμενων πεδίων επαφής + Δοκιμάστε το φιλτράρισμα διπλών επαφών + Διαχείριση εμφανιζόμενων καρτελών + Επαφές + Αγαπημένες + Πρόσφατες Κλήσεις + Εμφάνιση διαλόγου επιβεβαίωσης πριν από την έναρξη μιας κλήσης Email - Σπίτι + Οικία Εργασία Άλλο @@ -66,8 +66,8 @@ Αριθμός Κινητό Κύριο - Φαξ εργασίας - Φαξ σπιτιού + Φαξ Εργασίας + Φαξ Οικίας Βομβητής Δεν βρέθηκε τηλεφωνικός αριθμός @@ -76,11 +76,11 @@ Επέτειος - Φαίνεται ότι δεν έχεις προσθέσει αγαπημένες επαφές ακόμα. + Φαίνεται ότι δεν έχετε προσθέσει αγαπημένες επαφές ακόμη. Προσθήκη αγαπημένων - Προσθήκη στα αγαπημένα + Προσθήκη στις αγαπημένες Αφαίρεση από τα αγαπημένα - You must be at the Edit screen to modify a contact + Πρέπει να είστε στην οθόνη "Επεξεργασία" για να τροποποιήσετε μια επαφή Αναζήτηση επαφών @@ -96,34 +96,34 @@ Όνομα αρχείου (χωρίς .vcf) - Select fields to show - Prefix - Suffix - Phone numbers + Επιλογή εμφάνισης πεδίων + Πρόθεμα + Κατάληξη + Αριθμοί Τηλεφώνων Emails - Addresses - Events (birthdays, anniversaries) - Notes - Organization - Websites - Groups - Contact source + Διευθύνσεις + Εκδηλώσεις (γενέθλια, επετείους) + Σημειώσεις + Εταιρεία + Ιστοσελίδα + Ομάδες + Προέλευση επαφής - I want to change what fields are visible at contacts. Can I do it? - Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Θέλω να αλλάξω τα πεδία που θα είναι ορατά στις επαφές. Μπορώ να το κάνω? + Ναι, το μόνο που έχετε να κάνετε είναι να μεταβείτε στις Ρυθμίσεις -> Διαχείριση εμφανιζόμενων πεδίων επαφής. Εκεί μπορείτε να επιλέξετε ποια πεδία θα πρέπει να είναι ορατά. Κάποια από αυτά είναι ακόμη και απενεργοποιημένα από προεπιλογή, επομένως ίσως βρείτε κάποια νέα εκεί. - Μια εφαρμογή επαφών για να διαχειρίζεσαι τις επαφές σου χωρίς διαφημίσεις. + Μια εφαρμογή για την διαχείρηση των επαφών σου χωρίς διαφημίσεις. Μια απλή εφαρμογή για δημιουργία και διαχείριση των επαφών σου από κάθε πηγή. Οι επαφές μπορεί να είναι αποθηκευμένες μόνο στη συσκευή σου, αλλά μπορούν να συγχρονίζονται στο Google, ή σε κάποιο άλλο λογαριασμό. Μπορείς να εμφανίσεις τις αγαπημένες σου επαφές σε ξεχωριστή λίστα. - Μπορείς να τη χρησιμοποιήσεις για τη διαχείριση των email των χρηστών και τα γεγονότα. Έχει τη δυνατότητα ταξινόμησης/φιλτραρίσματος με διάφορες παραμέτρους, προαιρετικά να εμφανίζεται το επώνυμο σαν όνομα. + Μπορείτε να τη χρησιμοποιήσετε για τη διαχείριση των email και εκδηλώσεων επίσης. Έχει τη δυνατότητα ταξινόμησης/φιλτραρίσματος με διάφορες παραμέτρους, προαιρετικά να εμφανίζεται το επώνυμο πρώτα ή το όνομα. - Δεν περιέχει διαφημίσεις ή περιττές άδειες. Είναι πλήρως ανοικτού κώδικα, παρέχει δυνατότητα προσαρμογής των χρωμάτων. + Δεν περιέχει διαφημίσεις ή περιττές άδειες. Έιναι όλη ανοιχτού κώδικα και παρέχει προσαρμόσιμα χρώματα για την εφαρμογή. - Αυτή η εφαρμογή είναι ένα μικρό κομμάτι μιας μεγαλύτερης συλλογής εφαρμογών. Μπορείς να βρεις τις υπόλοιπες στο https://www.simplemobiletools.com + Αυτή η εφαρμογή είναι μέρος μιας σειράς εφαρμογών. Μπορείτε να βρείτε τις υπόλοιπες στο https://www.simplemobiletools.com Keine Gruppen @@ -50,11 +50,11 @@ Kontaktdetails anzeigen Bearbeite sichtbare Kontaktfelder Versuche Kontaktduplikate herauszufiltern - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Anzuzeigende Tabs festlegen + Kontakte + Favoriten + Anrufliste + Bestätigungsdialog zeigen, bevor ein Anruf durchgeführt wird E-Mail From 4a77eefad78a57ab51d0bbaa58d337d1cb5ba0c9 Mon Sep 17 00:00:00 2001 From: spkprs Date: Wed, 15 Aug 2018 18:56:17 +0300 Subject: [PATCH 128/135] Update strings.xml --- app/src/main/res/values-el/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9d3911ad..eba172cf 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -5,7 +5,7 @@ Εισαγωγή… Ενημέρωση… Μνήμη τηλεφώνου - Μνήμη τηλεφώνου (δεν είναι ορατή από άλλες εφαρμογές) + Μνήμη τηλεφώνου (κρυφές από άλλες εφαρμογές) Εταιρεία Θέση Εργασίας Ιστοσελίδα @@ -14,7 +14,7 @@ Αποστολή SMS σε ομάδες Αποστολή email σε ομάδες Κλήση %s - Ζητήστε τα απαιτούμενα δικαιώματα + Ζητούνται τα απαιτούμενα δικαιώματα Νέα επαφή Επεξεργασία επαφής @@ -48,7 +48,7 @@ Στην επιλογή επαφής Κλήση επαφής Εμφάνιση λεπτομερειών επαφής - Διαχείρηση εμφανιζόμενων πεδίων επαφής + Διαχείριση εμφανιζόμενων πεδίων επαφής Δοκιμάστε το φιλτράρισμα διπλών επαφών Διαχείριση εμφανιζόμενων καρτελών Επαφές From f65678790a5a49d83c83a73e0fbd7ea6a023df10 Mon Sep 17 00:00:00 2001 From: en2sv Date: Sun, 19 Aug 2018 20:06:52 +0200 Subject: [PATCH 129/135] Update Swedish translation --- app/src/main/res/values-sv/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 347d8536..c5f3e8a7 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -13,8 +13,8 @@ Skicka e-post till kontakter Skicka sms till grupp Skicka e-post till grupp - Call %s - Request the required permissions + Ring %s + Begär de behörigheter som krävs Ny kontakt Redigera kontakt @@ -23,7 +23,7 @@ Förnamn Mellannamn Efternamn - Nickname + Smeknamn Inga grupper @@ -50,11 +50,11 @@ Visa kontaktuppgifter Hantera visade kontaktfält Försök filtrera bort dubblettkontakter - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Hantera visade flikar + Kontakter + Favoriter + Senaste samtal + Visa en bekräftelsedialogruta före uppringning E-post From 6f9668d5200150a5d9600aac5b1132e7c2511610 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 21 Aug 2018 11:11:57 +0200 Subject: [PATCH 130/135] update target sdk to 28 --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 61e35152..75606435 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion "28.0.2" defaultConfig { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 29 versionName "4.2.2" setProperty("archivesBaseName", "contacts") @@ -45,7 +45,7 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:4.6.7' + implementation 'com.simplemobiletools:commons:4.6.15' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' From 57263cce15ee8c46bc0640e2cd453906b8b46239 Mon Sep 17 00:00:00 2001 From: tibbi Date: Tue, 21 Aug 2018 11:12:24 +0200 Subject: [PATCH 131/135] avoid ActivityNotFoundException at opening the contact editor --- .../contacts/activities/ViewContactActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt index 573b5124..399fa024 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -165,7 +165,11 @@ class ViewContactActivity : ContactActivity() { Intent().apply { action = ContactsContract.QuickContact.ACTION_QUICK_CONTACT data = getContactPublicUri(contact!!) - startActivity(this) + if (resolveActivity(packageManager) != null) { + startActivity(this) + } else { + toast(R.string.no_app_found) + } } } From 389d6e92666872e188da82fb7f7964a166d0a938 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 23 Aug 2018 22:27:26 +0200 Subject: [PATCH 132/135] ignore empty contact addresses --- .../com/simplemobiletools/contacts/helpers/ContactsHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 116f854a..17d26c16 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -319,7 +319,7 @@ class ContactsHelper(val activity: Activity) { if (cursor?.moveToFirst() == true) { do { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) - val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: "" + val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: continue val type = cursor.getIntValue(CommonDataKinds.StructuredPostal.TYPE) if (addresses[id] == null) { From c0720e3e7311b6299f979ae732ecbe7cd43df664 Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 23 Aug 2018 23:23:45 +0200 Subject: [PATCH 133/135] rewriting contact exporting/importing, rely on vcard at parsing --- app/build.gradle | 1 + .../contacts/activities/ContactActivity.kt | 31 -- .../activities/EditContactActivity.kt | 4 +- .../contacts/activities/MainActivity.kt | 2 +- .../activities/ViewContactActivity.kt | 2 +- .../contacts/extensions/String.kt | 34 ++ .../contacts/helpers/Constants.kt | 21 -- .../contacts/helpers/VcfExporter.kt | 203 +++++----- .../contacts/helpers/VcfImporter.kt | 354 ++++-------------- build.gradle | 2 +- 10 files changed, 215 insertions(+), 439 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt diff --git a/app/build.gradle b/app/build.gradle index 75606435..dd3e3290 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,6 +49,7 @@ dependencies { implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' + compile 'com.googlecode.ez-vcard:ez-vcard:0.10.4' debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt index 732ff08c..77e4e764 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt @@ -4,7 +4,6 @@ import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.provider.ContactsContract import android.widget.ImageView -import android.widget.TextView import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -17,7 +16,6 @@ import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.getColoredBitmap import com.simplemobiletools.commons.extensions.getContrastColor -import com.simplemobiletools.commons.helpers.getDateFormats import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.config @@ -26,10 +24,6 @@ import com.simplemobiletools.contacts.extensions.sendSMSIntent import com.simplemobiletools.contacts.extensions.shareContacts import com.simplemobiletools.contacts.helpers.ContactsHelper import com.simplemobiletools.contacts.models.Contact -import org.joda.time.DateTime -import org.joda.time.format.DateTimeFormat -import java.text.DateFormat -import java.text.SimpleDateFormat import java.util.* abstract class ContactActivity : SimpleActivity() { @@ -68,31 +62,6 @@ abstract class ContactActivity : SimpleActivity() { }).into(photoView) } - fun getDateTime(dateString: String, viewToUpdate: TextView? = null): DateTime { - val dateFormats = getDateFormats() - var date = DateTime() - for (format in dateFormats) { - try { - date = DateTime.parse(dateString, DateTimeFormat.forPattern(format)) - - val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()) - var localPattern = (formatter as SimpleDateFormat).toLocalizedPattern() - - val hasYear = format.contains("y") - if (!hasYear) { - localPattern = localPattern.replace("y", "").trim() - date = date.withYear(DateTime().year) - } - - val formattedString = date.toString(localPattern) - viewToUpdate?.text = formattedString - break - } catch (ignored: Exception) { - } - } - return date - } - fun deleteContact() { ConfirmationDialog(this) { if (contact != null) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index ad80cb37..5acf55af 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -427,7 +427,7 @@ class EditContactActivity : ContactActivity() { (eventHolder as ViewGroup).apply { val contactEvent = contact_event.apply { - getDateTime(event.value, this) + event.value.getDateTimeFromDateString(this) tag = event.value alpha = 1f } @@ -595,7 +595,7 @@ class EditContactActivity : ContactActivity() { } } - val date = getDateTime(eventField.tag?.toString() ?: "") + val date = (eventField.tag?.toString() ?: "").getDateTimeFromDateString() DatePickerDialog(this, getDialogTheme(), setDateListener, date.year, date.monthOfYear - 1, date.dayOfMonth).show() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index d080242b..19c92a75 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -73,8 +73,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } else { checkContactPermissions() } - } + storeStateVariables() checkWhatsNewDialog() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt index 399fa024..3b41f777 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -293,7 +293,7 @@ class ViewContactActivity : ContactActivity() { layoutInflater.inflate(R.layout.item_event, contact_events_holder, false).apply { contact_events_holder.addView(this) contact_event.alpha = 1f - getDateTime(it.value, contact_event) + it.value.getDateTimeFromDateString(contact_event) contact_event_type.setText(getEventTextId(it.type)) contact_event_remove.beGone() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt new file mode 100644 index 00000000..6134b1ad --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt @@ -0,0 +1,34 @@ +package com.simplemobiletools.contacts.extensions + +import android.widget.TextView +import com.simplemobiletools.commons.helpers.getDateFormats +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.* + +fun String.getDateTimeFromDateString(viewToUpdate: TextView? = null): DateTime { + val dateFormats = getDateFormats() + var date = DateTime() + for (format in dateFormats) { + try { + date = DateTime.parse(this, DateTimeFormat.forPattern(format)) + + val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()) + var localPattern = (formatter as SimpleDateFormat).toLocalizedPattern() + + val hasYear = format.contains("y") + if (!hasYear) { + localPattern = localPattern.replace("y", "").trim() + date = date.withYear(DateTime().year) + } + + val formattedString = date.toString(localPattern) + viewToUpdate?.text = formattedString + break + } catch (ignored: Exception) { + } + } + return date +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index a1c7e3a5..0f56d22b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -52,26 +52,6 @@ const val PHOTO_REMOVED = 2 const val PHOTO_CHANGED = 3 const val PHOTO_UNCHANGED = 4 -// export/import -const val BEGIN_VCARD = "BEGIN:VCARD" -const val END_VCARD = "END:VCARD" -const val N = "N" -const val NICKNAME = "NICKNAME" -const val TEL = "TEL" -const val BDAY = "BDAY:" -const val ANNIVERSARY = "ANNIVERSARY:" -const val PHOTO = "PHOTO" -const val EMAIL = "EMAIL" -const val ADR = "ADR" -const val NOTE = "NOTE" -const val ORG = "ORG" -const val TITLE = "TITLE" -const val URL = "URL" -const val ENCODING = "ENCODING" -const val BASE64 = "BASE64" -const val JPEG = "JPEG" -const val VERSION_2_1 = "VERSION:2.1" - // phone number/email types const val CELL = "CELL" const val WORK = "WORK" @@ -83,7 +63,6 @@ const val WORK_FAX = "WORK;FAX" const val HOME_FAX = "HOME;FAX" const val PAGER = "PAGER" const val MOBILE = "MOBILE" -const val VOICE = "VOICE" const val ON_CLICK_CALL_CONTACT = 1 const val ON_CLICK_VIEW_CONTACT = 2 diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt index 5e6adf06..4832c2ac 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -1,22 +1,30 @@ package com.simplemobiletools.contacts.helpers -import android.graphics.Bitmap import android.net.Uri import android.provider.ContactsContract.CommonDataKinds import android.provider.MediaStore -import android.util.Base64 import com.simplemobiletools.commons.activities.BaseSimpleActivity -import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.extensions.getFileOutputStream +import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.extensions.toFileDirItem +import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.extensions.getByteArray +import com.simplemobiletools.contacts.extensions.getDateTimeFromDateString import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.EXPORT_FAIL import com.simplemobiletools.contacts.models.Contact -import java.io.BufferedWriter -import java.io.ByteArrayOutputStream +import ezvcard.Ezvcard +import ezvcard.VCard +import ezvcard.parameter.AddressType +import ezvcard.parameter.EmailType +import ezvcard.parameter.ImageType +import ezvcard.parameter.TelephoneType +import ezvcard.property.* +import ezvcard.util.PartialDate import java.io.File +import java.util.* class VcfExporter { - private val ENCODED_PHOTO_LINE_LENGTH = 74 - enum class ExportResult { EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL } @@ -36,65 +44,93 @@ class VcfExporter { activity.toast(R.string.exporting) } - it.bufferedWriter().use { out -> - for (contact in contacts) { - out.writeLn(BEGIN_VCARD) - out.writeLn(VERSION_2_1) - out.writeLn("$N${getNames(contact)}") + val cards = ArrayList() + for (contact in contacts) { + val card = VCard() + StructuredName().apply { + prefixes.add(contact.prefix) + given = contact.firstName + additionalNames.add(contact.middleName) + family = contact.surname + suffixes.add(contact.suffix) + card.structuredName = this + } - if (contact.nickname.isNotEmpty()) { - out.writeLn("$NICKNAME:${contact.nickname}") - } + if (contact.nickname.isNotEmpty()) { + card.setNickname(contact.nickname) + } - contact.phoneNumbers.forEach { - out.writeLn("$TEL;${getPhoneNumberLabel(it.type)}:${it.value}") - } + contact.phoneNumbers.forEach { + val phoneNumber = Telephone(it.value) + phoneNumber.types.add(TelephoneType.find(getPhoneNumberLabel(it.type))) + card.addTelephoneNumber(phoneNumber) + } - contact.emails.forEach { - val type = getEmailTypeLabel(it.type) - val delimiterType = if (type.isEmpty()) "" else ";$type" - out.writeLn("$EMAIL$delimiterType:${it.value}") - } + contact.emails.forEach { + val email = Email(it.value) + email.types.add(EmailType.find(getEmailTypeLabel(it.type))) + card.addEmail(email) + } - contact.addresses.forEach { - val type = getAddressTypeLabel(it.type) - val delimiterType = if (type.isEmpty()) "" else ";$type" - out.writeLn("$ADR$delimiterType:;;${it.value.replace("\n", "\\n")};;;;") - } - - contact.events.forEach { - if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) { - out.writeLn("$BDAY${it.value}") + contact.events.forEach { + if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY || it.type == CommonDataKinds.Event.TYPE_ANNIVERSARY) { + val dateTime = it.value.getDateTimeFromDateString() + if (it.value.startsWith("--")) { + val partialDate = PartialDate.builder().year(null).month(dateTime.monthOfYear - 1).date(dateTime.dayOfMonth).build() + if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) { + card.birthdays.add(Birthday(partialDate)) + } else { + card.anniversaries.add(Anniversary(partialDate)) + } + } else { + Calendar.getInstance().apply { + clear() + set(Calendar.YEAR, dateTime.year) + set(Calendar.MONTH, dateTime.monthOfYear - 1) + set(Calendar.DAY_OF_MONTH, dateTime.dayOfMonth) + if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) { + card.birthdays.add(Birthday(time)) + } else { + card.anniversaries.add(Anniversary(time)) + } + } } } - - if (contact.notes.isNotEmpty()) { - out.writeLn("$NOTE:${contact.notes.replace("\n", "\\n")}") - } - - if (!contact.organization.isEmpty()) { - out.writeLn("$ORG:${contact.organization.company.replace("\n", "\\n")}") - out.writeLn("$TITLE:${contact.organization.jobPosition.replace("\n", "\\n")}") - } - - contact.websites.forEach { - out.writeLn("$URL:$it") - } - - if (contact.thumbnailUri.isNotEmpty()) { - val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)) - addBitmap(bitmap, out) - } - - if (contact.photo != null) { - addBitmap(contact.photo!!, out) - } - - out.writeLn(END_VCARD) - contactsExported++ } + + contact.addresses.forEach { + val address = Address() + address.streetAddress = it.value + address.types.add(AddressType.find(getAddressTypeLabel(it.type))) + card.addAddress(address) + } + + if (contact.notes.isNotEmpty()) { + card.addNote(contact.notes) + } + + if (!contact.organization.isEmpty()) { + val organization = Organization() + organization.values.add(contact.organization.company) + card.organization = organization + card.titles.add(Title(contact.organization.jobPosition)) + } + + contact.websites.forEach { + card.addUrl(it) + } + + if (contact.thumbnailUri.isNotEmpty()) { + val photoByteArray = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)).getByteArray() + val photo = Photo(photoByteArray, ImageType.JPEG) + card.addPhoto(photo) + } + + cards.add(card) + contactsExported++ } + Ezvcard.write(cards).go(file) } catch (e: Exception) { activity.showErrorToast(e) } @@ -107,71 +143,24 @@ class VcfExporter { } } - private fun addBitmap(bitmap: Bitmap, out: BufferedWriter) { - val firstLine = "$PHOTO;$ENCODING=$BASE64;$JPEG:" - val byteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.JPEG, 85, byteArrayOutputStream) - bitmap.recycle() - val byteArray = byteArrayOutputStream.toByteArray() - val encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP) - - val encodedFirstLineSection = encoded.substring(0, ENCODED_PHOTO_LINE_LENGTH - firstLine.length) - out.writeLn(firstLine + encodedFirstLineSection) - var curStartIndex = encodedFirstLineSection.length - do { - val part = encoded.substring(curStartIndex, Math.min(curStartIndex + ENCODED_PHOTO_LINE_LENGTH - 1, encoded.length)) - out.writeLn(" $part") - curStartIndex += ENCODED_PHOTO_LINE_LENGTH - 1 - } while (curStartIndex < encoded.length) - - out.writeLn("") - } - - private fun getNames(contact: Contact): String { - var result = "" - var firstName = contact.firstName - var surName = contact.surname - var middleName = contact.middleName - var prefix = contact.prefix - var suffix = contact.suffix - - if (QuotedPrintable.urlEncode(firstName) != firstName - || QuotedPrintable.urlEncode(surName) != surName - || QuotedPrintable.urlEncode(middleName) != middleName - || QuotedPrintable.urlEncode(prefix) != prefix - || QuotedPrintable.urlEncode(suffix) != suffix) { - firstName = QuotedPrintable.encode(firstName) - surName = QuotedPrintable.encode(surName) - middleName = QuotedPrintable.encode(middleName) - prefix = QuotedPrintable.encode(prefix) - suffix = QuotedPrintable.encode(suffix) - result += ";CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE" - } - - return "$result:$surName;$firstName;$middleName;$prefix;$suffix" - } - private fun getPhoneNumberLabel(type: Int) = when (type) { CommonDataKinds.Phone.TYPE_MOBILE -> CELL - CommonDataKinds.Phone.TYPE_HOME -> HOME CommonDataKinds.Phone.TYPE_WORK -> WORK CommonDataKinds.Phone.TYPE_MAIN -> PREF CommonDataKinds.Phone.TYPE_FAX_WORK -> WORK_FAX CommonDataKinds.Phone.TYPE_FAX_HOME -> HOME_FAX CommonDataKinds.Phone.TYPE_PAGER -> PAGER - else -> VOICE + else -> HOME } private fun getEmailTypeLabel(type: Int) = when (type) { - CommonDataKinds.Email.TYPE_HOME -> HOME CommonDataKinds.Email.TYPE_WORK -> WORK CommonDataKinds.Email.TYPE_MOBILE -> MOBILE - else -> "" + else -> HOME } private fun getAddressTypeLabel(type: Int) = when (type) { - CommonDataKinds.StructuredPostal.TYPE_HOME -> HOME CommonDataKinds.StructuredPostal.TYPE_WORK -> WORK - else -> "" + else -> HOME } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index c50168cc..d247b0ba 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -3,8 +3,6 @@ package com.simplemobiletools.contacts.helpers import android.graphics.Bitmap import android.graphics.BitmapFactory import android.provider.ContactsContract.CommonDataKinds -import android.text.TextUtils -import android.util.Base64 import android.widget.Toast import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.contacts.activities.SimpleActivity @@ -12,45 +10,19 @@ import com.simplemobiletools.contacts.extensions.getCachePhoto import com.simplemobiletools.contacts.extensions.getCachePhotoUri import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.* import com.simplemobiletools.contacts.models.* +import ezvcard.Ezvcard +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat import java.io.File import java.io.FileOutputStream +import java.util.* class VcfImporter(val activity: SimpleActivity) { enum class ImportResult { IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL } - private var curPrefix = "" - private var curFirstName = "" - private var curMiddleName = "" - private var curSurname = "" - private var curSuffix = "" - private var curNickname = "" - private var curPhotoUri = "" - private var curNotes = "" - private var curCompany = "" - private var curJobPosition = "" - private var curPhoneNumbers = ArrayList() - private var curEmails = ArrayList() - private var curEvents = ArrayList() - private var curAddresses = ArrayList
() - private var curGroups = ArrayList() - private var curWebsites = ArrayList() - - private var isGettingPhoto = false - private var currentPhotoString = StringBuilder() - private var currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG - - private var isGettingName = false - private var currentNameIsANSI = false - private var currentNameString = StringBuilder() - - private var isGettingNotes = false - private var currentNotesSB = StringBuilder() - - private var isGettingCompany = false - private var currentCompanyIsANSI = false - private var currentCompany = StringBuilder() + private val PATTERN = "EEE MMM dd HH:mm:ss 'GMT'ZZ YYYY" private var contactsImported = 0 private var contactsFailed = 0 @@ -63,51 +35,70 @@ class VcfImporter(val activity: SimpleActivity) { activity.assets.open(path) } - inputStream.bufferedReader().use { - while (true) { - val originalLine = it.readLine() ?: break - val line = originalLine.trim() - if (line.isEmpty()) { - if (isGettingPhoto) { - savePhoto() - isGettingPhoto = false - } - continue - } else if (line.startsWith('\t') && isGettingName) { - currentNameString.append(line.trimStart('\t')) - isGettingName = false - parseNames() - } else if (isGettingNotes) { - if (originalLine.startsWith(' ')) { - currentNotesSB.append(line.substring(1)) - } else { - curNotes = currentNotesSB.toString().replace("\\n", "\n").replace("\\,", ",") - isGettingNotes = false - } - } else if (isGettingCompany && currentCompanyIsANSI && line.startsWith("=")) { - currentCompany.append(line) - curCompany = QuotedPrintable.decode(currentCompany.toString().replace("==", "=")) - continue - } + val ezContacts = Ezvcard.parse(inputStream).all() + for (ezContact in ezContacts) { + val structuredName = ezContact.structuredName + val prefix = structuredName?.prefixes?.firstOrNull() ?: "" + val firstName = structuredName?.given ?: "" + val middleName = structuredName?.additionalNames?.firstOrNull() ?: "" + val surname = structuredName?.family ?: "" + val suffix = structuredName?.suffixes?.firstOrNull() ?: "" + val nickname = ezContact.nickname?.values?.firstOrNull() ?: "" + val photoUri = "" - when { - line.toUpperCase() == BEGIN_VCARD -> resetValues() - line.toUpperCase().startsWith(NOTE) -> addNotes(line.substring(NOTE.length)) - line.toUpperCase().startsWith(NICKNAME) -> addNickname(line.substring(NICKNAME.length)) - line.toUpperCase().startsWith(N) -> addNames(line.substring(N.length)) - line.toUpperCase().startsWith(TEL) -> addPhoneNumber(line.substring(TEL.length)) - line.toUpperCase().startsWith(EMAIL) -> addEmail(line.substring(EMAIL.length)) - line.toUpperCase().startsWith(ADR) -> addAddress(line.substring(ADR.length)) - line.toUpperCase().startsWith(BDAY) -> addBirthday(line.substring(BDAY.length)) - line.toUpperCase().startsWith(ANNIVERSARY) -> addAnniversary(line.substring(ANNIVERSARY.length)) - line.toUpperCase().startsWith(PHOTO) -> addPhoto(line.substring(PHOTO.length)) - line.toUpperCase().startsWith(ORG) -> addCompany(line.substring(ORG.length)) - line.toUpperCase().startsWith(TITLE) -> addJobPosition(line.substring(TITLE.length)) - line.toUpperCase().startsWith(URL) -> addWebsite(line.substring(URL.length)) - line.toUpperCase() == END_VCARD -> saveContact(targetContactSource) - isGettingPhoto -> currentPhotoString.append(line.trim()) + val phoneNumbers = ArrayList() + ezContact.telephoneNumbers.forEach { + val type = getPhoneNumberTypeId(it.types.firstOrNull()?.value ?: MOBILE) + val number = it.text + phoneNumbers.add(PhoneNumber(number, type)) + } + + val emails = ArrayList() + ezContact.emails.forEach { + val type = getEmailTypeId(it.types.firstOrNull()?.value ?: HOME) + val email = it.value + emails.add(Email(email, type)) + } + + val addresses = ArrayList
() + ezContact.addresses.forEach { + val type = getAddressTypeId(it.types.firstOrNull()?.value ?: HOME) + val address = it.streetAddress + if (address?.isNotEmpty() == true) { + addresses.add(Address(address, type)) } } + + val events = ArrayList() + ezContact.birthdays.forEach { + val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_BIRTHDAY) + events.add(event) + } + + ezContact.anniversaries.forEach { + val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_ANNIVERSARY) + events.add(event) + } + + val starred = 0 + val contactId = 0 + val notes = ezContact.notes.firstOrNull()?.value ?: "" + val groups = ArrayList() + val company = ezContact.organization?.values?.firstOrNull() ?: "" + val jobPosition = ezContact.titles?.firstOrNull()?.value ?: "" + val organization = Organization(company, jobPosition) + val websites = ezContact.urls.map { it.value } as ArrayList + + val photoData = ezContact.photos.firstOrNull()?.data + val photo = null + val thumbnailUri = savePhoto(photoData) + + val contact = Contact(0, prefix, firstName, middleName, surname, suffix, nickname, photoUri, phoneNumbers, emails, addresses, events, + targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites) + + if (ContactsHelper(activity).insertContact(contact)) { + contactsImported++ + } } } catch (e: Exception) { activity.showErrorToast(e, Toast.LENGTH_LONG) @@ -121,238 +112,51 @@ class VcfImporter(val activity: SimpleActivity) { } } - private fun addNames(names: String) { - val parts = names.split(":") - currentNameIsANSI = parts.first().toUpperCase().contains("QUOTED-PRINTABLE") - currentNameString.append(parts[1].trimEnd('=')) - if (!isGettingName && currentNameIsANSI && names.endsWith('=')) { - isGettingName = true - } else { - if (names.contains(";")) { - parseNames() - } else if (names.startsWith(":")) { - curFirstName = names.substring(1) - } - } + private fun formatDateToDayCode(date: Date): String { + val dateTime = DateTime.parse(date.toString(), DateTimeFormat.forPattern(PATTERN)) + return dateTime.toString("yyyy-MM-dd") } - private fun parseNames() { - val nameParts = currentNameString.split(";") - curSurname = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[0]) else nameParts[0] - curFirstName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[1]) else nameParts[1] - - if (nameParts.size > 2) { - curMiddleName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[2]) else nameParts[2] - curPrefix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[3]) else nameParts[3] - curSuffix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[4]) else nameParts[4] - } - } - - private fun addNickname(nickname: String) { - curNickname = if (nickname.startsWith(";CHARSET", true)) { - nickname.substringAfter(":") - } else { - nickname.substring(1) - } - } - - private fun addPhoneNumber(phoneNumber: String) { - val phoneParts = phoneNumber.trimStart(';').split(":") - var rawType = phoneParts[0] - var subType = "" - if (rawType.contains('=')) { - val types = rawType.split('=') - if (types.any { it.contains(';') }) { - subType = types[1].split(';')[0] - } - rawType = types.last() - } - - val type = getPhoneNumberTypeId(rawType.toUpperCase(), subType) - val value = phoneParts[1] - curPhoneNumbers.add(PhoneNumber(value, type)) - } - - private fun getPhoneNumberTypeId(type: String, subType: String) = when (type) { + private fun getPhoneNumberTypeId(type: String) = when (type.toUpperCase()) { CELL -> CommonDataKinds.Phone.TYPE_MOBILE HOME -> CommonDataKinds.Phone.TYPE_HOME WORK -> CommonDataKinds.Phone.TYPE_WORK PREF, MAIN -> CommonDataKinds.Phone.TYPE_MAIN WORK_FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK HOME_FAX -> CommonDataKinds.Phone.TYPE_FAX_HOME - FAX -> if (subType == WORK) CommonDataKinds.Phone.TYPE_FAX_WORK else CommonDataKinds.Phone.TYPE_FAX_HOME + FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK PAGER -> CommonDataKinds.Phone.TYPE_PAGER else -> CommonDataKinds.Phone.TYPE_OTHER } - private fun addEmail(email: String) { - val emailParts = email.trimStart(';').split(":") - var rawType = emailParts[0] - if (rawType.contains('=')) { - rawType = rawType.split('=').last() - } - val type = getEmailTypeId(rawType.toUpperCase()) - val value = emailParts[1] - curEmails.add(Email(value, type)) - } - - private fun getEmailTypeId(type: String) = when (type) { + private fun getEmailTypeId(type: String) = when (type.toUpperCase()) { HOME -> CommonDataKinds.Email.TYPE_HOME WORK -> CommonDataKinds.Email.TYPE_WORK MOBILE -> CommonDataKinds.Email.TYPE_MOBILE else -> CommonDataKinds.Email.TYPE_OTHER } - private fun addAddress(address: String) { - val addressParts = address.trimStart(';').split(":") - var rawType = addressParts[0] - if (rawType.contains('=')) { - rawType = rawType.split('=').last() - } - - val type = getAddressTypeId(rawType.toUpperCase()) - val addresses = addressParts[1].split(";") - if (addresses.size == 7) { - var parsedAddress = if (address.contains(";CHARSET=UTF-8:")) { - TextUtils.join(", ", addresses.filter { it.trim().isNotEmpty() }) - } else { - addresses[2].replace("\\n", "\n") - } - - if (address.contains("QUOTED-PRINTABLE")) { - parsedAddress = QuotedPrintable.decode(parsedAddress) - } - - curAddresses.add(Address(parsedAddress, type)) - } - } - - private fun getAddressTypeId(type: String) = when (type) { + private fun getAddressTypeId(type: String) = when (type.toUpperCase()) { HOME -> CommonDataKinds.Email.TYPE_HOME WORK -> CommonDataKinds.Email.TYPE_WORK else -> CommonDataKinds.Email.TYPE_OTHER } - private fun addBirthday(birthday: String) { - curEvents.add(Event(birthday, CommonDataKinds.Event.TYPE_BIRTHDAY)) - } - - private fun addAnniversary(anniversary: String) { - curEvents.add(Event(anniversary, CommonDataKinds.Event.TYPE_ANNIVERSARY)) - } - - private fun addPhoto(photo: String) { - val photoParts = photo.trimStart(';').split(';') - if (photoParts.size == 2) { - val typeParts = photoParts[1].split(':') - currentPhotoCompressionFormat = getPhotoCompressionFormat(typeParts[0]) - val encoding = photoParts[0].split('=').last() - if (encoding == BASE64) { - isGettingPhoto = true - currentPhotoString.append(typeParts[1].trim()) - } + private fun savePhoto(byteArray: ByteArray?): String { + if (byteArray == null) { + return "" } - } - private fun getPhotoCompressionFormat(type: String) = when (type.toLowerCase()) { - "png" -> Bitmap.CompressFormat.PNG - "webp" -> Bitmap.CompressFormat.WEBP - else -> Bitmap.CompressFormat.JPEG - } - - private fun savePhoto() { val file = activity.getCachePhoto() - val imageAsBytes = Base64.decode(currentPhotoString.toString().toByteArray(), Base64.DEFAULT) - val bitmap = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.size) + val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) var fileOutputStream: FileOutputStream? = null try { fileOutputStream = FileOutputStream(file) - bitmap.compress(currentPhotoCompressionFormat, 100, fileOutputStream) + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream) } finally { fileOutputStream?.close() } - curPhotoUri = activity.getCachePhotoUri(file).toString() - } - - private fun addNotes(notes: String) { - if (notes.startsWith(";CHARSET", true)) { - currentNotesSB.append(notes.substringAfter(":")) - } else { - currentNotesSB.append(notes.substring(1)) - } - isGettingNotes = true - } - - private fun addCompany(company: String) { - curCompany = if (company.startsWith(";")) { - company.substringAfter(":").trim(';') - } else { - company.trimStart(':') - } - - currentCompanyIsANSI = company.toUpperCase().contains("QUOTED-PRINTABLE") - currentCompany.append(curCompany) - isGettingCompany = true - } - - private fun addJobPosition(jobPosition: String) { - curJobPosition = if (jobPosition.startsWith(";")) { - jobPosition.substringAfter(":") - } else { - jobPosition.trimStart(':') - } - } - - private fun addWebsite(website: String) { - if (website.startsWith(";")) { - curWebsites.add(website.substringAfter(":")) - } else { - curWebsites.add(website.trimStart(':')) - } - } - - private fun saveContact(source: String) { - val organization = Organization(curCompany, curJobPosition) - val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curNickname, curPhotoUri, curPhoneNumbers, - curEmails, curAddresses, curEvents, source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites) - - if (ContactsHelper(activity).insertContact(contact)) { - contactsImported++ - } - } - - private fun resetValues() { - curPrefix = "" - curFirstName = "" - curMiddleName = "" - curSurname = "" - curSuffix = "" - curNickname = "" - curPhotoUri = "" - curNotes = "" - curCompany = "" - curJobPosition = "" - curPhoneNumbers = ArrayList() - curEmails = ArrayList() - curEvents = ArrayList() - curAddresses = ArrayList() - curGroups = ArrayList() - curWebsites = ArrayList() - - isGettingPhoto = false - currentPhotoString = StringBuilder() - currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG - - isGettingName = false - currentNameIsANSI = false - currentNameString = StringBuilder() - - isGettingNotes = false - currentNotesSB = StringBuilder() - - isGettingCompany = false - currentCompanyIsANSI = false - currentCompany = StringBuilder() + return activity.getCachePhotoUri(file).toString() } } diff --git a/build.gradle b/build.gradle index b0005124..1b8fb3c3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.60' + ext.kotlin_version = '1.2.61' repositories { google() From a9d43594462cdf4d70013d9b8f3fe0893e3c26b4 Mon Sep 17 00:00:00 2001 From: spkprs Date: Fri, 24 Aug 2018 13:21:04 +0300 Subject: [PATCH 134/135] Update strings.xml Title app --- app/src/main/res/values-el/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9d3911ad..8a48e366 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,5 +1,5 @@ - Απλές Επαφές + Simple Contacts Επαφές Διεύθυνση Εισαγωγή… From 713fd88a130c496f749b4aae0f78c0bebb3c332e Mon Sep 17 00:00:00 2001 From: tibbi Date: Fri, 24 Aug 2018 13:02:02 +0200 Subject: [PATCH 135/135] adding some ezvcard proguard rules --- app/proguard-rules.pro | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e69de29b..50dfb901 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -0,0 +1,5 @@ +# ez-vcard +-keep,includedescriptorclasses class ezvcard.property.** { *; } +-keep enum ezvcard.VCardVersion { *; } +-dontwarn ezvcard.io.json.** +-dontwarn freemarker.**